Compare commits
36 Commits
master
...
record-bui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
445c33fc1d | ||
|
|
2b266bd65d | ||
|
|
821718ac3f | ||
|
|
8ac34529e7 | ||
|
|
c701a09039 | ||
|
|
328bb7d660 | ||
|
|
41eddd46a3 | ||
|
|
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 |
83
README.md
83
README.md
@@ -1,5 +1,5 @@
|
|||||||
[](https://github.com/Randgalt/record-builder/actions)
|
[](https://github.com/Randgalt/record-builder/actions)
|
||||||
[](https://search.maven.org/search?q=g:io.soabase.record-builder%20a:record-builder)
|
[](https://search.maven.org/search?q=g:io.soabase.record-builder%20a:record-builder)
|
||||||
|
|
||||||
# RecordBuilder
|
# RecordBuilder
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ _Details:_
|
|||||||
- [Record From Interface Details](#RecordInterface-Example)
|
- [Record From Interface Details](#RecordInterface-Example)
|
||||||
- [Generation Via Includes](#generation-via-includes)
|
- [Generation Via Includes](#generation-via-includes)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Customizing](#customizing)
|
- [Customizing](customizing.md)
|
||||||
- [Java 15 Versions](#java-15-versions)
|
- [Java 15 Versions](#java-15-versions)
|
||||||
|
|
||||||
## RecordBuilder Example
|
## RecordBuilder Example
|
||||||
@@ -71,6 +71,15 @@ NameAndAge r4 = r3.with().age(101).name("baz").build();
|
|||||||
|
|
||||||
// alternate method of accessing the builder (note: no need to call "build()")
|
// alternate method of accessing the builder (note: no need to call "build()")
|
||||||
NameAndAge r5 = r4.with(b -> b.age(200).name("whatever"));
|
NameAndAge r5 = r4.with(b -> b.age(200).name("whatever"));
|
||||||
|
|
||||||
|
// perform some logic in addition to changing values
|
||||||
|
NameAndAge r5 = r4.with(b -> {
|
||||||
|
if (b.age() > 13) {
|
||||||
|
b.name("Teen " + b.name());
|
||||||
|
} else {
|
||||||
|
b.name("whatever"));
|
||||||
|
}
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
_Hat tip to [Benji Weber](https://benjiweber.co.uk/blog/2020/09/19/fun-with-java-records/) for the Withers idea._
|
_Hat tip to [Benji Weber](https://benjiweber.co.uk/blog/2020/09/19/fun-with-java-records/) for the Withers idea._
|
||||||
@@ -175,29 +184,26 @@ public class NameAndAgeBuilder {
|
|||||||
&& Objects.equals(name, b.name)
|
&& Objects.equals(name, b.name)
|
||||||
&& (age == b.age));
|
&& (age == b.age));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Downcast to {@code NameAndAge}
|
|
||||||
*/
|
|
||||||
private static NameAndAge _downcast(Object obj) {
|
|
||||||
try {
|
|
||||||
return (NameAndAge)obj;
|
|
||||||
}
|
|
||||||
catch (ClassCastException dummy) {
|
|
||||||
throw new RuntimeException("NameAndAgeBuilder.With can only be implemented for NameAndAge");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add withers to {@code NameAndAge}
|
* Add withers to {@code NameAndAge}
|
||||||
*/
|
*/
|
||||||
public interface With {
|
public interface With {
|
||||||
|
/**
|
||||||
|
* Return the current value for the {@code name} record component in the builder
|
||||||
|
*/
|
||||||
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current value for the {@code age} record component in the builder
|
||||||
|
*/
|
||||||
|
int age();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new record builder using the current values
|
* Return a new record builder using the current values
|
||||||
*/
|
*/
|
||||||
default NameAndAgeBuilder with() {
|
default NameAndAgeBuilder with() {
|
||||||
NameAndAge r = _downcast(this);
|
return new NameAndAgeBuilder(name(), age());
|
||||||
return NameAndAgeBuilder.builder(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,16 +219,14 @@ public class NameAndAgeBuilder {
|
|||||||
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
|
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
|
||||||
*/
|
*/
|
||||||
default NameAndAge withName(String name) {
|
default NameAndAge withName(String name) {
|
||||||
NameAndAge r = _downcast(this);
|
return new NameAndAge(name, age());
|
||||||
return new NameAndAge(name, r.age());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new instance of {@code NameAndAge} with a new value for {@code age}
|
* Return a new instance of {@code NameAndAge} with a new value for {@code age}
|
||||||
*/
|
*/
|
||||||
default NameAndAge withAge(int age) {
|
default NameAndAge withAge(int age) {
|
||||||
NameAndAge r = _downcast(this);
|
return new NameAndAge(name(), age);
|
||||||
return new NameAndAge(r.name(), age);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +272,7 @@ libraries where you are not able to annotate the source.
|
|||||||
|
|
||||||
E.g.
|
E.g.
|
||||||
|
|
||||||
```
|
```java
|
||||||
import some.library.code.ImportedRecord
|
import some.library.code.ImportedRecord
|
||||||
import some.library.code.ImportedInterface
|
import some.library.code.ImportedInterface
|
||||||
|
|
||||||
@@ -291,7 +295,7 @@ annotation. Use `packagePattern` to change this (see Javadoc for details).
|
|||||||
|
|
||||||
1) Add the dependency that contains the `@RecordBuilder` annotation.
|
1) Add the dependency that contains the `@RecordBuilder` annotation.
|
||||||
|
|
||||||
```
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder-core</artifactId>
|
<artifactId>record-builder-core</artifactId>
|
||||||
@@ -303,7 +307,7 @@ annotation. Use `packagePattern` to change this (see Javadoc for details).
|
|||||||
|
|
||||||
2) Enable the annotation processing for the Maven Compiler Plugin:
|
2) Enable the annotation processing for the Maven Compiler Plugin:
|
||||||
|
|
||||||
```
|
```xml
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
@@ -330,7 +334,7 @@ annotation. Use `packagePattern` to change this (see Javadoc for details).
|
|||||||
|
|
||||||
Add the following to your build.gradle file:
|
Add the following to your build.gradle file:
|
||||||
|
|
||||||
```
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$version-goes-here'
|
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$version-goes-here'
|
||||||
compileOnly 'io.soabase.record-builder:record-builder-core:$version-goes-here'
|
compileOnly 'io.soabase.record-builder:record-builder-core:$version-goes-here'
|
||||||
@@ -343,30 +347,13 @@ Depending on your IDE you are likely to need to enable Annotation Processing in
|
|||||||
|
|
||||||
## Customizing
|
## Customizing
|
||||||
|
|
||||||
The names of the generated methods, etc. are determined by [RecordBuilderMetaData](https://github.com/Randgalt/record-builder/blob/master/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilderMetaData.java). If you want to use your own meta data instance:
|
RecordBuilder can be customized to your needs and you can even create your
|
||||||
|
own custom RecordBuilder annotations. See [Customizing RecordBuilder](customizing.md)
|
||||||
- Create a class that implements RecordBuilderMetaData
|
for details.
|
||||||
- When compiling, make sure that the compiled class is in the processor path
|
|
||||||
- Add a "metaDataClass" compiler option with the class name. E.g. `javac ... -AmetaDataClass=foo.bar.MyMetaData`
|
|
||||||
|
|
||||||
Alternatively, you can provide values for each individual meta data (or combinations):
|
|
||||||
|
|
||||||
- `javac ... -Asuffix=foo`
|
|
||||||
- `javac ... -AinterfaceSuffix=foo`
|
|
||||||
- `javac ... -AcopyMethodName=foo`
|
|
||||||
- `javac ... -AbuilderMethodName=foo`
|
|
||||||
- `javac ... -AbuildMethodName=foo`
|
|
||||||
- `javac ... -AcomponentsMethodName=foo`
|
|
||||||
- `javac ... -AwithClassName=foo`
|
|
||||||
- `javac ... -AwithClassMethodPrefix=foo`
|
|
||||||
- `javac ... -AfileComment=foo`
|
|
||||||
- `javac ... -AfileIndent=foo`
|
|
||||||
- `javac ... -AprefixEnclosingClassNames=foo`
|
|
||||||
|
|
||||||
## Java 15 Versions
|
## Java 15 Versions
|
||||||
|
|
||||||
Artifacts compiled wth Java 15 are available. They are the same versions
|
Artifacts compiled wth Java 15 are available. These versions have `-java15` appended.
|
||||||
as the Java 16 versions with `-java15` appended.
|
|
||||||
|
|
||||||
Note: records are a preview feature only in Java 15. You'll need take a number of steps in order to try RecordBuilder:
|
Note: records are a preview feature only in Java 15. You'll need take a number of steps in order to try RecordBuilder:
|
||||||
|
|
||||||
@@ -380,7 +367,7 @@ You will need to enable preview in your build tools:
|
|||||||
|
|
||||||
### Maven
|
### Maven
|
||||||
|
|
||||||
```
|
```xml
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
@@ -421,7 +408,7 @@ Create a file in your project's root named `.mvn/jvm.config`. The file should ha
|
|||||||
|
|
||||||
### Gradle
|
### Gradle
|
||||||
|
|
||||||
```
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$record-builder-version-java15'
|
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$record-builder-version-java15'
|
||||||
compileOnly 'io.soabase.record-builder:record-builder-core:$record-builder-version-java15'
|
compileOnly 'io.soabase.record-builder:record-builder-core:$record-builder-version-java15'
|
||||||
|
|||||||
85
customizing.md
Normal file
85
customizing.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
[◀︎ RecordBuilder](README.md) • Customizing RecordBuilder
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
You can:
|
||||||
|
|
||||||
|
- [Customize an entire build](#customize-an-entire-build) - all uses of `@RecordBuilder` in your project
|
||||||
|
- [Customize a single record](#customize-a-single-record) annotated with `@RecordBuilder`
|
||||||
|
- [Create a custom annotation](#create-a-custom-annotation) that specifies your options and use that instead of `@RecordBuilder`
|
||||||
|
|
||||||
|
## Customize an entire build
|
||||||
|
|
||||||
|
To customize an entire build, use javac's annotation processor options via `-A` on the command line.
|
||||||
|
The options available are the same as the attributes in [@RecordBuilder.Options](record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java).
|
||||||
|
i.e. to disable "prefixing enclosing class names", compile with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
javac -AprefixEnclosingClassNames=false ...
|
||||||
|
```
|
||||||
|
|
||||||
|
_Note: use a separate `-A` for each option._
|
||||||
|
|
||||||
|
#### Maven
|
||||||
|
|
||||||
|
If you are using Maven, specify the options in the compiler plugin:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven-compiler-plugin-version}</version>
|
||||||
|
<configuration>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-AprefixEnclosingClassNames=false</arg>
|
||||||
|
<arg>-AfileComment="something different"</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gradle
|
||||||
|
|
||||||
|
For Gradle, specify the options:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
compilerArgs.addAll(['-AprefixEnclosingClassNames=false', '-AfileComment="something different"'])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customize a single record
|
||||||
|
|
||||||
|
To customize a single record, add `@RecordBuilder.Options` in addition to
|
||||||
|
`@RecordBuilder`.
|
||||||
|
|
||||||
|
E.g.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RecordBuilder.Options(withClassName = "Wither")
|
||||||
|
@RecordBuilder
|
||||||
|
public record MyRecord(String s){}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a custom annotation
|
||||||
|
|
||||||
|
Using `@RecordBuilder.Template` you can create your own RecordBuilder annotation
|
||||||
|
that uses the set of options you want. E.g. to create a custom annotation that
|
||||||
|
uses an alternate file comment and an alternate With classname:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RecordBuilder.Template(options = @RecordBuilder.Options(
|
||||||
|
fileComment = "MyCo license",
|
||||||
|
withClassName = "Wither"
|
||||||
|
))
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
public @interface MyCoRecordBuilder {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you can use `@MyCoRecordBuilder` instead of `@RecordBuilder` and the record
|
||||||
|
will be built with options as specified.
|
||||||
@@ -16,6 +16,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
jenv local 16-ea
|
jenv local 16
|
||||||
javahome
|
javahome
|
||||||
rm -fr .mvn
|
rm -fr .mvn
|
||||||
|
|||||||
52
pom.xml
52
pom.xml
@@ -5,12 +5,13 @@
|
|||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>1.19-SNAPSHOT</version>
|
<version>26</version>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>record-builder-core</module>
|
<module>record-builder-core</module>
|
||||||
<module>record-builder-processor</module>
|
<module>record-builder-processor</module>
|
||||||
<module>record-builder-test</module>
|
<module>record-builder-test</module>
|
||||||
|
<module>record-builder-validator</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -33,10 +34,16 @@
|
|||||||
<maven-shade-plugin-version>3.2.1</maven-shade-plugin-version>
|
<maven-shade-plugin-version>3.2.1</maven-shade-plugin-version>
|
||||||
<maven-release-plugin-version>2.5.3</maven-release-plugin-version>
|
<maven-release-plugin-version>2.5.3</maven-release-plugin-version>
|
||||||
<maven-surefire-plugin-version>3.0.0-M5</maven-surefire-plugin-version>
|
<maven-surefire-plugin-version>3.0.0-M5</maven-surefire-plugin-version>
|
||||||
|
<maven-jar-plugin-version>3.2.0</maven-jar-plugin-version>
|
||||||
|
|
||||||
|
<license-file-path>src/etc/header.txt</license-file-path>
|
||||||
|
|
||||||
<javapoet-version>1.12.1</javapoet-version>
|
<javapoet-version>1.12.1</javapoet-version>
|
||||||
<junit-jupiter-version>5.5.2</junit-jupiter-version>
|
<junit-jupiter-version>5.5.2</junit-jupiter-version>
|
||||||
<asm-version>7.2</asm-version>
|
<asm-version>7.2</asm-version>
|
||||||
|
<validation-api-version>2.0.1.Final</validation-api-version>
|
||||||
|
<hibernate-validator-version>6.0.20.Final</hibernate-validator-version>
|
||||||
|
<javax-el-version>3.0.1-b09</javax-el-version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<name>Record Builder</name>
|
<name>Record Builder</name>
|
||||||
@@ -73,7 +80,7 @@
|
|||||||
<url>https://github.com/randgalt/record-builder</url>
|
<url>https://github.com/randgalt/record-builder</url>
|
||||||
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
|
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
|
||||||
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
|
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
|
||||||
<tag>record-builder-1.16</tag>
|
<tag>record-builder-26</tag>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<issueManagement>
|
<issueManagement>
|
||||||
@@ -108,11 +115,35 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
|
<artifactId>record-builder-validator</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter</artifactId>
|
<artifactId>junit-jupiter</artifactId>
|
||||||
<version>${junit-jupiter-version}</version>
|
<version>${junit-jupiter-version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.validation</groupId>
|
||||||
|
<artifactId>validation-api</artifactId>
|
||||||
|
<version>${validation-api-version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.validator</groupId>
|
||||||
|
<artifactId>hibernate-validator</artifactId>
|
||||||
|
<version>${hibernate-validator-version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish</groupId>
|
||||||
|
<artifactId>javax.el</artifactId>
|
||||||
|
<version>${javax-el-version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -159,7 +190,7 @@
|
|||||||
<artifactId>maven-license-plugin</artifactId>
|
<artifactId>maven-license-plugin</artifactId>
|
||||||
<version>${maven-license-plugin-version}</version>
|
<version>${maven-license-plugin-version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<header>src/etc/header.txt</header>
|
<header>${license-file-path}</header>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>**/*.apt</exclude>
|
<exclude>**/*.apt</exclude>
|
||||||
<exclude>**/*.md</exclude>
|
<exclude>**/*.md</exclude>
|
||||||
@@ -185,6 +216,8 @@
|
|||||||
<exclude>**/jvm.config</exclude>
|
<exclude>**/jvm.config</exclude>
|
||||||
<exclude>**/.java-version</exclude>
|
<exclude>**/.java-version</exclude>
|
||||||
<exclude>**/.travis.yml</exclude>
|
<exclude>**/.travis.yml</exclude>
|
||||||
|
<exclude>**/gradlew</exclude>
|
||||||
|
<exclude>**/.github/**</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
<strictCheck>true</strictCheck>
|
<strictCheck>true</strictCheck>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -283,6 +316,18 @@
|
|||||||
<argLine>${enable-preview}</argLine>
|
<argLine>${enable-preview}</argLine>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>${maven-jar-plugin-version}</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
|
<version>${maven-gpg-plugin-version}</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|
||||||
@@ -326,7 +371,6 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-gpg-plugin</artifactId>
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
<version>${maven-gpg-plugin-version}</version>
|
|
||||||
<configuration>
|
<configuration>
|
||||||
<passphrase>${gpg.passphrase}</passphrase>
|
<passphrase>${gpg.passphrase}</passphrase>
|
||||||
<useAgent>true</useAgent>
|
<useAgent>true</useAgent>
|
||||||
|
|||||||
@@ -3,9 +3,29 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>1.19-SNAPSHOT</version>
|
<version>26</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>record-builder-core</artifactId>
|
<artifactId>record-builder-core</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestEntries>
|
||||||
|
<Automatic-Module-Name>io.soabase.recordbuilder.core</Automatic-Module-Name>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -15,16 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.core;
|
package io.soabase.recordbuilder.core;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
public @interface RecordBuilder {
|
public @interface RecordBuilder {
|
||||||
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Inherited
|
||||||
@interface Include {
|
@interface Include {
|
||||||
Class<?>[] value();
|
Class<?>[] value();
|
||||||
|
|
||||||
@@ -38,4 +37,119 @@ public @interface RecordBuilder {
|
|||||||
*/
|
*/
|
||||||
String packagePattern() default "@";
|
String packagePattern() default "@";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
@interface Options {
|
||||||
|
/**
|
||||||
|
* The builder class name will be the name of the record (prefixed with any enclosing class) plus this suffix. E.g.
|
||||||
|
* if the record name is "Foo", the builder will be named "FooBuilder".
|
||||||
|
*/
|
||||||
|
String suffix() default "Builder";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by {@code RecordInterface}. The generated record will have the same name as the annotated interface
|
||||||
|
* plus this suffix. E.g. if the interface name is "Foo", the record will be named "FooRecord".
|
||||||
|
*/
|
||||||
|
String interfaceSuffix() default "Record";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name to use for the copy builder
|
||||||
|
*/
|
||||||
|
String copyMethodName() default "builder";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name to use for the builder
|
||||||
|
*/
|
||||||
|
String builderMethodName() default "builder";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name to use for the build method
|
||||||
|
*/
|
||||||
|
String buildMethodName() default "build";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name to use for the method that returns the record components as a stream
|
||||||
|
*/
|
||||||
|
String componentsMethodName() default "stream";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name to use for the nested With class
|
||||||
|
*/
|
||||||
|
String withClassName() default "With";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix to use for the methods in the With class
|
||||||
|
*/
|
||||||
|
String withClassMethodPrefix() default "with";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the comment to place at the top of generated files. Return null or an empty string for no comment.
|
||||||
|
*/
|
||||||
|
String fileComment() default "Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the file indent to use
|
||||||
|
*/
|
||||||
|
String fileIndent() default " ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the record is declared inside of another class, the outer class's name will
|
||||||
|
* be prefixed to the builder name if this returns true.
|
||||||
|
*/
|
||||||
|
boolean prefixEnclosingClassNames() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, any annotations (if applicable) on record components are copied
|
||||||
|
* to the builder methods
|
||||||
|
*
|
||||||
|
* @return true/false
|
||||||
|
*/
|
||||||
|
boolean inheritComponentAnnotations() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default value of {@code Optional} record components to
|
||||||
|
* {@code Optional.empty()}
|
||||||
|
*/
|
||||||
|
boolean emptyDefaultForOptional() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add not-null checks for record components annotated with any annotation named either "NotNull",
|
||||||
|
* "NoNull", or "NonNull" (see {@link #interpretNotNullsPattern()} for the actual regex matching pattern).
|
||||||
|
*/
|
||||||
|
boolean interpretNotNulls() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #interpretNotNulls()} is true, this is the regex pattern used to determine if an annotation name
|
||||||
|
* means "not null"
|
||||||
|
*/
|
||||||
|
String interpretNotNullsPattern() default "(?i)((notnull)|(nonnull)|(nonull))";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Pass built records through the Java Validation API if it's available in the classpath.</p>
|
||||||
|
*
|
||||||
|
* <p>IMPORTANT:
|
||||||
|
* if this option is enabled you must include the {@code record-builder-validator} dependency in addition
|
||||||
|
* to {@code record-builder-core}. {@code record-builder-validator} is implemented completely via reflection and
|
||||||
|
* does not require other dependencies. Alternatively, you can define your own class with the package {@code package io.soabase.recordbuilder.validator;}
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(ElementType.ANNOTATION_TYPE)
|
||||||
|
@Inherited
|
||||||
|
@interface Template {
|
||||||
|
RecordBuilder.Options options();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,144 +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;
|
|
||||||
|
|
||||||
public interface RecordBuilderMetaData {
|
|
||||||
/**
|
|
||||||
* If you want to use your own meta data instance:
|
|
||||||
* <ul>
|
|
||||||
* <li>create a class that implements {@code RecordBuilderMetaData}</li>
|
|
||||||
* <li>When compiling, make sure that compiled class is in the processor path</li>
|
|
||||||
* <li>Add a "metaDataClass" compiler option with the class name. E.g. {@code javac ... -AmetaDataClass=foo.bar.MyMetaData}</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
String JAVAC_OPTION_NAME = "metaDataClass";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default meta data instance
|
|
||||||
*/
|
|
||||||
RecordBuilderMetaData DEFAULT = new RecordBuilderMetaData() {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The builder class name will be the name of the record (prefixed with any enclosing class) plus this suffix. E.g.
|
|
||||||
* if the record name is "Foo", the builder will be named "FooBuilder".
|
|
||||||
*
|
|
||||||
* @return suffix
|
|
||||||
*/
|
|
||||||
default String suffix() {
|
|
||||||
return "Builder";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by {@code RecordInterface}. The generated record will have the same name as the annotated interface
|
|
||||||
* plus this suffix. E.g. if the interface name is "Foo", the record will be named "FooRecord".
|
|
||||||
*
|
|
||||||
* @return suffix
|
|
||||||
*/
|
|
||||||
default String interfaceSuffix() {
|
|
||||||
return "Record";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name to use for the copy builder
|
|
||||||
*
|
|
||||||
* @return copy builder name
|
|
||||||
*/
|
|
||||||
default String copyMethodName() {
|
|
||||||
return builderMethodName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name to use for the builder
|
|
||||||
*
|
|
||||||
* @return builder name
|
|
||||||
*/
|
|
||||||
default String builderMethodName() {
|
|
||||||
return "builder";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name to use for the build method
|
|
||||||
*
|
|
||||||
* @return build method
|
|
||||||
*/
|
|
||||||
default String buildMethodName() {
|
|
||||||
return "build";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name to use for the downcast method
|
|
||||||
*
|
|
||||||
* @return downcast method
|
|
||||||
*/
|
|
||||||
default String downCastMethodName() {
|
|
||||||
return "_downcast";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name to use for the method that returns the record components as a stream
|
|
||||||
*
|
|
||||||
* @return build method
|
|
||||||
*/
|
|
||||||
default String componentsMethodName() {
|
|
||||||
return "stream";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name to use for the nested With class
|
|
||||||
*
|
|
||||||
* @return with class name
|
|
||||||
*/
|
|
||||||
default String withClassName() {
|
|
||||||
return "With";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The prefix to use for the methods in the With class
|
|
||||||
*
|
|
||||||
* @return prefix
|
|
||||||
*/
|
|
||||||
default String withClassMethodPrefix() {
|
|
||||||
return "with";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the comment to place at the top of generated files. Return null or an empty string for no comment.
|
|
||||||
*
|
|
||||||
* @return comment or empty
|
|
||||||
*/
|
|
||||||
default String fileComment() {
|
|
||||||
return "Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the file indent to use
|
|
||||||
*
|
|
||||||
* @return file index
|
|
||||||
*/
|
|
||||||
default String fileIndent() {
|
|
||||||
return " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the record is declared inside of another class, the outer class's name will
|
|
||||||
* be prefixed to the builder name if this returns true.
|
|
||||||
*
|
|
||||||
* @return true/false
|
|
||||||
*/
|
|
||||||
default boolean prefixEnclosingClassNames() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,17 +16,20 @@
|
|||||||
package io.soabase.recordbuilder.core;
|
package io.soabase.recordbuilder.core;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
public @interface RecordInterface {
|
public @interface RecordInterface {
|
||||||
boolean addRecordBuilder() default true;
|
boolean addRecordBuilder() default true;
|
||||||
|
|
||||||
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
@Target({ElementType.TYPE, ElementType.PACKAGE})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Inherited
|
||||||
@interface Include {
|
@interface Include {
|
||||||
Class<?>[] value();
|
Class<?>[] value();
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,16 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>1.19-SNAPSHOT</version>
|
<version>26</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>record-builder-processor</artifactId>
|
<artifactId>record-builder-processor</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup</groupId>
|
<groupId>com.squareup</groupId>
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* 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 javax.lang.model.element.Modifier;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||||
|
|
||||||
|
class CollectionBuilderUtils {
|
||||||
|
private final boolean enabled;
|
||||||
|
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, boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
|
||||||
|
listShimName = adjustShimName(recordComponents, "__list", 0);
|
||||||
|
mapShimName = adjustShimName(recordComponents, "__map", 0);
|
||||||
|
setShimName = adjustShimName(recordComponents, "__set", 0);
|
||||||
|
collectionShimName = adjustShimName(recordComponents, "__collection", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(CodeBlock.Builder builder, RecordClassType component) {
|
||||||
|
if (enabled) {
|
||||||
|
if (component.rawTypeName().equals(listType)) {
|
||||||
|
needsListShim = true;
|
||||||
|
builder.add("$L($L)", listShimName, component.name());
|
||||||
|
} else if (component.rawTypeName().equals(mapType)) {
|
||||||
|
needsMapShim = true;
|
||||||
|
builder.add("$L($L)", mapShimName, component.name());
|
||||||
|
} else if (component.rawTypeName().equals(setType)) {
|
||||||
|
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 (!enabled) {
|
||||||
|
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 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 (o != null) ? $T.copyOf(o) : $T.of()", setType, 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,11 +19,12 @@ import com.squareup.javapoet.ClassName;
|
|||||||
import com.squareup.javapoet.ParameterizedTypeName;
|
import com.squareup.javapoet.ParameterizedTypeName;
|
||||||
import com.squareup.javapoet.TypeName;
|
import com.squareup.javapoet.TypeName;
|
||||||
import com.squareup.javapoet.TypeVariableName;
|
import com.squareup.javapoet.TypeVariableName;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.AnnotationMirror;
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
import javax.lang.model.element.AnnotationValue;
|
import javax.lang.model.element.AnnotationValue;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.ElementKind;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.element.RecordComponentElement;
|
import javax.lang.model.element.RecordComponentElement;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
@@ -107,8 +108,10 @@ public class ElementUtils {
|
|||||||
return new ClassType(ParameterizedTypeName.get(builderClassName, typeNames), builderClassName.simpleName());
|
return new ClassType(ParameterizedTypeName.get(builderClassName, typeNames), builderClassName.simpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClassType getClassType(RecordComponentElement recordComponent) {
|
public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv, RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||||
return new ClassType(TypeName.get(recordComponent.asType()), recordComponent.getSimpleName().toString());
|
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 String getWithMethodName(ClassType component, String prefix) {
|
public static String getWithMethodName(ClassType component, String prefix) {
|
||||||
@@ -119,12 +122,29 @@ public class ElementUtils {
|
|||||||
return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getBuilderName(TypeElement element, RecordBuilderMetaData metaData, ClassType classType, String suffix) {
|
public static String getBuilderName(TypeElement element, RecordBuilder.Options metaData, ClassType classType, String suffix) {
|
||||||
// generate the class name
|
// generate the class name
|
||||||
var baseName = classType.name() + suffix;
|
var baseName = classType.name() + suffix;
|
||||||
return metaData.prefixEnclosingClassNames() ? (getBuilderNamePrefix(element.getEnclosingElement()) + baseName) : baseName;
|
return metaData.prefixEnclosingClassNames() ? (getBuilderNamePrefix(element.getEnclosingElement()) + baseName) : baseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<? extends Element> findCanonicalConstructor(TypeElement record) {
|
||||||
|
if ( record.getKind() != ElementKind.RECORD ) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on https://github.com/openjdk/jdk/pull/3556/files#diff-a6270f4b50989abe733607c69038b2036306d13f77276af005d023b7fc57f1a2R2368
|
||||||
|
var componentList = record.getRecordComponents().stream().map(e -> e.asType().toString()).collect(Collectors.toList());
|
||||||
|
return record.getEnclosedElements().stream()
|
||||||
|
.filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
|
||||||
|
.filter(element -> {
|
||||||
|
var parameters = ((ExecutableElement)element).getParameters();
|
||||||
|
var parametersList = parameters.stream().map(e -> e.asType().toString()).collect(Collectors.toList());
|
||||||
|
return componentList.equals(parametersList);
|
||||||
|
})
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
private static String getBuilderNamePrefix(Element element) {
|
private static String getBuilderNamePrefix(Element element) {
|
||||||
// prefix enclosing class names if nested in a class
|
// prefix enclosing class names if nested in a class
|
||||||
if (element instanceof TypeElement) {
|
if (element instanceof TypeElement) {
|
||||||
|
|||||||
@@ -15,26 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.processor;
|
package io.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
import com.squareup.javapoet.ClassName;
|
import com.squareup.javapoet.*;
|
||||||
import com.squareup.javapoet.CodeBlock;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
import com.squareup.javapoet.FieldSpec;
|
|
||||||
import com.squareup.javapoet.MethodSpec;
|
|
||||||
import com.squareup.javapoet.ParameterSpec;
|
|
||||||
import com.squareup.javapoet.ParameterizedTypeName;
|
|
||||||
import com.squareup.javapoet.TypeName;
|
|
||||||
import com.squareup.javapoet.TypeSpec;
|
|
||||||
import com.squareup.javapoet.TypeVariableName;
|
|
||||||
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
|
||||||
|
|
||||||
import javax.lang.model.element.Modifier;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.*;
|
||||||
|
import java.util.*;
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -43,32 +31,44 @@ import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
|||||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||||
|
|
||||||
class InternalRecordBuilderProcessor
|
class InternalRecordBuilderProcessor {
|
||||||
{
|
private final RecordBuilder.Options metaData;
|
||||||
private final RecordBuilderMetaData metaData;
|
|
||||||
private final ClassType recordClassType;
|
private final ClassType recordClassType;
|
||||||
private final String packageName;
|
private final String packageName;
|
||||||
private final ClassType builderClassType;
|
private final ClassType builderClassType;
|
||||||
private final List<TypeVariableName> typeVariables;
|
private final List<TypeVariableName> typeVariables;
|
||||||
private final List<ClassType> recordComponents;
|
private final List<RecordClassType> recordComponents;
|
||||||
private final TypeSpec builderType;
|
private final TypeSpec builderType;
|
||||||
private final TypeSpec.Builder builder;
|
private final TypeSpec.Builder builder;
|
||||||
private final String uniqueVarName;
|
private final String uniqueVarName;
|
||||||
|
private final Pattern notNullPattern;
|
||||||
|
private final CollectionBuilderUtils collectionBuilderUtils;
|
||||||
|
|
||||||
InternalRecordBuilderProcessor(TypeElement record, RecordBuilderMetaData metaData, Optional<String> packageNameOpt)
|
private static final TypeName overrideType = TypeName.get(Override.class);
|
||||||
{
|
private static final TypeName optionalType = TypeName.get(Optional.class);
|
||||||
this.metaData = metaData;
|
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 = getMetaData(record, metaData);
|
||||||
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
||||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(record));
|
packageName = packageNameOpt.orElse(recordActualPackage);
|
||||||
builderClassType = ElementUtils.getClassType(packageName, getBuilderName(record, metaData, recordClassType, metaData.suffix()), record.getTypeParameters());
|
builderClassType = ElementUtils.getClassType(packageName, getBuilderName(record, metaData, recordClassType, metaData.suffix()), record.getTypeParameters());
|
||||||
typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
|
typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
|
||||||
recordComponents = record.getRecordComponents().stream().map(ElementUtils::getClassType).collect(Collectors.toList());
|
recordComponents = buildRecordComponents(record);
|
||||||
uniqueVarName = getUniqueVarName();
|
uniqueVarName = getUniqueVarName();
|
||||||
|
notNullPattern = Pattern.compile(metaData.interpretNotNullsPattern());
|
||||||
|
collectionBuilderUtils = new CollectionBuilderUtils(recordComponents, this.metaData.useImmutableCollections());
|
||||||
|
|
||||||
builder = TypeSpec.classBuilder(builderClassType.name())
|
builder = TypeSpec.classBuilder(builderClassType.name())
|
||||||
.addModifiers(Modifier.PUBLIC)
|
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.addTypeVariables(typeVariables);
|
.addTypeVariables(typeVariables);
|
||||||
|
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
|
||||||
addWithNestedClass();
|
addWithNestedClass();
|
||||||
addDefaultConstructor();
|
addDefaultConstructor();
|
||||||
addStaticBuilder();
|
addStaticBuilder();
|
||||||
@@ -87,27 +87,53 @@ class InternalRecordBuilderProcessor
|
|||||||
add1SetterMethod(component);
|
add1SetterMethod(component);
|
||||||
add1GetterMethod(component);
|
add1GetterMethod(component);
|
||||||
});
|
});
|
||||||
addStaticDowncastMethod();
|
collectionBuilderUtils.addShims(builder);
|
||||||
builderType = builder.build();
|
builderType = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
String packageName()
|
String packageName() {
|
||||||
{
|
|
||||||
return packageName;
|
return packageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassType builderClassType()
|
ClassType builderClassType() {
|
||||||
{
|
|
||||||
return builderClassType;
|
return builderClassType;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeSpec builderType()
|
TypeSpec builderType() {
|
||||||
{
|
|
||||||
return builderType;
|
return builderType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWithNestedClass()
|
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());
|
||||||
|
var recordComponents = record.getRecordComponents();
|
||||||
|
return IntStream.range(0, recordComponents.size())
|
||||||
|
.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);
|
||||||
|
})
|
||||||
|
.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:
|
Adds a nested interface that adds withers similar to:
|
||||||
|
|
||||||
@@ -122,14 +148,14 @@ class InternalRecordBuilderProcessor
|
|||||||
.addJavadoc("Add withers to {@code $L}\n", recordClassType.name())
|
.addJavadoc("Add withers to {@code $L}\n", recordClassType.name())
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.addTypeVariables(typeVariables);
|
.addTypeVariables(typeVariables);
|
||||||
|
recordComponents.forEach(component -> addWithGetterMethod(classBuilder, component));
|
||||||
addWithBuilderMethod(classBuilder);
|
addWithBuilderMethod(classBuilder);
|
||||||
addWithSuppliedBuilderMethod(classBuilder);
|
addWithSuppliedBuilderMethod(classBuilder);
|
||||||
IntStream.range(0, recordComponents.size()).forEach(index -> add1WithMethod(classBuilder, recordComponents.get(index), index));
|
IntStream.range(0, recordComponents.size()).forEach(index -> add1WithMethod(classBuilder, recordComponents.get(index), index));
|
||||||
builder.addType(classBuilder.build());
|
builder.addType(classBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWithSuppliedBuilderMethod(TypeSpec.Builder classBuilder)
|
private void addWithSuppliedBuilderMethod(TypeSpec.Builder classBuilder) {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds a method that returns a pre-filled copy builder similar to:
|
Adds a method that returns a pre-filled copy builder similar to:
|
||||||
|
|
||||||
@@ -156,19 +182,18 @@ class InternalRecordBuilderProcessor
|
|||||||
classBuilder.addMethod(methodSpec);
|
classBuilder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addWithBuilderMethod(TypeSpec.Builder classBuilder)
|
private void addWithBuilderMethod(TypeSpec.Builder classBuilder) {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds a method that returns a pre-filled copy builder similar to:
|
Adds a method that returns a pre-filled copy builder similar to:
|
||||||
|
|
||||||
default MyRecordBuilder with() {
|
default MyRecordBuilder with() {
|
||||||
MyRecord r = _downcast(this);
|
|
||||||
return MyRecordBuilder.builder(r);
|
return MyRecordBuilder.builder(r);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var codeBlockBuilder = CodeBlock.builder()
|
var codeBlockBuilder = CodeBlock.builder()
|
||||||
.add("$T $L = $L(this);\n", recordClassType.typeName(), uniqueVarName, metaData.downCastMethodName())
|
.add("return new $L(", builderClassType.name());
|
||||||
.add("return $L.$L($L);", builderClassType.name(), metaData.copyMethodName(), uniqueVarName);
|
addComponentCallsAsArguments(-1, codeBlockBuilder);
|
||||||
|
codeBlockBuilder.add(");");
|
||||||
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
|
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.addJavadoc("Return a new record builder using the current values")
|
.addJavadoc("Return a new record builder using the current values")
|
||||||
@@ -179,64 +204,69 @@ class InternalRecordBuilderProcessor
|
|||||||
classBuilder.addMethod(methodSpec);
|
classBuilder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUniqueVarName()
|
private String getUniqueVarName() {
|
||||||
{
|
|
||||||
return getUniqueVarName("");
|
return getUniqueVarName("");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUniqueVarName(String prefix)
|
private String getUniqueVarName(String prefix) {
|
||||||
{
|
|
||||||
var name = prefix + "r";
|
var name = prefix + "r";
|
||||||
var alreadyExists = recordComponents.stream()
|
var alreadyExists = recordComponents.stream()
|
||||||
.map(ClassType::name)
|
.map(ClassType::name)
|
||||||
.anyMatch(n -> n.equals(name));
|
.anyMatch(n -> n.equals(name));
|
||||||
return alreadyExists ? getUniqueVarName(prefix + "_") : name;
|
return alreadyExists ? getUniqueVarName(prefix + "_") : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add1WithMethod(TypeSpec.Builder classBuilder, ClassType component, int index)
|
private void add1WithMethod(TypeSpec.Builder classBuilder, RecordClassType component, int index) {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds a with method for the component similar to:
|
Adds a with method for the component similar to:
|
||||||
|
|
||||||
default MyRecord withName(String name) {
|
default MyRecord withName(String name) {
|
||||||
MyRecord r = _downcast(this);
|
|
||||||
return new MyRecord(name, r.age());
|
return new MyRecord(name, r.age());
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var codeBlockBuilder = CodeBlock.builder();
|
var codeBlockBuilder = CodeBlock.builder();
|
||||||
if (recordComponents.size() > 1) {
|
addNullCheckCodeBlock(codeBlockBuilder, index);
|
||||||
codeBlockBuilder.add("$T $L = $L(this);\n", recordClassType.typeName(), uniqueVarName, metaData.downCastMethodName());
|
codeBlockBuilder.add("$[return ");
|
||||||
|
if (metaData.useValidationApi()) {
|
||||||
|
codeBlockBuilder.add("$T.validate(", validatorTypeName);
|
||||||
}
|
}
|
||||||
codeBlockBuilder.add("return new $T(", recordClassType.typeName());
|
codeBlockBuilder.add("new $T(", recordClassType.typeName());
|
||||||
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
|
addComponentCallsAsArguments(index, codeBlockBuilder);
|
||||||
if (parameterIndex > 0) {
|
codeBlockBuilder.add(")");
|
||||||
codeBlockBuilder.add(", ");
|
if (metaData.useValidationApi()) {
|
||||||
}
|
codeBlockBuilder.add(")");
|
||||||
ClassType parameterComponent = recordComponents.get(parameterIndex);
|
}
|
||||||
if (parameterIndex == index) {
|
codeBlockBuilder.add(";$]");
|
||||||
codeBlockBuilder.add(parameterComponent.name());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
codeBlockBuilder.add("$L.$L()", uniqueVarName, parameterComponent.name());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
codeBlockBuilder.add(");");
|
|
||||||
|
|
||||||
var methodName = getWithMethodName(component, metaData.withClassMethodPrefix());
|
var methodName = getWithMethodName(component, metaData.withClassMethodPrefix());
|
||||||
var parameterSpec = ParameterSpec.builder(component.typeName(), component.name()).build();
|
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||||
|
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||||
var methodSpec = MethodSpec.methodBuilder(methodName)
|
var methodSpec = MethodSpec.methodBuilder(methodName)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.addJavadoc("Return a new instance of {@code $L} with a new value for {@code $L}\n", recordClassType.name(), component.name())
|
.addJavadoc("Return a new instance of {@code $L} with a new value for {@code $L}\n", recordClassType.name(), component.name())
|
||||||
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||||
.addParameter(parameterSpec)
|
.addParameter(parameterSpecBuilder.build())
|
||||||
.addCode(codeBlockBuilder.build())
|
.addCode(codeBlockBuilder.build())
|
||||||
.returns(recordClassType.typeName())
|
.returns(recordClassType.typeName())
|
||||||
.build();
|
.build();
|
||||||
classBuilder.addMethod(methodSpec);
|
classBuilder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDefaultConstructor()
|
private void addComponentCallsAsArguments(int index, CodeBlock.Builder codeBlockBuilder) {
|
||||||
{
|
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
|
||||||
|
if (parameterIndex > 0) {
|
||||||
|
codeBlockBuilder.add(", ");
|
||||||
|
}
|
||||||
|
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||||
|
if (parameterIndex == index) {
|
||||||
|
collectionBuilderUtils.add(codeBlockBuilder, parameterComponent);
|
||||||
|
} else {
|
||||||
|
codeBlockBuilder.add("$L()", parameterComponent.name());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDefaultConstructor() {
|
||||||
/*
|
/*
|
||||||
Adds a default constructor similar to:
|
Adds a default constructor similar to:
|
||||||
|
|
||||||
@@ -250,8 +280,7 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(constructor);
|
builder.addMethod(constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStaticBuilder()
|
private void addStaticBuilder() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds an static builder similar to:
|
Adds an static builder similar to:
|
||||||
|
|
||||||
@@ -266,13 +295,38 @@ class InternalRecordBuilderProcessor
|
|||||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.returns(recordClassType.typeName())
|
.returns(recordClassType.typeName())
|
||||||
.addStatement(codeBlock);
|
.addCode(codeBlock);
|
||||||
recordComponents.forEach(component -> builder.addParameter(component.typeName(), component.name()));
|
recordComponents.forEach(component -> {
|
||||||
|
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||||
|
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||||
|
builder.addParameter(parameterSpecBuilder.build());
|
||||||
|
});
|
||||||
this.builder.addMethod(builder.build());
|
this.builder.addMethod(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAllArgsConstructor()
|
private void addNullCheckCodeBlock(CodeBlock.Builder builder) {
|
||||||
{
|
if (metaData.interpretNotNulls()) {
|
||||||
|
for (int i = 0; i < recordComponents.size(); ++i) {
|
||||||
|
addNullCheckCodeBlock(builder, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) {
|
||||||
|
if (metaData.interpretNotNulls()) {
|
||||||
|
var component = recordComponents.get(index);
|
||||||
|
if (!component.typeName().isPrimitive() && isNullAnnotated(component)) {
|
||||||
|
builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNullAnnotated(RecordClassType component) {
|
||||||
|
return component.getCanonicalConstructorAnnotations().stream()
|
||||||
|
.anyMatch(annotation -> notNullPattern.matcher(annotation.getAnnotationType().asElement().getSimpleName().toString()).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAllArgsConstructor() {
|
||||||
/*
|
/*
|
||||||
Adds an all-args constructor similar to:
|
Adds an all-args constructor similar to:
|
||||||
|
|
||||||
@@ -293,8 +347,7 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(constructorBuilder.build());
|
builder.addMethod(constructorBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToStringMethod()
|
private void addToStringMethod() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
add a toString() method similar to:
|
add a toString() method similar to:
|
||||||
|
|
||||||
@@ -323,8 +376,7 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addHashCodeMethod()
|
private void addHashCodeMethod() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
add a hashCode() method similar to:
|
add a hashCode() method similar to:
|
||||||
|
|
||||||
@@ -352,8 +404,7 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEqualsMethod()
|
private void addEqualsMethod() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
add an equals() method similar to:
|
add an equals() method similar to:
|
||||||
|
|
||||||
@@ -371,8 +422,7 @@ class InternalRecordBuilderProcessor
|
|||||||
String name = recordComponent.name();
|
String name = recordComponent.name();
|
||||||
if (recordComponent.typeName().isPrimitive()) {
|
if (recordComponent.typeName().isPrimitive()) {
|
||||||
codeBuilder.add("\n&& ($L == $L.$L)", name, uniqueVarName, name);
|
codeBuilder.add("\n&& ($L == $L.$L)", name, uniqueVarName, name);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
codeBuilder.add("\n&& $T.equals($L, $L.$L)", Objects.class, name, uniqueVarName, name);
|
codeBuilder.add("\n&& $T.equals($L, $L.$L)", Objects.class, name, uniqueVarName, name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -389,8 +439,7 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBuildMethod()
|
private void addBuildMethod() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds the build method that generates the record similar to:
|
Adds the build method that generates the record similar to:
|
||||||
|
|
||||||
@@ -404,7 +453,7 @@ class InternalRecordBuilderProcessor
|
|||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.returns(recordClassType.typeName())
|
.returns(recordClassType.typeName())
|
||||||
.addStatement(codeBlock)
|
.addCode(codeBlock)
|
||||||
.build();
|
.build();
|
||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
@@ -414,19 +463,28 @@ class InternalRecordBuilderProcessor
|
|||||||
Builds the code block for allocating the record from its parts
|
Builds the code block for allocating the record from its parts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var codeBuilder = CodeBlock.builder().add("return new $T(", recordClassType.typeName());
|
var codeBuilder = CodeBlock.builder();
|
||||||
|
addNullCheckCodeBlock(codeBuilder);
|
||||||
|
codeBuilder.add("$[return ");
|
||||||
|
if (metaData.useValidationApi()) {
|
||||||
|
codeBuilder.add("$T.validate(", validatorTypeName);
|
||||||
|
}
|
||||||
|
codeBuilder.add("new $T(", recordClassType.typeName());
|
||||||
IntStream.range(0, recordComponents.size()).forEach(index -> {
|
IntStream.range(0, recordComponents.size()).forEach(index -> {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
codeBuilder.add(", ");
|
codeBuilder.add(", ");
|
||||||
}
|
}
|
||||||
codeBuilder.add("$L", recordComponents.get(index).name());
|
collectionBuilderUtils.add(codeBuilder, recordComponents.get(index));
|
||||||
});
|
});
|
||||||
codeBuilder.add(")");
|
codeBuilder.add(")");
|
||||||
|
if (metaData.useValidationApi()) {
|
||||||
|
codeBuilder.add(")");
|
||||||
|
}
|
||||||
|
codeBuilder.add(";$]");
|
||||||
return codeBuilder.build();
|
return codeBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStaticCopyBuilderMethod()
|
private void addStaticCopyBuilderMethod() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds a copy builder method that pre-fills the builder with existing values similar to:
|
Adds a copy builder method that pre-fills the builder with existing values similar to:
|
||||||
|
|
||||||
@@ -455,8 +513,7 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStaticDefaultBuilderMethod()
|
private void addStaticDefaultBuilderMethod() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds a the default builder method similar to:
|
Adds a the default builder method similar to:
|
||||||
|
|
||||||
@@ -475,8 +532,7 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStaticComponentsMethod()
|
private void addStaticComponentsMethod() {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
Adds a static method that converts a record instance into a stream of its component parts
|
Adds a static method that converts a record instance into a stream of its component parts
|
||||||
|
|
||||||
@@ -510,51 +566,79 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStaticDowncastMethod()
|
private void add1Field(ClassType component) {
|
||||||
{
|
|
||||||
/*
|
|
||||||
Adds a method that downcasts to the record type
|
|
||||||
|
|
||||||
private static MyRecord _downcast(Object this) {
|
|
||||||
return (MyRecord)this;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
var codeBlockBuilder = CodeBlock.builder()
|
|
||||||
.add("try {\n")
|
|
||||||
.indent()
|
|
||||||
.add("return ($T)obj;\n", recordClassType.typeName())
|
|
||||||
.unindent()
|
|
||||||
.add("}\n")
|
|
||||||
.add("catch (ClassCastException dummy) {\n")
|
|
||||||
.indent()
|
|
||||||
.add("throw new RuntimeException($S);\n", builderClassType.name() + "." + metaData.withClassName() + " can only be implemented by " + recordClassType.name())
|
|
||||||
.unindent()
|
|
||||||
.add("}");
|
|
||||||
var methodSpec = MethodSpec.methodBuilder(metaData.downCastMethodName())
|
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
|
||||||
.addJavadoc("Downcast to {@code $L}\n", recordClassType.name())
|
|
||||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
|
||||||
.addParameter(Object.class, "obj")
|
|
||||||
.addTypeVariables(typeVariables)
|
|
||||||
.returns(recordClassType.typeName())
|
|
||||||
.addCode(codeBlockBuilder.build())
|
|
||||||
.build();
|
|
||||||
builder.addMethod(methodSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add1Field(ClassType component)
|
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
For a single record component, add a field similar to:
|
For a single record component, add a field similar to:
|
||||||
|
|
||||||
private T p;
|
private T p;
|
||||||
*/
|
*/
|
||||||
var fieldSpec = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE).build();
|
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
|
||||||
builder.addField(fieldSpec);
|
if (metaData.emptyDefaultForOptional()) {
|
||||||
|
TypeName thisOptionalType = null;
|
||||||
|
if (isOptional(component)) {
|
||||||
|
thisOptionalType = optionalType;
|
||||||
|
} else if (component.typeName().equals(optionalIntType)) {
|
||||||
|
thisOptionalType = optionalIntType;
|
||||||
|
} else if (component.typeName().equals(optionalLongType)) {
|
||||||
|
thisOptionalType = optionalLongType;
|
||||||
|
} else if (component.typeName().equals(optionalDoubleType)) {
|
||||||
|
thisOptionalType = optionalDoubleType;
|
||||||
|
}
|
||||||
|
if (thisOptionalType != null) {
|
||||||
|
var codeBlock = CodeBlock.builder().add("$T.empty()", thisOptionalType).build();
|
||||||
|
fieldSpecBuilder.initializer(codeBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.addField(fieldSpecBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add1GetterMethod(ClassType component)
|
private boolean isOptional(ClassType component) {
|
||||||
{
|
if (component.typeName().equals(optionalType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName) && parameterizedTypeName.rawType.equals(optionalType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addWithGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component) {
|
||||||
|
/*
|
||||||
|
For a single record component, add a getter similar to:
|
||||||
|
|
||||||
|
T p();
|
||||||
|
*/
|
||||||
|
var methodSpecBuilder = MethodSpec.methodBuilder(component.name())
|
||||||
|
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n", component.name())
|
||||||
|
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.returns(component.typeName());
|
||||||
|
addAccessorAnnotations(component, methodSpecBuilder);
|
||||||
|
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 void add1GetterMethod(RecordClassType component) {
|
||||||
/*
|
/*
|
||||||
For a single record component, add a getter similar to:
|
For a single record component, add a getter similar to:
|
||||||
|
|
||||||
@@ -562,18 +646,17 @@ class InternalRecordBuilderProcessor
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var methodSpec = MethodSpec.methodBuilder(component.name())
|
var methodSpecBuilder = MethodSpec.methodBuilder(component.name())
|
||||||
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n", component.name())
|
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n", component.name())
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.returns(component.typeName())
|
.returns(component.typeName())
|
||||||
.addStatement("return $L", component.name())
|
.addStatement("return $L", component.name());
|
||||||
.build();
|
addAccessorAnnotations(component, methodSpecBuilder);
|
||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpecBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void add1SetterMethod(ClassType component)
|
private void add1SetterMethod(RecordClassType component) {
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
For a single record component, add a setter similar to:
|
For a single record component, add a setter similar to:
|
||||||
|
|
||||||
@@ -582,12 +665,14 @@ class InternalRecordBuilderProcessor
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var parameterSpec = ParameterSpec.builder(component.typeName(), component.name()).build();
|
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||||
|
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||||
|
|
||||||
var methodSpec = MethodSpec.methodBuilder(component.name())
|
var methodSpec = MethodSpec.methodBuilder(component.name())
|
||||||
.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.addParameter(parameterSpec)
|
.addParameter(parameterSpecBuilder.build())
|
||||||
.returns(builderClassType.typeName())
|
.returns(builderClassType.typeName())
|
||||||
.addStatement("this.$L = $L", component.name(), component.name())
|
.addStatement("this.$L = $L", component.name(), component.name())
|
||||||
.addStatement("return this")
|
.addStatement("return this")
|
||||||
@@ -595,3 +680,4 @@ class InternalRecordBuilderProcessor
|
|||||||
builder.addMethod(methodSpec);
|
builder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import com.squareup.javapoet.TypeSpec;
|
|||||||
import com.squareup.javapoet.TypeVariableName;
|
import com.squareup.javapoet.TypeVariableName;
|
||||||
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.ElementKind;
|
import javax.lang.model.element.ElementKind;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
@@ -59,7 +58,7 @@ class InternalRecordInterfaceProcessor {
|
|||||||
|
|
||||||
private record Component(ExecutableElement element, Optional<String> alternateName){}
|
private record Component(ExecutableElement element, Optional<String> alternateName){}
|
||||||
|
|
||||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilderMetaData metaData, Optional<String> packageNameOpt) {
|
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||||
this.processingEnv = processingEnv;
|
this.processingEnv = processingEnv;
|
||||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
||||||
recordComponents = getRecordComponents(iface);
|
recordComponents = getRecordComponents(iface);
|
||||||
|
|||||||
@@ -1,175 +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 io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class OptionBasedRecordBuilderMetaData implements RecordBuilderMetaData {
|
|
||||||
/**
|
|
||||||
* @see #suffix()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_SUFFIX = "suffix";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #interfaceSuffix()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_INTERFACE_SUFFIX = "interfaceSuffix";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #copyMethodName()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_COPY_METHOD_NAME = "copyMethodName";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #builderMethodName()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_BUILDER_METHOD_NAME = "builderMethodName";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #buildMethodName()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_BUILD_METHOD_NAME = "buildMethodName";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #downCastMethodName()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_DOWN_CAST_METHOD_NAME = "downCastMethodName";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #componentsMethodName()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_COMPONENTS_METHOD_NAME = "componentsMethodName";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #fileComment()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_FILE_COMMENT = "fileComment";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #fileIndent()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_FILE_INDENT = "fileIndent";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #prefixEnclosingClassNames()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_PREFIX_ENCLOSING_CLASS_NAMES = "prefixEnclosingClassNames";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #withClassName()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_WITH_CLASS_NAME = "withClassName";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see #withClassMethodPrefix()
|
|
||||||
*/
|
|
||||||
public static final String OPTION_WITH_CLASS_METHOD_PREFIX = "withClassMethodPrefix";
|
|
||||||
|
|
||||||
private final String suffix;
|
|
||||||
private final String interfaceSuffix;
|
|
||||||
private final String copyMethodName;
|
|
||||||
private final String builderMethodName;
|
|
||||||
private final String buildMethodName;
|
|
||||||
private final String downCastMethodName;
|
|
||||||
private final String componentsMethodName;
|
|
||||||
private final String withClassName;
|
|
||||||
private final String withClassMethodPrefix;
|
|
||||||
private final String fileComment;
|
|
||||||
private final String fileIndent;
|
|
||||||
private final boolean prefixEnclosingClassNames;
|
|
||||||
|
|
||||||
public OptionBasedRecordBuilderMetaData(Map<String, String> options) {
|
|
||||||
suffix = options.getOrDefault(OPTION_SUFFIX, DEFAULT.suffix());
|
|
||||||
interfaceSuffix = options.getOrDefault(OPTION_INTERFACE_SUFFIX, DEFAULT.interfaceSuffix());
|
|
||||||
builderMethodName = options.getOrDefault(OPTION_BUILDER_METHOD_NAME, DEFAULT.builderMethodName());
|
|
||||||
copyMethodName = options.getOrDefault(OPTION_COPY_METHOD_NAME, DEFAULT.copyMethodName());
|
|
||||||
buildMethodName = options.getOrDefault(OPTION_BUILD_METHOD_NAME, DEFAULT.buildMethodName());
|
|
||||||
downCastMethodName = options.getOrDefault(OPTION_DOWN_CAST_METHOD_NAME, DEFAULT.downCastMethodName());
|
|
||||||
componentsMethodName = options.getOrDefault(OPTION_COMPONENTS_METHOD_NAME, DEFAULT.componentsMethodName());
|
|
||||||
withClassName = options.getOrDefault(OPTION_WITH_CLASS_NAME, DEFAULT.withClassName());
|
|
||||||
withClassMethodPrefix = options.getOrDefault(OPTION_WITH_CLASS_METHOD_PREFIX, DEFAULT.withClassMethodPrefix());
|
|
||||||
fileComment = options.getOrDefault(OPTION_FILE_COMMENT, DEFAULT.fileComment());
|
|
||||||
fileIndent = options.getOrDefault(OPTION_FILE_INDENT, DEFAULT.fileIndent());
|
|
||||||
String prefixenclosingclassnamesopt = options.getOrDefault(OPTION_PREFIX_ENCLOSING_CLASS_NAMES, String.valueOf(DEFAULT.prefixEnclosingClassNames()));
|
|
||||||
if (prefixenclosingclassnamesopt == null) {
|
|
||||||
prefixEnclosingClassNames = true;
|
|
||||||
} else {
|
|
||||||
prefixEnclosingClassNames = Boolean.parseBoolean(prefixenclosingclassnamesopt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String suffix() {
|
|
||||||
return suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String copyMethodName() {
|
|
||||||
return copyMethodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String builderMethodName() {
|
|
||||||
return builderMethodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String buildMethodName() {
|
|
||||||
return buildMethodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String downCastMethodName() {
|
|
||||||
return downCastMethodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String componentsMethodName() {
|
|
||||||
return componentsMethodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String withClassName() {
|
|
||||||
return withClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String withClassMethodPrefix() {
|
|
||||||
return withClassMethodPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String fileComment() {
|
|
||||||
return fileComment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String fileIndent() {
|
|
||||||
return fileIndent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean prefixEnclosingClassNames() {
|
|
||||||
return prefixEnclosingClassNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String interfaceSuffix() {
|
|
||||||
return interfaceSuffix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +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 io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
|
||||||
|
|
||||||
class RecordBuilderMetaDataLoader {
|
|
||||||
private final RecordBuilderMetaData metaData;
|
|
||||||
|
|
||||||
RecordBuilderMetaDataLoader(ProcessingEnvironment processingEnv, Consumer<String> logger) {
|
|
||||||
Map<String, String> options = processingEnv.getOptions();
|
|
||||||
String metaDataClassName = options.get(RecordBuilderMetaData.JAVAC_OPTION_NAME);
|
|
||||||
if ((metaDataClassName != null) && !metaDataClassName.isEmpty()) {
|
|
||||||
RecordBuilderMetaData loadedMetaData = null;
|
|
||||||
try {
|
|
||||||
Class<?> clazz = Class.forName(metaDataClassName);
|
|
||||||
loadedMetaData = (RecordBuilderMetaData) clazz.getDeclaredConstructor().newInstance();
|
|
||||||
logger.accept("Found meta data: " + clazz);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
// log the thrown exception instead of the invocation target exception
|
|
||||||
logger.accept("Could not load meta data: " + metaDataClassName + " - " + e.getCause());
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.accept("Could not load meta data: " + metaDataClassName + " - " + e);
|
|
||||||
}
|
|
||||||
metaData = (loadedMetaData != null) ? loadedMetaData : RecordBuilderMetaData.DEFAULT;
|
|
||||||
} else {
|
|
||||||
metaData = new OptionBasedRecordBuilderMetaData(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RecordBuilderMetaData getMetaData() {
|
|
||||||
return metaData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class RecordBuilderOptions {
|
||||||
|
private static final Map<String, Object> defaultValues = buildDefaultValues();
|
||||||
|
|
||||||
|
static RecordBuilder.Options build(Map<String, String> options) {
|
||||||
|
return (RecordBuilder.Options)Proxy.newProxyInstance(RecordBuilderOptions.class.getClassLoader(), new Class[]{RecordBuilder.Options.class}, (proxy, method, args) -> {
|
||||||
|
var name = method.getName();
|
||||||
|
var defaultValue = defaultValues.get(name);
|
||||||
|
var option = options.get(name);
|
||||||
|
if (option != null) {
|
||||||
|
if (defaultValue instanceof String) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
if (defaultValue instanceof Boolean) {
|
||||||
|
return Boolean.parseBoolean(option);
|
||||||
|
}
|
||||||
|
if (defaultValue instanceof Integer) {
|
||||||
|
return Integer.parseInt(option);
|
||||||
|
}
|
||||||
|
if (defaultValue instanceof Long) {
|
||||||
|
return Long.parseLong(option);
|
||||||
|
}
|
||||||
|
if (defaultValue instanceof Double) {
|
||||||
|
return Double.parseDouble(option);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unhandled option type: " + defaultValue.getClass());
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> buildDefaultValues() {
|
||||||
|
var workMap = new HashMap<String, Object>();
|
||||||
|
for ( Method method : RecordBuilder.Options.class.getDeclaredMethods()) {
|
||||||
|
workMap.put(method.getName(), method.getDefaultValue());
|
||||||
|
}
|
||||||
|
workMap.put("toString", "Generated RecordBuilder.Options");
|
||||||
|
return Map.copyOf(workMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecordBuilderOptions() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,8 @@ import com.squareup.javapoet.AnnotationSpec;
|
|||||||
import com.squareup.javapoet.JavaFile;
|
import com.squareup.javapoet.JavaFile;
|
||||||
import com.squareup.javapoet.TypeSpec;
|
import com.squareup.javapoet.TypeSpec;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilderMetaData;
|
|
||||||
import io.soabase.recordbuilder.core.RecordInterface;
|
import io.soabase.recordbuilder.core.RecordInterface;
|
||||||
|
|
||||||
import javax.annotation.processing.AbstractProcessor;
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
import javax.annotation.processing.Filer;
|
import javax.annotation.processing.Filer;
|
||||||
import javax.annotation.processing.Generated;
|
import javax.annotation.processing.Generated;
|
||||||
@@ -32,13 +32,16 @@ import javax.lang.model.element.TypeElement;
|
|||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import javax.tools.Diagnostic;
|
import javax.tools.Diagnostic;
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class RecordBuilderProcessor extends AbstractProcessor {
|
public class RecordBuilderProcessor
|
||||||
|
extends AbstractProcessor
|
||||||
|
{
|
||||||
private static final String RECORD_BUILDER = RecordBuilder.class.getName();
|
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_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.');
|
||||||
private static final String RECORD_INTERFACE = RecordInterface.class.getName();
|
private static final String RECORD_INTERFACE = RecordInterface.class.getName();
|
||||||
@@ -48,18 +51,21 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordInterface.class.getName()).build();
|
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordInterface.class.getName()).build();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
|
||||||
|
{
|
||||||
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> process(annotation, element)));
|
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> process(annotation, element)));
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getSupportedAnnotationTypes() {
|
public Set<String> getSupportedAnnotationTypes()
|
||||||
return Set.of(RECORD_BUILDER, RECORD_BUILDER_INCLUDE, RECORD_INTERFACE, RECORD_INTERFACE_INCLUDE);
|
{
|
||||||
|
return Set.of("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SourceVersion getSupportedSourceVersion() {
|
public SourceVersion getSupportedSourceVersion()
|
||||||
|
{
|
||||||
// we don't directly return RELEASE_14 as that may
|
// we don't directly return RELEASE_14 as that may
|
||||||
// not exist in prior releases
|
// not exist in prior releases
|
||||||
// if we're running on an older release, returning latest()
|
// if we're running on an older release, returning latest()
|
||||||
@@ -67,66 +73,57 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
return SourceVersion.latest();
|
return SourceVersion.latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void process(TypeElement annotation, Element element) {
|
private void process(TypeElement annotation, Element element)
|
||||||
var metaData = new RecordBuilderMetaDataLoader(processingEnv, s -> processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, s)).getMetaData();
|
{
|
||||||
|
|
||||||
String annotationClass = annotation.getQualifiedName().toString();
|
String annotationClass = annotation.getQualifiedName().toString();
|
||||||
if ( annotationClass.equals(RECORD_BUILDER) )
|
if (annotationClass.equals(RECORD_BUILDER)) {
|
||||||
{
|
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||||
processRecordBuilder((TypeElement)element, metaData, Optional.empty());
|
processRecordBuilder((TypeElement) element, metaData, Optional.empty());
|
||||||
}
|
}
|
||||||
else if ( annotationClass.equals(RECORD_INTERFACE) )
|
else if (annotationClass.equals(RECORD_INTERFACE)) {
|
||||||
{
|
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||||
processRecordInterface((TypeElement)element, element.getAnnotation(RecordInterface.class).addRecordBuilder(), metaData, Optional.empty());
|
processRecordInterface((TypeElement) element, element.getAnnotation(RecordInterface.class).addRecordBuilder(), metaData, Optional.empty());
|
||||||
}
|
}
|
||||||
else if ( annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE) )
|
else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||||
{
|
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||||
processIncludes(element, metaData, annotationClass);
|
processIncludes(element, metaData, annotationClass);
|
||||||
}
|
} else {
|
||||||
else
|
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
|
||||||
{
|
if (recordBuilderTemplate != null) {
|
||||||
throw new RuntimeException("Unknown annotation: " + annotation);
|
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processIncludes(Element element, RecordBuilderMetaData metaData, String annotationClass) {
|
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass)
|
||||||
|
{
|
||||||
var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass);
|
var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass);
|
||||||
if ( annotationMirrorOpt.isEmpty() )
|
if (annotationMirrorOpt.isEmpty()) {
|
||||||
{
|
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation mirror for: " + annotationClass, element);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation mirror for: " + annotationClass, element);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
var values = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirrorOpt.get());
|
var values = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirrorOpt.get());
|
||||||
var classes = ElementUtils.getAnnotationValue(values, "value");
|
var classes = ElementUtils.getAnnotationValue(values, "value");
|
||||||
if ( classes.isEmpty() )
|
if (classes.isEmpty()) {
|
||||||
{
|
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation value for: " + annotationClass, element);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation value for: " + annotationClass, element);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(values, "packagePattern").orElse(null), "*");
|
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(values, "packagePattern").orElse(null), "*");
|
||||||
var classesMirrors = ElementUtils.getClassesAttribute(classes.get());
|
var classesMirrors = ElementUtils.getClassesAttribute(classes.get());
|
||||||
for ( TypeMirror mirror : classesMirrors )
|
for (TypeMirror mirror : classesMirrors) {
|
||||||
{
|
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(mirror);
|
||||||
TypeElement typeElement = (TypeElement)processingEnv.getTypeUtils().asElement(mirror);
|
if (typeElement == null) {
|
||||||
if ( typeElement == null )
|
|
||||||
{
|
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + mirror, element);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + mirror, element);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
var packageName = buildPackageName(packagePattern, element, typeElement);
|
var packageName = buildPackageName(packagePattern, element, typeElement);
|
||||||
if (packageName != null)
|
if (packageName != null) {
|
||||||
{
|
if (annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||||
if ( annotationClass.equals(RECORD_INTERFACE_INCLUDE) )
|
|
||||||
{
|
|
||||||
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(values, "addRecordBuilder");
|
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(values, "addRecordBuilder");
|
||||||
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
||||||
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName));
|
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName));
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,77 +133,77 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass) {
|
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass)
|
||||||
|
{
|
||||||
PackageElement includedClassPackage = findPackageElement(includedClass, includedClass);
|
PackageElement includedClassPackage = findPackageElement(includedClass, includedClass);
|
||||||
if (includedClassPackage == null) {
|
if (includedClassPackage == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String replaced = packagePattern.replace("*", includedClassPackage.getQualifiedName().toString());
|
String replaced = packagePattern.replace("*", includedClassPackage.getQualifiedName().toString());
|
||||||
if (builderElement instanceof PackageElement) {
|
if (builderElement instanceof PackageElement) {
|
||||||
return replaced.replace("@", ((PackageElement)builderElement).getQualifiedName().toString());
|
return replaced.replace("@", ((PackageElement) builderElement).getQualifiedName().toString());
|
||||||
}
|
}
|
||||||
return replaced.replace("@", ((PackageElement)builderElement.getEnclosingElement()).getQualifiedName().toString());
|
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) {
|
if (includedClass == null) {
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Element has not package", actualElement);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Element has not package", actualElement);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (includedClass.getEnclosingElement() instanceof PackageElement) {
|
if (includedClass.getEnclosingElement() instanceof PackageElement) {
|
||||||
return (PackageElement)includedClass.getEnclosingElement();
|
return (PackageElement) includedClass.getEnclosingElement();
|
||||||
}
|
}
|
||||||
return findPackageElement(actualElement, includedClass.getEnclosingElement());
|
return findPackageElement(actualElement, includedClass.getEnclosingElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilderMetaData metaData, Optional<String> packageName) {
|
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName)
|
||||||
if ( !element.getKind().isInterface() )
|
{
|
||||||
{
|
if (!element.getKind().isInterface()) {
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordInterface only valid for interfaces.", element);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordInterface only valid for interfaces.", element);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName);
|
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName);
|
||||||
if ( !internalProcessor.isValid() )
|
if (!internalProcessor.isValid()) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord);
|
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecordBuilder(TypeElement record, RecordBuilderMetaData metaData, Optional<String> packageName) {
|
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageName)
|
||||||
|
{
|
||||||
// we use string based name comparison for the element kind,
|
// we use string based name comparison for the element kind,
|
||||||
// as the ElementKind.RECORD enum doesn't exist on JRE releases
|
// as the ElementKind.RECORD enum doesn't exist on JRE releases
|
||||||
// older than Java 14, and we don't want to throw unexpected
|
// older than Java 14, and we don't want to throw unexpected
|
||||||
// NoSuchFieldErrors
|
// NoSuchFieldErrors
|
||||||
if ( !"RECORD".equals(record.getKind().name()) )
|
if (!"RECORD".equals(record.getKind().name())) {
|
||||||
{
|
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var internalProcessor = new InternalRecordBuilderProcessor(record, metaData, packageName);
|
var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName);
|
||||||
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData);
|
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, RecordBuilderMetaData metaData) {
|
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, RecordBuilder.Options metaData)
|
||||||
|
{
|
||||||
// produces the Java file
|
// produces the Java file
|
||||||
JavaFile javaFile = javaFileBuilder(packageName, builderType, metaData);
|
JavaFile javaFile = javaFileBuilder(packageName, builderType, metaData);
|
||||||
Filer filer = processingEnv.getFiler();
|
Filer filer = processingEnv.getFiler();
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name() : (packageName + "." + builderClassType.name());
|
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name() : (packageName + "." + builderClassType.name());
|
||||||
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
||||||
try (Writer writer = sourceFile.openWriter())
|
try (Writer writer = sourceFile.openWriter()) {
|
||||||
{
|
|
||||||
javaFile.writeTo(writer);
|
javaFile.writeTo(writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( IOException e )
|
catch (IOException e) {
|
||||||
{
|
|
||||||
handleWriteError(record, e);
|
handleWriteError(record, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, TypeSpec type, RecordBuilderMetaData 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);
|
JavaFile javaFile = javaFileBuilder(packageName, type, metaData);
|
||||||
|
|
||||||
String classSourceCode = javaFile.toString();
|
String classSourceCode = javaFile.toString();
|
||||||
@@ -214,35 +211,32 @@ public class RecordBuilderProcessor extends AbstractProcessor {
|
|||||||
String recordSourceCode = toRecordProc.apply(classSourceCode);
|
String recordSourceCode = toRecordProc.apply(classSourceCode);
|
||||||
|
|
||||||
Filer filer = processingEnv.getFiler();
|
Filer filer = processingEnv.getFiler();
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
String fullyQualifiedName = packageName.isEmpty() ? classType.name() : (packageName + "." + classType.name());
|
String fullyQualifiedName = packageName.isEmpty() ? classType.name() : (packageName + "." + classType.name());
|
||||||
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
|
||||||
try (Writer writer = sourceFile.openWriter())
|
try (Writer writer = sourceFile.openWriter()) {
|
||||||
{
|
|
||||||
writer.write(recordSourceCode);
|
writer.write(recordSourceCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( IOException e )
|
catch (IOException e) {
|
||||||
{
|
|
||||||
handleWriteError(element, e);
|
handleWriteError(element, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilderMetaData metaData) {
|
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData)
|
||||||
|
{
|
||||||
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true).indent(metaData.fileIndent());
|
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true).indent(metaData.fileIndent());
|
||||||
var comment = metaData.fileComment();
|
var comment = metaData.fileComment();
|
||||||
if ( (comment != null) && !comment.isEmpty() )
|
if ((comment != null) && !comment.isEmpty()) {
|
||||||
{
|
|
||||||
javaFileBuilder.addFileComment(comment);
|
javaFileBuilder.addFileComment(comment);
|
||||||
}
|
}
|
||||||
return javaFileBuilder.build();
|
return javaFileBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleWriteError(TypeElement element, IOException e) {
|
private void handleWriteError(TypeElement element, IOException e)
|
||||||
|
{
|
||||||
String message = "Could not create source file";
|
String message = "Could not create source file";
|
||||||
if ( e.getMessage() != null )
|
if (e.getMessage() != null) {
|
||||||
{
|
|
||||||
message = message + ": " + e.getMessage();
|
message = message + ": " + e.getMessage();
|
||||||
}
|
}
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 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.TypeName;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
super(typeName, name);
|
||||||
|
this.rawTypeName = rawTypeName;
|
||||||
|
this.accessorAnnotations = accessorAnnotations;
|
||||||
|
this.canonicalConstructorAnnotations = canonicalConstructorAnnotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeName rawTypeName() {
|
||||||
|
return rawTypeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<? extends AnnotationMirror> getAccessorAnnotations() {
|
||||||
|
return accessorAnnotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<? extends AnnotationMirror> getCanonicalConstructorAnnotations() {
|
||||||
|
return canonicalConstructorAnnotations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,19 +3,45 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>1.19-SNAPSHOT</version>
|
<version>26</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>record-builder-test</artifactId>
|
<artifactId>record-builder-test</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.validation</groupId>
|
||||||
|
<artifactId>validation-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder-core</artifactId>
|
<artifactId>record-builder-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
|
<artifactId>record-builder-validator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.validator</groupId>
|
||||||
|
<artifactId>hibernate-validator</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish</groupId>
|
||||||
|
<artifactId>javax.el</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter</artifactId>
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
import javax.validation.constraints.Max;
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Null;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
public record Annotated(@NotNull @Null String hey, @Min(10) @Max(100) int i, double d) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
public record MutableCollectionRecord(List<String> l) implements MutableCollectionRecordBuilder.With {
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
@RecordBuilder.Template(options = @RecordBuilder.Options(
|
||||||
|
fileComment = "This is a test",
|
||||||
|
withClassName = "Com"
|
||||||
|
))
|
||||||
|
public @interface MyTemplate
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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.Optional;
|
||||||
|
import java.util.OptionalDouble;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
|
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
||||||
|
@RecordBuilder
|
||||||
|
public record RecordWithOptional(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.Options(interpretNotNulls = true)
|
||||||
|
@RecordBuilder
|
||||||
|
public record RequiredRecord(@NotNull String hey, @NotNull int i) implements RequiredRecordBuilder.With {
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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.Options(useValidationApi = true)
|
||||||
|
@RecordBuilder
|
||||||
|
public record RequiredRecord2(@NotNull String hey, @NotNull int i) implements RequiredRecord2Builder.With {
|
||||||
|
}
|
||||||
@@ -18,5 +18,6 @@ package io.soabase.recordbuilder.test;
|
|||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
@RecordBuilder
|
@RecordBuilder
|
||||||
|
@RecordBuilder.Options(prefixEnclosingClassNames = false)
|
||||||
public record SimpleRecord(int i, String s) {
|
public record SimpleRecord(int i, String s) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@MyTemplate
|
||||||
|
public record TemplateTest(String text, Instant date) implements TemplateTestBuilder.Com
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test.visibility;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
record PackagePrivateRecord(int i) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* 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 javax.validation.constraints.Max;
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
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);
|
||||||
|
var parameters = method.getParameters();
|
||||||
|
Assertions.assertEquals(3, parameters.length);
|
||||||
|
assertHey(parameters[0]);
|
||||||
|
assertI(parameters[1]);
|
||||||
|
assertD(parameters[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetters() throws NoSuchMethodException {
|
||||||
|
var method = AnnotatedBuilder.class.getMethod("hey", String.class);
|
||||||
|
var parameters = method.getParameters();
|
||||||
|
Assertions.assertEquals(1, parameters.length);
|
||||||
|
assertHey(parameters[0]);
|
||||||
|
|
||||||
|
method = AnnotatedBuilder.class.getMethod("i", Integer.TYPE);
|
||||||
|
parameters = method.getParameters();
|
||||||
|
Assertions.assertEquals(1, parameters.length);
|
||||||
|
assertI(parameters[0]);
|
||||||
|
|
||||||
|
method = AnnotatedBuilder.class.getMethod("d", Double.TYPE);
|
||||||
|
parameters = method.getParameters();
|
||||||
|
Assertions.assertEquals(1, parameters.length);
|
||||||
|
assertD(parameters[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetters() throws NoSuchMethodException {
|
||||||
|
var method = AnnotatedBuilder.class.getMethod("hey");
|
||||||
|
assertHey(method);
|
||||||
|
|
||||||
|
method = AnnotatedBuilder.class.getMethod("i");
|
||||||
|
assertI(method);
|
||||||
|
|
||||||
|
method = AnnotatedBuilder.class.getMethod("d");
|
||||||
|
assertD(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWitherSetters() throws NoSuchMethodException {
|
||||||
|
var method = AnnotatedBuilder.With.class.getMethod("withHey", String.class);
|
||||||
|
var parameters = method.getParameters();
|
||||||
|
Assertions.assertEquals(1, parameters.length);
|
||||||
|
assertHey(parameters[0]);
|
||||||
|
|
||||||
|
method = AnnotatedBuilder.With.class.getMethod("withI", Integer.TYPE);
|
||||||
|
parameters = method.getParameters();
|
||||||
|
Assertions.assertEquals(1, parameters.length);
|
||||||
|
assertI(parameters[0]);
|
||||||
|
|
||||||
|
method = AnnotatedBuilder.With.class.getMethod("withD", Double.TYPE);
|
||||||
|
parameters = method.getParameters();
|
||||||
|
Assertions.assertEquals(1, parameters.length);
|
||||||
|
assertD(parameters[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertD(AnnotatedElement d) {
|
||||||
|
Assertions.assertEquals(0, d.getAnnotations().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertI(AnnotatedElement i) {
|
||||||
|
Assertions.assertNotNull(i.getAnnotation(Min.class));
|
||||||
|
Assertions.assertEquals(i.getAnnotation(Min.class).value(), 10);
|
||||||
|
Assertions.assertNotNull(i.getAnnotation(Max.class));
|
||||||
|
Assertions.assertEquals(i.getAnnotation(Max.class).value(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertHey(AnnotatedElement hey) {
|
||||||
|
Assertions.assertNotNull(hey.getAnnotation(NotNull.class));
|
||||||
|
Assertions.assertNotNull(hey.getAnnotation(Null.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* 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.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static io.soabase.recordbuilder.test.foo.PointBuilder.Point;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class TestCollections {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalDouble;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
|
class TestOptional {
|
||||||
|
@Test
|
||||||
|
void testDefaultEmpty() {
|
||||||
|
var record = RecordWithOptionalBuilder.builder();
|
||||||
|
Assertions.assertEquals(Optional.empty(), record.value());
|
||||||
|
Assertions.assertEquals(Optional.empty(), record.raw());
|
||||||
|
Assertions.assertEquals(OptionalInt.empty(), record.i());
|
||||||
|
Assertions.assertEquals(OptionalLong.empty(), record.l());
|
||||||
|
Assertions.assertEquals(OptionalDouble.empty(), record.d());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
class TestTemplate {
|
||||||
|
@Test
|
||||||
|
void testTemplate() {
|
||||||
|
var t = TemplateTestBuilder.TemplateTest("one", Instant.MIN);
|
||||||
|
var w = t.withText("other");
|
||||||
|
Assertions.assertEquals("one", t.text());
|
||||||
|
Assertions.assertEquals("other", w.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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 javax.validation.ValidationException;
|
||||||
|
|
||||||
|
class TestValidation {
|
||||||
|
@Test
|
||||||
|
void testNotNulls() {
|
||||||
|
Assertions.assertThrows(NullPointerException.class, () -> RequiredRecordBuilder.builder().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidation() {
|
||||||
|
Assertions.assertThrows(ValidationException.class, () -> RequiredRecord2Builder.builder().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNotNullsWithNewProperty() {
|
||||||
|
var valid = RequiredRecordBuilder.builder().hey("hey").i(1).build();
|
||||||
|
Assertions.assertThrows(NullPointerException.class, () -> valid.withHey(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValidationWithNewProperty() {
|
||||||
|
var valid = RequiredRecord2Builder.builder().hey("hey").i(1).build();
|
||||||
|
Assertions.assertThrows(ValidationException.class, () -> valid.withHey(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,11 +59,4 @@ class TestWithers {
|
|||||||
Assertions.assertEquals(20, r3.i());
|
Assertions.assertEquals(20, r3.i());
|
||||||
Assertions.assertEquals("twenty", r3.s());
|
Assertions.assertEquals("twenty", r3.s());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BadSubclass implements PersonRecordBuilder.With {}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testBadWithSubclass() {
|
|
||||||
Assertions.assertThrows(RuntimeException.class, () -> new BadSubclass().withAge(10));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
31
record-builder-validator/pom.xml
Normal file
31
record-builder-validator/pom.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
|
<artifactId>record-builder</artifactId>
|
||||||
|
<version>26</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>record-builder-validator</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestEntries>
|
||||||
|
<Automatic-Module-Name>io.soabase.recordbuilder.validator</Automatic-Module-Name>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* 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.validator;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
// complete Java Validation via reflection to avoid dependencies
|
||||||
|
public class RecordBuilderValidator {
|
||||||
|
private static final Object validator;
|
||||||
|
private static final Method validationMethod;
|
||||||
|
private static final Constructor<?> constraintViolationExceptionCtor;
|
||||||
|
private static final Class<?>[] emptyGroups = new Class<?>[0];
|
||||||
|
|
||||||
|
private static final boolean PRINT_ERROR_STACKTRACE = Boolean.getBoolean("record_builder_validator_errors");
|
||||||
|
|
||||||
|
static {
|
||||||
|
Object localValidator = null;
|
||||||
|
Method localValidationMethod = null;
|
||||||
|
Constructor<?> localConstraintViolationExceptionCtor = null;
|
||||||
|
try {
|
||||||
|
var validationClass = Class.forName("javax.validation.Validation");
|
||||||
|
var factoryClass = validationClass.getDeclaredMethod("buildDefaultValidatorFactory");
|
||||||
|
var factory = factoryClass.invoke(null);
|
||||||
|
var getValidatorMethod = factory.getClass().getMethod("getValidator");
|
||||||
|
var constraintViolationExceptionClass = Class.forName("javax.validation.ConstraintViolationException");
|
||||||
|
localValidator = getValidatorMethod.invoke(factory);
|
||||||
|
localValidationMethod = localValidator.getClass().getMethod("validate", Object.class, Class[].class);
|
||||||
|
localConstraintViolationExceptionCtor = constraintViolationExceptionClass.getConstructor(Set.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (PRINT_ERROR_STACKTRACE) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validator = localValidator;
|
||||||
|
validationMethod = localValidationMethod;
|
||||||
|
constraintViolationExceptionCtor = localConstraintViolationExceptionCtor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T validate(T o) {
|
||||||
|
if ((validator != null) && (validationMethod != null)) {
|
||||||
|
try {
|
||||||
|
var violations = validationMethod.invoke(validator, o, emptyGroups);
|
||||||
|
if (!((Collection<?>) violations).isEmpty()) {
|
||||||
|
throw (RuntimeException) constraintViolationExceptionCtor.newInstance(violations);
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException | InstantiationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
if (e.getCause() != null) {
|
||||||
|
if (e.getCause() instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) e.getCause();
|
||||||
|
}
|
||||||
|
throw new RuntimeException(e.getCause());
|
||||||
|
}
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecordBuilderValidator() {
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user