Complete refactor of AOT concepts

Remove the AOT code that now has an alternative API.

Closes gh-28414
This commit is contained in:
Phillip Webb
2022-05-04 20:23:24 -07:00
parent 702207d9ee
commit 16e7f1f212
83 changed files with 9 additions and 10950 deletions

View File

@@ -1,53 +0,0 @@
/*
* 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.generator;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.support.MultiStatement;
/**
* A code contribution that gathers the code, the {@linkplain RuntimeHints
* runtime hints}, and the {@linkplain ProtectedElement protected elements}
* that are necessary to execute it.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface CodeContribution {
/**
* Return the {@linkplain MultiStatement statements} that can be used to
* append code.
* @return the statements instance to use to contribute code
*/
MultiStatement statements();
/**
* Return the {@linkplain RuntimeHints hints} to use to register
* potential optimizations for contributed code.
* @return the runtime hints
*/
RuntimeHints runtimeHints();
/**
* Return the {@linkplain ProtectedAccess protected access} to use to
* analyze any privileged access, if necessary.
* @return the protected access
*/
ProtectedAccess protectedAccess();
}

View File

@@ -1,68 +0,0 @@
/*
* 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.generator;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.support.MultiStatement;
/**
* A default {@link CodeContribution} implementation.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class DefaultCodeContribution implements CodeContribution {
private final MultiStatement statements;
private final RuntimeHints runtimeHints;
private final ProtectedAccess protectedAccess;
protected DefaultCodeContribution(MultiStatement statements, RuntimeHints runtimeHints,
ProtectedAccess protectedAccess) {
this.statements = statements;
this.runtimeHints = runtimeHints;
this.protectedAccess = protectedAccess;
}
/**
* Create an instance with the {@link RuntimeHints} instance to use.
* @param runtimeHints the runtime hints instance to use
*/
public DefaultCodeContribution(RuntimeHints runtimeHints) {
this(new MultiStatement(), runtimeHints, new ProtectedAccess());
}
@Override
public MultiStatement statements() {
return this.statements;
}
@Override
public RuntimeHints runtimeHints() {
return this.runtimeHints;
}
@Override
public ProtectedAccess protectedAccess() {
return this.protectedAccess;
}
}

View File

@@ -1,92 +0,0 @@
/*
* 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.generator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.javapoet.JavaFile;
/**
* Default {@link GeneratedTypeContext} implementation.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class DefaultGeneratedTypeContext implements GeneratedTypeContext {
private final String packageName;
private final RuntimeHints runtimeHints;
private final Function<String, GeneratedType> generatedTypeFactory;
private final Map<String, GeneratedType> generatedTypes;
/**
* Create a context targeting the specified package name and using the specified
* factory to create a {@link GeneratedType} per requested package name.
* @param packageName the main package name
* @param generatedTypeFactory the factory to use to create a {@link GeneratedType}
* based on a package name.
*/
public DefaultGeneratedTypeContext(String packageName, Function<String, GeneratedType> generatedTypeFactory) {
this.packageName = packageName;
this.runtimeHints = new RuntimeHints();
this.generatedTypeFactory = generatedTypeFactory;
this.generatedTypes = new LinkedHashMap<>();
}
@Override
public RuntimeHints runtimeHints() {
return this.runtimeHints;
}
@Override
public GeneratedType getGeneratedType(String packageName) {
return this.generatedTypes.computeIfAbsent(packageName, this.generatedTypeFactory);
}
@Override
public GeneratedType getMainGeneratedType() {
return getGeneratedType(this.packageName);
}
/**
* Specify if a {@link GeneratedType} for the specified package name is registered.
* @param packageName the package name to use
* @return {@code true} if a type is registered for that package
*/
public boolean hasGeneratedType(String packageName) {
return this.generatedTypes.containsKey(packageName);
}
/**
* Return the list of {@link JavaFile} of known generated type.
* @return the java files of bootstrap classes in this instance
*/
public List<JavaFile> toJavaFiles() {
return this.generatedTypes.values().stream()
.map(GeneratedType::toJavaFile)
.collect(Collectors.toList());
}
}

View File

@@ -1,125 +0,0 @@
/*
* 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.generator;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.lang.model.element.Modifier;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
/**
* Wrapper for a generated {@linkplain TypeSpec type}.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class GeneratedType {
private final ClassName className;
private final TypeSpec.Builder type;
private final List<MethodSpec> methods;
GeneratedType(ClassName className, Consumer<TypeSpec.Builder> type) {
this.className = className;
this.type = TypeSpec.classBuilder(className);
type.accept(this.type);
this.methods = new ArrayList<>();
}
/**
* Create an instance for the specified {@link ClassName}, customizing the type with
* the specified {@link Consumer consumer callback}.
* @param className the class name
* @param type a callback to customize the type, i.e. to change default modifiers
* @return a new {@link GeneratedType}
*/
public static GeneratedType of(ClassName className, Consumer<TypeSpec.Builder> type) {
return new GeneratedType(className, type);
}
/**
* Create an instance for the specified {@link ClassName}, as a {@code public} type.
* @param className the class name
* @return a new {@link GeneratedType}
*/
public static GeneratedType of(ClassName className) {
return of(className, type -> type.addModifiers(Modifier.PUBLIC));
}
/**
* Return the {@link ClassName} of this instance.
* @return the class name
*/
public ClassName getClassName() {
return this.className;
}
/**
* Customize the type of this instance.
* @param type the consumer of the type builder
* @return this for method chaining
*/
public GeneratedType customizeType(Consumer<TypeSpec.Builder> type) {
type.accept(this.type);
return this;
}
/**
* Add a method using the state of the specified {@link MethodSpec.Builder},
* updating the name of the method if a similar method already exists.
* @param method a method builder representing the method to add
* @return the added method
*/
public MethodSpec addMethod(MethodSpec.Builder method) {
MethodSpec methodToAdd = createUniqueNameIfNecessary(method.build());
this.methods.add(methodToAdd);
return methodToAdd;
}
/**
* Return a {@link JavaFile} with the state of this instance.
* @return a java file
*/
public JavaFile toJavaFile() {
return JavaFile.builder(this.className.packageName(),
this.type.addMethods(this.methods).build()).indent("\t").build();
}
private MethodSpec createUniqueNameIfNecessary(MethodSpec method) {
List<MethodSpec> candidates = this.methods.stream().filter(isSimilar(method)).toList();
if (candidates.isEmpty()) {
return method;
}
MethodSpec updatedMethod = method.toBuilder().setName(method.name + "_").build();
return createUniqueNameIfNecessary(updatedMethod);
}
private Predicate<MethodSpec> isSimilar(MethodSpec method) {
return candidate -> method.name.equals(candidate.name)
&& method.parameters.size() == candidate.parameters.size();
}
}

View File

