Compare commits
2 Commits
master
...
record-bui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e690800a02 | ||
|
|
410cae4d32 |
25
README.md
25
README.md
@@ -231,6 +231,31 @@ Notes:
|
|||||||
- Methods with default implementations are used in the generation unless they are annotated with `@IgnoreDefaultMethod`
|
- Methods with default implementations are used in the generation unless they are annotated with `@IgnoreDefaultMethod`
|
||||||
- If you do not want a record builder generated, annotate your interface as `@RecordInterface(addRecordBuilder = false)`
|
- If you do not want a record builder generated, annotate your interface as `@RecordInterface(addRecordBuilder = false)`
|
||||||
|
|
||||||
|
## Generation Via Includes
|
||||||
|
|
||||||
|
An alternate method of generation is to use the Include variants of the annotations. These variants
|
||||||
|
act on lists of specified classes. This allows the source classes to be pristine or even come from
|
||||||
|
libraries where you are not able to annotate the source.
|
||||||
|
|
||||||
|
E.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
import some.library.code.ImportedRecord
|
||||||
|
import some.library.code.ImportedInterface
|
||||||
|
|
||||||
|
@RecordBuilder.Include({
|
||||||
|
ImportedRecord.class // generates a record builder for ImportedRecord
|
||||||
|
})
|
||||||
|
@RecordInterface.Include({
|
||||||
|
ImportedInterface.class // generates a record interface for ImportedInterface
|
||||||
|
})
|
||||||
|
public void Placeholder {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The target package for generation is the same as the package that contains the "Include"
|
||||||
|
annotation. Use `packagePattern` to change this (see Javadoc for details).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Maven
|
### Maven
|
||||||
|
|||||||
4
pom.xml
4
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>1.11.ea-SNAPSHOT</version>
|
<version>1.11.ea</version>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>record-builder-core</module>
|
<module>record-builder-core</module>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<url>https://github.com/randgalt/record-builder</url>
|
<url>https://github.com/randgalt/record-builder</url>
|
||||||
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
|
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
|
||||||
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
|
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
|
||||||
<tag>HEAD</tag>
|
<tag>record-builder-1.11.ea</tag>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<issueManagement>
|
<issueManagement>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>1.11.ea-SNAPSHOT</version>
|
<version>1.11.ea</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -23,4 +23,19 @@ import java.lang.annotation.Target;
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public @interface RecordBuilder {
|
public @interface RecordBuilder {
|
||||||
|
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@interface Include {
|
||||||
|
Class<?>[] value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern used to generate the package for the generated class. The value
|
||||||
|
* is the literal package name however two replacement values can be used. '@'
|
||||||
|
* is replaced with the package of the Include annotation. '*' is replaced with
|
||||||
|
* the package of the included class.
|
||||||
|
*
|
||||||
|
* @return package pattern
|
||||||
|
*/
|
||||||
|
String packagePattern() default "@";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,22 @@ import java.lang.annotation.Target;
|
|||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public @interface RecordInterface {
|
public @interface RecordInterface {
|
||||||
boolean addRecordBuilder() default true;
|
boolean addRecordBuilder() default true;
|
||||||
|
|
||||||
|
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@interface Include {
|
||||||
|
Class<?>[] value();
|
||||||
|
|
||||||
|
boolean addRecordBuilder() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern used to generate the package for the generated class. The value
|
||||||
|
* is the literal package name however two replacement values can be used. '@'
|
||||||
|
* is replaced with the package of the Include annotation. '*' is replaced with
|
||||||
|
* the package of the included class.
|
||||||
|
*
|
||||||
|
* @return package pattern
|
||||||
|
*/
|
||||||
|
String packagePattern() default "@";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>1.11.ea-SNAPSHOT</version>
|
<version>1.11.ea</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,63 @@ import com.squareup.javapoet.ParameterizedTypeName;
|
|||||||
import com.squareup.javapoet.TypeName;
|
import com.squareup.javapoet.TypeName;
|
||||||
import com.squareup.javapoet.TypeVariableName;
|
import com.squareup.javapoet.TypeVariableName;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
||||||
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
|
import javax.lang.model.element.AnnotationValue;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.element.RecordComponentElement;
|
import javax.lang.model.element.RecordComponentElement;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.element.TypeParameterElement;
|
import javax.lang.model.element.TypeParameterElement;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ElementUtils {
|
public class ElementUtils {
|
||||||
|
public static Optional<? extends AnnotationMirror> findAnnotationMirror(ProcessingEnvironment processingEnv, Element element, String annotationClass) {
|
||||||
|
return processingEnv.getElementUtils().getAllAnnotationMirrors(element).stream()
|
||||||
|
.filter(e -> e.getAnnotationType().toString().equals(annotationClass))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<? extends AnnotationValue> getAnnotationValue(Map<? extends ExecutableElement, ? extends AnnotationValue> values, String name) {
|
||||||
|
return values.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(e -> e.getKey().getSimpleName().toString().equals(name))
|
||||||
|
.map(Map.Entry::getValue)
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static List<TypeMirror> getClassesAttribute(AnnotationValue attribute)
|
||||||
|
{
|
||||||
|
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
|
||||||
|
return values.stream().map(v -> (TypeMirror)v.getValue()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getBooleanAttribute(AnnotationValue attribute)
|
||||||
|
{
|
||||||
|
Object value = (attribute != null) ? attribute.getValue() : null;
|
||||||
|
if ( value != null )
|
||||||
|
{
|
||||||
|
return Boolean.parseBoolean(String.valueOf(value));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStringAttribute(AnnotationValue attribute, String defaultValue)
|
||||||
|
{
|
||||||
|
Object value = (attribute != null) ? attribute.getValue() : null;
|
||||||
|
if ( value != null )
|
||||||
|
{
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getPackageName(TypeElement typeElement) {
|
public static String getPackageName(TypeElement typeElement) {
|
||||||
while (typeElement.getNestingKind().isNested()) {
|
while (typeElement.getNestingKind().isNested()) {
|
||||||
Element enclosingElement = typeElement.getEnclosingElement();
|
Element enclosingElement = typeElement.getEnclosingElement();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import java.util.AbstractMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
@@ -53,11 +54,11 @@ class InternalRecordBuilderProcessor
|
|||||||
private final TypeSpec builderType;
|
private final TypeSpec builderType;
|
||||||
private final TypeSpec.Builder builder;
|
private final TypeSpec.Builder builder;
|
||||||
|
|
||||||
InternalRecordBuilderProcessor(TypeElement record, RecordBuilderMetaData metaData)
|
InternalRecordBuilderProcessor(TypeElement record, RecordBuilderMetaData metaData, Optional<String> packageNameOpt)
|
||||||
{
|
{
|
||||||
this.metaData = metaData;
|
this.metaData = metaData;
|
||||||
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
||||||
packageName = ElementUtils.getPackageName(record);
|
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(record));
|
||||||
builderClassType = ElementUtils.getClassType(packageName, getBuilderName(record, metaData, recordClassType, metaData.suffix()), record.getTypeParameters());
|
builderClassType = ElementUtils.getClassType(packageName, getBuilderName(record, metaData, recordClassType, metaData.suffix()), record.getTypeParameters());
|
||||||
typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
|
typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
|
||||||
recordComponents = record.getRecordComponents().stream().map(ElementUtils::getClassType).collect(Collectors.toList());
|
recordComponents = record.getRecordComponents().stream().map(ElementUtils::getClassType).collect(Collectors.toList());
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.processor;
|
package io.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
import com.squareup.javapoet.*;
|
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 io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
||||||
import io.soabase.recordbuilder.core.RecordInterface;
|
|
||||||
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
|
||||||
|
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.ElementKind;
|
import javax.lang.model.element.ElementKind;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
@@ -28,7 +30,13 @@ import javax.lang.model.element.Modifier;
|
|||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.type.TypeKind;
|
import javax.lang.model.type.TypeKind;
|
||||||
import javax.tools.Diagnostic;
|
import javax.tools.Diagnostic;
|
||||||
import java.util.*;
|
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.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -46,9 +54,9 @@ class InternalRecordInterfaceProcessor {
|
|||||||
|
|
||||||
private static final String FAKE_METHOD_NAME = "__FAKE__";
|
private static final String FAKE_METHOD_NAME = "__FAKE__";
|
||||||
|
|
||||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, RecordInterface recordInterface, RecordBuilderMetaData metaData) {
|
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilderMetaData metaData, Optional<String> packageNameOpt) {
|
||||||
this.processingEnv = processingEnv;
|
this.processingEnv = processingEnv;
|
||||||
packageName = ElementUtils.getPackageName(iface);
|
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
||||||
recordComponents = getRecordComponents(iface);
|
recordComponents = getRecordComponents(iface);
|
||||||
this.iface = iface;
|
this.iface = iface;
|
||||||
|
|
||||||
@@ -65,7 +73,7 @@ class InternalRecordInterfaceProcessor {
|
|||||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||||
.addTypeVariables(typeVariables);
|
.addTypeVariables(typeVariables);
|
||||||
|
|
||||||
if (recordInterface.addRecordBuilder()) {
|
if (addRecordBuilder) {
|
||||||
ClassType builderClassType = ElementUtils.getClassType(packageName, getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "." + metaData.withClassName(), iface.getTypeParameters());
|
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());
|
builder.addSuperinterface(builderClassType.typeName());
|
||||||
|
|||||||
@@ -18,39 +18,46 @@ package io.soabase.recordbuilder.processor;
|
|||||||
import com.squareup.javapoet.AnnotationSpec;
|
import com.squareup.javapoet.AnnotationSpec;
|
||||||
import com.squareup.javapoet.JavaFile;
|
import com.squareup.javapoet.JavaFile;
|
||||||
import com.squareup.javapoet.TypeSpec;
|
import com.squareup.javapoet.TypeSpec;
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
||||||
import io.soabase.recordbuilder.core.RecordInterface;
|
import io.soabase.recordbuilder.core.RecordInterface;
|
||||||
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
import javax.annotation.processing.*;
|
import javax.annotation.processing.Filer;
|
||||||
|
import javax.annotation.processing.Generated;
|
||||||
|
import javax.annotation.processing.RoundEnvironment;
|
||||||
import javax.lang.model.SourceVersion;
|
import javax.lang.model.SourceVersion;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.PackageElement;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
import javax.tools.Diagnostic;
|
import javax.tools.Diagnostic;
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.RECORD_BUILDER;
|
|
||||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.RECORD_INTERFACE;
|
|
||||||
|
|
||||||
@SupportedAnnotationTypes({RECORD_BUILDER, RECORD_INTERFACE})
|
|
||||||
public class RecordBuilderProcessor extends AbstractProcessor {
|
public class RecordBuilderProcessor extends AbstractProcessor {
|
||||||
public static final String RECORD_BUILDER = "io.soabase.recordbuilder.core.RecordBuilder";
|
private static final String RECORD_BUILDER = RecordBuilder.class.getName();
|
||||||
public static final String RECORD_INTERFACE = "io.soabase.recordbuilder.core.RecordInterface";
|
private static final String RECORD_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.');
|
||||||
|
private static final String RECORD_INTERFACE = RecordInterface.class.getName();
|
||||||
|
private static final String RECORD_INTERFACE_INCLUDE = RecordInterface.Include.class.getName().replace('$', '.');
|
||||||
|
|
||||||
static final AnnotationSpec generatedRecordBuilderAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RECORD_BUILDER).build();
|
static final AnnotationSpec generatedRecordBuilderAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordBuilder.class.getName()).build();
|
||||||
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RECORD_INTERFACE).build();
|
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordInterface.class.getName()).build();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||||
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation)
|
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> process(annotation, element)));
|
||||||
.forEach(element -> process(annotation, element))
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getSupportedAnnotationTypes() {
|
||||||
|
return Set.of(RECORD_BUILDER, RECORD_BUILDER_INCLUDE, RECORD_INTERFACE, RECORD_INTERFACE_INCLUDE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SourceVersion getSupportedSourceVersion() {
|
public SourceVersion getSupportedSourceVersion() {
|
||||||
// we don't directly return RELEASE_14 as that may
|
// we don't directly return RELEASE_14 as that may
|
||||||
@@ -63,37 +70,102 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
private void process(TypeElement annotation, Element element) {
|
private void process(TypeElement annotation, Element element) {
|
||||||
var metaData = new RecordBuilderMetaDataLoader(processingEnv, s -> processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, s)).getMetaData();
|
var metaData = new RecordBuilderMetaDataLoader(processingEnv, s -> processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, s)).getMetaData();
|
||||||
|
|
||||||
if (annotation.getQualifiedName().toString().equals(RECORD_BUILDER)) {
|
String annotationClass = annotation.getQualifiedName().toString();
|
||||||
processRecordBuilder((TypeElement) element, metaData);
|
if ( annotationClass.equals(RECORD_BUILDER) )
|
||||||
} else if (annotation.getQualifiedName().toString().equals(RECORD_INTERFACE)) {
|
{
|
||||||
processRecordInterface((TypeElement) element, element.getAnnotation(RecordInterface.class), metaData);
|
processRecordBuilder((TypeElement)element, metaData, Optional.empty());
|
||||||
} else {
|
}
|
||||||
|
else if ( annotationClass.equals(RECORD_INTERFACE) )
|
||||||
|
{
|
||||||
|
processRecordInterface((TypeElement)element, element.getAnnotation(RecordInterface.class).addRecordBuilder(), metaData, Optional.empty());
|
||||||
|
}
|
||||||
|
else if ( annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE) )
|
||||||
|
{
|
||||||
|
processIncludes(element, metaData, annotationClass);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
throw new RuntimeException("Unknown annotation: " + annotation);
|
throw new RuntimeException("Unknown annotation: " + annotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecordInterface(TypeElement element, RecordInterface recordInterface, RecordBuilderMetaData metaData) {
|
private void processIncludes(Element element, RecordBuilderMetaData metaData, String annotationClass) {
|
||||||
if (!element.getKind().isInterface()) {
|
var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass);
|
||||||
|
if ( annotationMirrorOpt.isEmpty() )
|
||||||
|
{
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation mirror for: " + annotationClass, element);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var values = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirrorOpt.get());
|
||||||
|
var classes = ElementUtils.getAnnotationValue(values, "value");
|
||||||
|
if ( classes.isEmpty() )
|
||||||
|
{
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation value for: " + annotationClass, element);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(values, "packagePattern").orElse(null), "*");
|
||||||
|
var classesMirrors = ElementUtils.getClassesAttribute(classes.get());
|
||||||
|
for ( TypeMirror mirror : classesMirrors )
|
||||||
|
{
|
||||||
|
TypeElement typeElement = (TypeElement)processingEnv.getTypeUtils().asElement(mirror);
|
||||||
|
if ( typeElement == null )
|
||||||
|
{
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + mirror, element);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var packageName = buildPackageName(packagePattern, element, typeElement);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass) {
|
||||||
|
String replaced = packagePattern.replace("*", ((PackageElement)includedClass.getEnclosingElement()).getQualifiedName().toString());
|
||||||
|
if (builderElement instanceof PackageElement) {
|
||||||
|
return replaced.replace("@", ((PackageElement)builderElement).getQualifiedName().toString());
|
||||||
|
}
|
||||||
|
return replaced.replace("@", ((PackageElement)builderElement.getEnclosingElement()).getQualifiedName().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilderMetaData metaData, Optional<String> packageName) {
|
||||||
|
if ( !element.getKind().isInterface() )
|
||||||
|
{
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordInterface only valid for interfaces.", element);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordInterface only valid for interfaces.", element);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, recordInterface, metaData);
|
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName);
|
||||||
if (!internalProcessor.isValid()) {
|
if ( !internalProcessor.isValid() )
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord);
|
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecordBuilder(TypeElement record, RecordBuilderMetaData metaData) {
|
private void processRecordBuilder(TypeElement record, RecordBuilderMetaData metaData, Optional<String> packageName) {
|
||||||
// we use string based name comparison for the element kind,
|
// we use string based name comparison for the element kind,
|
||||||
// as the ElementKind.RECORD enum doesn't exist on JRE releases
|
// as the ElementKind.RECORD enum doesn't exist on JRE releases
|
||||||
// older than Java 14, and we don't want to throw unexpected
|
// older than Java 14, and we don't want to throw unexpected
|
||||||
// NoSuchFieldErrors
|
// NoSuchFieldErrors
|
||||||
if (!"RECORD".equals(record.getKind().name())) {
|
if ( !"RECORD".equals(record.getKind().name()) )
|
||||||
|
{
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var internalProcessor = new InternalRecordBuilderProcessor(record, metaData);
|
var internalProcessor = new InternalRecordBuilderProcessor(record, metaData, packageName);
|
||||||
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData);
|
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +177,7 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
{
|
{
|
||||||
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name() : (packageName + "." + builderClassType.name());
|
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name() : (packageName + "." + builderClassType.name());
|
||||||
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
||||||
try ( Writer writer = sourceFile.openWriter() )
|
try (Writer writer = sourceFile.openWriter())
|
||||||
{
|
{
|
||||||
javaFile.writeTo(writer);
|
javaFile.writeTo(writer);
|
||||||
}
|
}
|
||||||
@@ -128,7 +200,7 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
{
|
{
|
||||||
String fullyQualifiedName = packageName.isEmpty() ? classType.name() : (packageName + "." + classType.name());
|
String fullyQualifiedName = packageName.isEmpty() ? classType.name() : (packageName + "." + classType.name());
|
||||||
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
||||||
try ( Writer writer = sourceFile.openWriter() )
|
try (Writer writer = sourceFile.openWriter())
|
||||||
{
|
{
|
||||||
writer.write(recordSourceCode);
|
writer.write(recordSourceCode);
|
||||||
}
|
}
|
||||||
@@ -140,11 +212,10 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilderMetaData metaData) {
|
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilderMetaData metaData) {
|
||||||
var javaFileBuilder = JavaFile.builder(packageName, type)
|
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true).indent(metaData.fileIndent());
|
||||||
.skipJavaLangImports(true)
|
|
||||||
.indent(metaData.fileIndent());
|
|
||||||
var comment = metaData.fileComment();
|
var comment = metaData.fileComment();
|
||||||
if ((comment != null) && !comment.isEmpty()) {
|
if ( (comment != null) && !comment.isEmpty() )
|
||||||
|
{
|
||||||
javaFileBuilder.addFileComment(comment);
|
javaFileBuilder.addFileComment(comment);
|
||||||
}
|
}
|
||||||
return javaFileBuilder.build();
|
return javaFileBuilder.build();
|
||||||
@@ -152,7 +223,8 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
|
|
||||||
private void handleWriteError(TypeElement element, IOException e) {
|
private void handleWriteError(TypeElement element, IOException e) {
|
||||||
String message = "Could not create source file";
|
String message = "Could not create source file";
|
||||||
if (e.getMessage() != null) {
|
if ( e.getMessage() != null )
|
||||||
|
{
|
||||||
message = message + ": " + e.getMessage();
|
message = message + ": " + e.getMessage();
|
||||||
}
|
}
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>1.11.ea-SNAPSHOT</version>
|
<version>1.11.ea</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.RecordInterface;
|
||||||
|
|
||||||
|
@RecordInterface.Include({
|
||||||
|
Thingy.class
|
||||||
|
})
|
||||||
|
public class Builder {
|
||||||
|
}
|
||||||
@@ -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 java.time.Instant;
|
||||||
|
|
||||||
|
public interface Customer {
|
||||||
|
String name();
|
||||||
|
|
||||||
|
String address();
|
||||||
|
|
||||||
|
Instant activeDate();
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public record Pair<T, U>(T t, U u) {}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public record Point(int x, int y) {}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
public interface Thingy<T> {
|
||||||
|
T getIt();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RecordBuilder.Include(value = {Point.class, Pair.class}, packagePattern = "*.foo")
|
||||||
|
@RecordInterface.Include(value = Customer.class, addRecordBuilder = false, packagePattern = "*.bar")
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
import io.soabase.recordbuilder.core.RecordInterface;
|
||||||
Reference in New Issue
Block a user