Support options for RecordInterface
- Copy any @RecordBuilder.Options to the generated Record - Support RecordInterface templates as well Closes #64
This commit is contained in:
@@ -84,3 +84,6 @@ public @interface MyCoRecordBuilder {
|
||||
|
||||
Now, you can use `@MyCoRecordBuilder` instead of `@RecordBuilder` and the record
|
||||
will be built with options as specified.
|
||||
|
||||
Note: the template mechanism also supports `@RecordInterface` templates via the `asRecordInterface` attribute.
|
||||
When it is set a `@RecordInterface` template is created instead.
|
||||
|
||||
@@ -151,5 +151,7 @@ public @interface RecordBuilder {
|
||||
@Inherited
|
||||
@interface Template {
|
||||
RecordBuilder.Options options();
|
||||
|
||||
boolean asRecordInterface() default false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.core;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
|
||||
@@ -55,7 +55,7 @@ class InternalRecordBuilderProcessor {
|
||||
InternalRecordBuilderProcessor(ProcessingEnvironment processingEnv, TypeElement record, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
this.processingEnv = processingEnv;
|
||||
var recordActualPackage = ElementUtils.getPackageName(record);
|
||||
this.metaData = getMetaData(record, metaData);
|
||||
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());
|
||||
@@ -128,11 +128,6 @@ class InternalRecordBuilderProcessor {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private RecordBuilder.Options getMetaData(TypeElement record, RecordBuilder.Options metaData) {
|
||||
var recordSpecificMetaData = record.getAnnotation(RecordBuilder.Options.class);
|
||||
return (recordSpecificMetaData != null) ? recordSpecificMetaData : metaData;
|
||||
}
|
||||
|
||||
private void addWithNestedClass() {
|
||||
/*
|
||||
Adds a nested interface that adds withers similar to:
|
||||
|
||||
@@ -15,13 +15,10 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.ParameterSpec;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@@ -29,13 +26,7 @@ import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -56,9 +47,10 @@ class InternalRecordInterfaceProcessor {
|
||||
|
||||
private static final Set<String> javaBeanPrefixes = Set.of("get", "is");
|
||||
|
||||
private record Component(ExecutableElement element, Optional<String> alternateName){}
|
||||
private record Component(ExecutableElement element, Optional<String> alternateName) {
|
||||
}
|
||||
|
||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt, boolean fromTemplate) {
|
||||
this.processingEnv = processingEnv;
|
||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
||||
recordComponents = getRecordComponents(iface);
|
||||
@@ -81,6 +73,14 @@ class InternalRecordInterfaceProcessor {
|
||||
ClassType builderClassType = ElementUtils.getClassType(packageName, getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "." + metaData.withClassName(), iface.getTypeParameters());
|
||||
builder.addAnnotation(RecordBuilder.class);
|
||||
builder.addSuperinterface(builderClassType.typeName());
|
||||
if (fromTemplate) {
|
||||
builder.addAnnotation(AnnotationSpec.get(metaData));
|
||||
} else {
|
||||
var options = iface.getAnnotation(RecordBuilder.Options.class);
|
||||
if (options != null) {
|
||||
builder.addAnnotation(AnnotationSpec.get(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alternateMethods = buildAlternateMethods(recordComponents);
|
||||
@@ -88,8 +88,7 @@ class InternalRecordInterfaceProcessor {
|
||||
recordType = builder.build();
|
||||
}
|
||||
|
||||
boolean isValid()
|
||||
{
|
||||
boolean isValid() {
|
||||
return !recordComponents.isEmpty();
|
||||
}
|
||||
|
||||
@@ -105,8 +104,7 @@ class InternalRecordInterfaceProcessor {
|
||||
return recordClassType;
|
||||
}
|
||||
|
||||
String toRecord(String classSource)
|
||||
{
|
||||
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:
|
||||
/*
|
||||
@@ -139,8 +137,7 @@ class InternalRecordInterfaceProcessor {
|
||||
return fixedRecord.toString();
|
||||
}
|
||||
|
||||
private MethodSpec generateArgumentList()
|
||||
{
|
||||
private MethodSpec generateArgumentList() {
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder(FAKE_METHOD_NAME);
|
||||
recordComponents.forEach(component -> {
|
||||
String name = component.alternateName.orElseGet(() -> component.element.getSimpleName().toString());
|
||||
@@ -153,18 +150,18 @@ class InternalRecordInterfaceProcessor {
|
||||
|
||||
private List<String> buildAlternateMethods(List<Component> recordComponents) {
|
||||
return recordComponents.stream()
|
||||
.filter(component -> component.alternateName.isPresent())
|
||||
.map(component -> {
|
||||
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||
.returns(ClassName.get(component.element.getReturnType()))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addCode("return $L();", component.alternateName.get())
|
||||
.build();
|
||||
return method.toString();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
.filter(component -> component.alternateName.isPresent())
|
||||
.map(component -> {
|
||||
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||
.returns(ClassName.get(component.element.getReturnType()))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addCode("return $L();", component.alternateName.get())
|
||||
.build();
|
||||
return method.toString();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Component> getRecordComponents(TypeElement iface) {
|
||||
@@ -180,8 +177,8 @@ class InternalRecordInterfaceProcessor {
|
||||
}
|
||||
return components;
|
||||
}
|
||||
private static class IllegalInterface extends RuntimeException
|
||||
{
|
||||
|
||||
private static class IllegalInterface extends RuntimeException {
|
||||
public IllegalInterface(String message) {
|
||||
super(message);
|
||||
}
|
||||
@@ -219,14 +216,13 @@ class InternalRecordInterfaceProcessor {
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<String> stripBeanPrefix(String name)
|
||||
{
|
||||
private Optional<String> stripBeanPrefix(String name) {
|
||||
return javaBeanPrefixes.stream()
|
||||
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
|
||||
.findFirst()
|
||||
.map(prefix -> {
|
||||
var stripped = name.substring(prefix.length());
|
||||
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
|
||||
});
|
||||
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
|
||||
.findFirst()
|
||||
.map(prefix -> {
|
||||
var stripped = name.substring(prefix.length());
|
||||
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,12 +77,12 @@ public class RecordBuilderProcessor
|
||||
{
|
||||
String annotationClass = annotation.getQualifiedName().toString();
|
||||
if (annotationClass.equals(RECORD_BUILDER)) {
|
||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
processRecordBuilder((TypeElement) element, metaData, Optional.empty());
|
||||
var typeElement = (TypeElement) element;
|
||||
processRecordBuilder(typeElement, getMetaData(typeElement), Optional.empty());
|
||||
}
|
||||
else if (annotationClass.equals(RECORD_INTERFACE)) {
|
||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
processRecordInterface((TypeElement) element, element.getAnnotation(RecordInterface.class).addRecordBuilder(), metaData, Optional.empty());
|
||||
var typeElement = (TypeElement) element;
|
||||
processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(), getMetaData(typeElement), Optional.empty(), false);
|
||||
}
|
||||
else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
@@ -90,11 +90,20 @@ public class RecordBuilderProcessor
|
||||
} else {
|
||||
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
|
||||
if (recordBuilderTemplate != null) {
|
||||
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
||||
if (recordBuilderTemplate.asRecordInterface()) {
|
||||
processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(), Optional.empty(), true);
|
||||
} else {
|
||||
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RecordBuilder.Options getMetaData(TypeElement typeElement) {
|
||||
var recordSpecificMetaData = typeElement.getAnnotation(RecordBuilder.Options.class);
|
||||
return (recordSpecificMetaData != null) ? recordSpecificMetaData : RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
}
|
||||
|
||||
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass)
|
||||
{
|
||||
var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass);
|
||||
@@ -121,7 +130,7 @@ public class RecordBuilderProcessor
|
||||
if (annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(values, "addRecordBuilder");
|
||||
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
||||
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName));
|
||||
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName), false);
|
||||
}
|
||||
else {
|
||||
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
||||
@@ -158,13 +167,13 @@ public class RecordBuilderProcessor
|
||||
return findPackageElement(actualElement, includedClass.getEnclosingElement());
|
||||
}
|
||||
|
||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName)
|
||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName, boolean fromTemplate)
|
||||
{
|
||||
if (!element.getKind().isInterface()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordInterface only valid for interfaces.", element);
|
||||
return;
|
||||
}
|
||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName);
|
||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName, fromTemplate);
|
||||
if (!internalProcessor.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import io.soabase.recordbuilder.core.RecordInterface;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordInterface
|
||||
@RecordBuilder.Options(useImmutableCollections = true)
|
||||
public interface CollectionInterface<T, X extends Point> {
|
||||
List<T> l();
|
||||
|
||||
Set<T> s();
|
||||
|
||||
Map<T, X> m();
|
||||
|
||||
Collection<X> c();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.test;
|
||||
|
||||
@MyInterfaceTemplate
|
||||
public interface InterfaceTemplateTest {
|
||||
String name();
|
||||
|
||||
int age();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder.Template(options = @RecordBuilder.Options(
|
||||
fileComment = "This is a test",
|
||||
withClassName = "Com"),
|
||||
asRecordInterface = true
|
||||
)
|
||||
public @interface MyInterfaceTemplate {
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
@@ -23,6 +24,15 @@ import static io.soabase.recordbuilder.test.foo.PointBuilder.Point;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TestCollections {
|
||||
@Test
|
||||
void testRecordBuilderOptionsCopied() {
|
||||
try {
|
||||
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", List.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
Assertions.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionRecordDefaultValues() {
|
||||
var defaultValues = CollectionRecordBuilder.builder().build();
|
||||
|
||||
Reference in New Issue
Block a user