diff --git a/pom.xml b/pom.xml index d0be3a6..203bc84 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ UTF-8 UTF-8 - 16 + 17 3.8.1 3.2.0 diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java index 09c75b7..3f632bb 100644 --- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/ElementUtils.java @@ -26,7 +26,6 @@ import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeMirror; @@ -115,10 +114,10 @@ public class ElementUtils { return new ClassType(ParameterizedTypeName.get(builderClassName, typeNames), builderClassName.simpleName()); } - public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv, RecordComponentElement recordComponent, List accessorAnnotations, List canonicalConstructorAnnotations) { - var typeName = TypeName.get(recordComponent.asType()); - var rawTypeName = TypeName.get(processingEnv.getTypeUtils().erasure(recordComponent.asType())); - return new RecordClassType(typeName, rawTypeName, recordComponent.getSimpleName().toString(), accessorAnnotations, canonicalConstructorAnnotations); + public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv, TypeMirror recordComponent, String simpleName, List accessorAnnotations, List canonicalConstructorAnnotations) { + var typeName = TypeName.get(recordComponent); + var rawTypeName = TypeName.get(processingEnv.getTypeUtils().erasure(recordComponent)); + return new RecordClassType(typeName, rawTypeName, simpleName, accessorAnnotations, canonicalConstructorAnnotations); } public static String getWithMethodName(ClassType component, String prefix) { @@ -132,6 +131,10 @@ public class ElementUtils { public static String getBuilderName(TypeElement element, RecordBuilder.Options metaData, ClassType classType, String suffix) { // generate the class name var baseName = classType.name() + suffix; + return getBuilderName(baseName, element, metaData); + } + + public static String getBuilderName(String baseName, TypeElement element, RecordBuilder.Options metaData) { return metaData.prefixEnclosingClassNames() ? (getBuilderNamePrefix(element.getEnclosingElement()) + baseName) : baseName; } diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java index e2c902e..ad9797c 100644 --- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java @@ -15,15 +15,10 @@ */ package io.soabase.recordbuilder.processor; -import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.EXCLUDE_WILDCARD_TYPES; -import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.STANDARD_FOR_SETTER; -import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName; -import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName; -import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation; -import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation; +import com.squareup.javapoet.*; +import io.soabase.recordbuilder.core.RecordBuilder; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; +import javax.lang.model.element.Modifier; import java.util.*; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -33,16 +28,17 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import com.squareup.javapoet.*; -import io.soabase.recordbuilder.core.RecordBuilder; +import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.EXCLUDE_WILDCARD_TYPES; +import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.STANDARD_FOR_SETTER; +import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName; +import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation; +import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation; class InternalRecordBuilderProcessor { private final RecordBuilder.Options metaData; - private final ClassType recordClassType; private final String packageName; private final ClassType builderClassType; private final List typeVariables; - private final List recordComponents; private final TypeSpec builderType; private final TypeSpec.Builder builder; private final String uniqueVarName; @@ -53,20 +49,17 @@ class InternalRecordBuilderProcessor { private static final TypeName validType = ClassName.get("javax.validation", "Valid"); private static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator", "RecordBuilderValidator"); private static final TypeVariableName rType = TypeVariableName.get("R"); - private final ProcessingEnvironment processingEnv; + private final RecordSpecification recordSpecification; - InternalRecordBuilderProcessor(ProcessingEnvironment processingEnv, TypeElement record, RecordBuilder.Options metaData, Optional packageNameOpt) { - this.processingEnv = processingEnv; - var recordActualPackage = ElementUtils.getPackageName(record); + InternalRecordBuilderProcessor(RecordSpecification recordSpecification, RecordBuilder.Options metaData, Optional packageNameOpt) { + this.recordSpecification = recordSpecification; this.metaData = metaData; - recordClassType = ElementUtils.getClassType(record, record.getTypeParameters()); - packageName = packageNameOpt.orElse(recordActualPackage); - builderClassType = ElementUtils.getClassType(packageName, getBuilderName(record, metaData, recordClassType, metaData.suffix()), record.getTypeParameters()); - typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()); - recordComponents = buildRecordComponents(record); + packageName = packageNameOpt.orElse(recordSpecification.recordActualPackage()); + builderClassType = ElementUtils.getClassType(packageName, recordSpecification.builderName(), recordSpecification.typeParameters()); + typeVariables = recordSpecification.typeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()); uniqueVarName = getUniqueVarName(); notNullPattern = Pattern.compile(metaData.interpretNotNullsPattern()); - collectionBuilderUtils = new CollectionBuilderUtils(recordComponents, this.metaData); + collectionBuilderUtils = new CollectionBuilderUtils(recordSpecification.recordComponents(), this.metaData); builder = TypeSpec.classBuilder(builderClassType.name()) .addAnnotation(generatedRecordBuilderAnnotation) @@ -75,7 +68,7 @@ class InternalRecordBuilderProcessor { if (metaData.addClassRetainedGenerated()) { builder.addAnnotation(recordBuilderGeneratedAnnotation); } - addVisibility(recordActualPackage.equals(packageName), record.getModifiers()); + addVisibility(recordSpecification.recordActualPackage().equals(packageName), recordSpecification.modifiers()); if (metaData.enableWither()) { addWithNestedClass(); } @@ -86,7 +79,7 @@ class InternalRecordBuilderProcessor { if (metaData.addStaticBuilder()) { addStaticBuilder(); } - if (recordComponents.size() > 0) { + if (recordSpecification.recordComponents().size() > 0) { addAllArgsConstructor(); } addStaticDefaultBuilderMethod(); @@ -99,7 +92,7 @@ class InternalRecordBuilderProcessor { addToStringMethod(); addHashCodeMethod(); addEqualsMethod(); - recordComponents.forEach(component -> { + recordSpecification.recordComponents().forEach(component -> { add1Field(component); add1SetterMethod(component); if (metaData.enableGetters()) { @@ -139,19 +132,6 @@ class InternalRecordBuilderProcessor { } } - private List buildRecordComponents(TypeElement record) { - var accessorAnnotations = record.getRecordComponents().stream().map(e -> e.getAccessor().getAnnotationMirrors()).collect(Collectors.toList()); - var canonicalConstructorAnnotations = ElementUtils.findCanonicalConstructor(record).map(constructor -> ((ExecutableElement) constructor).getParameters().stream().map(Element::getAnnotationMirrors).collect(Collectors.toList())).orElse(List.of()); - var recordComponents = record.getRecordComponents(); - return IntStream.range(0, recordComponents.size()) - .mapToObj(index -> { - var thisAccessorAnnotations = (accessorAnnotations.size() > index) ? accessorAnnotations.get(index) : List.of(); - var thisCanonicalConstructorAnnotations = (canonicalConstructorAnnotations.size() > index) ? canonicalConstructorAnnotations.get(index) : List.of(); - return ElementUtils.getRecordClassType(processingEnv, recordComponents.get(index), thisAccessorAnnotations, thisCanonicalConstructorAnnotations); - }) - .collect(Collectors.toList()); - } - private void addWithNestedClass() { /* Adds a nested interface that adds withers similar to: @@ -164,16 +144,16 @@ class InternalRecordBuilderProcessor { */ var classBuilder = TypeSpec.interfaceBuilder(metaData.withClassName()) .addAnnotation(generatedRecordBuilderAnnotation) - .addJavadoc("Add withers to {@code $L}\n", recordClassType.name()) + .addJavadoc("Add withers to {@code $L}\n", recordSpecification.recordClassType().name()) .addModifiers(Modifier.PUBLIC) .addTypeVariables(typeVariables); if (metaData.addClassRetainedGenerated()) { classBuilder.addAnnotation(recordBuilderGeneratedAnnotation); } - recordComponents.forEach(component -> addNestedGetterMethod(classBuilder, component, prefixedName(component, true))); + recordSpecification.recordComponents().forEach(component -> addNestedGetterMethod(classBuilder, component, prefixedName(component, true))); addWithBuilderMethod(classBuilder); addWithSuppliedBuilderMethod(classBuilder); - IntStream.range(0, recordComponents.size()).forEach(index -> add1WithMethod(classBuilder, recordComponents.get(index), index)); + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> add1WithMethod(classBuilder, recordSpecification.recordComponents().get(index), index)); if (metaData.addFunctionalMethodsToWith()) { classBuilder.addType(buildFunctionalInterface("Function", true)) .addType(buildFunctionalInterface("Consumer", false)) @@ -195,10 +175,10 @@ class InternalRecordBuilderProcessor { */ var classBuilder = TypeSpec.interfaceBuilder(metaData.beanClassName()) .addAnnotation(generatedRecordBuilderAnnotation) - .addJavadoc("Add getters to {@code $L}\n", recordClassType.name()) + .addJavadoc("Add getters to {@code $L}\n", recordSpecification.recordClassType().name()) .addModifiers(Modifier.PUBLIC) .addTypeVariables(typeVariables); - recordComponents.forEach(component -> { + recordSpecification.recordComponents().forEach(component -> { if (prefixedName(component, true).equals(component.name())) { return; } @@ -229,7 +209,7 @@ class InternalRecordBuilderProcessor { .addJavadoc("Return a new record built from the builder passed to the given consumer") .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) .addParameter(parameter) - .returns(recordClassType.typeName()) + .returns(recordSpecification.recordClassType().typeName()) .addCode(codeBlockBuilder.build()) .build(); classBuilder.addMethod(methodSpec); @@ -263,7 +243,7 @@ class InternalRecordBuilderProcessor { private String getUniqueVarName(String prefix) { var name = prefix + "r"; - var alreadyExists = recordComponents.stream() + var alreadyExists = recordSpecification.recordComponents().stream() .map(ClassType::name) .anyMatch(n -> n.equals(name)); return alreadyExists ? getUniqueVarName(prefix + "_") : name; @@ -283,7 +263,7 @@ class InternalRecordBuilderProcessor { if (metaData.useValidationApi()) { codeBlockBuilder.add("$T.validate(", validatorTypeName); } - codeBlockBuilder.add("new $T(", recordClassType.typeName()); + codeBlockBuilder.add("new $T(", recordSpecification.recordClassType().typeName()); addComponentCallsAsArguments(index, codeBlockBuilder); codeBlockBuilder.add(")"); if (metaData.useValidationApi()) { @@ -296,11 +276,11 @@ class InternalRecordBuilderProcessor { addConstructorAnnotations(component, parameterSpecBuilder); var methodSpec = MethodSpec.methodBuilder(methodName) .addAnnotation(generatedRecordBuilderAnnotation) - .addJavadoc("Return a new instance of {@code $L} with a new value for {@code $L}\n", recordClassType.name(), component.name()) + .addJavadoc("Return a new instance of {@code $L} with a new value for {@code $L}\n", recordSpecification.recordClassType().name(), component.name()) .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) .addParameter(parameterSpecBuilder.build()) .addCode(codeBlockBuilder.build()) - .returns(recordClassType.typeName()) + .returns(recordSpecification.recordClassType().typeName()) .build(); classBuilder.addMethod(methodSpec); } @@ -328,11 +308,11 @@ class InternalRecordBuilderProcessor { } private void addComponentCallsAsArguments(int index, CodeBlock.Builder codeBlockBuilder) { - IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> { + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(parameterIndex -> { if (parameterIndex > 0) { codeBlockBuilder.add(", "); } - RecordClassType parameterComponent = recordComponents.get(parameterIndex); + RecordClassType parameterComponent = recordSpecification.recordComponents().get(parameterIndex); if (parameterIndex == index) { collectionBuilderUtils.addShimCall(codeBlockBuilder, parameterComponent); } else { @@ -364,14 +344,14 @@ class InternalRecordBuilderProcessor { } */ CodeBlock codeBlock = buildCodeBlock(); - var builder = MethodSpec.methodBuilder(recordClassType.name()) - .addJavadoc("Static constructor/builder. Can be used instead of new $L(...)\n", recordClassType.name()) + var builder = MethodSpec.methodBuilder(recordSpecification.recordClassType().name()) + .addJavadoc("Static constructor/builder. Can be used instead of new $L(...)\n", recordSpecification.recordClassType().name()) .addTypeVariables(typeVariables) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addAnnotation(generatedRecordBuilderAnnotation) - .returns(recordClassType.typeName()) + .returns(recordSpecification.recordClassType().typeName()) .addCode(codeBlock); - recordComponents.forEach(component -> { + recordSpecification.recordComponents().forEach(component -> { var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name()); addConstructorAnnotations(component, parameterSpecBuilder); builder.addParameter(parameterSpecBuilder.build()); @@ -381,7 +361,7 @@ class InternalRecordBuilderProcessor { private void addNullCheckCodeBlock(CodeBlock.Builder builder) { if (metaData.interpretNotNulls()) { - for (int i = 0; i < recordComponents.size(); ++i) { + for (int i = 0; i < recordSpecification.recordComponents().size(); ++i) { addNullCheckCodeBlock(builder, i); } } @@ -389,7 +369,7 @@ class InternalRecordBuilderProcessor { private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) { if (metaData.interpretNotNulls()) { - var component = recordComponents.get(index); + var component = recordSpecification.recordComponents().get(index); if (!collectionBuilderUtils.isImmutableCollection(component)) { if (!component.typeName().isPrimitive() && isNullAnnotated(component)) { builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required"); @@ -416,7 +396,7 @@ class InternalRecordBuilderProcessor { var constructorBuilder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) .addAnnotation(generatedRecordBuilderAnnotation); - recordComponents.forEach(component -> { + recordSpecification.recordComponents().forEach(component -> { constructorBuilder.addParameter(component.typeName(), component.name()); constructorBuilder.addStatement("this.$L = $L", component.name(), component.name()); }); @@ -433,11 +413,11 @@ class InternalRecordBuilderProcessor { } */ var codeBuilder = CodeBlock.builder().add("return \"$L[", builderClassType.name()); - IntStream.range(0, recordComponents.size()).forEach(index -> { + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> { if (index > 0) { codeBuilder.add(", "); } - String name = recordComponents.get(index).name(); + String name = recordSpecification.recordComponents().get(index).name(); codeBuilder.add("$L=\" + $L + \"", name, name); }); codeBuilder.add("]\""); @@ -462,11 +442,11 @@ class InternalRecordBuilderProcessor { } */ var codeBuilder = CodeBlock.builder().add("return $T.hash(", Objects.class); - IntStream.range(0, recordComponents.size()).forEach(index -> { + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> { if (index > 0) { codeBuilder.add(", "); } - codeBuilder.add("$L", recordComponents.get(index).name()); + codeBuilder.add("$L", recordSpecification.recordComponents().get(index).name()); }); codeBuilder.add(")"); @@ -499,7 +479,7 @@ class InternalRecordBuilderProcessor { String wildcardList = typeVariables.stream().map(__ -> "?").collect(Collectors.joining(",")); codeBuilder.add("(o instanceof $L<$L> $L)", builderClassType.name(), wildcardList, uniqueVarName); } - recordComponents.forEach(recordComponent -> { + recordSpecification.recordComponents().forEach(recordComponent -> { String name = recordComponent.name(); if (recordComponent.typeName().isPrimitive()) { codeBuilder.add("\n&& ($L == $L.$L)", name, uniqueVarName, name); @@ -533,7 +513,7 @@ class InternalRecordBuilderProcessor { .addJavadoc("Return a new record instance with all fields set to the current values in this builder\n") .addModifiers(Modifier.PUBLIC) .addAnnotation(generatedRecordBuilderAnnotation) - .returns(recordClassType.typeName()) + .returns(recordSpecification.recordClassType().typeName()) .addCode(codeBlock) .build(); builder.addMethod(methodSpec); @@ -546,11 +526,11 @@ class InternalRecordBuilderProcessor { var codeBuilder = CodeBlock.builder(); - IntStream.range(0, recordComponents.size()).forEach(index -> { - var recordComponent = recordComponents.get(index); + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> { + var recordComponent = recordSpecification.recordComponents().get(index); if (collectionBuilderUtils.isImmutableCollection(recordComponent)) { codeBuilder.add("$[$L = ", recordComponent.name()); - collectionBuilderUtils.addShimCall(codeBuilder, recordComponents.get(index)); + collectionBuilderUtils.addShimCall(codeBuilder, recordSpecification.recordComponents().get(index)); codeBuilder.add(";\n$]"); } }); @@ -560,12 +540,12 @@ class InternalRecordBuilderProcessor { if (metaData.useValidationApi()) { codeBuilder.add("$T.validate(", validatorTypeName); } - codeBuilder.add("new $T(", recordClassType.typeName()); - IntStream.range(0, recordComponents.size()).forEach(index -> { + codeBuilder.add("new $T(", recordSpecification.recordClassType().typeName()); + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> { if (index > 0) { codeBuilder.add(", "); } - codeBuilder.add("$L", recordComponents.get(index).name()); + codeBuilder.add("$L", recordSpecification.recordComponents().get(index).name()); }); codeBuilder.add(")"); if (metaData.useValidationApi()) { @@ -612,17 +592,17 @@ class InternalRecordBuilderProcessor { fromWithClassBuilder.addAnnotation(recordBuilderGeneratedAnnotation); } - fromWithClassBuilder.addField(recordClassType.typeName(), "from", Modifier.PRIVATE, Modifier.FINAL); + fromWithClassBuilder.addField(recordSpecification.recordClassType().typeName(), "from", Modifier.PRIVATE, Modifier.FINAL); MethodSpec constructorSpec = MethodSpec.constructorBuilder() - .addParameter(recordClassType.typeName(), "from") + .addParameter(recordSpecification.recordClassType().typeName(), "from") .addStatement("this.from = from") .addModifiers(Modifier.PRIVATE) .addAnnotation(generatedRecordBuilderAnnotation) .build(); fromWithClassBuilder.addMethod(constructorSpec); - IntStream.range(0, recordComponents.size()).forEach(index -> { - var component = recordComponents.get(index); + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> { + var component = recordSpecification.recordComponents().get(index); MethodSpec methodSpec = MethodSpec.methodBuilder(prefixedName(component, true)) .returns(component.typeName()) .addAnnotation(Override.class) @@ -651,7 +631,7 @@ class InternalRecordBuilderProcessor { .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addAnnotation(generatedRecordBuilderAnnotation) .addTypeVariables(typeVariables) - .addParameter(recordClassType.typeName(), metaData.fromMethodName()) + .addParameter(recordSpecification.recordClassType().typeName(), metaData.fromMethodName()) .returns(buildWithTypeName()) .addStatement("return new $L$L(from)", metaData.fromWithClassName(), typeVariables.isEmpty() ? "" : "<>") .build(); @@ -667,11 +647,11 @@ class InternalRecordBuilderProcessor { } */ var codeBuilder = CodeBlock.builder().add("return new $T(", builderClassType.typeName()); - IntStream.range(0, recordComponents.size()).forEach(index -> { + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> { if (index > 0) { codeBuilder.add(", "); } - codeBuilder.add("from.$L()", recordComponents.get(index).name()); + codeBuilder.add("from.$L()", recordSpecification.recordComponents().get(index).name()); }); codeBuilder.add(")"); @@ -680,7 +660,7 @@ class InternalRecordBuilderProcessor { .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addAnnotation(generatedRecordBuilderAnnotation) .addTypeVariables(typeVariables) - .addParameter(recordClassType.typeName(), "from") + .addParameter(recordSpecification.recordClassType().typeName(), "from") .returns(builderClassType.typeName()) .addStatement(codeBuilder.build()) .build(); @@ -716,11 +696,11 @@ class InternalRecordBuilderProcessor { } */ var codeBuilder = CodeBlock.builder().add("return $T.of(", Stream.class); - IntStream.range(0, recordComponents.size()).forEach(index -> { + IntStream.range(0, recordSpecification.recordComponents().size()).forEach(index -> { if (index > 0) { codeBuilder.add(",\n "); } - var name = recordComponents.get(index).name(); + var name = recordSpecification.recordComponents().get(index).name(); codeBuilder.add("new $T<>($S, record.$L())", AbstractMap.SimpleImmutableEntry.class, name, name); }); codeBuilder.add(")"); @@ -729,7 +709,7 @@ class InternalRecordBuilderProcessor { var methodSpec = MethodSpec.methodBuilder(metaData.componentsMethodName()) .addJavadoc("Return a stream of the record components as map entries keyed with the component name and the value as the component value\n") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addParameter(recordClassType.typeName(), "record") + .addParameter(recordSpecification.recordClassType().typeName(), "record") .addAnnotation(generatedRecordBuilderAnnotation) .addTypeVariables(typeVariables) .returns(mapEntryType) @@ -1070,7 +1050,7 @@ class InternalRecordBuilderProcessor { */ var localTypeVariables = isMap ? typeVariablesWithReturn() : typeVariables; var methodBuilder = MethodSpec.methodBuilder("apply").addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); - recordComponents.forEach(component -> { + recordSpecification.recordComponents().forEach(component -> { var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name()); addConstructorAnnotations(component, parameterSpecBuilder); methodBuilder.addParameter(parameterSpecBuilder.build()); diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordInterfaceProcessor.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordInterfaceProcessor.java index 8f30f5f..80f7a8f 100644 --- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordInterfaceProcessor.java +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordInterfaceProcessor.java @@ -48,7 +48,7 @@ class InternalRecordInterfaceProcessor { private static final Set javaBeanPrefixes = Set.of("get", "is"); - private record Component(ExecutableElement element, Optional alternateName) { + record Component(ExecutableElement element, Optional alternateName) { } InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional packageNameOpt, boolean fromTemplate) { @@ -75,7 +75,7 @@ class InternalRecordInterfaceProcessor { if (addRecordBuilder) { ClassType builderClassType = ElementUtils.getClassType(packageName, getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "." + metaData.withClassName(), iface.getTypeParameters()); - builder.addAnnotation(RecordBuilder.class); +// builder.addAnnotation(RecordBuilder.class); builder.addSuperinterface(builderClassType.typeName()); if (fromTemplate) { builder.addAnnotation(AnnotationSpec.get(metaData)); @@ -108,6 +108,10 @@ class InternalRecordInterfaceProcessor { return recordClassType; } + List recordComponents() { + return recordComponents; + } + String toRecord(String classSource) { // javapoet does yet support records - so a class was created and we can reshape it // The class will look something like this: diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java index 4d50aea..a47080c 100644 --- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordBuilderProcessor.java @@ -52,7 +52,7 @@ public class RecordBuilderProcessor @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> process(annotation, element))); - return false; + return true; } @Override @@ -154,6 +154,12 @@ public class RecordBuilderProcessor return; } writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord); + + if (addRecordBuilder) { + RecordSpecification recordSpecification = RecordSpecification.fromInterfaceProcessor(processingEnv, metaData, internalProcessor.recordComponents(), internalProcessor.recordClassType(), element, internalProcessor.packageName()); + var internalRecordProcessor = new InternalRecordBuilderProcessor(recordSpecification, metaData, packageName); + writeRecordBuilderJavaFile(element, internalProcessor.packageName(), internalRecordProcessor.builderClassType(), internalRecordProcessor.builderType(), metaData); + } } private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional packageName) { @@ -165,7 +171,8 @@ public class RecordBuilderProcessor processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record); return; } - var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName); + RecordSpecification recordSpecification = RecordSpecification.fromRecordTypeElement(processingEnv, metaData, record); + var internalProcessor = new InternalRecordBuilderProcessor(recordSpecification, metaData, packageName); writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData); } diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordSpecification.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordSpecification.java new file mode 100644 index 0000000..20eab78 --- /dev/null +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/RecordSpecification.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Jordan Zimmerman + * + * 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. + */ +package io.soabase.recordbuilder.processor; + +import io.soabase.recordbuilder.core.RecordBuilder; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.*; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName; + +public record RecordSpecification(ClassType recordClassType, String builderName, String recordActualPackage, List typeParameters, List recordComponents, Set modifiers) { + public static RecordSpecification fromRecordTypeElement(ProcessingEnvironment processingEnv, RecordBuilder.Options metaData, TypeElement record) { + var recordClassType = ElementUtils.getClassType(record, record.getTypeParameters()); + var builderName = getBuilderName(record, metaData, recordClassType, metaData.suffix()); + var recordActualPackage = ElementUtils.getPackageName(record); + var recordComponents = buildRecordComponents(processingEnv, record); + return new RecordSpecification(recordClassType, builderName, recordActualPackage, record.getTypeParameters(), recordComponents, record.getModifiers()); + } + + public static RecordSpecification fromInterfaceProcessor(ProcessingEnvironment processingEnv, RecordBuilder.Options metaData, List recordComponents, ClassType recordClassType, TypeElement iface, String packageName) { + List mappedRecordComponents = recordComponents.stream() + .map(recordComponent -> { + var accessorAnnotations = recordComponent.element().getAnnotationMirrors(); + return ElementUtils.getRecordClassType(processingEnv, recordComponent.element().getReturnType(), recordComponent.element().getSimpleName().toString(), accessorAnnotations, List.of()); + }) + .toList(); + var builderName = getBuilderName(recordClassType.name(), iface, metaData) + metaData.suffix(); + return new RecordSpecification(recordClassType, builderName, packageName, iface.getTypeParameters(), mappedRecordComponents, iface.getModifiers()); + } + + private static List buildRecordComponents(ProcessingEnvironment processingEnv, TypeElement record) { + var accessorAnnotations = record.getRecordComponents().stream().map(e -> e.getAccessor().getAnnotationMirrors()).toList(); + var canonicalConstructorAnnotations = ElementUtils.findCanonicalConstructor(record).map(constructor -> ((ExecutableElement) constructor).getParameters().stream().map(Element::getAnnotationMirrors).collect(Collectors.toList())).orElse(List.of()); + var recordComponents = record.getRecordComponents(); + return IntStream.range(0, recordComponents.size()) + .mapToObj(index -> { + var thisAccessorAnnotations = (accessorAnnotations.size() > index) ? accessorAnnotations.get(index) : List.of(); + var thisCanonicalConstructorAnnotations = (canonicalConstructorAnnotations.size() > index) ? canonicalConstructorAnnotations.get(index) : List.of(); + RecordComponentElement recordComponentElement = recordComponents.get(index); + return ElementUtils.getRecordClassType(processingEnv, recordComponentElement.asType(), recordComponentElement.getSimpleName().toString(), thisAccessorAnnotations, thisCanonicalConstructorAnnotations); + }) + .collect(Collectors.toList()); + } +}