Add support for generating methods
Add `GeneratedMethods` and `GeneratedMethod` support classes which can be used during code generation to generation additional methods that will ultimately be included in a `JavaFile`. See gh-28414
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.MethodSpec.Builder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A generated method.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
* @see GeneratedMethods
|
||||
* @see MethodGenerator
|
||||
*/
|
||||
public final class GeneratedMethod {
|
||||
|
||||
private final String name;
|
||||
|
||||
private MethodSpec spec;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link GeneratedMethod} instance with the given name. This
|
||||
* constructor is package-private since names should only be generated via a
|
||||
* {@link GeneratedMethods}.
|
||||
* @param name the generated name
|
||||
*/
|
||||
GeneratedMethod(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the generated name of the method.
|
||||
* @return the name of the generated method
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link MethodSpec} for this generated method.
|
||||
* @return the method spec
|
||||
* @throws IllegalStateException if one of the {@code generateBy(...)}
|
||||
* methods has not been called
|
||||
*/
|
||||
public MethodSpec getSpec() {
|
||||
Assert.state(this.spec != null,
|
||||
() -> String.format("Method '%s' has no method spec defined", this.name));
|
||||
return this.spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the method using the given consumer.
|
||||
* @param builder a consumer that will accept a method spec builder and
|
||||
* configure it as necessary
|
||||
* @return this instance
|
||||
*/
|
||||
public GeneratedMethod using(Consumer<MethodSpec.Builder> builder) {
|
||||
Builder builderToUse = MethodSpec.methodBuilder(this.name);
|
||||
builder.accept(builderToUse);
|
||||
MethodSpec spec = builderToUse.build();
|
||||
assertNameHasNotBeenChanged(spec);
|
||||
this.spec = spec;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void assertNameHasNotBeenChanged(MethodSpec spec) {
|
||||
Assert.isTrue(this.name.toString().equals(spec.name), () -> String
|
||||
.format("'spec' must use the generated name \"%s\"", this.name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (this.spec != null) ? this.spec.toString() : this.name.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A managed collection of generated methods.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
* @see GeneratedMethod
|
||||
*/
|
||||
public class GeneratedMethods implements Iterable<GeneratedMethod>, MethodGenerator {
|
||||
|
||||
private final MethodNameGenerator methodNameGenerator;
|
||||
|
||||
private final List<GeneratedMethod> generatedMethods = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link GeneratedMethods} instance backed by a new
|
||||
* {@link MethodNameGenerator}.
|
||||
*/
|
||||
public GeneratedMethods() {
|
||||
this(new MethodNameGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link GeneratedMethods} instance backed by the given
|
||||
* {@link MethodNameGenerator}.
|
||||
* @param methodNameGenerator the method name generator
|
||||
*/
|
||||
public GeneratedMethods(MethodNameGenerator methodNameGenerator) {
|
||||
Assert.notNull(methodNameGenerator, "'methodNameGenerator' must not be null");
|
||||
this.methodNameGenerator = methodNameGenerator;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public GeneratedMethod generateMethod(Object... methodNameParts) {
|
||||
return add(methodNameParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new {@link GeneratedMethod}. The returned instance must define the
|
||||
* method spec by calling {@code using(builder -> ...)}.
|
||||
* @param methodNameParts the method name parts that should be used to
|
||||
* generate a unique method name
|
||||
* @return the newly added {@link GeneratedMethod}
|
||||
*/
|
||||
public GeneratedMethod add(Object... methodNameParts) {
|
||||
GeneratedMethod method = new GeneratedMethod(
|
||||
this.methodNameGenerator.generateMethodName(methodNameParts));
|
||||
this.generatedMethods.add(method);
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the given action with each of the {@link MethodSpec MethodSpecs}
|
||||
* that have been added to this collection.
|
||||
* @param action the action to perform
|
||||
*/
|
||||
public void doWithMethodSpecs(Consumer<MethodSpec> action) {
|
||||
stream().map(GeneratedMethod::getSpec).forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GeneratedMethod> iterator() {
|
||||
return this.generatedMethods.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Stream} of all the methods in this collection.
|
||||
* @return a stream of {@link GeneratedMethod} instances
|
||||
*/
|
||||
public Stream<GeneratedMethod> stream() {
|
||||
return this.generatedMethods.stream();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
/**
|
||||
* Generates new {@link GeneratedMethod} instances.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
* @see GeneratedMethods
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MethodGenerator {
|
||||
|
||||
/**
|
||||
* Generate a new {@link GeneratedMethod}. The returned instance must define
|
||||
* the method spec by calling {@code using(builder -> ...)}.
|
||||
* @param methodNameParts the method name parts that should be used to
|
||||
* generate a unique method name
|
||||
* @return the newly added {@link GeneratedMethod}
|
||||
*/
|
||||
GeneratedMethod generateMethod(Object... methodNameParts);
|
||||
|
||||
/**
|
||||
* Return a new {@link MethodGenerator} instance that generates method with
|
||||
* additional implicit method name parts. The final generated name will be
|
||||
* of the following form:
|
||||
* <p>
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <th>Original</th>
|
||||
* <th>Updated</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>run</td>
|
||||
* <td><name>Run</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>getValue</td>
|
||||
* <td>get<Name>Value</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>setValue</td>
|
||||
* <td>set<Name>Value</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>isEnabled</td>
|
||||
* <td>is<Name>Enabled</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* @param nameParts the implicit name parts
|
||||
* @return a new {@link MethodGenerator} instance
|
||||
*/
|
||||
default MethodGenerator withName(Object... nameParts) {
|
||||
return new MethodGeneratorWithName(this, nameParts);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Internal class used to support {@link MethodGenerator#withName(Object...)}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
class MethodGeneratorWithName implements MethodGenerator {
|
||||
|
||||
private static final String[] PREFIXES = { "get", "set", "is" };
|
||||
|
||||
private final MethodGenerator methodGenerator;
|
||||
|
||||
private final Object[] nameParts;
|
||||
|
||||
|
||||
MethodGeneratorWithName(MethodGenerator methodGenerator, Object[] nameParts) {
|
||||
this.methodGenerator = methodGenerator;
|
||||
this.nameParts = nameParts;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public GeneratedMethod generateMethod(Object... methodNameParts) {
|
||||
return this.methodGenerator.generateMethod(generateName(methodNameParts));
|
||||
}
|
||||
|
||||
private Object[] generateName(Object... methodNameParts) {
|
||||
String joined = MethodNameGenerator.join(methodNameParts);
|
||||
String prefix = getPrefix(joined);
|
||||
String suffix = joined.substring(prefix.length());
|
||||
Object[] result = this.nameParts;
|
||||
if (StringUtils.hasLength(prefix)) {
|
||||
result = ObjectUtils.addObjectToArray(result, prefix, 0);
|
||||
}
|
||||
if (StringUtils.hasLength(suffix)) {
|
||||
result = ObjectUtils.addObjectToArray(result, suffix);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getPrefix(String name) {
|
||||
for (String candidate : PREFIXES) {
|
||||
if (name.startsWith(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedMethod}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GeneratedMethodTests {
|
||||
|
||||
private static final String NAME = "spring";
|
||||
|
||||
@Test
|
||||
void getNameReturnsName() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
assertThat(method.getName()).isSameAs(NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSpecReturnsSpec() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
method.using(builder -> builder.addJavadoc("Test"));
|
||||
assertThat(method.getSpec().javadoc.toString()).contains("Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSpecReturnsSpecWhenNoSpecDefinedThrowsException() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
assertThatIllegalStateException().isThrownBy(() -> method.getSpec())
|
||||
.withMessage("Method 'spring' has no method spec defined");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingAddsSpec() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
method.using(builder -> builder.addModifiers(Modifier.PUBLIC));
|
||||
assertThat(method.getSpec().toString())
|
||||
.isEqualToIgnoringNewLines("public void spring() {}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void usingWhenBuilderChanagesNameThrowsException() {
|
||||
GeneratedMethod method = new GeneratedMethod(NAME);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> method.using(builder -> builder.setName("badname")))
|
||||
.withMessage("'spec' must use the generated name \"spring\"");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link GeneratedMethods}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class GeneratedMethodsTests {
|
||||
|
||||
private final GeneratedMethods methods = new GeneratedMethods();
|
||||
|
||||
@Test
|
||||
void createWhenMethodNameGeneratorIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new GeneratedMethods(null))
|
||||
.withMessage("'methodNameGenerator' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithExistingGeneratorUsesGenerator() {
|
||||
MethodNameGenerator generator = new MethodNameGenerator();
|
||||
generator.generateMethodName("test");
|
||||
GeneratedMethods methods = new GeneratedMethods(generator);
|
||||
assertThat(methods.add("test").getName()).hasToString("test1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void addAddsMethod() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
assertThat(
|
||||
this.methods.stream().map(GeneratedMethod::getName).map(Object::toString))
|
||||
.containsExactly("springBeans", "springContext");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doWithMethodSpecsAcceptsMethodSpecs() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
List<String> names = new ArrayList<>();
|
||||
this.methods.doWithMethodSpecs(spec -> names.add(spec.name));
|
||||
assertThat(names).containsExactly("springBeans", "springContext");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doWithMethodSpecsWhenMethodHasNotHadSpecDefinedThrowsException() {
|
||||
this.methods.add("spring");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.methods.doWithMethodSpecs(spec -> {
|
||||
})).withMessage("Method 'spring' has no method spec defined");
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorIteratesMethods() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
Iterator<GeneratedMethod> iterator = this.methods.iterator();
|
||||
assertThat(iterator.next().getName()).hasToString("springBeans");
|
||||
assertThat(iterator.next().getName()).hasToString("springContext");
|
||||
assertThat(iterator.hasNext()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamStreamsMethods() {
|
||||
this.methods.add("spring", "beans").using(this::build);
|
||||
this.methods.add("spring", "context").using(this::build);
|
||||
assertThat(this.methods.stream()).hasSize(2);
|
||||
}
|
||||
|
||||
private void build(MethodSpec.Builder builder) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.aot.generate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests {@link MethodGeneratorWithName}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
class MethodGeneratorWithNameTests {
|
||||
|
||||
private final GeneratedMethods generatedMethods = new GeneratedMethods();
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingGetMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("get", "test");
|
||||
assertThat(generateMethod.getName()).hasToString("getMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingSetMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("set", "test");
|
||||
assertThat(generateMethod.getName()).hasToString("setMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingIsMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("is", "test");
|
||||
assertThat(generateMethod.getName()).hasToString("isMyBeanTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNameWhenGeneratingOtherMethod() {
|
||||
GeneratedMethod generateMethod = generatedMethods.withName("my", "bean")
|
||||
.generateMethod("test");
|
||||
assertThat(generateMethod.getName()).hasToString("myBeanTest");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user