@@ -1,52 +0,0 @@
/*
* 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.generator;
import org.springframework.aot.hint.RuntimeHints;
/**
* Context passed to object that can generate code, giving access to a main
* {@link GeneratedType} as well as to a {@link GeneratedType} in a given
* package if privileged access is required.
*
* @author Stephane Nicoll
* @since 6.0
*/
public interface GeneratedTypeContext {
/**
* Return the {@link RuntimeHints} instance to use to contribute hints for
* generated types.
* @return the runtime hints
*/
RuntimeHints runtimeHints();
/**
* Return a {@link GeneratedType} for the specified package. If it does not
* exist, it is created.
* @param packageName the package name to use
* @return a generated type
*/
GeneratedType getGeneratedType(String packageName);
/**
* Return the main {@link GeneratedType}.
* @return the generated type for the target package
*/
GeneratedType getMainGeneratedType();
}

View File

@@ -1,60 +0,0 @@
/*
* 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.generator;
import org.springframework.aot.hint.AbstractTypeReference;
import org.springframework.aot.hint.TypeReference;
import org.springframework.javapoet.ClassName;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A {@link TypeReference} for a generated {@linkplain ClassName type}.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class GeneratedTypeReference extends AbstractTypeReference {
private final ClassName className;
private GeneratedTypeReference(ClassName className) {
super(className.packageName(), className.simpleName(), safeCreate(className.enclosingClassName()));
this.className = className;
}
@Nullable
private static GeneratedTypeReference safeCreate(@Nullable ClassName className) {
return (className != null ? new GeneratedTypeReference(className) : null);
}
public static GeneratedTypeReference of(ClassName className) {
Assert.notNull(className, "ClassName must not be null");
return new GeneratedTypeReference(className);
}
@Override
public String getCanonicalName() {
return this.className.canonicalName();
}
@Override
protected boolean isPrimitive() {
return this.className.isPrimitive();
}
}

View File

@@ -1,281 +0,0 @@
/*
* 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.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* Gather the need of non-public access and determine the privileged package
* to use, if necessary.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class ProtectedAccess {
private final List<ProtectedElement> elements;
public ProtectedAccess() {
this.elements = new ArrayList<>();
}
/**
* Specify whether the protected elements registered in this instance are
* accessible from the specified package name.
* @param packageName the target package name
* @return {@code true} if the registered access can be safely used from
* the specified package name
*/
public boolean isAccessible(String packageName) {
return getProtectedElements(packageName).isEmpty();
}
/**
* Return the privileged package name to use for the specified package
* name, or {@code null} if none is required.
* @param packageName the target package name to use
* @return the privileged package name to use, or {@code null}
* @throws ProtectedAccessException if a single privileged package cannot
* be identified
* @see #isAccessible(String)
*/
@Nullable
public String getPrivilegedPackageName(String packageName) throws ProtectedAccessException {
List<ProtectedElement> protectedElements = getProtectedElements(packageName);
if (protectedElements.isEmpty()) {
return null;
}
List<String> packageNames = protectedElements.stream()
.map(element -> element.getType().getPackageName())
.distinct().toList();
if (packageNames.size() == 1) {
return packageNames.get(0);
}
throw new ProtectedAccessException("Multiple packages require a privileged access: "
+ packageNames, protectedElements);
}
private List<ProtectedElement> getProtectedElements(String packageName) {
List<ProtectedElement> matches = new ArrayList<>();
for (ProtectedElement element : this.elements) {
if (!element.getType().getPackage().getName().equals(packageName)) {
matches.add(element);
}
}
return matches;
}
/**
* Analyze the specified {@linkplain ResolvableType type}, including its
* full type signature.
* @param type the type to analyze
*/
public void analyze(ResolvableType type) {
Class<?> protectedType = isProtected(type);
if (protectedType != null) {
registerProtectedType(protectedType, null);
}
}
/**
* Analyze accessing the specified {@link Member} using the specified
* {@link Options options}.
* @param member the member to analyze
* @param options the options to use
*/
public void analyze(Member member, Options options) {
if (isProtected(member.getDeclaringClass())) {
registerProtectedType(member.getDeclaringClass(), member);
}
if (isProtected(member.getModifiers()) && !options.useReflection.apply(member)) {
registerProtectedType(member.getDeclaringClass(), member);
}
if (member instanceof Field field) {
ResolvableType fieldType = ResolvableType.forField(field);
Class<?> protectedType = isProtected(fieldType);
if (protectedType != null && options.assignReturnType.apply(field)) {
registerProtectedType(protectedType, field);
}
}
else if (member instanceof Constructor<?> constructor) {
analyzeParameterTypes(constructor, i ->
ResolvableType.forConstructorParameter(constructor, i));
}
else if (member instanceof Method method) {
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
Class<?> protectedType = isProtected(returnType);
if (protectedType != null && options.assignReturnType.apply(method)) {
registerProtectedType(protectedType, method);
}
analyzeParameterTypes(method, i -> ResolvableType.forMethodParameter(method, i));
}
}
private void analyzeParameterTypes(Executable executable, Function<Integer,
ResolvableType> parameterTypeFactory) {
for (int i = 0; i < executable.getParameters().length; i++) {
ResolvableType parameterType = parameterTypeFactory.apply(i);
Class<?> protectedType = isProtected(parameterType);
if (protectedType != null) {
registerProtectedType(protectedType, executable);
}
}
}
@Nullable
Class<?> isProtected(ResolvableType resolvableType) {
return isProtected(new HashSet<>(), resolvableType);
}
@Nullable
private Class<?> isProtected(Set<ResolvableType> seen, ResolvableType target) {
if (seen.contains(target)) {
return null;
}
seen.add(target);
ResolvableType nonProxyTarget = target.as(ClassUtils.getUserClass(target.toClass()));
Class<?> rawClass = nonProxyTarget.toClass();
if (isProtected(rawClass)) {
return rawClass;
}
Class<?> declaringClass = rawClass.getDeclaringClass();
if (declaringClass != null) {
if (isProtected(declaringClass)) {
return declaringClass;
}
}
if (nonProxyTarget.hasGenerics()) {
for (ResolvableType generic : nonProxyTarget.getGenerics()) {
return isProtected(seen, generic);
}
}
return null;
}
private boolean isProtected(Class<?> type) {
Class<?> candidate = ClassUtils.getUserClass(type);
return isProtected(candidate.getModifiers());
}
private boolean isProtected(int modifiers) {
return !Modifier.isPublic(modifiers);
}
private void registerProtectedType(Class<?> type, @Nullable Member member) {
this.elements.add(ProtectedElement.of(type, member));
}
/**
* Options to use to analyze if invoking a {@link Member} requires
* privileged access.
*/
public static final class Options {
private final Function<Member, Boolean> assignReturnType;
private final Function<Member, Boolean> useReflection;
private Options(Builder builder) {
this.assignReturnType = builder.assignReturnType;
this.useReflection = builder.useReflection;
}
/**
* Initialize a {@link Builder} with default options, that is use
* reflection if the member is private and does not assign the
* return type.
* @return an options builder
*/
public static Builder defaults() {
return new Builder(member -> false,
member -> Modifier.isPrivate(member.getModifiers()));
}
public static final class Builder {
private Function<Member, Boolean> assignReturnType;
private Function<Member, Boolean> useReflection;
private Builder(Function<Member, Boolean> assignReturnType,
Function<Member, Boolean> useReflection) {
this.assignReturnType = assignReturnType;
this.useReflection = useReflection;
}
/**
* Specify if the return type is assigned so that its type can be
* analyzed if necessary.
* @param assignReturnType whether the return type is assigned
* @return {@code this}, to facilitate method chaining
*/
public Builder assignReturnType(boolean assignReturnType) {
return assignReturnType(member -> assignReturnType);
}
/**
* Specify a function that determines whether the return type is
* assigned so that its type can be analyzed.
* @param assignReturnType whether the return type is assigned
* @return {@code this}, to facilitate method chaining
*/
public Builder assignReturnType(Function<Member, Boolean> assignReturnType) {
this.assignReturnType = assignReturnType;
return this;
}
/**
* Specify a function that determines whether reflection can be
* used for a given {@link Member}.
* @param useReflection whether reflection can be used
* @return {@code this}, to facilitate method chaining
*/
public Builder useReflection(Function<Member, Boolean> useReflection) {
this.useReflection = useReflection;
return this;
}
/**
* Build an {@link Options} instance based on the state of this
* builder.
* @return a new options instance
*/
public Options build() {
return new Options(this);
}
}
}
}

