Complete refactor of AOT concepts
Remove the AOT code that now has an alternative API. Closes gh-28414
This commit is contained in:
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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(")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
""");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user