Compare commits
29 Commits
record-bui
...
record-bui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0f4bfbe88 | ||
|
|
09168b6104 | ||
|
|
d4e24993c9 | ||
|
|
8304cb1f83 | ||
|
|
7215ad3241 | ||
|
|
f7d65c7619 | ||
|
|
29ebe52914 | ||
|
|
d9c143aa8b | ||
|
|
0647b66bcd | ||
|
|
08a4471d15 | ||
|
|
a1acfb2863 | ||
|
|
82bc1c1625 | ||
|
|
2d029a2786 | ||
|
|
2625b3d849 | ||
|
|
cc5e189f94 | ||
|
|
9125ba0660 | ||
|
|
664809fc69 | ||
|
|
9d011b82aa | ||
|
|
35125f550d | ||
|
|
cb2bd68697 | ||
|
|
f40cfd48ee | ||
|
|
d9f2adc2f9 | ||
|
|
99832d50ae | ||
|
|
1203109108 | ||
|
|
7e4675f7c0 | ||
|
|
8ab9d8bdca | ||
|
|
24edc5e70c | ||
|
|
3832cb3881 | ||
|
|
802dd1f880 |
@@ -19,6 +19,6 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 16
|
||||
java-version: 16.0.0-ea
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
24
.github/workflows/maven_java17.yml
vendored
24
.github/workflows/maven_java17.yml
vendored
@@ -1,24 +0,0 @@
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Maven Build - Java 17
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
11
README.md
11
README.md
@@ -21,7 +21,7 @@ _Details:_
|
||||
- [Record From Interface Details](#RecordInterface-Example)
|
||||
- [Generation Via Includes](#generation-via-includes)
|
||||
- [Usage](#usage)
|
||||
- [Customizing](customizing.md) (e.g. add immutable collections, etc.)
|
||||
- [Customizing](customizing.md)
|
||||
- [Java 15 Versions](#java-15-versions)
|
||||
|
||||
## RecordBuilder Example
|
||||
@@ -86,8 +86,6 @@ _Hat tip to [Benji Weber](https://benjiweber.co.uk/blog/2020/09/19/fun-with-java
|
||||
|
||||
## Builder Class Definition
|
||||
|
||||
(Note: you can see a builder class built using `@RecordBuilderFull` here: [SingleItemsBuilder.java](https://gist.github.com/Randgalt/8aa487a847ea2acdd76d702f7cf17d6a))
|
||||
|
||||
The full builder class is defined as:
|
||||
|
||||
```java
|
||||
@@ -166,8 +164,8 @@ public class NameAndAgeBuilder {
|
||||
* Return a stream of the record components as map entries keyed with the component name and the value as the component value
|
||||
*/
|
||||
public static Stream<Map.Entry<String, Object>> stream(NameAndAge record) {
|
||||
return Stream.of(Map.entry("name", record.name()),
|
||||
Map.entry("age", record.age()));
|
||||
return Stream.of(new AbstractMap.SimpleEntry<>("name", record.name()),
|
||||
new AbstractMap.SimpleEntry<>("age", record.age()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,9 +286,6 @@ public void Placeholder {
|
||||
}
|
||||
```
|
||||
|
||||
`@RecordBuilder.Include` also supports a `packages` attribute that includes all records
|
||||
in the listed packages.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
[◀︎ RecordBuilder](README.md) • Customizing RecordBuilder
|
||||
|
||||
# RecordBuilderFull
|
||||
|
||||
Note: `@RecordBuilderFull` has most optional features enabled. It's an alternate
|
||||
form of `@RecordBuilder` that uses the [templating mechanism](#create-a-custom-annotation) to
|
||||
enable optional features.
|
||||
|
||||
# Customizing RecordBuilder
|
||||
|
||||
RecordBuilder can be customized in a number of ways. The types of customizations will change over time. See
|
||||
[@RecordBuilder.Options](record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java)
|
||||
for the current set of customizations and their default values. For example, the `useImmutableCollections` option
|
||||
adds special handling for record components of type `java.util.List`, `java.util.Set`, `java.util.Map` and `java.util.Collection`. When the record is built, any components of these types are passed through an added shim method that uses the corresponding immutable collection (e.g. `List.copyOf(o)`) or an empty immutable collection if the component is `null`.
|
||||
for the current set of customizations and their default values.
|
||||
|
||||
You can:
|
||||
|
||||
@@ -90,6 +83,3 @@ 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.
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -5,7 +5,7 @@
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>28</version>
|
||||
<version>24</version>
|
||||
|
||||
<modules>
|
||||
<module>record-builder-core</module>
|
||||
@@ -80,7 +80,7 @@
|
||||
<url>https://github.com/randgalt/record-builder</url>
|
||||
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
|
||||
<tag>record-builder-28</tag>
|
||||
<tag>record-builder-24</tag>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>28</version>
|
||||
<version>24</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -25,32 +25,12 @@ public @interface RecordBuilder {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Inherited
|
||||
@interface Include {
|
||||
/**
|
||||
* @return list of classes to include
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
|
||||
/**
|
||||
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
|
||||
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
|
||||
* (i.e. a union of classes from both attributes).
|
||||
*
|
||||
* @return list of classes
|
||||
*/
|
||||
Class<?>[] classes() default {};
|
||||
|
||||
/**
|
||||
* Optional list of package names. All records in the packages will get processed as
|
||||
* if they were listed as classes to include.
|
||||
*
|
||||
* @return list of package names
|
||||
*/
|
||||
String[] packages() default {};
|
||||
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 {@code Include} annotation. '*' is replaced with
|
||||
* is replaced with the package of the Include annotation. '*' is replaced with
|
||||
* the package of the included class.
|
||||
*
|
||||
* @return package pattern
|
||||
@@ -156,35 +136,12 @@ public @interface RecordBuilder {
|
||||
* named {@code RecordBuilderValidator} which has a public static method: {@code public static <T> T validate(T o)}.</p>
|
||||
*/
|
||||
boolean useValidationApi() default false;
|
||||
|
||||
/**
|
||||
* Adds special handling for record components of type {@link java.util.List}, {@link java.util.Set},
|
||||
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components
|
||||
* of these types are passed through an added shim method that uses the corresponding immutable collection
|
||||
* (e.g. {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
|
||||
*/
|
||||
boolean useImmutableCollections() default false;
|
||||
|
||||
/**
|
||||
* When enabled, collection types ({@code List}, {@code Set} and {@code Map}) are handled specially.
|
||||
* The setters for these types now create an internal collection and items are added to that
|
||||
* collection. Additionally, "adder" methods prefixed with {@link #singleItemBuilderPrefix()} are created
|
||||
* to add single items to these collections.
|
||||
*/
|
||||
boolean addSingleItemCollectionBuilders() default false;
|
||||
|
||||
/**
|
||||
* The prefix for adder methods when {@link #addSingleItemCollectionBuilders()} is enabled
|
||||
*/
|
||||
String singleItemBuilderPrefix() default "add";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Inherited
|
||||
@interface Template {
|
||||
RecordBuilder.Options options();
|
||||
|
||||
boolean asRecordInterface() default false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 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.core;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@RecordBuilder.Template(options = @RecordBuilder.Options(
|
||||
interpretNotNulls = true,
|
||||
useImmutableCollections = true,
|
||||
addSingleItemCollectionBuilders = true
|
||||
))
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
/**
|
||||
* An alternate form of {@code @RecordBuilder} that has most
|
||||
* optional features turned on
|
||||
*/
|
||||
public @interface RecordBuilderFull {
|
||||
}
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.core;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
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;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
@@ -27,31 +31,14 @@ public @interface RecordInterface {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Inherited
|
||||
@interface Include {
|
||||
/**
|
||||
* @return list of classes to include
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
Class<?>[] value();
|
||||
|
||||
/**
|
||||
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
|
||||
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
|
||||
* (i.e. a union of classes from both attributes).
|
||||
*
|
||||
* @return list of classes
|
||||
*/
|
||||
Class<?>[] classes() default {};
|
||||
|
||||
/**
|
||||
* If true the generated record is annotated with {@code @RecordBuilder}
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
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 {@code Include} annotation. '*' is replaced with
|
||||
* is replaced with the package of the Include annotation. '*' is replaced with
|
||||
* the package of the included class.
|
||||
*
|
||||
* @return package pattern
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>28</version>
|
||||
<version>24</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
/**
|
||||
* 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 com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
|
||||
class CollectionBuilderUtils {
|
||||
private final boolean useImmutableCollections;
|
||||
private final boolean addSingleItemCollectionBuilders;
|
||||
private final String listShimName;
|
||||
private final String mapShimName;
|
||||
private final String setShimName;
|
||||
private final String collectionShimName;
|
||||
|
||||
private boolean needsListShim;
|
||||
private boolean needsMapShim;
|
||||
private boolean needsSetShim;
|
||||
private boolean needsCollectionShim;
|
||||
|
||||
private static final TypeName listType = TypeName.get(List.class);
|
||||
private static final TypeName mapType = TypeName.get(Map.class);
|
||||
private static final TypeName setType = TypeName.get(Set.class);
|
||||
private static final TypeName collectionType = TypeName.get(Collection.class);
|
||||
|
||||
private static final TypeVariableName tType = TypeVariableName.get("T");
|
||||
private static final TypeVariableName kType = TypeVariableName.get("K");
|
||||
private static final TypeVariableName vType = TypeVariableName.get("V");
|
||||
private static final ParameterizedTypeName parameterizedListType = ParameterizedTypeName.get(ClassName.get(List.class), tType);
|
||||
private static final ParameterizedTypeName parameterizedMapType = ParameterizedTypeName.get(ClassName.get(Map.class), kType, vType);
|
||||
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName.get(ClassName.get(Set.class), tType);
|
||||
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName.get(ClassName.get(Collection.class), tType);
|
||||
|
||||
CollectionBuilderUtils(List<RecordClassType> recordComponents, RecordBuilder.Options metaData) {
|
||||
useImmutableCollections = metaData.useImmutableCollections();
|
||||
addSingleItemCollectionBuilders = metaData.addSingleItemCollectionBuilders();
|
||||
|
||||
listShimName = adjustShimName(recordComponents, "__list", 0);
|
||||
mapShimName = adjustShimName(recordComponents, "__map", 0);
|
||||
setShimName = adjustShimName(recordComponents, "__set", 0);
|
||||
collectionShimName = adjustShimName(recordComponents, "__collection", 0);
|
||||
}
|
||||
|
||||
enum SingleItemsMetaDataMode {
|
||||
STANDARD,
|
||||
STANDARD_FOR_SETTER,
|
||||
EXCLUDE_WILDCARD_TYPES
|
||||
}
|
||||
|
||||
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {}
|
||||
|
||||
Optional<SingleItemsMetaData> singleItemsMetaData(RecordClassType component, SingleItemsMetaDataMode mode) {
|
||||
if (addSingleItemCollectionBuilders && (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)) {
|
||||
Class<?> collectionClass = null;
|
||||
ClassName wildcardClass = null;
|
||||
int typeArgumentQty = 0;
|
||||
if (isList(component)) {
|
||||
collectionClass = ArrayList.class;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isSet(component)) {
|
||||
collectionClass = HashSet.class;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isMap(component)) {
|
||||
collectionClass = HashMap.class;
|
||||
wildcardClass = (ClassName) component.rawTypeName();
|
||||
typeArgumentQty = 2;
|
||||
}
|
||||
var hasWildcardTypeArguments = hasWildcardTypeArguments(parameterizedTypeName, typeArgumentQty);
|
||||
if (collectionClass != null) {
|
||||
return switch (mode) {
|
||||
case STANDARD -> singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||
|
||||
case STANDARD_FOR_SETTER -> {
|
||||
if (hasWildcardTypeArguments) {
|
||||
yield Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, component.typeName()));
|
||||
}
|
||||
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||
}
|
||||
|
||||
case EXCLUDE_WILDCARD_TYPES -> {
|
||||
if (hasWildcardTypeArguments) {
|
||||
yield Optional.empty();
|
||||
}
|
||||
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
boolean isList(RecordClassType component) {
|
||||
return component.rawTypeName().equals(listType);
|
||||
}
|
||||
|
||||
boolean isMap(RecordClassType component) {
|
||||
return component.rawTypeName().equals(mapType);
|
||||
}
|
||||
|
||||
boolean isSet(RecordClassType component) {
|
||||
return component.rawTypeName().equals(setType);
|
||||
}
|
||||
|
||||
void add(CodeBlock.Builder builder, RecordClassType component) {
|
||||
if (useImmutableCollections) {
|
||||
if (isList(component)) {
|
||||
needsListShim = true;
|
||||
builder.add("$L($L)", listShimName, component.name());
|
||||
} else if (isMap(component)) {
|
||||
needsMapShim = true;
|
||||
builder.add("$L($L)", mapShimName, component.name());
|
||||
} else if (isSet(component)) {
|
||||
needsSetShim = true;
|
||||
builder.add("$L($L)", setShimName, component.name());
|
||||
} else if (component.rawTypeName().equals(collectionType)) {
|
||||
needsCollectionShim = true;
|
||||
builder.add("$L($L)", collectionShimName, component.name());
|
||||
} else {
|
||||
builder.add("$L", component.name());
|
||||
}
|
||||
} else {
|
||||
builder.add("$L", component.name());
|
||||
}
|
||||
}
|
||||
|
||||
void addShims(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsListShim) {
|
||||
builder.addMethod(buildMethod(listShimName, listType, parameterizedListType, tType));
|
||||
}
|
||||
if (needsSetShim) {
|
||||
builder.addMethod(buildMethod(setShimName, setType, parameterizedSetType, tType));
|
||||
}
|
||||
if (needsMapShim) {
|
||||
builder.addMethod(buildMethod(mapShimName, mapType, parameterizedMapType, kType, vType));
|
||||
}
|
||||
if (needsCollectionShim) {
|
||||
builder.addMethod(buildCollectionsMethod());
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<SingleItemsMetaData> singleItemsMetaDataWithWildType(ParameterizedTypeName parameterizedTypeName, Class<?> collectionClass, ClassName wildcardClass, int typeArgumentQty) {
|
||||
TypeName wildType;
|
||||
if (typeArgumentQty == 1) {
|
||||
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)));
|
||||
} else { // if (typeArgumentQty == 2)
|
||||
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)), WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(1)));
|
||||
}
|
||||
return Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, wildType));
|
||||
}
|
||||
|
||||
private boolean hasWildcardTypeArguments(ParameterizedTypeName parameterizedTypeName, int argumentCount) {
|
||||
for (int i = 0; i < argumentCount; ++i) {
|
||||
if (parameterizedTypeName.typeArguments.size() > i) {
|
||||
if (parameterizedTypeName.typeArguments.get(i) instanceof WildcardTypeName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String adjustShimName(List<RecordClassType> recordComponents, String baseName, int index) {
|
||||
var name = (index == 0) ? baseName : (baseName + index);
|
||||
if (recordComponents.stream().anyMatch(component -> component.name().equals(name))) {
|
||||
return adjustShimName(recordComponents, baseName, index + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private MethodSpec buildMethod(String name, TypeName mainType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
var code = CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
|
||||
return MethodSpec.methodBuilder(name)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.returns(parameterizedType)
|
||||
.addParameter(parameterizedType, "o")
|
||||
.addStatement(code)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCollectionsMethod() {
|
||||
var code = CodeBlock.builder()
|
||||
.add("if (o instanceof Set) {\n")
|
||||
.indent()
|
||||
.addStatement("return $T.copyOf(o)", setType)
|
||||
.unindent()
|
||||
.addStatement("}")
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listType, listType)
|
||||
.build();
|
||||
return MethodSpec.methodBuilder(collectionShimName)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariable(tType)
|
||||
.returns(parameterizedCollectionType)
|
||||
.addParameter(parameterizedCollectionType, "o")
|
||||
.addCode(code)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -52,19 +52,12 @@ public class ElementUtils {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<TypeMirror> getAttributeTypeMirrorList(AnnotationValue attribute)
|
||||
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());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<String> getAttributeStringList(AnnotationValue attribute)
|
||||
{
|
||||
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
|
||||
return values.stream().map(v -> (String)v.getValue()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static boolean getBooleanAttribute(AnnotationValue attribute)
|
||||
{
|
||||
Object value = (attribute != null) ? attribute.getValue() : null;
|
||||
@@ -115,10 +108,8 @@ public class ElementUtils {
|
||||
return new ClassType(ParameterizedTypeName.get(builderClassName, typeNames), builderClassName.simpleName());
|
||||
}
|
||||
|
||||
public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv, RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> 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(RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||
return new RecordClassType(TypeName.get(recordComponent.asType()), recordComponent.getSimpleName().toString(), accessorAnnotations, canonicalConstructorAnnotations);
|
||||
}
|
||||
|
||||
public static String getWithMethodName(ClassType component, String prefix) {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* 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 javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class IncludeHelper {
|
||||
private final boolean isValid;
|
||||
private final List<TypeElement> classTypeElements;
|
||||
private final Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues;
|
||||
|
||||
IncludeHelper(ProcessingEnvironment processingEnv, Element element, AnnotationMirror annotationMirror, boolean packagesSupported) {
|
||||
annotationValues = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
|
||||
var value = ElementUtils.getAnnotationValue(annotationValues, "value");
|
||||
var classes = ElementUtils.getAnnotationValue(annotationValues, "classes");
|
||||
var packages = ElementUtils.getAnnotationValue(annotationValues, "packages");
|
||||
var isValid = true;
|
||||
var classTypeElements = new ArrayList<TypeElement>();
|
||||
if (value.isPresent() || classes.isPresent() || packages.isPresent()) {
|
||||
var valueList = value.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
|
||||
var classesList = classes.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
|
||||
var packagesList = packages.map(ElementUtils::getAttributeStringList).orElseGet(List::of);
|
||||
if (valueList.isEmpty() && classesList.isEmpty() && packagesList.isEmpty()) {
|
||||
if (packagesSupported) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\", \"classes\" or \"packages\" required", element);
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\" or \"classes\" required", element);
|
||||
}
|
||||
isValid = false;
|
||||
}
|
||||
isValid = processList(processingEnv, isValid, element, valueList, classTypeElements);
|
||||
isValid = processList(processingEnv, isValid, element, classesList, classTypeElements);
|
||||
packages.ifPresent(annotationValue -> processPackages(processingEnv, classTypeElements, packagesList));
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not read attribute for annotation", element);
|
||||
isValid = false;
|
||||
}
|
||||
this.isValid = isValid;
|
||||
this.classTypeElements = List.copyOf(classTypeElements);
|
||||
}
|
||||
|
||||
Map<? extends ExecutableElement, ? extends AnnotationValue> getAnnotationValues() {
|
||||
return annotationValues;
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
List<TypeElement> getClassTypeElements() {
|
||||
return classTypeElements;
|
||||
}
|
||||
|
||||
private boolean processList(ProcessingEnvironment processingEnv, boolean isValid, Element element, List<TypeMirror> list, ArrayList<TypeElement> classTypeElements) {
|
||||
for (var typeMirror : list) {
|
||||
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
|
||||
if (typeElement == null) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + typeMirror, element);
|
||||
isValid = false;
|
||||
} else {
|
||||
classTypeElements.add(typeElement);
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void processPackages(ProcessingEnvironment processingEnv, List<TypeElement> classTypeElements, List<String> packagesList) {
|
||||
for (var packageName : packagesList) {
|
||||
var packageElement = processingEnv.getElementUtils().getPackageElement(packageName);
|
||||
for (var child : packageElement.getEnclosedElements()) {
|
||||
if (child.getKind() == ElementKind.RECORD) {
|
||||
classTypeElements.add((TypeElement) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package io.soabase.recordbuilder.processor;
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
@@ -27,7 +26,6 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.*;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
@@ -43,33 +41,27 @@ class InternalRecordBuilderProcessor {
|
||||
private final TypeSpec.Builder builder;
|
||||
private final String uniqueVarName;
|
||||
private final Pattern notNullPattern;
|
||||
private final CollectionBuilderUtils collectionBuilderUtils;
|
||||
|
||||
private static final TypeName overrideType = TypeName.get(Override.class);
|
||||
private static final TypeName optionalType = TypeName.get(Optional.class);
|
||||
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
|
||||
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
|
||||
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
|
||||
private static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator", "RecordBuilderValidator");
|
||||
private final ProcessingEnvironment processingEnv;
|
||||
|
||||
InternalRecordBuilderProcessor(ProcessingEnvironment processingEnv, TypeElement record, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
this.processingEnv = processingEnv;
|
||||
var recordActualPackage = ElementUtils.getPackageName(record);
|
||||
this.metaData = metaData;
|
||||
InternalRecordBuilderProcessor(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
this.metaData = getMetaData(record, metaData);
|
||||
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
||||
packageName = packageNameOpt.orElse(recordActualPackage);
|
||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(record));
|
||||
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);
|
||||
uniqueVarName = getUniqueVarName();
|
||||
notNullPattern = Pattern.compile(metaData.interpretNotNullsPattern());
|
||||
collectionBuilderUtils = new CollectionBuilderUtils(recordComponents, this.metaData);
|
||||
|
||||
builder = TypeSpec.classBuilder(builderClassType.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addTypeVariables(typeVariables);
|
||||
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
|
||||
addWithNestedClass();
|
||||
addDefaultConstructor();
|
||||
addStaticBuilder();
|
||||
@@ -87,10 +79,7 @@ class InternalRecordBuilderProcessor {
|
||||
add1Field(component);
|
||||
add1SetterMethod(component);
|
||||
add1GetterMethod(component);
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, EXCLUDE_WILDCARD_TYPES);
|
||||
collectionMetaData.ifPresent(meta -> add1CollectionBuilders(meta, component));
|
||||
});
|
||||
collectionBuilderUtils.addShims(builder);
|
||||
builderType = builder.build();
|
||||
}
|
||||
|
||||
@@ -106,17 +95,6 @@ class InternalRecordBuilderProcessor {
|
||||
return builderType;
|
||||
}
|
||||
|
||||
private void addVisibility(boolean builderIsInRecordPackage, Set<Modifier> modifiers) {
|
||||
if (builderIsInRecordPackage) {
|
||||
if (modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
|
||||
builder.addModifiers(Modifier.PUBLIC); // builders are top level classes - can only be public or package-private
|
||||
}
|
||||
// is package-private
|
||||
} else {
|
||||
builder.addModifiers(Modifier.PUBLIC);
|
||||
}
|
||||
}
|
||||
|
||||
private List<RecordClassType> 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());
|
||||
@@ -125,11 +103,16 @@ class InternalRecordBuilderProcessor {
|
||||
.mapToObj(index -> {
|
||||
var thisAccessorAnnotations = (accessorAnnotations.size() > index) ? accessorAnnotations.get(index) : List.<AnnotationMirror>of();
|
||||
var thisCanonicalConstructorAnnotations = (canonicalConstructorAnnotations.size() > index) ? canonicalConstructorAnnotations.get(index) : List.<AnnotationMirror>of();
|
||||
return ElementUtils.getRecordClassType(processingEnv, recordComponents.get(index), thisAccessorAnnotations, thisCanonicalConstructorAnnotations);
|
||||
return ElementUtils.getRecordClassType(recordComponents.get(index), thisAccessorAnnotations, thisCanonicalConstructorAnnotations);
|
||||
})
|
||||
.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:
|
||||
@@ -236,9 +219,8 @@ class InternalRecordBuilderProcessor {
|
||||
codeBlockBuilder.add(";$]");
|
||||
|
||||
var methodName = getWithMethodName(component, metaData.withClassMethodPrefix());
|
||||
var singleItemsMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD_FOR_SETTER);
|
||||
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||
component.getCanonicalConstructorAnnotations().forEach(annotationMirror -> parameterSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
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())
|
||||
@@ -255,9 +237,9 @@ class InternalRecordBuilderProcessor {
|
||||
if (parameterIndex > 0) {
|
||||
codeBlockBuilder.add(", ");
|
||||
}
|
||||
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||
ClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||
if (parameterIndex == index) {
|
||||
collectionBuilderUtils.add(codeBlockBuilder, parameterComponent);
|
||||
codeBlockBuilder.add(parameterComponent.name());
|
||||
} else {
|
||||
codeBlockBuilder.add("$L()", parameterComponent.name());
|
||||
}
|
||||
@@ -296,7 +278,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addCode(codeBlock);
|
||||
recordComponents.forEach(component -> {
|
||||
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||
component.getCanonicalConstructorAnnotations().forEach(annotationMirror -> parameterSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
builder.addParameter(parameterSpecBuilder.build());
|
||||
});
|
||||
this.builder.addMethod(builder.build());
|
||||
@@ -339,9 +321,8 @@ class InternalRecordBuilderProcessor {
|
||||
.addAnnotation(generatedRecordBuilderAnnotation);
|
||||
recordComponents.forEach(component -> {
|
||||
constructorBuilder.addParameter(component.typeName(), component.name());
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD);
|
||||
collectionMetaData.ifPresentOrElse(meta -> constructorBuilder.addStatement("this.$L = new $T<>($L)", component.name(), meta.singleItemCollectionClass(), component.name()),
|
||||
() -> constructorBuilder.addStatement("this.$L = $L", component.name(), component.name()));
|
||||
var codeBuilder = CodeBlock.builder().add("this.$L = $L", component.name(), component.name());
|
||||
constructorBuilder.addStatement(codeBuilder.build());
|
||||
});
|
||||
builder.addMethod(constructorBuilder.build());
|
||||
}
|
||||
@@ -473,7 +454,7 @@ class InternalRecordBuilderProcessor {
|
||||
if (index > 0) {
|
||||
codeBuilder.add(", ");
|
||||
}
|
||||
collectionBuilderUtils.add(codeBuilder, recordComponents.get(index));
|
||||
codeBuilder.add("$L", recordComponents.get(index).name());
|
||||
});
|
||||
codeBuilder.add(")");
|
||||
if (metaData.useValidationApi()) {
|
||||
@@ -536,8 +517,10 @@ class InternalRecordBuilderProcessor {
|
||||
Adds a static method that converts a record instance into a stream of its component parts
|
||||
|
||||
public static Stream<Map.Entry<String, Object>> stream(MyRecord record) {
|
||||
return Stream.of(Map.entry("p1", record.p1()),
|
||||
Map.entry("p2", record.p2()));
|
||||
return Stream.of(
|
||||
new AbstractMap.SimpleEntry<>("p1", record.p1()),
|
||||
new AbstractMap.SimpleEntry<>("p2", record.p2())
|
||||
);
|
||||
}
|
||||
*/
|
||||
var codeBuilder = CodeBlock.builder().add("return $T.of(", Stream.class);
|
||||
@@ -546,7 +529,7 @@ class InternalRecordBuilderProcessor {
|
||||
codeBuilder.add(",\n ");
|
||||
}
|
||||
var name = recordComponents.get(index).name();
|
||||
codeBuilder.add("$T.entry($S, record.$L())", Map.class, name, name);
|
||||
codeBuilder.add("new $T<>($S, record.$L())", AbstractMap.SimpleEntry.class, name, name);
|
||||
});
|
||||
codeBuilder.add(")");
|
||||
var mapEntryTypeVariables = ParameterizedTypeName.get(Map.Entry.class, String.class, Object.class);
|
||||
@@ -607,157 +590,10 @@ class InternalRecordBuilderProcessor {
|
||||
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(component.typeName());
|
||||
addAccessorAnnotations(component, methodSpecBuilder);
|
||||
component.getAccessorAnnotations().forEach(annotationMirror -> methodSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
classBuilder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
|
||||
private boolean filterOutOverride(AnnotationSpec annotationSpec) {
|
||||
return !annotationSpec.type.equals(overrideType);
|
||||
}
|
||||
|
||||
private void addConstructorAnnotations(RecordClassType component, ParameterSpec.Builder parameterSpecBuilder) {
|
||||
if (metaData.inheritComponentAnnotations()) {
|
||||
component.getCanonicalConstructorAnnotations()
|
||||
.stream()
|
||||
.map(AnnotationSpec::get)
|
||||
.filter(this::filterOutOverride)
|
||||
.forEach(parameterSpecBuilder::addAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAccessorAnnotations(RecordClassType component, MethodSpec.Builder methodSpecBuilder) {
|
||||
if (metaData.inheritComponentAnnotations()) {
|
||||
component.getAccessorAnnotations()
|
||||
.stream()
|
||||
.map(AnnotationSpec::get)
|
||||
.filter(this::filterOutOverride)
|
||||
.forEach(methodSpecBuilder::addAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
private String capitalize(String s) {
|
||||
return (s.length() < 2) ? s.toUpperCase(Locale.ROOT) : (Character.toUpperCase(s.charAt(0)) + s.substring(1));
|
||||
}
|
||||
|
||||
private void add1CollectionBuilders(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||
if (collectionBuilderUtils.isList(component) || collectionBuilderUtils.isSet(component)) {
|
||||
add1ListBuilder(meta, component);
|
||||
} else if (collectionBuilderUtils.isMap(component)) {
|
||||
add1MapBuilder(meta, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void add1MapBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||
/*
|
||||
For a single map record component, add a methods similar to:
|
||||
|
||||
public T addP(K key, V value) {
|
||||
if (this.p == null) {
|
||||
this.p = new HashMap<>();
|
||||
}
|
||||
this.p.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
i.forEach(this.p::put);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
i.forEach(this.p::put);
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), HashMap.class)
|
||||
.endControlFlow();
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
.addJavadoc("Add to the internally allocated {@code HashMap} for {@code $L}\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(builderClassType.typeName());
|
||||
if (i == 0) {
|
||||
methodSpecBuilder.addParameter(meta.typeArguments().get(0), "key");
|
||||
methodSpecBuilder.addParameter(meta.typeArguments().get(1), "value");
|
||||
codeBlockBuilder.addStatement("this.$L.put(key, value)", component.name());
|
||||
} else {
|
||||
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
|
||||
var entryType = ParameterizedTypeName.get(ClassName.get(Map.Entry.class), WildcardTypeName.subtypeOf(meta.typeArguments().get(0)), WildcardTypeName.subtypeOf(meta.typeArguments().get(1)));
|
||||
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(parameterClass, WildcardTypeName.subtypeOf(entryType));
|
||||
methodSpecBuilder.addParameter(parameterizedTypeName, "i");
|
||||
codeBlockBuilder.addStatement("i.forEach(entry -> this.$L.put(entry.getKey(), entry.getValue()))", component.name());
|
||||
}
|
||||
codeBlockBuilder.addStatement("return this");
|
||||
methodSpecBuilder.addCode(codeBlockBuilder.build());
|
||||
builder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void add1ListBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||
/*
|
||||
For a single list or set record component, add methods similar to:
|
||||
|
||||
public T addP(I i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.p.add(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
var addClockBlock = CodeBlock.builder();
|
||||
TypeName parameter;
|
||||
if (i == 0) {
|
||||
addClockBlock.addStatement("this.$L.add(i)", component.name());
|
||||
parameter = meta.typeArguments().get(0);
|
||||
} else {
|
||||
addClockBlock.addStatement("i.forEach(this.$L::add)", component.name());
|
||||
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
|
||||
parameter = ParameterizedTypeName.get(parameterClass, WildcardTypeName.subtypeOf(meta.typeArguments().get(0)));
|
||||
}
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||
.endControlFlow()
|
||||
.add(addClockBlock.build())
|
||||
.addStatement("return this");
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
.addJavadoc("Add to the internally allocated {@code $L} for {@code $L}\n", meta.singleItemCollectionClass().getSimpleName(), component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(builderClassType.typeName())
|
||||
.addParameter(parameter, "i")
|
||||
.addCode(codeBlockBuilder.build());
|
||||
builder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void add1GetterMethod(RecordClassType component) {
|
||||
/*
|
||||
For a single record component, add a getter similar to:
|
||||
@@ -772,7 +608,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(component.typeName())
|
||||
.addStatement("return $L", component.name());
|
||||
addAccessorAnnotations(component, methodSpecBuilder);
|
||||
component.getAccessorAnnotations().forEach(annotationMirror -> methodSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
builder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
|
||||
@@ -785,27 +621,19 @@ class InternalRecordBuilderProcessor {
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||
component.getCanonicalConstructorAnnotations().forEach(annotationMirror -> parameterSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
|
||||
var methodSpec = MethodSpec.methodBuilder(component.name())
|
||||
.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(builderClassType.typeName());
|
||||
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD_FOR_SETTER);
|
||||
var parameterSpecBuilder = collectionMetaData.map(meta -> {
|
||||
CodeBlock.Builder codeSpec = CodeBlock.builder();
|
||||
codeSpec.addStatement("this.$L = ($L != null) ? new $T<>($L) : null", component.name(), component.name(), meta.singleItemCollectionClass(), component.name());
|
||||
methodSpec.addJavadoc("Re-create the internally allocated {@code $L} for {@code $L} by copying the argument\n", meta.singleItemCollectionClass().getSimpleName(), component.name())
|
||||
.addCode(codeSpec.build());
|
||||
return ParameterSpec.builder(meta.wildType(), component.name());
|
||||
}).orElseGet(() -> {
|
||||
methodSpec.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addStatement("this.$L = $L", component.name(), component.name());
|
||||
return ParameterSpec.builder(component.typeName(), component.name());
|
||||
});
|
||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
|
||||
builder.addMethod(methodSpec.build());
|
||||
.addParameter(parameterSpecBuilder.build())
|
||||
.returns(builderClassType.typeName())
|
||||
.addStatement("this.$L = $L", component.name(), component.name())
|
||||
.addStatement("return this")
|
||||
.build();
|
||||
builder.addMethod(methodSpec);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
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 javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@@ -26,7 +29,13 @@ 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.*;
|
||||
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.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -47,10 +56,9 @@ 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, boolean fromTemplate) {
|
||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
this.processingEnv = processingEnv;
|
||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
||||
recordComponents = getRecordComponents(iface);
|
||||
@@ -73,14 +81,6 @@ 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,7 +88,8 @@ class InternalRecordInterfaceProcessor {
|
||||
recordType = builder.build();
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
boolean isValid()
|
||||
{
|
||||
return !recordComponents.isEmpty();
|
||||
}
|
||||
|
||||
@@ -104,7 +105,8 @@ 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:
|
||||
/*
|
||||
@@ -137,7 +139,8 @@ 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());
|
||||
@@ -150,18 +153,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) {
|
||||
@@ -177,8 +180,8 @@ class InternalRecordInterfaceProcessor {
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
private static class IllegalInterface extends RuntimeException {
|
||||
private static class IllegalInterface extends RuntimeException
|
||||
{
|
||||
public IllegalInterface(String message) {
|
||||
super(message);
|
||||
}
|
||||
@@ -216,13 +219,14 @@ 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,10 @@ import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.PackageElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Optional;
|
||||
@@ -38,7 +40,8 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RecordBuilderProcessor
|
||||
extends AbstractProcessor {
|
||||
extends AbstractProcessor
|
||||
{
|
||||
private static final String RECORD_BUILDER = RecordBuilder.class.getName();
|
||||
private static final String RECORD_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.');
|
||||
private static final String RECORD_INTERFACE = RecordInterface.class.getName();
|
||||
@@ -48,18 +51,21 @@ public class RecordBuilderProcessor
|
||||
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordInterface.class.getName()).build();
|
||||
|
||||
@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).forEach(element -> process(annotation, element)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedAnnotationTypes() {
|
||||
public Set<String> getSupportedAnnotationTypes()
|
||||
{
|
||||
return Set.of("*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
public SourceVersion getSupportedSourceVersion()
|
||||
{
|
||||
// we don't directly return RELEASE_14 as that may
|
||||
// not exist in prior releases
|
||||
// if we're running on an older release, returning latest()
|
||||
@@ -67,52 +73,59 @@ public class RecordBuilderProcessor
|
||||
return SourceVersion.latest();
|
||||
}
|
||||
|
||||
private void process(TypeElement annotation, Element element) {
|
||||
private void process(TypeElement annotation, Element element)
|
||||
{
|
||||
String annotationClass = annotation.getQualifiedName().toString();
|
||||
if (annotationClass.equals(RECORD_BUILDER)) {
|
||||
var typeElement = (TypeElement) element;
|
||||
processRecordBuilder(typeElement, getMetaData(typeElement), Optional.empty());
|
||||
} else if (annotationClass.equals(RECORD_INTERFACE)) {
|
||||
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());
|
||||
processRecordBuilder((TypeElement) element, metaData, 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());
|
||||
}
|
||||
else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
processIncludes(element, metaData, annotationClass);
|
||||
} else {
|
||||
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
|
||||
if (recordBuilderTemplate != null) {
|
||||
if (recordBuilderTemplate.asRecordInterface()) {
|
||||
processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(), Optional.empty(), true);
|
||||
} else {
|
||||
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
||||
}
|
||||
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 isRecordBuilderInclude = annotationClass.equals(RECORD_BUILDER_INCLUDE);
|
||||
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass)
|
||||
{
|
||||
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 includeHelper = new IncludeHelper(processingEnv, element, annotationMirrorOpt.get(), isRecordBuilderInclude);
|
||||
if (includeHelper.isValid()) {
|
||||
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "packagePattern").orElse(null), "*");
|
||||
for (var typeElement : includeHelper.getClassTypeElements()) {
|
||||
var packageName = buildPackageName(packagePattern, element, typeElement);
|
||||
if (packageName != null) {
|
||||
if (isRecordBuilderInclude) {
|
||||
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
||||
} else {
|
||||
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "addRecordBuilder");
|
||||
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
||||
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName), false);
|
||||
}
|
||||
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 (packageName != null) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +133,8 @@ public class RecordBuilderProcessor
|
||||
}
|
||||
}
|
||||
|
||||
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass) {
|
||||
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass)
|
||||
{
|
||||
PackageElement includedClassPackage = findPackageElement(includedClass, includedClass);
|
||||
if (includedClassPackage == null) {
|
||||
return null;
|
||||
@@ -132,7 +146,8 @@ public class RecordBuilderProcessor
|
||||
return replaced.replace("@", ((PackageElement) builderElement.getEnclosingElement()).getQualifiedName().toString());
|
||||
}
|
||||
|
||||
private PackageElement findPackageElement(Element actualElement, Element includedClass) {
|
||||
private PackageElement findPackageElement(Element actualElement, Element includedClass)
|
||||
{
|
||||
if (includedClass == null) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Element has not package", actualElement);
|
||||
return null;
|
||||
@@ -143,19 +158,21 @@ public class RecordBuilderProcessor
|
||||
return findPackageElement(actualElement, includedClass.getEnclosingElement());
|
||||
}
|
||||
|
||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName, boolean fromTemplate) {
|
||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName)
|
||||
{
|
||||
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, fromTemplate);
|
||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName);
|
||||
if (!internalProcessor.isValid()) {
|
||||
return;
|
||||
}
|
||||
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord);
|
||||
}
|
||||
|
||||
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageName) {
|
||||
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageName)
|
||||
{
|
||||
// we use string based name comparison for the element kind,
|
||||
// as the ElementKind.RECORD enum doesn't exist on JRE releases
|
||||
// older than Java 14, and we don't want to throw unexpected
|
||||
@@ -164,11 +181,12 @@ public class RecordBuilderProcessor
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record);
|
||||
return;
|
||||
}
|
||||
var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName);
|
||||
var internalProcessor = new InternalRecordBuilderProcessor(record, metaData, packageName);
|
||||
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData);
|
||||
}
|
||||
|
||||
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, RecordBuilder.Options metaData) {
|
||||
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, RecordBuilder.Options metaData)
|
||||
{
|
||||
// produces the Java file
|
||||
JavaFile javaFile = javaFileBuilder(packageName, builderType, metaData);
|
||||
Filer filer = processingEnv.getFiler();
|
||||
@@ -178,12 +196,14 @@ public class RecordBuilderProcessor
|
||||
try (Writer writer = sourceFile.openWriter()) {
|
||||
javaFile.writeTo(writer);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
handleWriteError(record, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc) {
|
||||
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc)
|
||||
{
|
||||
JavaFile javaFile = javaFileBuilder(packageName, type, metaData);
|
||||
|
||||
String classSourceCode = javaFile.toString();
|
||||
@@ -197,12 +217,14 @@ public class RecordBuilderProcessor
|
||||
try (Writer writer = sourceFile.openWriter()) {
|
||||
writer.write(recordSourceCode);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
handleWriteError(element, e);
|
||||
}
|
||||
}
|
||||
|
||||
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData) {
|
||||
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData)
|
||||
{
|
||||
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true).indent(metaData.fileIndent());
|
||||
var comment = metaData.fileComment();
|
||||
if ((comment != null) && !comment.isEmpty()) {
|
||||
@@ -211,7 +233,8 @@ public class RecordBuilderProcessor
|
||||
return javaFileBuilder.build();
|
||||
}
|
||||
|
||||
private void handleWriteError(TypeElement element, IOException e) {
|
||||
private void handleWriteError(TypeElement element, IOException e)
|
||||
{
|
||||
String message = "Could not create source file";
|
||||
if (e.getMessage() != null) {
|
||||
message = message + ": " + e.getMessage();
|
||||
|
||||
@@ -21,21 +21,15 @@ import javax.lang.model.element.AnnotationMirror;
|
||||
import java.util.List;
|
||||
|
||||
public class RecordClassType extends ClassType {
|
||||
private final TypeName rawTypeName;
|
||||
private final List<? extends AnnotationMirror> accessorAnnotations;
|
||||
private final List<? extends AnnotationMirror> canonicalConstructorAnnotations;
|
||||
|
||||
public RecordClassType(TypeName typeName, TypeName rawTypeName, String name, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||
public RecordClassType(TypeName typeName, String name, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||
super(typeName, name);
|
||||
this.rawTypeName = rawTypeName;
|
||||
this.accessorAnnotations = accessorAnnotations;
|
||||
this.canonicalConstructorAnnotations = canonicalConstructorAnnotations;
|
||||
}
|
||||
|
||||
public TypeName rawTypeName() {
|
||||
return rawTypeName;
|
||||
}
|
||||
|
||||
public List<? extends AnnotationMirror> getAccessorAnnotations() {
|
||||
return accessorAnnotations;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>28</version>
|
||||
<version>24</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* 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 java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(useImmutableCollections = true)
|
||||
public record CollectionRecord<T, X extends Point>(List<T> l, Set<T> s, Map<T, X> m, Collection<X> c) implements CollectionRecordBuilder.With<T, X> {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* 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 java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(useImmutableCollections = true)
|
||||
public record CollectionRecordConflicts(List<String> __list, Set<String> __set, Map<String, String> __map, Collection<String> __collection) implements CollectionRecordConflictsBuilder.With {
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 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 javax.lang.model.type.ErrorType;
|
||||
import java.util.List;
|
||||
|
||||
@RecordBuilder
|
||||
public record ExceptionDetails(
|
||||
String internalMessage, String endUserMessage, String httpStatus,
|
||||
ErrorType errorType, List<String> jsonProblems, Throwable cause
|
||||
) {
|
||||
@Override
|
||||
public List<String> jsonProblems() {
|
||||
if (jsonProblems == null) {
|
||||
return List.of();
|
||||
}
|
||||
return jsonProblems;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 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.RecordBuilderFull;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RecordBuilderFull
|
||||
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords) {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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 javax.validation.constraints.NotNull;
|
||||
|
||||
@RecordBuilder()
|
||||
@RecordBuilder.Options(inheritComponentAnnotations = false)
|
||||
public record IgnoreAnnotated(@NotNull String s) {
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* 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 java.util.List;
|
||||
|
||||
@RecordBuilder
|
||||
public record MutableCollectionRecord(List<String> l) implements MutableCollectionRecordBuilder.With {
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 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 {
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 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 java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
addSingleItemCollectionBuilders = true,
|
||||
singleItemBuilderPrefix = "add1",
|
||||
useImmutableCollections = true
|
||||
)
|
||||
public record SingleItems<T>(List<String> strings, Set<List<T>> sets, Map<Instant, T> map, Collection<T> collection) implements SingleItemsBuilder.With<T> {
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* 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 java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
addSingleItemCollectionBuilders = true,
|
||||
useImmutableCollections = true
|
||||
)
|
||||
public record WildcardSingleItems<T>(List<? extends String> strings, Set<? extends List<? extends T>> sets, Map<? extends Instant, ? extends T> map, Collection<? extends T> collection) implements WildcardSingleItemsBuilder.With<T> {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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.includes;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder.Include(
|
||||
packages = "io.soabase.recordbuilder.test.includes.pack",
|
||||
classes = JustATest.class
|
||||
)
|
||||
public class IncludeFactory {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 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.includes;
|
||||
|
||||
public record JustATest(int i) {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 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.includes.pack;
|
||||
|
||||
public interface AlsoIgnoreMe {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 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.includes.pack;
|
||||
|
||||
public class IgnoreMe {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 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.includes.pack;
|
||||
|
||||
public record PackRecord1(String name) {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 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.includes.pack;
|
||||
|
||||
public record PackRecord2(String name, int age) {
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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.includes.pack;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record PackRecord3(Instant time, int age) {
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* 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.visibility;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder
|
||||
record PackagePrivateRecord(int i) {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* 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.visibility;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
class Wrapper {
|
||||
@RecordBuilder
|
||||
protected record ProtectedRecord(int i) {
|
||||
}
|
||||
|
||||
@RecordBuilder
|
||||
record PackagePrivateRecord(int i) {
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,6 @@ import javax.validation.constraints.Null;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
class TestAnnotated {
|
||||
@Test
|
||||
void testInheritComponentAnnotationsFalse() throws NoSuchMethodException {
|
||||
var method = IgnoreAnnotatedBuilder.class.getMethod("s");
|
||||
Assertions.assertNull(method.getAnnotation(NotNull.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStaticConstructor() throws NoSuchMethodException {
|
||||
var method = AnnotatedBuilder.class.getMethod("Annotated", String.class, Integer.TYPE, Double.TYPE);
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* 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 org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
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();
|
||||
assertNotNull(defaultValues.l());
|
||||
assertNotNull(defaultValues.s());
|
||||
assertNotNull(defaultValues.m());
|
||||
assertNotNull(defaultValues.c());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionRecordImmutable() {
|
||||
var list = new ArrayList<String>();
|
||||
list.add("one");
|
||||
var set = new HashSet<String>();
|
||||
set.add("one");
|
||||
var map = new HashMap<String, Point>();
|
||||
map.put("one", Point(10, 20));
|
||||
var collectionAsSet = new HashSet<Point>();
|
||||
collectionAsSet.add(Point(30, 40));
|
||||
var r = CollectionRecordBuilder.<String, Point>builder()
|
||||
.l(list)
|
||||
.s(set)
|
||||
.m(map)
|
||||
.c(collectionAsSet)
|
||||
.build();
|
||||
|
||||
assertValues(r, list, set, map, collectionAsSet);
|
||||
assertValueChanges(r, list, set, map, collectionAsSet);
|
||||
assertImmutable(r);
|
||||
|
||||
var collectionAsList = new ArrayList<Point>();
|
||||
var x = CollectionRecordBuilder.<String, Point>builder()
|
||||
.l(list)
|
||||
.s(set)
|
||||
.m(map)
|
||||
.c(collectionAsList)
|
||||
.build();
|
||||
assertTrue(x.c() instanceof List);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionRecordImmutableWithers() {
|
||||
var r = CollectionRecordBuilder.<String, Point>builder().build();
|
||||
|
||||
var list = new ArrayList<String>();
|
||||
list.add("one");
|
||||
var set = new HashSet<String>();
|
||||
set.add("one");
|
||||
var map = new HashMap<String, Point>();
|
||||
map.put("one", Point(10, 20));
|
||||
var collectionAsSet = new HashSet<Point>();
|
||||
collectionAsSet.add(Point(30, 40));
|
||||
var x = r.withL(list).withS(set).withM(map).withC(collectionAsSet);
|
||||
|
||||
assertValues(x, list, set, map, collectionAsSet);
|
||||
assertValueChanges(x, list, set, map, collectionAsSet);
|
||||
assertImmutable(x);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMutableCollectionRecord() {
|
||||
var r = MutableCollectionRecordBuilder.builder().build();
|
||||
assertNull(r.l());
|
||||
|
||||
var list = new ArrayList<String>();
|
||||
list.add("one");
|
||||
r = MutableCollectionRecordBuilder.builder().l(list).build();
|
||||
|
||||
assertEquals(r.l(), list);
|
||||
list.add("two");
|
||||
assertEquals(r.l(), list);
|
||||
}
|
||||
|
||||
private void assertImmutable(CollectionRecord<String, Point> r) {
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.l().add("hey"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.s().add("hey"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.m().put("hey", Point(1, 2)));
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.c().add(Point(1, 2)));
|
||||
}
|
||||
|
||||
private void assertValueChanges(CollectionRecord<String, Point> r, ArrayList<String> list, HashSet<String> set, HashMap<String, Point> map, HashSet<Point> collectionAsSet) {
|
||||
list.add("two");
|
||||
set.add("two");
|
||||
map.put("two", Point(50, 60));
|
||||
collectionAsSet.add(Point(70, 80));
|
||||
|
||||
assertNotEquals(r.l(), list);
|
||||
assertNotEquals(r.s(), set);
|
||||
assertNotEquals(r.m(), map);
|
||||
assertNotEquals(r.c(), collectionAsSet);
|
||||
}
|
||||
|
||||
private void assertValues(CollectionRecord<String, Point> r, ArrayList<String> list, HashSet<String> set, HashMap<String, Point> map, HashSet<Point> collectionAsSet) {
|
||||
assertEquals(r.l(), list);
|
||||
assertEquals(r.s(), set);
|
||||
assertEquals(r.m(), map);
|
||||
assertEquals(r.c(), collectionAsSet);
|
||||
assertTrue(r.c() instanceof Set);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* 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 org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
class TestRecordBuilderFull {
|
||||
@Test
|
||||
void testNonNull() {
|
||||
Assertions.assertThrows(NullPointerException.class, () -> FullRecordBuilder.builder().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImmutable() {
|
||||
var record = FullRecordBuilder.builder()
|
||||
.fullRecords(new HashMap<>())
|
||||
.numbers(new ArrayList<>())
|
||||
.build();
|
||||
Assertions.assertThrows(UnsupportedOperationException.class, () -> record.fullRecords().put(1, record));
|
||||
Assertions.assertThrows(UnsupportedOperationException.class, () -> record.numbers().add(1));
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* 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 org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class TestSingleItems {
|
||||
@Test
|
||||
public void testInternalCollections()
|
||||
{
|
||||
var now = Instant.now();
|
||||
var item = SingleItemsBuilder.<String>builder()
|
||||
.add1Map(now, "now")
|
||||
.add1Map(Instant.MIN, "before")
|
||||
.add1Sets(Arrays.asList("1", "2"))
|
||||
.add1Sets(List.of("3"))
|
||||
.add1Strings("a")
|
||||
.add1Strings("b")
|
||||
.add1Strings("c")
|
||||
.build();
|
||||
Assertions.assertEquals(item.map(), Map.of(now, "now", Instant.MIN, "before"));
|
||||
Assertions.assertEquals(item.sets(), Set.of(List.of("1", "2"), List.of("3")));
|
||||
Assertions.assertEquals(item.strings(), List.of("a", "b", "c"));
|
||||
|
||||
var copy = item.with()
|
||||
.add1Strings("new")
|
||||
.add1Map(Instant.MAX, "after")
|
||||
.add1Sets(List.of("10", "20", "30"))
|
||||
.build();
|
||||
Assertions.assertNotEquals(item, copy);
|
||||
Assertions.assertEquals(copy.map(), Map.of(now, "now", Instant.MIN, "before", Instant.MAX, "after"));
|
||||
Assertions.assertEquals(copy.sets(), Set.of(List.of("1", "2"), List.of("3"), List.of("10", "20", "30")));
|
||||
Assertions.assertEquals(copy.strings(), List.of("a", "b", "c", "new"));
|
||||
|
||||
var stringsToAdd = Arrays.asList("x", "y", "z");
|
||||
var listToAdd = Arrays.asList(List.of("aa", "bb"), List.of("cc"));
|
||||
var mapToAdd = Map.of(now.plusMillis(1), "now+1", now.plusMillis(2), "now+2");
|
||||
var streamed = SingleItemsBuilder.builder(item)
|
||||
.add1Strings(stringsToAdd.stream())
|
||||
.add1Sets(listToAdd.stream())
|
||||
.add1Map(mapToAdd.entrySet().stream())
|
||||
.build();
|
||||
Assertions.assertEquals(streamed.map(), Map.of(now, "now", Instant.MIN, "before", now.plusMillis(1), "now+1", now.plusMillis(2), "now+2"));
|
||||
Assertions.assertEquals(streamed.sets(), Set.of(List.of("1", "2"), List.of("3"), List.of("aa", "bb"), List.of("cc")));
|
||||
Assertions.assertEquals(streamed.strings(), Arrays.asList("a", "b", "c", "x", "y", "z"));
|
||||
|
||||
var nulls = SingleItemsBuilder.builder(item)
|
||||
.strings(null)
|
||||
.sets(null)
|
||||
.map(null)
|
||||
.build();
|
||||
Assertions.assertEquals(nulls.map(), Map.of());
|
||||
Assertions.assertEquals(nulls.sets(), Set.of());
|
||||
Assertions.assertEquals(nulls.strings(), List.of());
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* 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.visibility;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
class TestVisibility {
|
||||
@Test
|
||||
void testMatches() {
|
||||
Assertions.assertFalse(Modifier.isPublic(PackagePrivateRecordBuilder.class.getModifiers()));
|
||||
Assertions.assertFalse(Modifier.isPrivate(PackagePrivateRecordBuilder.class.getModifiers()));
|
||||
Assertions.assertFalse(Modifier.isProtected(PackagePrivateRecordBuilder.class.getModifiers()));
|
||||
|
||||
Assertions.assertTrue(Modifier.isPublic(WrapperProtectedRecordBuilder.class.getModifiers()));
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>28</version>
|
||||
<version>24</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user