View File

@@ -1,45 +0,0 @@
/*
* 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.generator;
import java.util.List;
/**
* Thrown when a code block requires privileged access on multiple packages.
*
* @author Stephane Nicoll
* @since 6.0
*/
@SuppressWarnings("serial")
public class ProtectedAccessException extends RuntimeException {
private final List<ProtectedElement> protectedElements;
public ProtectedAccessException(String message, List<ProtectedElement> protectedElements) {
super(message);
this.protectedElements = protectedElements;
}
/**
* Return the {@linkplain ProtectedElement protected elements}.
* @return the protected access
*/
public List<ProtectedElement> getProtectedElements() {
return this.protectedElements;
}
}

View File

@@ -1,68 +0,0 @@
/*
* 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.generator;
import java.lang.reflect.Member;
import org.springframework.lang.Nullable;
/**
* A {@link Member} that is non-public, with the related type.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class ProtectedElement {
private final Class<?> type;
@Nullable
private final Member target;
private ProtectedElement(Class<?> type, @Nullable Member member) {
this.type = type;
this.target = member;
}
/**
* Return the {@link Class type} that is non-public. For a plain
* protected {@link Member member} access, the type of the declaring class
* is used. Otherwise, the type in the member signature, such as a parameter
* type for an executable, or the return type of a field is used. If the
* type is generic, the type that is protected in the generic signature is
* used.
* @return the type that is not public
*/
public Class<?> getType() {
return this.type;
}
/**
* Return the {@link Member} that is not publicly accessible.
* @return the member
*/
@Nullable
public Member getMember() {
return this.target;
}
static ProtectedElement of(Class<?> type, @Nullable Member member) {
return new ProtectedElement(type, member);
}
}

View File

@@ -1,68 +0,0 @@
/*
* 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.generator;
import java.util.Arrays;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.support.MultiCodeBlock;
import org.springframework.util.ClassUtils;
/**
* Code generator for {@link ResolvableType}.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class ResolvableTypeGenerator {
/**
* Generate a type signature for the specified {@link ResolvableType}.
* @param target the type to generate
* @return the representation of that type
*/
public CodeBlock generateTypeFor(ResolvableType target) {
CodeBlock.Builder code = CodeBlock.builder();
generate(code, target, false);
return code.build();
}
private void generate(CodeBlock.Builder code, ResolvableType target, boolean forceResolvableType) {
Class<?> type = ClassUtils.getUserClass(target.toClass());
if (!target.hasGenerics()) {
if (forceResolvableType) {
code.add("$T.forClass($T.class)", ResolvableType.class, type);
}
else {
code.add("$T.class", type);
}
}
else {
code.add("$T.forClassWithGenerics($T.class, ", ResolvableType.class, type);
ResolvableType[] generics = target.getGenerics();
boolean hasGenericParameter = Arrays.stream(generics).anyMatch(ResolvableType::hasGenerics);
MultiCodeBlock multi = new MultiCodeBlock();
for (int i = 0; i < generics.length; i++) {
ResolvableType parameter = target.getGeneric(i);
multi.add(parameterCode -> generate(parameterCode, parameter, hasGenericParameter));
}
code.add(multi.join(", ")).add(")");
}
}
}

View File

@@ -1,10 +0,0 @@
/**
* Support classes for components that contribute generated code equivalent
* to a runtime behavior.
*/
@NonNullApi
@NonNullFields
package org.springframework.aot.generator;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -22,7 +22,7 @@ import org.springframework.lang.Nullable;
* Contract for registering {@link RuntimeHints} in a static fashion.
* <p>Implementations will contribute hints without any knowledge of the application context
* and can only use the given {@link ClassLoader} to conditionally contribute hints.
* <p>{@code RuntimeHintsRegistrar} can be declared as {@code spring.factories} entries;
* <p>{@code RuntimeHintsRegistrar} can be declared as {@code spring/aot.factories} entries;
* the registrar will be processed as soon as its declaration is found in the classpath.
* A standard no-arg constructor is required for implementations.
*

View File

