From 269ec4f2524f9cb6e0ea5b5fc71fd010e3c1f5a9 Mon Sep 17 00:00:00 2001 From: pawellabaj Date: Thu, 9 Nov 2023 11:06:55 +0000 Subject: [PATCH 1/2] Create draft PR for #138 [skip ci] From 6ea2e862392f0f905861f0e50b22641ffc598ae8 Mon Sep 17 00:00:00 2001 From: Pawel_Labaj Date: Thu, 9 Nov 2023 14:40:16 +0100 Subject: [PATCH 2/2] fix: Generates records with complicated Generics structure --- .../generic/GenericGenerationTest.java | 56 ++++++++++++ .../src/test/resources/in/BaseType.java | 20 ++++ .../src/test/resources/in/ConcreteType.java | 20 ++++ .../src/test/resources/in/WithGeneric.java | 37 ++++++++ .../src/test/resources/in/WithSubGeneric.java | 36 ++++++++ .../resources/out/WithSubGenericRecord.java | 91 +++++++++++++++++++ .../processor/context/ComponentsFinder.java | 22 ++--- .../processor/context/ContextBuilder.java | 8 +- .../processor/context/InternalMethod.java | 8 +- .../processor/context/Memoization.java | 5 +- .../processor/context/MemoizationFinder.java | 53 ++++++++--- .../autorecord/processor/context/Method.java | 90 ++++++++++++++++++ .../processor/context/MethodDefinition.java | 2 +- .../context/SpecialMethodsFinder.java | 44 +++------ .../generator/MemoizationGenerator.java | 31 ++++++- .../processor/utils/Annotations.java | 2 +- .../autorecord/processor/utils/Methods.java | 54 ----------- 17 files changed, 454 insertions(+), 125 deletions(-) create mode 100644 modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/generic/GenericGenerationTest.java create mode 100644 modules/auto-record-tests/src/test/resources/in/BaseType.java create mode 100644 modules/auto-record-tests/src/test/resources/in/ConcreteType.java create mode 100644 modules/auto-record-tests/src/test/resources/in/WithGeneric.java create mode 100644 modules/auto-record-tests/src/test/resources/in/WithSubGeneric.java create mode 100644 modules/auto-record-tests/src/test/resources/out/WithSubGenericRecord.java create mode 100644 modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Method.java delete mode 100644 modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Methods.java diff --git a/modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/generic/GenericGenerationTest.java b/modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/generic/GenericGenerationTest.java new file mode 100644 index 0000000..c3398d5 --- /dev/null +++ b/modules/auto-record-tests/src/test/java/pl/com/labaj/autorecord/generic/GenericGenerationTest.java @@ -0,0 +1,56 @@ +package pl.com.labaj.autorecord.generic; + +/*- + * Copyright © 2023 Auto Record + * + * 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 + * + * http://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. + */ + +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import org.junit.jupiter.api.Test; +import pl.com.labaj.autorecord.processor.AutoRecordProcessor; + +import java.util.List; + +import static com.google.testing.compile.Compiler.javac; +import static org.junit.jupiter.api.Assertions.assertAll; +import static pl.com.labaj.autorecord.test.TestUtils.assertThat; +import static pl.com.labaj.autorecord.test.TestUtils.expectedGenearatedRecordName; +import static pl.com.labaj.autorecord.test.TestUtils.expectedResourceName; +import static pl.com.labaj.autorecord.test.TestUtils.inputResourceName; + +class GenericGenerationTest { + + private final Compiler compiler = javac().withProcessors(new AutoRecordProcessor()); + + @Test + void shouldGenerateRecordWithProperGenerics() { + //given + var inputInterfaces = List.of( + JavaFileObjects.forResource(inputResourceName("BaseType")), + JavaFileObjects.forResource(inputResourceName("ConcreteType")), + JavaFileObjects.forResource(inputResourceName("WithGeneric")), + JavaFileObjects.forResource(inputResourceName("WithSubGeneric"))); + var expectedRecord = JavaFileObjects.forResource(expectedResourceName("WithSubGeneric")); + + //when + var compilation = compiler.compile(inputInterfaces); + + //then + assertAll( + () -> assertThat(compilation).succeeded(), + () -> assertThat(compilation).generatedSourceFile(expectedGenearatedRecordName("WithSubGeneric")).hasSourceEquivalentTo(expectedRecord) + ); + } +} \ No newline at end of file diff --git a/modules/auto-record-tests/src/test/resources/in/BaseType.java b/modules/auto-record-tests/src/test/resources/in/BaseType.java new file mode 100644 index 0000000..e364a2c --- /dev/null +++ b/modules/auto-record-tests/src/test/resources/in/BaseType.java @@ -0,0 +1,20 @@ +package pl.com.labaj.autorecord.testcase; + +/*- + * Copyright © 2023 Auto Record + * + * 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 + * + * http://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. + */ + +interface BaseType { +} \ No newline at end of file diff --git a/modules/auto-record-tests/src/test/resources/in/ConcreteType.java b/modules/auto-record-tests/src/test/resources/in/ConcreteType.java new file mode 100644 index 0000000..732af76 --- /dev/null +++ b/modules/auto-record-tests/src/test/resources/in/ConcreteType.java @@ -0,0 +1,20 @@ +package pl.com.labaj.autorecord.testcase; + +/*- + * Copyright © 2023 Auto Record + * + * 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 + * + * http://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. + */ + +interface ConcreteType extends BaseType { +} \ No newline at end of file diff --git a/modules/auto-record-tests/src/test/resources/in/WithGeneric.java b/modules/auto-record-tests/src/test/resources/in/WithGeneric.java new file mode 100644 index 0000000..c1958d6 --- /dev/null +++ b/modules/auto-record-tests/src/test/resources/in/WithGeneric.java @@ -0,0 +1,37 @@ +package pl.com.labaj.autorecord.testcase; + +/*- + * Copyright © 2023 Auto Record + * + * 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 + * + * http://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. + */ + +import pl.com.labaj.autorecord.Memoized; + +import java.util.List; + +interface WithGeneric { + A a(); + List listOfA(); + B b(); + + @Memoized + default A memA() { + return a(); + } + + @Memoized + default String fromAB(A anA, B anB) { + return String.valueOf(anA) + String.valueOf(anB); + } +} diff --git a/modules/auto-record-tests/src/test/resources/in/WithSubGeneric.java b/modules/auto-record-tests/src/test/resources/in/WithSubGeneric.java new file mode 100644 index 0000000..5973e35 --- /dev/null +++ b/modules/auto-record-tests/src/test/resources/in/WithSubGeneric.java @@ -0,0 +1,36 @@ +package pl.com.labaj.autorecord.testcase; + +/*- + * Copyright © 2023 Auto Record + * + * 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 + * + * http://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. + */ + +import pl.com.labaj.autorecord.AutoRecord; +import pl.com.labaj.autorecord.Memoized; + +import java.util.Collection; +import java.util.List; + +@AutoRecord +interface WithSubGeneric> extends WithGeneric { + Y y(); + + @Memoized + default Y memY() { + return y(); + } + + @Memoized + String toString(); +} diff --git a/modules/auto-record-tests/src/test/resources/out/WithSubGenericRecord.java b/modules/auto-record-tests/src/test/resources/out/WithSubGenericRecord.java new file mode 100644 index 0000000..c3d6061 --- /dev/null +++ b/modules/auto-record-tests/src/test/resources/out/WithSubGenericRecord.java @@ -0,0 +1,91 @@ +package pl.com.labaj.autorecord.testcase; + +/*- + * Copyright © 2023 Auto Record + * + * 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 + * + * http://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. + */ + +import static java.util.Objects.requireNonNull; +import static java.util.Objects.requireNonNullElseGet; + +import java.lang.Override; +import java.lang.String; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.processing.Generated; + +import pl.com.labaj.autorecord.GeneratedWithAutoRecord; +import pl.com.labaj.autorecord.Memoized; +import pl.com.labaj.autorecord.memoizer.Memoizer; + +@Generated("pl.com.labaj.autorecord.AutoRecord") +@GeneratedWithAutoRecord +record WithSubGenericRecord>(ConcreteType a, + List listOfA, + X b, + Y y, + @Nullable Memoizer memAMemoizer, + @Nullable Memoizer fromABMemoizer, + @Nullable Memoizer memYMemoizer, + @Nullable Memoizer toStringMemoizer) implements WithSubGeneric { + WithSubGenericRecord { + requireNonNull(a, "a must not be null"); + requireNonNull(listOfA, "listOfA must not be null"); + requireNonNull(b, "b must not be null"); + requireNonNull(y, "y must not be null"); + + memAMemoizer = requireNonNullElseGet(memAMemoizer, Memoizer::new); + fromABMemoizer = requireNonNullElseGet(fromABMemoizer, Memoizer::new); + memYMemoizer = requireNonNullElseGet(memYMemoizer, Memoizer::new); + toStringMemoizer = requireNonNullElseGet(toStringMemoizer, Memoizer::new); + } + + WithSubGenericRecord(ConcreteType a, List listOfA, X b, Y y) { + this(a, listOfA, b, y, new Memoizer<>(), new Memoizer<>(), new Memoizer<>(), new Memoizer<>()); + } + + @Memoized + @Override + public ConcreteType memA() { + return memAMemoizer.computeIfAbsent(WithSubGeneric.super::memA); + } + + @Memoized + @Override + public String fromAB(ConcreteType anA, X anB) { + return fromABMemoizer.computeIfAbsent(() -> WithSubGeneric.super.fromAB(anA, anB)); + } + + @Memoized + @Override + public Y memY() { + return memYMemoizer.computeIfAbsent(WithSubGeneric.super::memY); + } + + @Memoized + @Override + public String toString() { + return toStringMemoizer.computeIfAbsent(this::_toString); + } + + private String _toString() { + return "WithSubGenericRecord[" + + "a = " + a + ", " + + "listOfA = " + listOfA + ", " + + "b = " + b + ", " + + "y = " + y + + "]"; + } +} diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ComponentsFinder.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ComponentsFinder.java index 72bc74d..20a1513 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ComponentsFinder.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ComponentsFinder.java @@ -18,9 +18,7 @@ import pl.com.labaj.autorecord.context.RecordComponent; import pl.com.labaj.autorecord.processor.AutoRecordProcessorException; -import pl.com.labaj.autorecord.processor.utils.Methods; -import javax.lang.model.element.ExecutableElement; import java.util.List; import java.util.Set; import java.util.function.Predicate; @@ -34,28 +32,28 @@ class ComponentsFinder { public static final String ERROR_INDICATOR = ""; - List getComponents(List allMethods, Predicate isNotSpecial) { + List getComponents(List allMethods, Predicate isNotSpecial) { return allMethods.stream() - .filter(Methods::isAbstract) + .filter(Method::isAbstract) .filter(isNotSpecial) - .filter(Methods::hasNoParameters) - .filter(Methods::isNotVoid) + .filter(Method::hasNoParameters) + .filter(Method::isNotVoid) .filter(InternalMethod::isNotInternal) .map(this::toRecordComponent) .toList(); } - private RecordComponent toRecordComponent(ExecutableElement method) { - var returnType = method.getReturnType(); + private RecordComponent toRecordComponent(Method method) { + var name = method.name(); + var returnType = method.returnType(); if (returnType.getKind() == ERROR && returnType.toString().equals(ERROR_INDICATOR)) { - throw new AutoRecordProcessorException("Cannot infer type of " + method.getSimpleName() + "() method. " + + throw new AutoRecordProcessorException("Cannot infer type of " + name + "() method. " + "Probably it is generic and not in classpath or sourcepath yet. " + - "Try to move the type class into classpath or remove generic clause from " + method.getSimpleName() + "() method."); + "Try to move the type class into classpath or remove generic clause from " + name + "() method."); } - var name = method.getSimpleName().toString(); - var annotations = annotationsAllowedFor(method.getAnnotationMirrors(), Set.of(PARAMETER, RECORD_COMPONENT)); + var annotations = annotationsAllowedFor(method.annotations(), Set.of(PARAMETER, RECORD_COMPONENT)); return new ProcessorRecordComponent(returnType, name, annotations); } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ContextBuilder.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ContextBuilder.java index 5273c0b..02d8d44 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ContextBuilder.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/ContextBuilder.java @@ -19,7 +19,6 @@ import io.soabase.recordbuilder.core.RecordBuilder; import pl.com.labaj.autorecord.AutoRecord; import pl.com.labaj.autorecord.extension.AutoRecordExtension; -import pl.com.labaj.autorecord.processor.utils.Methods; import javax.annotation.Nullable; import javax.annotation.processing.ProcessingEnvironment; @@ -30,6 +29,7 @@ import java.util.Map; import java.util.function.Consumer; +import static javax.lang.model.element.ElementKind.METHOD; import static javax.lang.model.element.Modifier.PUBLIC; import static pl.com.labaj.autorecord.processor.utils.Annotations.createAnnotationIfNeeded; @@ -61,16 +61,18 @@ public ProcessorContext buildContext(TypeElement sourceInterface, var nonNullBuilderOptions = createAnnotationIfNeeded(builderOptions, RecordBuilder.Options.class, getBuilderOptionsEnforcedValues(extensions)); var elementUtils = processingEnv.getElementUtils(); + var typeUtils = processingEnv.getTypeUtils(); var allMethods = elementUtils.getAllMembers(sourceInterface).stream() - .filter(Methods::isMethod) + .filter(element -> element.getKind() == METHOD) .map(ExecutableElement.class::cast) + .map(method -> Method.from(method, typeUtils, sourceInterface)) .toList(); boolean isPublic = sourceInterface.getModifiers().contains(PUBLIC); var typeParameters = getTypeParameters(sourceInterface); var specialMethodAnnotations = specialMethodsFinder.findSpecialMethods(allMethods); - var memoizationItems = memoizationFinder.findMemoizationItems(allMethods, nonNullRecordOptions, specialMethodsFinder::isSpecial); + var memoizationItems = memoizationFinder.findMemoizationItems(allMethods, nonNullRecordOptions, specialMethodsFinder::isSpecial, logger); var components = componentsFinder.getComponents(allMethods, specialMethodsFinder::isNotSpecial); return new ProcessorContext(processingEnv, diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/InternalMethod.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/InternalMethod.java index e6f6885..eeb0561 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/InternalMethod.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/InternalMethod.java @@ -22,7 +22,6 @@ import pl.com.labaj.autorecord.processor.AutoRecordProcessorException; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVisitor; @@ -80,12 +79,11 @@ public static Stream allInternalMethods() { return ALL_METHODS.stream(); } - public static boolean isInternal(ExecutableElement method) { - var methodName = method.getSimpleName().toString(); - return METHOD_NAMES.contains(methodName) && method.getParameters().isEmpty(); + public static boolean isInternal(Method method) { + return METHOD_NAMES.contains(method.name()) && method.hasNoParameters(); } - public static boolean isNotInternal(ExecutableElement method) { + public static boolean isNotInternal(Method method) { return !isInternal(method); } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Memoization.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Memoization.java index 1c0763d..967a595 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Memoization.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Memoization.java @@ -57,7 +57,7 @@ public boolean isPresent() { return !isEmpty(); } - public record Item(TypeMirror type, String name, List annotations, boolean internal) { + public record Item(TypeMirror type, String name, List annotations, List parameters, boolean internal) { public String getMemoizerName() { return name + "Memoizer"; } @@ -66,7 +66,8 @@ public Item mergeWith(Item otherItem) { var mergedAnnotations = Stream.concat(annotations.stream(), otherItem.annotations.stream()) .distinct() .toList(); - return new Item(type, name, mergedAnnotations, internal || otherItem.internal); + + return new Item(type, name, mergedAnnotations, parameters, internal || otherItem.internal); } } } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MemoizationFinder.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MemoizationFinder.java index 85b4ac8..b93c81a 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MemoizationFinder.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MemoizationFinder.java @@ -18,12 +18,11 @@ import pl.com.labaj.autorecord.AutoRecord; import pl.com.labaj.autorecord.Memoized; -import pl.com.labaj.autorecord.processor.utils.Methods; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; @@ -32,17 +31,37 @@ import static java.util.stream.Collectors.toMap; import static pl.com.labaj.autorecord.processor.context.InternalMethod.allInternalMethods; import static pl.com.labaj.autorecord.processor.context.InternalMethod.isInternal; -import static pl.com.labaj.autorecord.processor.utils.Methods.isAnnotatedWith; class MemoizationFinder { - List findMemoizationItems(List allMethods, AutoRecord.Options recordOptions, Predicate isSpecial) { + + private static final String MEMOIZED_CLASS_NAME = Memoized.class.getName(); + + List findMemoizationItems(List allMethods, + AutoRecord.Options recordOptions, + Predicate isSpecial, + MessagerLogger logger) { var itemsFromOptions = allInternalMethods() .filter(internalMethod -> internalMethod.isMemoizedInOptions(recordOptions)) - .map(internalMethod -> new Memoization.Item(internalMethod.type(), internalMethod.methodName(), List.of(), true)); + .map(internalMethod -> new Memoization.Item(internalMethod.type(), internalMethod.methodName(), List.of(), List.of(), true)); + + var memoizedMethods = allMethods.stream() + .filter(this::isMemoized) + .toList(); + + memoizedMethods.stream() + .filter(method -> !method.isNotVoid()) + .forEach(method -> logger.error("\"" + method.name() + "\" method is void. Can't memoize such method.")); - var itemsFromAnnotation = allMethods.stream() - .filter(method -> isAnnotatedWith(method, Memoized.class)) - .filter(Methods::isNotVoid) + memoizedMethods = memoizedMethods.stream() + .filter(Method::isNotVoid) + .toList(); + + memoizedMethods.stream() + .filter(method -> !method.hasNoParameters()) + .forEach(method -> logger.mandatoryWarning("\"" + method.name() + "\" method accepts parameters. " + + "It's not a good idea to memoize it, unless it returns result independent from parameters.")); + + var itemsFromAnnotation = memoizedMethods.stream() .map(method -> toMemoizedItem(method, isSpecial)); return Stream.concat(itemsFromOptions, itemsFromAnnotation) @@ -57,14 +76,18 @@ List findMemoizationItems(List allMethods, )); } - private Memoization.Item toMemoizedItem(ExecutableElement method, Predicate isSpecial) { - var annotations = method.getAnnotationMirrors().stream() - .map(AnnotationMirror.class::cast) - .toList(); + private boolean isMemoized(Method method) { + return method.annotations().stream() + .map(AnnotationMirror::getAnnotationType) + .map(Objects::toString) + .anyMatch(MEMOIZED_CLASS_NAME::equals); + } - return new Memoization.Item(method.getReturnType(), - method.getSimpleName().toString(), - annotations, + private Memoization.Item toMemoizedItem(Method method, Predicate isSpecial) { + return new Memoization.Item(method.returnType(), + method.name(), + method.annotations(), + method.parameters(), isInternal(method) || isSpecial.test(method)); } } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Method.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Method.java new file mode 100644 index 0000000..703b115 --- /dev/null +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/Method.java @@ -0,0 +1,90 @@ +package pl.com.labaj.autorecord.processor.context; + +/*- + * Copyright © 2023 Auto Record + * + * 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 + * + * http://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. + */ + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import java.util.ArrayList; +import java.util.List; + +import static javax.lang.model.element.Modifier.ABSTRACT; +import static javax.lang.model.type.TypeKind.VOID; + +public record Method(String name, + boolean isAbstract, + List parameters, + List annotations, + TypeMirror returnType) { + @SuppressWarnings("unchecked") + public static Method from(ExecutableElement method, Types typeUtils, TypeElement sourceInterface) { + var name = method.getSimpleName().toString(); + var modifiers = method.getModifiers(); + var isAbstract = modifiers.contains(ABSTRACT); + var returnType = findReturnType(method, typeUtils, sourceInterface); + var parameters = findParameters(method, typeUtils, sourceInterface); + var annotations = (List) method.getAnnotationMirrors(); + + return new Method(name, isAbstract, parameters, annotations, returnType); + } + + public boolean hasNoParameters() { + return parameters().isEmpty(); + } + + public boolean isNotVoid() { + return returnType.getKind() != VOID; + } + + private static TypeMirror findReturnType(ExecutableElement method, Types typeUtils, TypeElement sourceInterface) { + var methodAsMember = (ExecutableType) typeUtils.asMemberOf((DeclaredType) sourceInterface.asType(), method); + + return methodAsMember.getReturnType(); + } + + @SuppressWarnings("unchecked") + private static List findParameters(ExecutableElement method, Types typeUtils, TypeElement sourceInterface) { + var variableElements = method.getParameters(); + + if (variableElements.isEmpty()) { + return List.of(); + } + + var methodAsMember = (ExecutableType) typeUtils.asMemberOf((DeclaredType) sourceInterface.asType(), method); + var variableTypes = methodAsMember.getParameterTypes(); + + var parameters = new ArrayList(); + for (int i = 0; i < variableElements.size(); i++) { + var variable = variableElements.get(i); + + var name = variable.getSimpleName().toString(); + var type = variableTypes.get(i); + var annotations = (List) variable.getAnnotationMirrors(); + var parameter = new Parameter(name, type, annotations); + + parameters.add(parameter); + } + + return List.copyOf(parameters); + } + + public record Parameter(String name, TypeMirror type, List annotations) {} +} diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MethodDefinition.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MethodDefinition.java index 452b46f..af6da63 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MethodDefinition.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/MethodDefinition.java @@ -19,5 +19,5 @@ import java.util.List; @SuppressWarnings("rawtypes") -public record MethodDefinition(String methodName, List parameterClasses) { +public record MethodDefinition(String methodName, List parameterClasses) { } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/SpecialMethodsFinder.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/SpecialMethodsFinder.java index 3f9556c..bf4fa44 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/SpecialMethodsFinder.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/context/SpecialMethodsFinder.java @@ -18,12 +18,8 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import pl.com.labaj.autorecord.processor.AutoRecordProcessorException; -import pl.com.labaj.autorecord.processor.utils.Methods; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,51 +33,41 @@ final class SpecialMethodsFinder { SpecialMethodsFinder() {} - Map> findSpecialMethods(List allMethods) { - record Pair(MethodDefinition specialMethod, List annotations) {} + Map> findSpecialMethods(List allMethods) { + record MethodWithAnnotations(MethodDefinition specialMethod, List annotations) {} return allMethods.stream() - .filter(Methods::isAbstract) + .filter(Method::isAbstract) .filter(InternalMethod::isNotInternal) .map(method -> { var methodDefinition = toMethodDefinition(method); - var annotations = method.getAnnotationMirrors().stream() - .map(AnnotationMirror.class::cast) - .toList(); - return new Pair(methodDefinition, annotations); + return new MethodWithAnnotations(methodDefinition, method.annotations()); }) - .filter(pair -> SPECIAL_METHODS.contains(pair.specialMethod)) - .collect(toMap(Pair::specialMethod, Pair::annotations)); + .filter(methodWithAnnotations -> SPECIAL_METHODS.contains(methodWithAnnotations.specialMethod)) + .collect(toMap(MethodWithAnnotations::specialMethod, MethodWithAnnotations::annotations)); } - boolean isSpecial(ExecutableElement method) { + boolean isSpecial(Method method) { var specialMethod = toMethodDefinition(method); return SPECIAL_METHODS.contains(specialMethod); } - boolean isNotSpecial(ExecutableElement method) { + boolean isNotSpecial(Method method) { return !isSpecial(method); } - private MethodDefinition toMethodDefinition(ExecutableElement method) { - var methodName = method.getSimpleName().toString(); - var rawClasses = method.getParameters().stream() - .map(VariableElement::asType) + private MethodDefinition toMethodDefinition(Method method) { + var rawClasses = method.parameters().stream() + .map(Method.Parameter::type) .map(TypeName::get) - .map(this::forName) + .map(this::toClassName) .toList(); - return new MethodDefinition(methodName, rawClasses); + return new MethodDefinition(method.name(), rawClasses); } - @SuppressWarnings("rawtypes") - private Class forName(TypeName typeName) { - var className = typeName instanceof ParameterizedTypeName parameterizedTypeName ? parameterizedTypeName.rawType.toString() : typeName.toString(); - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - throw new AutoRecordProcessorException("Cannot find class", e); - } + private String toClassName(TypeName typeName) { + return typeName instanceof ParameterizedTypeName parameterizedTypeName ? parameterizedTypeName.rawType.toString() : typeName.toString(); } } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/MemoizationGenerator.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/MemoizationGenerator.java index 714af8d..8cc20e5 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/MemoizationGenerator.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/generator/MemoizationGenerator.java @@ -18,6 +18,7 @@ import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import pl.com.labaj.autorecord.Memoized; @@ -25,12 +26,14 @@ import pl.com.labaj.autorecord.extension.AutoRecordExtension; import pl.com.labaj.autorecord.processor.context.Memoization; import pl.com.labaj.autorecord.processor.context.MemoizerType; +import pl.com.labaj.autorecord.processor.context.Method; import pl.com.labaj.autorecord.processor.context.ProcessorContext; import java.util.List; import java.util.Set; import static java.lang.annotation.ElementType.METHOD; +import static java.util.stream.Collectors.joining; import static javax.lang.model.element.Modifier.PUBLIC; import static pl.com.labaj.autorecord.processor.utils.Annotations.createAnnotationSpecs; @@ -47,6 +50,7 @@ public void generate(TypeSpec.Builder recordBuilder, StaticImports staticImports .forEach(recordBuilder::addMethod)); } + @SuppressWarnings("unchecked") private MethodSpec toMemoizedMethodSpec(Memoization.Item item) { var name = item.name(); var annotations = createAnnotationSpecs(item.annotations(), @@ -57,10 +61,23 @@ private MethodSpec toMemoizedMethodSpec(Memoization.Item item) { var statement = methodStatement(item, name, memoizerType); context.memoizerCollector().accept(memoizerType); - return MethodSpec.methodBuilder(name) + var methodSpecBuilder = MethodSpec.methodBuilder(name) .addModifiers(PUBLIC) .addAnnotations(annotations) - .returns(TypeName.get(item.type())) + .returns(TypeName.get(item.type())); + + item.parameters().stream() + .map(parameter -> { + var type = TypeName.get(parameter.type()); + var parameterAnnotations = createAnnotationSpecs(parameter.annotations()); + + return ParameterSpec.builder(type, parameter.name()) + .addAnnotations(parameterAnnotations) + .build(); + }) + .forEach(methodSpecBuilder::addParameter); + + return methodSpecBuilder .addStatement(statement) .build(); } @@ -73,6 +90,14 @@ private CodeBlock methodStatement(Memoization.Item item, String name, MemoizerTy return CodeBlock.of("return $L.$L(this::_$L)", memoizerName, computeMethod, name); } - return CodeBlock.of("return $L.$L($L.super::$L)", memoizerName, computeMethod, context.interfaceName(), name); + if (item.parameters().isEmpty()) { + return CodeBlock.of("return $L.$L($L.super::$L)", memoizerName, computeMethod, context.interfaceName(), name); + } + + var parameterNames = item.parameters().stream() + .map(Method.Parameter::name) + .collect(joining(", ", "(", ")")); + + return CodeBlock.of("return $L.$L(() -> $L.super.$L$L)", memoizerName, computeMethod, context.interfaceName(), name, parameterNames); } } diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java index f7f2ec3..75da806 100644 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java +++ b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Annotations.java @@ -167,7 +167,7 @@ record Member(String name, CodeBlock value) {} return Stream.of(annotationBuilder.build()); } catch (ClassNotFoundException e) { - throw new AutoRecordProcessorException("Cannot merge annptations", e); + throw new AutoRecordProcessorException("Cannot merge annotations", e); } }) .toList(); diff --git a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Methods.java b/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Methods.java deleted file mode 100644 index 74ca8f2..0000000 --- a/modules/auto-record/src/main/java/pl/com/labaj/autorecord/processor/utils/Methods.java +++ /dev/null @@ -1,54 +0,0 @@ -package pl.com.labaj.autorecord.processor.utils; - -/*- - * Copyright © 2023 Auto Record - * - * 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 - * - * http://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. - */ - -import org.apiguardian.api.API; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import java.lang.annotation.Annotation; - -import static javax.lang.model.element.ElementKind.METHOD; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.type.TypeKind.VOID; -import static org.apiguardian.api.API.Status.INTERNAL; -import static pl.com.labaj.autorecord.processor.utils.Annotations.getAnnotation; - -@API(status = INTERNAL) -public final class Methods { - private Methods() {} - - public static boolean isMethod(Element element) { - return element.getKind() == METHOD; - } - - public static boolean hasNoParameters(ExecutableElement method) { - return method.getParameters().isEmpty(); - } - - public static boolean isNotVoid(ExecutableElement method) { - return method.getReturnType().getKind() != VOID; - } - - public static boolean isAbstract(ExecutableElement method) { - return method.getModifiers().contains(ABSTRACT); - } - - public static boolean isAnnotatedWith(ExecutableElement method, Class annotationClass) { - return getAnnotation(method, annotationClass).isPresent(); - } -}