@@ -1,188 +0,0 @@
/*
* 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.javapoet.support;
import java.io.IOException;
import java.io.StringWriter;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
/**
* A code snippet using tabs indentation that is fully processed by JavaPoet so
* that imports are resolved.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class CodeSnippet {
private static final String START_SNIPPET = "// start-snippet\n";
private static final String END_SNIPPET = "// end-snippet";
private final String fileContent;
private final String snippet;
CodeSnippet(String fileContent, String snippet) {
this.fileContent = fileContent;
this.snippet = snippet;
}
String getFileContent() {
return this.fileContent;
}
/**
* Return the rendered code snippet.
* @return a code snippet where imports have been resolved
*/
public String getSnippet() {
return this.snippet;
}
/**
* Specify if an import statement for the specified type is present.
* @param type the type to check
* @return true if this type has an import statement, false otherwise
*/
public boolean hasImport(Class<?> type) {
return hasImport(type.getName());
}
/**
* Specify if an import statement for the specified class name is present.
* @param className the name of the class to check
* @return true if this type has an import statement, false otherwise
*/
public boolean hasImport(String className) {
return getFileContent().lines().anyMatch(candidate ->
candidate.equals(String.format("import %s;", className)));
}
/**
* Return a new {@link CodeSnippet} where the specified number of indentations
* have been removed.
* @param indent the number of indent to remove
* @return a CodeSnippet instance with the number of indentations removed
*/
public CodeSnippet removeIndent(int indent) {
return new CodeSnippet(this.fileContent, this.snippet.lines().map(line ->
removeIndent(line, indent)).collect(Collectors.joining("\n")));
}
/**
* Create a {@link CodeSnippet} using the specified code.
* @param code the code snippet
* @return a {@link CodeSnippet} instance
*/
public static CodeSnippet of(CodeBlock code) {
return new Builder().build(code);
}
/**
* Process the specified code and return a fully-processed code snippet
* as a String.
* @param code a consumer to use to generate the code snippet
* @return a resolved code snippet
*/
public static String process(Consumer<CodeBlock.Builder> code) {
CodeBlock.Builder body = CodeBlock.builder();
code.accept(body);
return process(body.build());
}
/**
* Process the specified {@link CodeBlock code} and return a
* fully-processed code snippet as a String.
* @param code the code snippet
* @return a resolved code snippet
*/
public static String process(CodeBlock code) {
return of(code).getSnippet();
}
private String removeIndent(String line, int indent) {
for (int i = 0; i < indent; i++) {
if (line.startsWith("\t")) {
line = line.substring(1);
}
}
return line;
}
private static final class Builder {
private static final String INDENT = "\t";
private static final String SNIPPET_INDENT = INDENT + INDENT;
public CodeSnippet build(CodeBlock code) {
MethodSpec.Builder method = MethodSpec.methodBuilder("test")
.addModifiers(Modifier.PUBLIC);
CodeBlock.Builder body = CodeBlock.builder();
body.add(START_SNIPPET);
body.add(code);
body.add(END_SNIPPET);
method.addCode(body.build());
String fileContent = write(createTestJavaFile(method.build()));
String snippet = isolateGeneratedContent(fileContent);
return new CodeSnippet(fileContent, snippet);
}
private String isolateGeneratedContent(String javaFile) {
int start = javaFile.indexOf(START_SNIPPET);
String tmp = javaFile.substring(start + START_SNIPPET.length());
int end = tmp.indexOf(END_SNIPPET);
tmp = tmp.substring(0, end);
// Remove indent
return tmp.lines().map(line -> {
if (!line.startsWith(SNIPPET_INDENT)) {
throw new IllegalStateException("Missing indent for " + line);
}
return line.substring(SNIPPET_INDENT.length());
}).collect(Collectors.joining("\n"));
}
private JavaFile createTestJavaFile(MethodSpec method) {
return JavaFile.builder("example", TypeSpec.classBuilder("Test")
.addModifiers(Modifier.PUBLIC)
.addMethod(method).build()).indent(INDENT).build();
}
private String write(JavaFile file) {
try {
StringWriter out = new StringWriter();
file.writeTo(out);
return out.toString();
}
catch (IOException ex) {
throw new IllegalStateException("Failed to write " + file, ex);
}
}
}
}

View File

@@ -1,82 +0,0 @@
/*
* 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.javapoet.support;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
/**
* A {@link CodeBlock} wrapper for joining multiple blocks.
*
* @author Stephane Nicoll
* @since 6.0
*/
public class MultiCodeBlock {
private final List<CodeBlock> codeBlocks = new ArrayList<>();
/**
* Add the specified {@link CodeBlock}.
* @param code the code block to add
*/
public void add(CodeBlock code) {
if (code.isEmpty()) {
throw new IllegalArgumentException("Could not add empty CodeBlock");
}
this.codeBlocks.add(code);
}
/**
* Add a {@link CodeBlock} using the specified callback.
* @param code the callback to use
*/
public void add(Consumer<Builder> code) {
Builder builder = CodeBlock.builder();
code.accept(builder);
add(builder.build());
}
/**
* Add a code block using the specified formatted String and the specified
* arguments.
* @param code the code
* @param arguments the arguments
* @see Builder#add(String, Object...)
*/
public void add(String code, Object... arguments) {
add(CodeBlock.of(code, arguments));
}
/**
* Return a {@link CodeBlock} that joins the different blocks registered in
* this instance with the specified delimiter.
* @param delimiter the delimiter to use (not {@literal null})
* @return a {@link CodeBlock} joining the blocks of this instance with the
* specified {@code delimiter}
* @see CodeBlock#join(Iterable, String)
*/
public CodeBlock join(String delimiter) {
return CodeBlock.join(this.codeBlocks, delimiter);
}
}

View File

@@ -1,232 +0,0 @@
/*
* 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.javapoet.support;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
/**
* A {@link CodeBlock} wrapper for multiple statements.
*
* @author Stephane Nicoll
* @since 6.0
*/
public final class MultiStatement {
private final List<Statement> statements = new ArrayList<>();
/**
* Specify if this instance is empty.
* @return {@code true} if no statement is registered, {@code false} otherwise
*/
public boolean isEmpty() {
return this.statements.isEmpty();
}
/**
* Add the statements defined in the specified multi statement to this instance.
* @param multiStatement the statements to add
* @return {@code this}, to facilitate method chaining
*/
public MultiStatement add(MultiStatement multiStatement) {
this.statements.addAll(multiStatement.statements);
return this;
}
/**
* Add the specified {@link CodeBlock codeblock} rendered as-is.
* @param codeBlock the code block to add
* @return {@code this}, to facilitate method chaining
* @see #addStatement(CodeBlock) to add a code block that represents
* a statement
*/
public MultiStatement add(CodeBlock codeBlock) {
this.statements.add(Statement.of(codeBlock));
return this;
}
/**
* Add a {@link CodeBlock} rendered as-is using the specified callback.
* @param code the callback to use
* @return {@code this}, to facilitate method chaining
* @see #addStatement(CodeBlock) to add a code block that represents
* a statement
*/
public MultiStatement add(Consumer<Builder> code) {
CodeBlock.Builder builder = CodeBlock.builder();
code.accept(builder);
add(builder.build());
return this;
}
/**
* Add a statement.
* @param statement the statement to add
* @return {@code this}, to facilitate method chaining
*/
public MultiStatement addStatement(CodeBlock statement) {
this.statements.add(Statement.ofStatement(statement));
return this;
}
/**
* Add a statement using the specified callback.
* @param code the callback to use
* @return {@code this}, to facilitate method chaining
*/
public MultiStatement addStatement(Consumer<Builder> code) {
CodeBlock.Builder builder = CodeBlock.builder();
code.accept(builder);
return addStatement(builder.build());
}
/**
* Add a statement using the specified formatted String and the specified
* arguments.
* @param code the code of the statement
* @param args the arguments for placeholders
* @return {@code this}, to facilitate method chaining
* @see CodeBlock#of(String, Object...)
*/
public MultiStatement addStatement(String code, Object... args) {
return addStatement(CodeBlock.of(code, args));
}
/**
* Add the statements produced from the {@code itemGenerator} applied on the specified
* items.
* @param items the items to handle, each item is represented as a statement
* @param itemGenerator the item generator
* @param <T> the type of the item
* @return {@code this}, to facilitate method chaining
*/
public <T> MultiStatement addAll(Iterable<T> items, Function<T, CodeBlock> itemGenerator) {
items.forEach(element -> addStatement(itemGenerator.apply(element)));
return this;
}
/**
* Return a {@link CodeBlock} that applies all the {@code statements} of
* this instance.
* @return the code block
*/
public CodeBlock toCodeBlock() {
Builder code = CodeBlock.builder();
this.statements.forEach(statement -> statement.add(code));
return code.build();
}
/**
* Return a {@link CodeBlock} that applies all the {@code statements} of this
* instance. If only one statement is available, it is not completed using the
* {@code ;} termination so that it can be used in the context of a lambda.
* @return the body of the lambda
*/
public CodeBlock toLambdaBody() {
Builder code = CodeBlock.builder();
for (int i = 0; i < this.statements.size(); i++) {
Statement statement = this.statements.get(i);
statement.contribute(code, this.isMulti(), i == this.statements.size() - 1);
}
return code.build();
}
/**
* Return a {@link CodeBlock} that applies all the {@code statements} of this
* instance in the context of a lambda.
* @param lambdaParameter the parameter(s) of the lambda, must end with {@code ->}
* @return a lambda whose body is generated from the statements of this instance
*/
public CodeBlock toLambda(CodeBlock lambdaParameter) {
Builder code = CodeBlock.builder();
code.add(lambdaParameter);
if (isMulti()) {
code.beginControlFlow("");
}
else {
code.add(" ");
}
code.add(toLambdaBody());
if (isMulti()) {
code.add("\n").unindent().add("}");
}
return code.build();
}
/**
* Return a {@link CodeBlock} that applies all the {@code statements} of this
* instance in the context of a lambda.
* @param lambdaParameter the parameter(s) of the lambda, must end with {@code ->}
* @return a lambda whose body is generated from the statements of this instance
*/
public CodeBlock toLambda(String lambdaParameter) {
return toLambda(CodeBlock.of(lambdaParameter));
}
private boolean isMulti() {
return this.statements.size() > 1;
}
private static class Statement {
private final CodeBlock codeBlock;
private final boolean addStatementTermination;
Statement(CodeBlock codeBlock, boolean addStatementTermination) {
this.codeBlock = codeBlock;
this.addStatementTermination = addStatementTermination;
}
void add(CodeBlock.Builder code) {
code.add(this.codeBlock);
if (this.addStatementTermination) {
code.add(";\n");
}
}
void contribute(CodeBlock.Builder code, boolean multi, boolean isLastStatement) {
code.add(this.codeBlock);
if (this.addStatementTermination) {
if (!isLastStatement) {
code.add(";\n");
}
else if (multi) {
code.add(";");
}
}
}
static Statement ofStatement(CodeBlock codeBlock) {
return new Statement(codeBlock, true);
}
static Statement of(CodeBlock codeBlock) {
return new Statement(codeBlock, false);
}
}
}

View File

@@ -1,9 +0,0 @@
/**
* Support classes for JavaPoet usage.
*/
@NonNullApi
@NonNullFields
package org.springframework.javapoet.support;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -1,46 +0,0 @@
/*
* 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.generator;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.RuntimeHints;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultCodeContribution}.
*
* @author Stephane Nicoll
*/
class DefaultCodeContributionTests {
@Test
void newCodeContributionIsEmpty() {
CodeContribution contribution = new DefaultCodeContribution(new RuntimeHints());
assertThat(contribution.statements().isEmpty()).isTrue();
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
}
@Test
void codeContributionReusesRuntimeHints() {
RuntimeHints runtimeHints = new RuntimeHints();
CodeContribution contribution = new DefaultCodeContribution(runtimeHints);
assertThat(contribution.runtimeHints()).isSameAs(runtimeHints);
}
}

View File

@@ -1,100 +0,0 @@
/*
* 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.generator;
import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Test;
import org.springframework.javapoet.ClassName;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultGeneratedTypeContext}.
*
* @author Stephane Nicoll
*/
class DefaultGeneratedTypeContextTests {
@Test
void runtimeHints() {
DefaultGeneratedTypeContext context = createComAcmeContext();
assertThat(context.runtimeHints()).isNotNull();
}
@Test
void getGeneratedTypeMatchesGetMainGeneratedTypeForMainPackage() {
DefaultGeneratedTypeContext context = createComAcmeContext();
assertThat(context.getMainGeneratedType().getClassName()).isEqualTo(ClassName.get("com.acme", "Main"));
assertThat(context.getGeneratedType("com.acme")).isSameAs(context.getMainGeneratedType());
}
@Test
void getMainGeneratedTypeIsLazilyCreated() {
DefaultGeneratedTypeContext context = createComAcmeContext();
assertThat(context.hasGeneratedType("com.acme")).isFalse();
context.getMainGeneratedType();
assertThat(context.hasGeneratedType("com.acme")).isTrue();
}
@Test
void getGeneratedTypeRegisterInstance() {
DefaultGeneratedTypeContext context = createComAcmeContext();
assertThat(context.hasGeneratedType("com.example")).isFalse();
GeneratedType generatedType = context.getGeneratedType("com.example");
assertThat(generatedType).isNotNull();
assertThat(generatedType.getClassName().simpleName()).isEqualTo("Main");
assertThat(context.hasGeneratedType("com.example")).isTrue();
}
@Test
void getGeneratedTypeReuseInstance() {
DefaultGeneratedTypeContext context = createComAcmeContext();
GeneratedType generatedType = context.getGeneratedType("com.example");
assertThat(generatedType.getClassName().packageName()).isEqualTo("com.example");
assertThat(context.getGeneratedType("com.example")).isSameAs(generatedType);
}
@Test
void toJavaFilesWithNoTypeIsEmpty() {
DefaultGeneratedTypeContext writerContext = createComAcmeContext();
assertThat(writerContext.toJavaFiles()).hasSize(0);
}
@Test
void toJavaFilesWithDefaultTypeIsAddedLazily() {
DefaultGeneratedTypeContext writerContext = createComAcmeContext();
writerContext.getMainGeneratedType();
assertThat(writerContext.toJavaFiles()).hasSize(1);
}
@Test
void toJavaFilesWithDefaultTypeAndAdditionaTypes() {
DefaultGeneratedTypeContext writerContext = createComAcmeContext();
writerContext.getGeneratedType("com.example");
writerContext.getGeneratedType("com.another");
writerContext.getGeneratedType("com.another.another");
assertThat(writerContext.toJavaFiles()).hasSize(3);
}
private DefaultGeneratedTypeContext createComAcmeContext() {
return new DefaultGeneratedTypeContext("com.acme", packageName ->
GeneratedType.of(ClassName.get(packageName, "Main"), type -> type.addModifiers(Modifier.PUBLIC)));
}
}

View File

@@ -1,82 +0,0 @@
/*
* 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.generator;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.aot.hint.TypeReference;
import org.springframework.javapoet.ClassName;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link GeneratedTypeReference}.
*
* @author Stephane Nicoll
*/
class GeneratedTypeReferenceTests {
@ParameterizedTest
@MethodSource("reflectionTargetNames")
void hasSuitableReflectionTargetName(TypeReference typeReference, String binaryName) {
assertThat(typeReference.getName()).isEqualTo(binaryName);
}
static Stream<Arguments> reflectionTargetNames() {
return Stream.of(
Arguments.of(GeneratedTypeReference.of(ClassName.get("com.example", "Test")), "com.example.Test"),
Arguments.of(GeneratedTypeReference.of(ClassName.get("com.example", "Test", "Inner")), "com.example.Test$Inner"));
}
@Test
void createWithClassName() {
GeneratedTypeReference typeReference = GeneratedTypeReference.of(
ClassName.get("com.example", "Test"));
assertThat(typeReference.getPackageName()).isEqualTo("com.example");
assertThat(typeReference.getSimpleName()).isEqualTo("Test");
assertThat(typeReference.getCanonicalName()).isEqualTo("com.example.Test");
assertThat(typeReference.getEnclosingType()).isNull();
}
@Test
void createWithClassNameAndParent() {
GeneratedTypeReference typeReference = GeneratedTypeReference.of(
ClassName.get("com.example", "Test").nestedClass("Nested"));
assertThat(typeReference.getPackageName()).isEqualTo("com.example");
assertThat(typeReference.getSimpleName()).isEqualTo("Nested");
assertThat(typeReference.getCanonicalName()).isEqualTo("com.example.Test.Nested");
assertThat(typeReference.getEnclosingType()).satisfies(parentTypeReference -> {
assertThat(parentTypeReference.getPackageName()).isEqualTo("com.example");
assertThat(parentTypeReference.getSimpleName()).isEqualTo("Test");
assertThat(parentTypeReference.getCanonicalName()).isEqualTo("com.example.Test");
assertThat(parentTypeReference.getEnclosingType()).isNull();
});
}
@Test
void equalsWithIdenticalCanonicalNameIsTrue() {
assertThat(GeneratedTypeReference.of(ClassName.get("java.lang", "String")))
.isEqualTo(TypeReference.of(String.class));
}
}

View File

@@ -1,129 +0,0 @@
/*
* 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.generator;
import java.io.IOException;
import java.io.StringWriter;
import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.Test;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.FieldSpec;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeName;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link GeneratedType}.
*
* @author Stephane Nicoll
*/
class GeneratedTypeTests {
private static final ClassName TEST_CLASS_NAME = ClassName.get("com.acme", "Test");
@Test
void className() {
GeneratedType generatedType = new GeneratedType(TEST_CLASS_NAME,
type -> type.addModifiers(Modifier.STATIC));
assertThat(generatedType.getClassName()).isEqualTo(TEST_CLASS_NAME);
assertThat(generateCode(generatedType)).contains("static class Test {");
}
@Test
void createWithCustomField() {
GeneratedType generatedType = new GeneratedType(TEST_CLASS_NAME,
type -> type.addField(FieldSpec.builder(TypeName.BOOLEAN, "enabled").build()));
assertThat(generateCode(generatedType)).contains("boolean enabled;");
}
@Test
void customizeType() {
GeneratedType generatedType = createTestGeneratedType();
generatedType.customizeType(type -> type.addJavadoc("Test javadoc."))
.customizeType(type -> type.addJavadoc(" Another test javadoc"));
assertThat(generateCode(generatedType)).containsSequence(
"/**\n",
" * Test javadoc. Another test javadoc\n",
" */");
}
@Test
void addMethod() {
GeneratedType generatedType = createTestGeneratedType();
generatedType.addMethod(MethodSpec.methodBuilder("test").returns(Integer.class)
.addCode(CodeBlock.of("return 42;")));
assertThat(generateCode(generatedType)).containsSequence(
"\tInteger test() {\n",
"\t\treturn 42;\n",
"\t}");
}
@Test
void addMultipleMethods() {
GeneratedType generatedType = createTestGeneratedType();
generatedType.addMethod(MethodSpec.methodBuilder("first"));
generatedType.addMethod(MethodSpec.methodBuilder("second"));
assertThat(generateCode(generatedType))
.containsSequence("\tvoid first() {\n", "\t}")
.containsSequence("\tvoid second() {\n", "\t}");
}
@Test
void addSimilarMethodGenerateUniqueNames() {
GeneratedType generatedType = createTestGeneratedType();
MethodSpec firstMethod = generatedType.addMethod(MethodSpec.methodBuilder("test"));
MethodSpec secondMethod = generatedType.addMethod(MethodSpec.methodBuilder("test"));
MethodSpec thirdMethod = generatedType.addMethod(MethodSpec.methodBuilder("test"));
assertThat(firstMethod.name).isEqualTo("test");
assertThat(secondMethod.name).isEqualTo("test_");
assertThat(thirdMethod.name).isEqualTo("test__");
assertThat(generateCode(generatedType))
.containsSequence("\tvoid test() {\n", "\t}")
.containsSequence("\tvoid test_() {\n", "\t}")
.containsSequence("\tvoid test__() {\n", "\t}");
}
@Test
void addMethodWithSameNameAndDifferentArgumentsDoesNotChangeName() {
GeneratedType generatedType = createTestGeneratedType();
generatedType.addMethod(MethodSpec.methodBuilder("test"));
MethodSpec secondMethod = generatedType.addMethod(MethodSpec.methodBuilder("test")
.addParameter(String.class, "param"));
assertThat(secondMethod.name).isEqualTo("test");
}
private GeneratedType createTestGeneratedType() {
return GeneratedType.of(TEST_CLASS_NAME);
}
private String generateCode(GeneratedType generatedType) {
try {
StringWriter out = new StringWriter();
generatedType.toJavaFile().writeTo(out);
return out.toString();
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@@ -1,273 +0,0 @@
/*
* 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.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generator.ProtectedAccess.Options;
import org.springframework.core.ResolvableType;
import org.springframework.core.testfixture.aot.generator.visibility.ProtectedGenericParameter;
import org.springframework.core.testfixture.aot.generator.visibility.ProtectedParameter;
import org.springframework.core.testfixture.aot.generator.visibility.PublicFactoryBean;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link ProtectedAccess}.
*
* @author Stephane Nicoll
*/
class ProtectedAccessTests {
public static final Options DEFAULT_OPTIONS = Options.defaults().build();
private final ProtectedAccess protectedAccess = new ProtectedAccess();
@Test
void analyzeWithPublicConstructor() throws NoSuchMethodException {
this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS);
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void analyzeWithPackagePrivateConstructor() {
this.protectedAccess.analyze(ProtectedAccessor.class.getDeclaredConstructors()[0],
DEFAULT_OPTIONS);
assertPrivilegedAccess(ProtectedAccessor.class);
}
@Test
void analyzeWithPackagePrivateConstructorAndReflectionEnabled() {
Constructor<?> constructor = ProtectedAccessor.class.getDeclaredConstructors()[0];
this.protectedAccess.analyze(constructor,
Options.defaults().useReflection(member -> member.equals(constructor)).build());
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void analyzeWithPackagePrivateClass() {
this.protectedAccess.analyze(ProtectedClass.class.getDeclaredConstructors()[0], DEFAULT_OPTIONS);
assertPrivilegedAccess(ProtectedClass.class);
}
@Test
void analyzeWithPackagePrivateDeclaringType() {
this.protectedAccess.analyze(method(ProtectedClass.class, "stringBean"), DEFAULT_OPTIONS);
assertPrivilegedAccess(ProtectedClass.class);
}
@Test
void analyzeWithPackagePrivateConstructorParameter() {
this.protectedAccess.analyze(ProtectedParameter.class.getConstructors()[0], DEFAULT_OPTIONS);
assertPrivilegedAccess(ProtectedParameter.class);
}
@Test
void analyzeWithPackagePrivateConstructorGenericParameter() {
this.protectedAccess.analyze(ProtectedGenericParameter.class.getConstructors()[0], DEFAULT_OPTIONS);
assertPrivilegedAccess(ProtectedParameter.class);
}
@Test
void analyzeWithPackagePrivateMethod() {
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"), DEFAULT_OPTIONS);
assertPrivilegedAccess(PublicClass.class);
}
@Test
void analyzeWithPackagePrivateMethodAndReflectionEnabled() {
this.protectedAccess.analyze(method(PublicClass.class, "getProtectedMethod"),
Options.defaults().useReflection(member -> !Modifier.isPublic(member.getModifiers())).build());
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void analyzeWithPackagePrivateMethodReturnType() {
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"), DEFAULT_OPTIONS);
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnTypeFunction() {
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"),
Options.defaults().assignReturnType(member -> false).build());
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void analyzeWithPackagePrivateMethodReturnTypeAndAssignReturnType() {
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedReturnType"),
Options.defaults().assignReturnType(true).build());
assertPrivilegedAccess(ProtectedAccessor.class);
}
@Test
void analyzeWithPackagePrivateMethodParameter() {
this.protectedAccess.analyze(method(ProtectedAccessor.class, "methodWithProtectedParameter",
ProtectedClass.class), DEFAULT_OPTIONS);
assertPrivilegedAccess(ProtectedAccessor.class);
}
@Test
void analyzeWithPackagePrivateField() {
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"), DEFAULT_OPTIONS);
assertPrivilegedAccess(PublicClass.class);
}
@Test
void analyzeWithPackagePrivateFieldAndReflectionEnabled() {
this.protectedAccess.analyze(field(PublicClass.class, "protectedField"),
Options.defaults().useReflection(member -> true).build());
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void analyzeWithPublicFieldAndProtectedType() {
this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"), DEFAULT_OPTIONS);
assertThat(this.protectedAccess.isAccessible("com.example")).isTrue();
}
@Test
void analyzeWithPublicFieldAndProtectedTypeAssigned() {
this.protectedAccess.analyze(field(PublicClass.class, "protectedClassField"),
Options.defaults().assignReturnType(true).build());
assertPrivilegedAccess(ProtectedClass.class);
}
@Test
void analyzeWithPackagePrivateGenericArgument() {
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"),
Options.defaults().assignReturnType(true).build());
assertPrivilegedAccess(PublicFactoryBean.class);
}
@Test
void analyzeTypeWithProtectedGenericArgument() {
this.protectedAccess.analyze(PublicFactoryBean.resolveToProtectedGenericParameter());
assertPrivilegedAccess(PublicFactoryBean.class);
}
@Test
void analyzeWithRecursiveType() {
assertThat(this.protectedAccess.isProtected(ResolvableType.forClassWithGenerics(
SelfReference.class, SelfReference.class))).isEqualTo(SelfReference.class);
}
@Test
void getProtectedPackageWithPublicAccess() throws NoSuchMethodException {
this.protectedAccess.analyze(PublicClass.class.getConstructor(), DEFAULT_OPTIONS);
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isNull();
}
@Test
void getProtectedPackageWithProtectedAccessInOnePackage() {
this.protectedAccess.analyze(method(PublicFactoryBean.class, "protectedTypeFactoryBean"),
Options.defaults().assignReturnType(true).build());
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example"))
.isEqualTo(PublicFactoryBean.class.getPackageName());
}
@Test
void getProtectedPackageWithProtectedAccessInSeveralPackages() {
Method protectedMethodFirstPackage = method(PublicFactoryBean.class, "protectedTypeFactoryBean");
Method protectedMethodSecondPackage = method(ProtectedAccessor.class, "methodWithProtectedParameter",
ProtectedClass.class);
this.protectedAccess.analyze(protectedMethodFirstPackage,
Options.defaults().assignReturnType(true).build());
this.protectedAccess.analyze(protectedMethodSecondPackage, DEFAULT_OPTIONS);
assertThatThrownBy(() -> this.protectedAccess.getPrivilegedPackageName("com.example"))
.isInstanceOfSatisfying(ProtectedAccessException.class, ex ->
assertThat(ex.getProtectedElements().stream().map(ProtectedElement::getMember))
.containsOnly(protectedMethodFirstPackage, protectedMethodSecondPackage));
}
private void assertPrivilegedAccess(Class<?> target) {
assertThat(this.protectedAccess.isAccessible("com.example")).isFalse();
assertThat(this.protectedAccess.getPrivilegedPackageName("com.example")).isEqualTo(target.getPackageName());
assertThat(this.protectedAccess.isAccessible(target.getPackageName())).isTrue();
}
private static Method method(Class<?> type, String name, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(type, name, parameterTypes);
assertThat(method).isNotNull();
return method;
}
private static Field field(Class<?> type, String name) {
Field field = ReflectionUtils.findField(type, name);
assertThat(field).isNotNull();
return field;
}
@SuppressWarnings("unused")
public static class PublicClass {
String protectedField;
public ProtectedClass protectedClassField;
String getProtectedMethod() {
return this.protectedField;
}
}
@SuppressWarnings("unused")
public static class ProtectedAccessor {
ProtectedAccessor() {
}
public String methodWithProtectedParameter(ProtectedClass type) {
return "test";
}
public ProtectedClass methodWithProtectedReturnType() {
return new ProtectedClass();
}
}
@SuppressWarnings("unused")
static class ProtectedClass {
public ProtectedClass() {
}
public String stringBean() {
return "public";
}
}
static class SelfReference<T extends SelfReference<T>> {
@SuppressWarnings("unchecked")
T getThis() {
return (T) this;
}
}
}

View File

@@ -1,62 +0,0 @@
/*
* 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.generator;
import java.util.function.Function;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.support.CodeSnippet;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ResolvableTypeGenerator}.
*
* @author Stephane Nicoll
*/
class ResolvableTypeGeneratorTests {
@Test
void generateTypeForResolvableTypeWithGenericParameter() {
assertThat(generateTypeFor(
ResolvableType.forClassWithGenerics(Function.class,
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))))
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
+ "ResolvableType.forClassWithGenerics(Supplier.class, Integer.class))");
}
@Test
void generateTypeForResolvableTypeWithMixedParameter() {
assertThat(generateTypeFor(
ResolvableType.forClassWithGenerics(Function.class,
ResolvableType.forClassWithGenerics(Supplier.class, String.class),
ResolvableType.forClass(Integer.class))))
.isEqualTo("ResolvableType.forClassWithGenerics(Function.class, "
+ "ResolvableType.forClassWithGenerics(Supplier.class, String.class), "
+ "ResolvableType.forClass(Integer.class))");
}
private String generateTypeFor(ResolvableType type) {
return CodeSnippet.process(new ResolvableTypeGenerator().generateTypeFor(type));
}
}

View File

@@ -1,75 +0,0 @@
/*
* 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.javapoet.support;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.javapoet.CodeBlock;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CodeSnippet}.
*
* @author Stephane Nicoll
*/
class CodeSnippetTests {
@Test
void snippetUsesTabs() {
CodeBlock.Builder code = CodeBlock.builder();
code.beginControlFlow("if (condition)");
code.addStatement("bean.doThis()");
code.endControlFlow();
CodeSnippet codeSnippet = CodeSnippet.of(code.build());
assertThat(codeSnippet.getSnippet()).isEqualTo("""
if (condition) {
bean.doThis();
}
""");
}
@Test
void snippetResolvesImports() {
CodeSnippet codeSnippet = CodeSnippet.of(
CodeBlock.of("$T list = new $T<>()", List.class, ArrayList.class));
assertThat(codeSnippet.getSnippet()).isEqualTo("List list = new ArrayList<>()");
assertThat(codeSnippet.hasImport(List.class)).isTrue();
assertThat(codeSnippet.hasImport(ArrayList.class)).isTrue();
}
@Test
void removeIndent() {
CodeBlock.Builder code = CodeBlock.builder();
code.beginControlFlow("if (condition)");
code.addStatement("doStuff()");
code.endControlFlow();
CodeSnippet snippet = CodeSnippet.of(code.build());
assertThat(snippet.getSnippet().lines()).contains("\tdoStuff();");
assertThat(snippet.removeIndent(1).getSnippet().lines()).contains("doStuff();");
}
@Test
void processProvidesSnippet() {
assertThat(CodeSnippet.process(code -> code.add("$T list;", List.class)))
.isEqualTo("List list;");
}
}

View File

@@ -1,61 +0,0 @@
/*
* 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.javapoet.support;
import org.junit.jupiter.api.Test;
import org.springframework.javapoet.CodeBlock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link MultiCodeBlock}.
*
* @author Stephane Nicoll
*/
class MultiCodeBlockTests {
@Test
void joinWithNoElement() {
MultiCodeBlock multi = new MultiCodeBlock();
assertThat(multi.join(", ").isEmpty()).isTrue();
}
@Test
void joinWithEmptyElement() {
MultiCodeBlock multi = new MultiCodeBlock();
assertThatIllegalArgumentException().isThrownBy(() -> multi.add(CodeBlock.builder().build()));
}
@Test
void joinWithSingleElement() {
MultiCodeBlock multi = new MultiCodeBlock();
multi.add(CodeBlock.of("$S", "Hello"));
assertThat(multi.join(", ")).hasToString("\"Hello\"");
}
@Test
void joinWithSeveralElement() {
MultiCodeBlock multi = new MultiCodeBlock();
multi.add(CodeBlock.of("$S", "Hello"));
multi.add(code -> code.add("42"));
multi.add("null");
assertThat(multi.join(", ")).hasToString("\"Hello\", 42, null");
}
}

View File

@@ -1,166 +0,0 @@
/*
* 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.javapoet.support;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.javapoet.CodeBlock;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MultiStatement}.
*
* @author Stephane Nicoll
*/
class MultiStatementTests {
@Test
void isEmptyWithNoStatement() {
assertThat(new MultiStatement().isEmpty()).isTrue();
}
@Test
void isEmptyWithStatement() {
MultiStatement statements = new MultiStatement();
statements.addStatement(CodeBlock.of("int i = 0"));
assertThat(statements.isEmpty()).isFalse();
}
@Test
void singleStatementCodeBlock() {
MultiStatement statements = new MultiStatement();
statements.addStatement("field.method($S)", "hello");
CodeBlock codeBlock = statements.toCodeBlock();
assertThat(codeBlock.toString()).isEqualTo("""
field.method("hello");
""");
}
@Test
void multiStatementsCodeBlock() {
MultiStatement statements = new MultiStatement();
statements.addStatement("field.method($S)", "hello");
statements.addStatement("field.another($S)", "test");
CodeBlock codeBlock = statements.toCodeBlock();
assertThat(codeBlock.toString()).isEqualTo("""
field.method("hello");
field.another("test");
""");
}
@Test
void singleStatementLambdaBody() {
MultiStatement statements = new MultiStatement();
statements.addStatement("field.method($S)", "hello");
CodeBlock codeBlock = statements.toLambdaBody();
assertThat(codeBlock.toString()).isEqualTo("field.method(\"hello\")");
}
@Test
void singleStatementWithCallbackLambdaBody() {
MultiStatement statements = new MultiStatement();
statements.addStatement(code -> code.add("field.method($S)", "hello"));
CodeBlock codeBlock = statements.toLambdaBody();
assertThat(codeBlock.toString()).isEqualTo("field.method(\"hello\")");
}
@Test
void singleStatementWithCodeBlockLambdaBody() {
MultiStatement statements = new MultiStatement();
statements.addStatement(CodeBlock.of("field.method($S)", "hello"));
CodeBlock codeBlock = statements.toLambdaBody();
assertThat(codeBlock.toString()).isEqualTo("field.method(\"hello\")");
}
@Test
void multiStatementsLambdaBody() {
MultiStatement statements = new MultiStatement();
statements.addStatement("field.method($S)", "hello");
statements.addStatement("field.anotherMethod($S)", "hello");
CodeBlock codeBlock = statements.toLambdaBody();
assertThat(codeBlock.toString()).isEqualTo("""
field.method("hello");
field.anotherMethod("hello");""");
}
@Test
void multiStatementsWithCodeBlockRenderedAsIsLambdaBody() {
MultiStatement statements = new MultiStatement();
statements.addStatement("field.method($S)", "hello");
statements.add(CodeBlock.of(("// Hello\n")));
statements.add(code -> code.add("// World\n"));
statements.addStatement("field.anotherMethod($S)", "hello");
CodeBlock codeBlock = statements.toLambdaBody();
assertThat(codeBlock.toString()).isEqualTo("""
field.method("hello");
// Hello
// World
field.anotherMethod("hello");""");
}
@Test
void singleStatementWithLambda() {
MultiStatement statements = new MultiStatement();
statements.addStatement("field.method($S)", "hello");
CodeBlock codeBlock = statements.toLambda(CodeBlock.of("() ->"));
assertThat(codeBlock.toString()).isEqualTo("() -> field.method(\"hello\")");
}
@Test
void multiStatementsWithLambda() {
MultiStatement statements = new MultiStatement();
statements.addStatement("field.method($S)", "hello");
statements.addStatement("field.anotherMethod($S)", "hello");
CodeBlock codeBlock = statements.toLambda(CodeBlock.of("() ->"));
assertThat(codeBlock.toString().lines()).containsExactly(
"() -> {",
" field.method(\"hello\");",
" field.anotherMethod(\"hello\");",
"}");
}
@Test
void multiStatementsWithAddAllAndLambda() {
MultiStatement statements = new MultiStatement();
statements.addAll(List.of(0, 1, 2),
index -> CodeBlock.of("field[$L] = $S", index, "hello"));
CodeBlock codeBlock = statements.toLambda("() ->");
assertThat(codeBlock.toString().lines()).containsExactly(
"() -> {",
" field[0] = \"hello\";",
" field[1] = \"hello\";",
" field[2] = \"hello\";",
"}");
}
@Test
void addWithAnotherMultiStatement() {
MultiStatement statements = new MultiStatement();
statements.addStatement(CodeBlock.of("test.invoke()"));
MultiStatement another = new MultiStatement();
another.addStatement(CodeBlock.of("test.another()"));
statements.add(another);
assertThat(statements.toCodeBlock().toString()).isEqualTo("""
test.invoke();
test.another();
""");
}
}