Compare commits
89 Commits
record-bui
...
record-bui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abfc12bdb0 | ||
|
|
6c0fac0dff | ||
|
|
ae527cd8e5 | ||
|
|
73ba62057a | ||
|
|
87998aba68 | ||
|
|
04a0904d3f | ||
|
|
aa072af8e1 | ||
|
|
b435b5d3fd | ||
|
|
d3c1bb36f3 | ||
|
|
c3719326c9 | ||
|
|
661d0818c0 | ||
|
|
79bc8396f2 | ||
|
|
b2149622e4 | ||
|
|
0718e37f76 | ||
|
|
d112a1b352 | ||
|
|
86093b6bad | ||
|
|
7b6ad4d7ba | ||
|
|
cd059f1207 | ||
|
|
642dd01421 | ||
|
|
efd1a6b0d4 | ||
|
|
b525eddc76 | ||
|
|
d3828eda74 | ||
|
|
fef69af183 | ||
|
|
99f9639b82 | ||
|
|
43bc65e258 | ||
|
|
bae1b771b7 | ||
|
|
1dd00b2c65 | ||
|
|
3b34b5dee3 | ||
|
|
7248bad2bd | ||
|
|
7e494d8753 | ||
|
|
3954499d4b | ||
|
|
13959dee2a | ||
|
|
af759c0570 | ||
|
|
9a7d73e78c | ||
|
|
3b8c3ff9e3 | ||
|
|
9943667af1 | ||
|
|
b0c8f10711 | ||
|
|
0d3c2f37c1 | ||
|
|
eabcb2f179 | ||
|
|
5fef81191d | ||
|
|
8dbec027e4 | ||
|
|
ef09d68b78 | ||
|
|
0a608f3b20 | ||
|
|
3494d01b2b | ||
|
|
584bdf4a25 | ||
|
|
9268c295fe | ||
|
|
0d2fe2ffe7 | ||
|
|
71e22042e9 | ||
|
|
3c8e305002 | ||
|
|
76f93039b0 | ||
|
|
5f82af97ae | ||
|
|
896aab18aa | ||
|
|
75010d0add | ||
|
|
26a6141bef | ||
|
|
cdd9009fa8 | ||
|
|
0624a6caca | ||
|
|
238ef872bf | ||
|
|
ef2a3692d1 | ||
|
|
cb9ceb0529 | ||
|
|
bd8fbeb045 | ||
|
|
5b10284d4e | ||
|
|
49a19ca8f8 | ||
|
|
f9fcd1e540 | ||
|
|
eebb2b2bfd | ||
|
|
67f219bd67 | ||
|
|
d9f27bc5a6 | ||
|
|
f7b6f47bbf | ||
|
|
7e90e64988 | ||
|
|
cbe3b7cef4 | ||
|
|
6c15bdd825 | ||
|
|
53aaf6b7e9 | ||
|
|
09ed5905e3 | ||
|
|
41f59e704f | ||
|
|
d0c56e6fdf | ||
|
|
2f3477a1ca | ||
|
|
091e9493b6 | ||
|
|
a0c03e88cc | ||
|
|
d21953c39a | ||
|
|
d9d98e765e | ||
|
|
5320c8dfa4 | ||
|
|
5d7bea565f | ||
|
|
b7c4e22066 | ||
|
|
19803592a2 | ||
|
|
9e25c6fa47 | ||
|
|
c16e3bb14b | ||
|
|
57e5d43b07 | ||
|
|
feecc29eea | ||
|
|
428d3c0378 | ||
|
|
61d355b9fc |
@@ -19,6 +19,6 @@ jobs:
|
|||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 16.0.0-ea
|
java-version: 16
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
run: mvn -B package --file pom.xml
|
run: mvn -B package --file pom.xml
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# This workflow will build a Java project with Maven
|
# This workflow will build a Java project with Maven
|
||||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||||
|
|
||||||
name: Maven Build - Java 15
|
name: Maven Build - Java 17
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -19,10 +19,6 @@ jobs:
|
|||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 15
|
java-version: 17
|
||||||
- name: Create Maven Directory
|
|
||||||
run: mkdir -p .mvn/
|
|
||||||
- name: Create Maven JVM file
|
|
||||||
run: echo "--enable-preview" > .mvn/jvm.config
|
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
run: mvn -P java15 -B package --file pom.xml
|
run: mvn -B package --file pom.xml
|
||||||
192
README.md
192
README.md
@@ -21,8 +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.md)
|
- [Customizing](customizing.md) (e.g. add immutable collections, etc.)
|
||||||
- [Java 15 Versions](#java-15-versions)
|
|
||||||
|
|
||||||
## RecordBuilder Example
|
## RecordBuilder Example
|
||||||
|
|
||||||
@@ -80,12 +79,18 @@ NameAndAge r5 = r4.with(b -> {
|
|||||||
b.name("whatever"));
|
b.name("whatever"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// or, if you cannot add the "With" interface to your record...
|
||||||
|
NameAndAge r6 = NameAndAgeBuilder.from(r5).with(b -> b.age(200).name("whatever"));
|
||||||
|
NameAndAge r7 = NameAndAgeBuilder.from(r5).withName("boop");
|
||||||
```
|
```
|
||||||
|
|
||||||
_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._
|
||||||
|
|
||||||
## Builder Class Definition
|
## Builder Class Definition
|
||||||
|
|
||||||
|
(Note: you can see a builder class built using `@RecordBuilderFull` here: [FullRecordBuilder.java](https://gist.github.com/Randgalt/8aa487a847ea2acdd76d702f7cf17d6a))
|
||||||
|
|
||||||
The full builder class is defined as:
|
The full builder class is defined as:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
@@ -123,6 +128,21 @@ public class NameAndAgeBuilder {
|
|||||||
return new NameAndAgeBuilder(from.name(), from.age());
|
return new NameAndAgeBuilder(from.name(), from.age());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a "with"er for an existing record instance
|
||||||
|
*/
|
||||||
|
public static NameAndAgeBuilder.With from(NameAndAge from) {
|
||||||
|
return new _FromWith(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a stream of the record components as map entries keyed with the component name and the value as the component value
|
||||||
|
*/
|
||||||
|
public static Stream<Map.Entry<String, Object>> stream(NameAndAge record) {
|
||||||
|
return Stream.of(new AbstractMap.SimpleImmutableEntry<>("name", record.name()),
|
||||||
|
new AbstractMap.SimpleImmutableEntry<>("age", record.age()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new record instance with all fields set to the current values in this builder
|
* Return a new record instance with all fields set to the current values in this builder
|
||||||
*/
|
*/
|
||||||
@@ -130,6 +150,23 @@ public class NameAndAgeBuilder {
|
|||||||
return new NameAndAge(name, age);
|
return new NameAndAge(name, age);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(name, age);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return (this == o) || ((o instanceof NameAndAgeBuilder r)
|
||||||
|
&& Objects.equals(name, r.name)
|
||||||
|
&& (age == r.age));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a new value for the {@code name} record component in the builder
|
* Set a new value for the {@code name} record component in the builder
|
||||||
*/
|
*/
|
||||||
@@ -160,31 +197,6 @@ public class NameAndAgeBuilder {
|
|||||||
return age;
|
return age;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a stream of the record components as map entries keyed with the component name and the value as the component value
|
|
||||||
*/
|
|
||||||
public static Stream<Map.Entry<String, Object>> stream(NameAndAge record) {
|
|
||||||
return Stream.of(new AbstractMap.SimpleEntry<>("name", record.name()),
|
|
||||||
new AbstractMap.SimpleEntry<>("age", record.age()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(name, age);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return (this == o) || ((o instanceof NameAndAgeBuilder b)
|
|
||||||
&& Objects.equals(name, b.name)
|
|
||||||
&& (age == b.age));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add withers to {@code NameAndAge}
|
* Add withers to {@code NameAndAge}
|
||||||
*/
|
*/
|
||||||
@@ -229,6 +241,24 @@ public class NameAndAgeBuilder {
|
|||||||
return new NameAndAge(name(), age);
|
return new NameAndAge(name(), age);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class _FromWith implements NameAndAgeBuilder.With {
|
||||||
|
private final NameAndAge from;
|
||||||
|
|
||||||
|
private _FromWith(NameAndAge from) {
|
||||||
|
this.from = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return from.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int age() {
|
||||||
|
return from.age();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -286,6 +316,9 @@ public void Placeholder {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`@RecordBuilder.Include` also supports a `packages` attribute that includes all records
|
||||||
|
in the listed packages.
|
||||||
|
|
||||||
The target package for generation is the same as the package that contains the "Include"
|
The target package for generation is the same as the package that contains the "Include"
|
||||||
annotation. Use `packagePattern` to change this (see Javadoc for details).
|
annotation. Use `packagePattern` to change this (see Javadoc for details).
|
||||||
|
|
||||||
@@ -293,41 +326,15 @@ annotation. Use `packagePattern` to change this (see Javadoc for details).
|
|||||||
|
|
||||||
### Maven
|
### Maven
|
||||||
|
|
||||||
1) Add the dependency that contains the `@RecordBuilder` annotation.
|
Add a dependency that contains the discoverable annotation processor:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder-core</artifactId>
|
<artifactId>record-builder-processor</artifactId>
|
||||||
<version>set-version-here</version>
|
<version>${record.builder.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
2) Enable the annotation processing for the Maven Compiler Plugin:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>set-version-here</version>
|
|
||||||
<configuration>
|
|
||||||
<annotationProcessorPaths>
|
|
||||||
<annotationProcessorPath>
|
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
|
||||||
<artifactId>record-builder-processor</artifactId>
|
|
||||||
<version>set-version-here</version>
|
|
||||||
</annotationProcessorPath>
|
|
||||||
</annotationProcessorPaths>
|
|
||||||
<annotationProcessors>
|
|
||||||
<annotationProcessor>io.soabase.recordbuilder.processor.RecordBuilderProcessor</annotationProcessor>
|
|
||||||
</annotationProcessors>
|
|
||||||
|
|
||||||
|
|
||||||
... any other options here ...
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Gradle
|
### Gradle
|
||||||
@@ -350,76 +357,3 @@ Depending on your IDE you are likely to need to enable Annotation Processing in
|
|||||||
RecordBuilder can be customized to your needs and you can even create your
|
RecordBuilder can be customized to your needs and you can even create your
|
||||||
own custom RecordBuilder annotations. See [Customizing RecordBuilder](customizing.md)
|
own custom RecordBuilder annotations. See [Customizing RecordBuilder](customizing.md)
|
||||||
for details.
|
for details.
|
||||||
|
|
||||||
## Java 15 Versions
|
|
||||||
|
|
||||||
Artifacts compiled wth Java 15 are available. These versions have `-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:
|
|
||||||
|
|
||||||
- Install and make active Java 15 or later
|
|
||||||
- Make sure your development tool is using Java 15 or later and is configured to enable preview features (for Maven I've documented how to do this here: [https://stackoverflow.com/a/59363152/2048051](https://stackoverflow.com/a/59363152/2048051))
|
|
||||||
- Bear in mind that this is not yet meant for production and there are numerous bugs in the tools and JDKs.
|
|
||||||
|
|
||||||
Note: I've seen some very odd compilation bugs with the current Java 15 and Maven. If you get internal Javac errors I suggest rebuilding with `mvn clean package` and/or `mvn clean install`.
|
|
||||||
|
|
||||||
You will need to enable preview in your build tools:
|
|
||||||
|
|
||||||
### Maven
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
|
||||||
<artifactId>record-builder-core</artifactId>
|
|
||||||
<version>record-builder-version-java15</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>maven-compiler-version</version>
|
|
||||||
<configuration>
|
|
||||||
<annotationProcessorPaths>
|
|
||||||
<annotationProcessorPath>
|
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
|
||||||
<artifactId>record-builder-processor</artifactId>
|
|
||||||
<version>record-builder-version-java15</version>
|
|
||||||
</annotationProcessorPath>
|
|
||||||
</annotationProcessorPaths>
|
|
||||||
<annotationProcessors>
|
|
||||||
<annotationProcessor>io.soabase.recordbuilder.processor.RecordBuilderProcessor</annotationProcessor>
|
|
||||||
</annotationProcessors>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- "release" and "enable-preview" are required while records are preview features -->
|
|
||||||
<release>15</release>
|
|
||||||
<compilerArgs>
|
|
||||||
<arg>--enable-preview</arg>
|
|
||||||
</compilerArgs>
|
|
||||||
|
|
||||||
... any other options here ...
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a file in your project's root named `.mvn/jvm.config`. The file should have 1 line with the value: `--enable-preview`. (see: https://stackoverflow.com/questions/58023240)
|
|
||||||
|
|
||||||
### Gradle
|
|
||||||
|
|
||||||
```groovy
|
|
||||||
dependencies {
|
|
||||||
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$record-builder-version-java15'
|
|
||||||
compileOnly 'io.soabase.record-builder:record-builder-core:$record-builder-version-java15'
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType(JavaCompile) {
|
|
||||||
options.fork = true
|
|
||||||
options.forkOptions.jvmArgs += '--enable-preview'
|
|
||||||
options.compilerArgs += '--enable-preview'
|
|
||||||
}
|
|
||||||
tasks.withType(Test) {
|
|
||||||
jvmArgs += "--enable-preview"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
[◀︎ RecordBuilder](README.md) • Customizing RecordBuilder
|
[◀︎ RecordBuilder](README.md) • Customizing RecordBuilder
|
||||||
|
|
||||||
|
# RecordBuilderFull
|
||||||
|
|
||||||
|
Note: `@RecordBuilderFull` has most optional features enabled. It's an alternate
|
||||||
|
form of `@RecordBuilder` that uses the [templating mechanism](#create-a-custom-annotation) to
|
||||||
|
enable optional features.
|
||||||
|
|
||||||
# Customizing RecordBuilder
|
# Customizing RecordBuilder
|
||||||
|
|
||||||
RecordBuilder can be customized in a number of ways. The types of customizations will change over time. See
|
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)
|
[@RecordBuilder.Options](record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java)
|
||||||
for the current set of customizations and their default values.
|
for the current set of customizations and their default values. For example, the `useImmutableCollections` option
|
||||||
|
adds special handling for record components of type `java.util.List`, `java.util.Set`, `java.util.Map` and `java.util.Collection`. When the record is built, any components of these types are passed through an added shim method that uses the corresponding immutable collection (e.g. `List.copyOf(o)`) or an empty immutable collection if the component is `null`.
|
||||||
|
|
||||||
You can:
|
You can:
|
||||||
|
|
||||||
@@ -83,3 +90,6 @@ public @interface MyCoRecordBuilder {
|
|||||||
|
|
||||||
Now, you can use `@MyCoRecordBuilder` instead of `@RecordBuilder` and the record
|
Now, you can use `@MyCoRecordBuilder` instead of `@RecordBuilder` and the record
|
||||||
will be built with options as specified.
|
will be built with options as specified.
|
||||||
|
|
||||||
|
Note: the template mechanism also supports `@RecordInterface` templates via the `asRecordInterface` attribute.
|
||||||
|
When it is set a `@RecordInterface` template is created instead.
|
||||||
|
|||||||
22
java15.sh
22
java15.sh
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
jenv local 15
|
|
||||||
javahome
|
|
||||||
mkdir -p .mvn/
|
|
||||||
echo "--enable-preview" > .mvn/jvm.config
|
|
||||||
21
java16.sh
21
java16.sh
@@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
jenv local 16
|
|
||||||
javahome
|
|
||||||
rm -fr .mvn
|
|
||||||
53
pom.xml
53
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>25-java15</version>
|
<version>34</version>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>record-builder-core</module>
|
<module>record-builder-core</module>
|
||||||
@@ -19,8 +19,6 @@
|
|||||||
<project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
|
<project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
|
||||||
<enable-preview />
|
|
||||||
|
|
||||||
<jdk-version>16</jdk-version>
|
<jdk-version>16</jdk-version>
|
||||||
|
|
||||||
<maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
|
<maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
|
||||||
@@ -33,8 +31,10 @@
|
|||||||
<maven-clean-plugin-version>3.1.0</maven-clean-plugin-version>
|
<maven-clean-plugin-version>3.1.0</maven-clean-plugin-version>
|
||||||
<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-jar-plugin-version>3.2.0</maven-jar-plugin-version>
|
<maven-jar-plugin-version>3.2.0</maven-jar-plugin-version>
|
||||||
|
<maven-surefire-plugin-version>3.0.0-M5</maven-surefire-plugin-version>
|
||||||
|
|
||||||
|
<jacoco-maven-plugin-version>0.8.7</jacoco-maven-plugin-version>
|
||||||
|
|
||||||
<license-file-path>src/etc/header.txt</license-file-path>
|
<license-file-path>src/etc/header.txt</license-file-path>
|
||||||
|
|
||||||
@@ -80,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-25-java15</tag>
|
<tag>record-builder-34</tag>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<issueManagement>
|
<issueManagement>
|
||||||
@@ -115,6 +115,12 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
|
<artifactId>record-builder-processor</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder-validator</artifactId>
|
<artifactId>record-builder-validator</artifactId>
|
||||||
@@ -150,15 +156,18 @@
|
|||||||
<build>
|
<build>
|
||||||
<pluginManagement>
|
<pluginManagement>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven-surefire-plugin-version}</version>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>${maven-compiler-plugin-version}</version>
|
<version>${maven-compiler-plugin-version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>${jdk-version}</release>
|
<release>${jdk-version}</release>
|
||||||
<compilerArgs>
|
|
||||||
<arg>${enable-preview}</arg>
|
|
||||||
</compilerArgs>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
@@ -308,15 +317,6 @@
|
|||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
|
||||||
<version>${maven-surefire-plugin-version}</version>
|
|
||||||
<configuration>
|
|
||||||
<argLine>${enable-preview}</argLine>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
@@ -328,6 +328,12 @@
|
|||||||
<artifactId>maven-gpg-plugin</artifactId>
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
<version>${maven-gpg-plugin-version}</version>
|
<version>${maven-gpg-plugin-version}</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jacoco</groupId>
|
||||||
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
<version>${jacoco-maven-plugin-version}</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|
||||||
@@ -361,6 +367,11 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-release-plugin</artifactId>
|
<artifactId>maven-release-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
@@ -388,13 +399,5 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
|
||||||
<profile>
|
|
||||||
<id>java15</id>
|
|
||||||
<properties>
|
|
||||||
<jdk-version>15</jdk-version>
|
|
||||||
<enable-preview>--enable-preview</enable-preview>
|
|
||||||
</properties>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
</profiles>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>25-java15</version>
|
<version>34</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,32 @@ public @interface RecordBuilder {
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Inherited
|
@Inherited
|
||||||
@interface Include {
|
@interface Include {
|
||||||
Class<?>[] value();
|
/**
|
||||||
|
* @return list of classes to include
|
||||||
|
*/
|
||||||
|
Class<?>[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
|
||||||
|
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
|
||||||
|
* (i.e. a union of classes from both attributes).
|
||||||
|
*
|
||||||
|
* @return list of classes
|
||||||
|
*/
|
||||||
|
Class<?>[] classes() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional list of package names. All records in the packages will get processed as
|
||||||
|
* if they were listed as classes to include.
|
||||||
|
*
|
||||||
|
* @return list of package names
|
||||||
|
*/
|
||||||
|
String[] packages() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pattern used to generate the package for the generated class. The value
|
* Pattern used to generate the package for the generated class. The value
|
||||||
* is the literal package name however two replacement values can be used. '@'
|
* is the literal package name however two replacement values can be used. '@'
|
||||||
* is replaced with the package of the Include annotation. '*' is replaced with
|
* is replaced with the package of the {@code Include} annotation. '*' is replaced with
|
||||||
* the package of the included class.
|
* the package of the included class.
|
||||||
*
|
*
|
||||||
* @return package pattern
|
* @return package pattern
|
||||||
@@ -69,11 +89,21 @@ public @interface RecordBuilder {
|
|||||||
*/
|
*/
|
||||||
String buildMethodName() default "build";
|
String buildMethodName() default "build";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name to use for the from-to-wither method
|
||||||
|
*/
|
||||||
|
String fromMethodName() default "from";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name to use for the method that returns the record components as a stream
|
* The name to use for the method that returns the record components as a stream
|
||||||
*/
|
*/
|
||||||
String componentsMethodName() default "stream";
|
String componentsMethodName() default "stream";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, a "With" interface is generated and an associated static factory
|
||||||
|
*/
|
||||||
|
boolean enableWither() default true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name to use for the nested With class
|
* The name to use for the nested With class
|
||||||
*/
|
*/
|
||||||
@@ -114,6 +144,11 @@ public @interface RecordBuilder {
|
|||||||
*/
|
*/
|
||||||
boolean emptyDefaultForOptional() default true;
|
boolean emptyDefaultForOptional() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add non-optional setter methods for optional record components.
|
||||||
|
*/
|
||||||
|
boolean addConcreteSettersForOptional() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add not-null checks for record components annotated with any annotation named either "NotNull",
|
* 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).
|
* "NoNull", or "NonNull" (see {@link #interpretNotNullsPattern()} for the actual regex matching pattern).
|
||||||
@@ -144,12 +179,105 @@ public @interface RecordBuilder {
|
|||||||
* (e.g. {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
|
* (e.g. {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
|
||||||
*/
|
*/
|
||||||
boolean useImmutableCollections() default false;
|
boolean useImmutableCollections() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When enabled, collection types ({@code List}, {@code Set} and {@code Map}) are handled specially.
|
||||||
|
* The setters for these types now create an internal collection and items are added to that
|
||||||
|
* collection. Additionally, "adder" methods prefixed with {@link #singleItemBuilderPrefix()} are created
|
||||||
|
* to add single items to these collections.
|
||||||
|
*/
|
||||||
|
boolean addSingleItemCollectionBuilders() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The prefix for adder methods when {@link #addSingleItemCollectionBuilders()} is enabled
|
||||||
|
*/
|
||||||
|
String singleItemBuilderPrefix() default "add";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When enabled, adds functional methods to the nested "With" class (such as {@code map()} and {@code accept()}).
|
||||||
|
*/
|
||||||
|
boolean addFunctionalMethodsToWith() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, all builder setter methods will be prefixed with this string. Camel-casing will
|
||||||
|
* still be enforced, so if this option is set to "set" a field named "myField" will get
|
||||||
|
* a corresponding setter named "setMyField".
|
||||||
|
*/
|
||||||
|
String setterPrefix() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, getters will be generated for the Builder class.
|
||||||
|
*/
|
||||||
|
boolean enableGetters() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, all builder getter methods will be prefixed with this string. Camel-casing will
|
||||||
|
* still be enforced, so if this option is set to "get", a field named "myField" will get
|
||||||
|
* a corresponding getter named "getMyField".
|
||||||
|
*/
|
||||||
|
String getterPrefix() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, all boolean builder getter methods will be prefixed with this string.
|
||||||
|
* Camel-casing will still be enforced, so if this option is set to "is", a field named
|
||||||
|
* "myField" will get a corresponding getter named "isMyField".
|
||||||
|
*/
|
||||||
|
String booleanPrefix() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the Builder will contain an internal interface with this name. This interface
|
||||||
|
* contains getters for all the fields in the Record prefixed with the value supplied in
|
||||||
|
* {@link this.getterPrefix} and {@link this.booleanPrefix}. This interface can be
|
||||||
|
* implemented by the original Record to have proper bean-style prefixed getters.
|
||||||
|
*
|
||||||
|
* Please note that unless either of the aforementioned prefixes are set,
|
||||||
|
* this option does nothing.
|
||||||
|
*/
|
||||||
|
String beanClassName() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, generated classes are annotated with {@code RecordBuilderGenerated} which has a retention
|
||||||
|
* policy of {@code CLASS}. This ensures that analyzers such as Jacoco will ignore the generated class.
|
||||||
|
*/
|
||||||
|
boolean addClassRetainedGenerated() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link #fromMethodName} method instantiates an internal private class. This is the
|
||||||
|
* name of that class.
|
||||||
|
*/
|
||||||
|
String fromWithClassName() default "_FromWith";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, a functional-style builder is added so that record instances can be instantiated
|
||||||
|
* without {@code new}.
|
||||||
|
*/
|
||||||
|
boolean addStaticBuilder() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||||
|
* uses an internal class to track changes to lists. This is the name of that class.
|
||||||
|
*/
|
||||||
|
String mutableListClassName() default "_MutableList";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||||
|
* uses an internal class to track changes to sets. This is the name of that class.
|
||||||
|
*/
|
||||||
|
String mutableSetClassName() default "_MutableSet";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||||
|
* uses an internal class to track changes to maps. This is the name of that class.
|
||||||
|
*/
|
||||||
|
String mutableMapClassName() default "_MutableMap";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@Target(ElementType.ANNOTATION_TYPE)
|
@Target(ElementType.ANNOTATION_TYPE)
|
||||||
@Inherited
|
@Inherited
|
||||||
@interface Template {
|
@interface Template {
|
||||||
RecordBuilder.Options options();
|
RecordBuilder.Options options();
|
||||||
|
|
||||||
|
boolean asRecordInterface() default false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.core;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@RecordBuilder.Template(options = @RecordBuilder.Options(
|
||||||
|
interpretNotNulls = true,
|
||||||
|
useImmutableCollections = true,
|
||||||
|
addSingleItemCollectionBuilders = true,
|
||||||
|
addFunctionalMethodsToWith = true,
|
||||||
|
addClassRetainedGenerated = true
|
||||||
|
))
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
/**
|
||||||
|
* An alternate form of {@code @RecordBuilder} that has most
|
||||||
|
* optional features turned on
|
||||||
|
*/
|
||||||
|
public @interface RecordBuilderFull {
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.core;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jacoco ignores classes and methods annotated with `*Generated`
|
||||||
|
*/
|
||||||
|
@Target({PACKAGE, TYPE, METHOD, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, PARAMETER})
|
||||||
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
public @interface RecordBuilderGenerated {
|
||||||
|
}
|
||||||
@@ -15,11 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.core;
|
package io.soabase.recordbuilder.core;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.*;
|
||||||
import java.lang.annotation.Inherited;
|
|
||||||
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)
|
||||||
@@ -31,14 +27,31 @@ public @interface RecordInterface {
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Inherited
|
@Inherited
|
||||||
@interface Include {
|
@interface Include {
|
||||||
Class<?>[] value();
|
/**
|
||||||
|
* @return list of classes to include
|
||||||
|
*/
|
||||||
|
Class<?>[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
|
||||||
|
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
|
||||||
|
* (i.e. a union of classes from both attributes).
|
||||||
|
*
|
||||||
|
* @return list of classes
|
||||||
|
*/
|
||||||
|
Class<?>[] classes() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true the generated record is annotated with {@code @RecordBuilder}
|
||||||
|
*
|
||||||
|
* @return true/false
|
||||||
|
*/
|
||||||
boolean addRecordBuilder() default true;
|
boolean addRecordBuilder() default true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pattern used to generate the package for the generated class. The value
|
* Pattern used to generate the package for the generated class. The value
|
||||||
* is the literal package name however two replacement values can be used. '@'
|
* is the literal package name however two replacement values can be used. '@'
|
||||||
* is replaced with the package of the Include annotation. '*' is replaced with
|
* is replaced with the package of the {@code Include} annotation. '*' is replaced with
|
||||||
* the package of the included class.
|
* the package of the included class.
|
||||||
*
|
*
|
||||||
* @return package pattern
|
* @return package pattern
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>25-java15</version>
|
<version>34</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -16,28 +16,44 @@
|
|||||||
package io.soabase.recordbuilder.processor;
|
package io.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
import com.squareup.javapoet.*;
|
import com.squareup.javapoet.*;
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
import javax.lang.model.element.Modifier;
|
import javax.lang.model.element.Modifier;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||||
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||||
|
|
||||||
class CollectionBuilderUtils {
|
class CollectionBuilderUtils {
|
||||||
private final boolean enabled;
|
private final boolean useImmutableCollections;
|
||||||
|
private final boolean addSingleItemCollectionBuilders;
|
||||||
|
private final boolean addClassRetainedGenerated;
|
||||||
private final String listShimName;
|
private final String listShimName;
|
||||||
private final String mapShimName;
|
private final String mapShimName;
|
||||||
private final String setShimName;
|
private final String setShimName;
|
||||||
private final String collectionShimName;
|
private final String collectionShimName;
|
||||||
|
|
||||||
|
private final String listMakerMethodName;
|
||||||
|
private final String mapMakerMethodName;
|
||||||
|
private final String setMakerMethodName;
|
||||||
|
|
||||||
private boolean needsListShim;
|
private boolean needsListShim;
|
||||||
private boolean needsMapShim;
|
private boolean needsMapShim;
|
||||||
private boolean needsSetShim;
|
private boolean needsSetShim;
|
||||||
private boolean needsCollectionShim;
|
private boolean needsCollectionShim;
|
||||||
|
|
||||||
private static final TypeName listType = TypeName.get(List.class);
|
private boolean needsListMutableMaker;
|
||||||
private static final TypeName mapType = TypeName.get(Map.class);
|
private boolean needsMapMutableMaker;
|
||||||
private static final TypeName setType = TypeName.get(Set.class);
|
private boolean needsSetMutableMaker;
|
||||||
private static final TypeName collectionType = TypeName.get(Collection.class);
|
|
||||||
|
private static final Class<?> listType = List.class;
|
||||||
|
private static final Class<?> mapType = Map.class;
|
||||||
|
private static final Class<?> setType = Set.class;
|
||||||
|
private static final Class<?> collectionType = Collection.class;
|
||||||
|
private static final TypeName listTypeName = TypeName.get(listType);
|
||||||
|
private static final TypeName mapTypeName = TypeName.get(mapType);
|
||||||
|
private static final TypeName setTypeName = TypeName.get(setType);
|
||||||
|
private static final TypeName collectionTypeName = TypeName.get(collectionType);
|
||||||
|
|
||||||
private static final TypeVariableName tType = TypeVariableName.get("T");
|
private static final TypeVariableName tType = TypeVariableName.get("T");
|
||||||
private static final TypeVariableName kType = TypeVariableName.get("K");
|
private static final TypeVariableName kType = TypeVariableName.get("K");
|
||||||
@@ -47,27 +63,117 @@ class CollectionBuilderUtils {
|
|||||||
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName.get(ClassName.get(Set.class), tType);
|
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName.get(ClassName.get(Set.class), tType);
|
||||||
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName.get(ClassName.get(Collection.class), tType);
|
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName.get(ClassName.get(Collection.class), tType);
|
||||||
|
|
||||||
CollectionBuilderUtils(List<RecordClassType> recordComponents, boolean enabled) {
|
private static final Class<?> mutableListType = ArrayList.class;
|
||||||
this.enabled = enabled;
|
private static final Class<?> mutableMapType = HashMap.class;
|
||||||
|
private static final Class<?> mutableSetType = HashSet.class;
|
||||||
|
private static final ClassName mutableListTypeName = ClassName.get(mutableListType);
|
||||||
|
private static final ClassName mutableMapTypeName = ClassName.get(mutableMapType);
|
||||||
|
private static final ClassName mutableSetTypeName = ClassName.get(mutableSetType);
|
||||||
|
private final TypeSpec mutableListSpec;
|
||||||
|
private final TypeSpec mutableSetSpec;
|
||||||
|
private final TypeSpec mutableMapSpec;
|
||||||
|
|
||||||
listShimName = adjustShimName(recordComponents, "__list", 0);
|
CollectionBuilderUtils(List<RecordClassType> recordComponents, RecordBuilder.Options metaData) {
|
||||||
mapShimName = adjustShimName(recordComponents, "__map", 0);
|
useImmutableCollections = metaData.useImmutableCollections();
|
||||||
setShimName = adjustShimName(recordComponents, "__set", 0);
|
addSingleItemCollectionBuilders = metaData.addSingleItemCollectionBuilders();
|
||||||
collectionShimName = adjustShimName(recordComponents, "__collection", 0);
|
addClassRetainedGenerated = metaData.addClassRetainedGenerated();
|
||||||
|
|
||||||
|
listShimName = disambiguateGeneratedMethodName(recordComponents, "__list", 0);
|
||||||
|
mapShimName = disambiguateGeneratedMethodName(recordComponents, "__map", 0);
|
||||||
|
setShimName = disambiguateGeneratedMethodName(recordComponents, "__set", 0);
|
||||||
|
collectionShimName = disambiguateGeneratedMethodName(recordComponents, "__collection", 0);
|
||||||
|
|
||||||
|
listMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureListMutable", 0);
|
||||||
|
setMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureSetMutable", 0);
|
||||||
|
mapMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureMapMutable", 0);
|
||||||
|
|
||||||
|
mutableListSpec = buildMutableCollectionSubType(metaData.mutableListClassName(), mutableListTypeName, parameterizedListType, tType);
|
||||||
|
mutableSetSpec = buildMutableCollectionSubType(metaData.mutableSetClassName(), mutableSetTypeName, parameterizedSetType, tType);
|
||||||
|
mutableMapSpec = buildMutableCollectionSubType(metaData.mutableMapClassName(), mutableMapTypeName, parameterizedMapType, kType, vType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(CodeBlock.Builder builder, RecordClassType component) {
|
enum SingleItemsMetaDataMode {
|
||||||
if (enabled) {
|
STANDARD,
|
||||||
if (component.rawTypeName().equals(listType)) {
|
STANDARD_FOR_SETTER,
|
||||||
|
EXCLUDE_WILDCARD_TYPES
|
||||||
|
}
|
||||||
|
|
||||||
|
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<SingleItemsMetaData> singleItemsMetaData(RecordClassType component, SingleItemsMetaDataMode mode) {
|
||||||
|
if (addSingleItemCollectionBuilders && (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)) {
|
||||||
|
Class<?> collectionClass = null;
|
||||||
|
ClassName wildcardClass = null;
|
||||||
|
int typeArgumentQty = 0;
|
||||||
|
if (isList(component)) {
|
||||||
|
collectionClass = mutableListType;
|
||||||
|
wildcardClass = ClassName.get(Collection.class);
|
||||||
|
typeArgumentQty = 1;
|
||||||
|
} else if (isSet(component)) {
|
||||||
|
collectionClass = mutableSetType;
|
||||||
|
wildcardClass = ClassName.get(Collection.class);
|
||||||
|
typeArgumentQty = 1;
|
||||||
|
} else if (isMap(component)) {
|
||||||
|
collectionClass = mutableMapType;
|
||||||
|
wildcardClass = (ClassName) component.rawTypeName();
|
||||||
|
typeArgumentQty = 2;
|
||||||
|
}
|
||||||
|
var hasWildcardTypeArguments = hasWildcardTypeArguments(parameterizedTypeName, typeArgumentQty);
|
||||||
|
if (collectionClass != null) {
|
||||||
|
return switch (mode) {
|
||||||
|
case STANDARD -> singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||||
|
|
||||||
|
case STANDARD_FOR_SETTER -> {
|
||||||
|
if (hasWildcardTypeArguments) {
|
||||||
|
yield Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, component.typeName()));
|
||||||
|
}
|
||||||
|
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXCLUDE_WILDCARD_TYPES -> {
|
||||||
|
if (hasWildcardTypeArguments) {
|
||||||
|
yield Optional.empty();
|
||||||
|
}
|
||||||
|
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isImmutableCollection(RecordClassType component) {
|
||||||
|
return useImmutableCollections && (isList(component) || isMap(component) || isSet(component) || component.rawTypeName().equals(collectionTypeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isList(RecordClassType component) {
|
||||||
|
return component.rawTypeName().equals(listTypeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isMap(RecordClassType component) {
|
||||||
|
return component.rawTypeName().equals(mapTypeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSet(RecordClassType component) {
|
||||||
|
return component.rawTypeName().equals(setTypeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addShimCall(CodeBlock.Builder builder, RecordClassType component) {
|
||||||
|
if (useImmutableCollections) {
|
||||||
|
if (isList(component)) {
|
||||||
needsListShim = true;
|
needsListShim = true;
|
||||||
|
needsListMutableMaker = true;
|
||||||
builder.add("$L($L)", listShimName, component.name());
|
builder.add("$L($L)", listShimName, component.name());
|
||||||
} else if (component.rawTypeName().equals(mapType)) {
|
} else if (isMap(component)) {
|
||||||
needsMapShim = true;
|
needsMapShim = true;
|
||||||
|
needsMapMutableMaker = true;
|
||||||
builder.add("$L($L)", mapShimName, component.name());
|
builder.add("$L($L)", mapShimName, component.name());
|
||||||
} else if (component.rawTypeName().equals(setType)) {
|
} else if (isSet(component)) {
|
||||||
needsSetShim = true;
|
needsSetShim = true;
|
||||||
|
needsSetMutableMaker = true;
|
||||||
builder.add("$L($L)", setShimName, component.name());
|
builder.add("$L($L)", setShimName, component.name());
|
||||||
} else if (component.rawTypeName().equals(collectionType)) {
|
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||||
needsCollectionShim = true;
|
needsCollectionShim = true;
|
||||||
builder.add("$L($L)", collectionShimName, component.name());
|
builder.add("$L($L)", collectionShimName, component.name());
|
||||||
} else {
|
} else {
|
||||||
@@ -78,54 +184,156 @@ class CollectionBuilderUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String shimName(RecordClassType component) {
|
||||||
|
if (isList(component)) {
|
||||||
|
return listShimName;
|
||||||
|
} else if (isMap(component)) {
|
||||||
|
return mapShimName;
|
||||||
|
} else if (isSet(component)) {
|
||||||
|
return setShimName;
|
||||||
|
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||||
|
return collectionShimName;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(component + " is not a supported collection type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String mutableMakerName(RecordClassType component) {
|
||||||
|
if (isList(component)) {
|
||||||
|
return listMakerMethodName;
|
||||||
|
} else if (isMap(component)) {
|
||||||
|
return mapMakerMethodName;
|
||||||
|
} else if (isSet(component)) {
|
||||||
|
return setMakerMethodName;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(component + " is not a supported collection type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void addShims(TypeSpec.Builder builder) {
|
void addShims(TypeSpec.Builder builder) {
|
||||||
if (!enabled) {
|
if (!useImmutableCollections) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsListShim) {
|
if (needsListShim) {
|
||||||
builder.addMethod(buildMethod(listShimName, listType, parameterizedListType, tType));
|
builder.addMethod(buildShimMethod(listShimName, listTypeName, collectionType, parameterizedListType, tType));
|
||||||
}
|
}
|
||||||
if (needsSetShim) {
|
if (needsSetShim) {
|
||||||
builder.addMethod(buildMethod(setShimName, setType, parameterizedSetType, tType));
|
builder.addMethod(buildShimMethod(setShimName, setTypeName, collectionType, parameterizedSetType, tType));
|
||||||
}
|
}
|
||||||
if (needsMapShim) {
|
if (needsMapShim) {
|
||||||
builder.addMethod(buildMethod(mapShimName, mapType, parameterizedMapType, kType, vType));
|
builder.addMethod(buildShimMethod(mapShimName, mapTypeName, mapType, parameterizedMapType, kType, vType));
|
||||||
}
|
}
|
||||||
if (needsCollectionShim) {
|
if (needsCollectionShim) {
|
||||||
builder.addMethod(buildCollectionsMethod());
|
builder.addMethod(buildCollectionsShimMethod());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String adjustShimName(List<RecordClassType> recordComponents, String baseName, int index)
|
void addMutableMakers(TypeSpec.Builder builder) {
|
||||||
{
|
if (!useImmutableCollections) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsListMutableMaker) {
|
||||||
|
builder.addMethod(buildMutableMakerMethod(listMakerMethodName, mutableListSpec.name, parameterizedListType, tType));
|
||||||
|
builder.addType(mutableListSpec);
|
||||||
|
}
|
||||||
|
if (needsSetMutableMaker) {
|
||||||
|
builder.addMethod(buildMutableMakerMethod(setMakerMethodName, mutableSetSpec.name, parameterizedSetType, tType));
|
||||||
|
builder.addType(mutableSetSpec);
|
||||||
|
}
|
||||||
|
if (needsMapMutableMaker) {
|
||||||
|
builder.addMethod(buildMutableMakerMethod(mapMakerMethodName, mutableMapSpec.name, parameterizedMapType, kType, vType));
|
||||||
|
builder.addType(mutableMapSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<SingleItemsMetaData> singleItemsMetaDataWithWildType(ParameterizedTypeName parameterizedTypeName, Class<?> collectionClass, ClassName wildcardClass, int typeArgumentQty) {
|
||||||
|
TypeName wildType;
|
||||||
|
if (typeArgumentQty == 1) {
|
||||||
|
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)));
|
||||||
|
} else { // if (typeArgumentQty == 2)
|
||||||
|
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)), WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(1)));
|
||||||
|
}
|
||||||
|
return Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, wildType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasWildcardTypeArguments(ParameterizedTypeName parameterizedTypeName, int argumentCount) {
|
||||||
|
for (int i = 0; i < argumentCount; ++i) {
|
||||||
|
if (parameterizedTypeName.typeArguments.size() > i) {
|
||||||
|
if (parameterizedTypeName.typeArguments.get(i) instanceof WildcardTypeName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String disambiguateGeneratedMethodName(List<RecordClassType> recordComponents, String baseName, int index) {
|
||||||
var name = (index == 0) ? baseName : (baseName + index);
|
var name = (index == 0) ? baseName : (baseName + index);
|
||||||
if (recordComponents.stream().anyMatch(component -> component.name().equals(name))) {
|
if (recordComponents.stream().anyMatch(component -> component.name().equals(name))) {
|
||||||
return adjustShimName(recordComponents, baseName, index + 1);
|
return disambiguateGeneratedMethodName(recordComponents, baseName, index + 1);
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodSpec buildMethod(String name, TypeName mainType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
private MethodSpec buildShimMethod(String name, TypeName mainType, Class<?> abstractType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||||
var code = CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
|
var code = CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
|
||||||
|
TypeName[] wildCardTypeArguments = parameterizedType.typeArguments.stream().map(WildcardTypeName::subtypeOf).toList().toArray(new TypeName[0]);
|
||||||
|
var extendedParameterizedType = ParameterizedTypeName.get(ClassName.get(abstractType), wildCardTypeArguments);
|
||||||
|
return MethodSpec.methodBuilder(name)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||||
|
.addTypeVariables(Arrays.asList(typeVariables))
|
||||||
|
.returns(parameterizedType)
|
||||||
|
.addParameter(extendedParameterizedType, "o")
|
||||||
|
.addStatement(code)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodSpec buildMutableMakerMethod(String name, String mutableCollectionType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||||
|
var nullCase = CodeBlock.of("if (o == null) return new $L<>()", mutableCollectionType);
|
||||||
|
var isMutableCase = CodeBlock.of("if (o instanceof $L) return o", mutableCollectionType);
|
||||||
|
var defaultCase = CodeBlock.of("return new $L<>(o)", mutableCollectionType);
|
||||||
return MethodSpec.methodBuilder(name)
|
return MethodSpec.methodBuilder(name)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||||
.addTypeVariables(Arrays.asList(typeVariables))
|
.addTypeVariables(Arrays.asList(typeVariables))
|
||||||
.returns(parameterizedType)
|
.returns(parameterizedType)
|
||||||
.addParameter(parameterizedType, "o")
|
.addParameter(parameterizedType, "o")
|
||||||
.addStatement(code)
|
.addStatement(nullCase)
|
||||||
|
.addStatement(isMutableCase)
|
||||||
|
.addStatement(defaultCase)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodSpec buildCollectionsMethod() {
|
private TypeSpec buildMutableCollectionSubType(String className, ClassName mutableCollectionType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||||
|
TypeName[] typeArguments = new TypeName[]{};
|
||||||
|
typeArguments = Arrays.stream(typeVariables).toList().toArray(typeArguments);
|
||||||
|
|
||||||
|
TypeSpec.Builder builder = TypeSpec.classBuilder(className)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||||
|
.superclass(ParameterizedTypeName.get(mutableCollectionType, typeArguments))
|
||||||
|
.addTypeVariables(Arrays.asList(typeVariables))
|
||||||
|
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation).addStatement("super()").build())
|
||||||
|
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation).addParameter(parameterizedType, "o").addStatement("super(o)").build());
|
||||||
|
|
||||||
|
if (addClassRetainedGenerated) {
|
||||||
|
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodSpec buildCollectionsShimMethod() {
|
||||||
var code = CodeBlock.builder()
|
var code = CodeBlock.builder()
|
||||||
.add("if (o instanceof Set) {\n")
|
.add("if (o instanceof Set) {\n")
|
||||||
.indent()
|
.indent()
|
||||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", setType, setType)
|
.addStatement("return $T.copyOf(o)", setTypeName)
|
||||||
.unindent()
|
.unindent()
|
||||||
.addStatement("}")
|
.addStatement("}")
|
||||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listType, listType)
|
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listTypeName, listTypeName)
|
||||||
.build();
|
.build();
|
||||||
return MethodSpec.methodBuilder(collectionShimName)
|
return MethodSpec.methodBuilder(collectionShimName)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
|||||||
@@ -52,12 +52,19 @@ public class ElementUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static List<TypeMirror> getClassesAttribute(AnnotationValue attribute)
|
public static List<TypeMirror> getAttributeTypeMirrorList(AnnotationValue attribute)
|
||||||
{
|
{
|
||||||
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
|
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
|
||||||
return values.stream().map(v -> (TypeMirror)v.getValue()).collect(Collectors.toList());
|
return values.stream().map(v -> (TypeMirror)v.getValue()).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static List<String> getAttributeStringList(AnnotationValue attribute)
|
||||||
|
{
|
||||||
|
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
|
||||||
|
return values.stream().map(v -> (String)v.getValue()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean getBooleanAttribute(AnnotationValue attribute)
|
public static boolean getBooleanAttribute(AnnotationValue attribute)
|
||||||
{
|
{
|
||||||
Object value = (attribute != null) ? attribute.getValue() : null;
|
Object value = (attribute != null) ? attribute.getValue() : null;
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
|
import javax.lang.model.element.*;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
import javax.tools.Diagnostic;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class IncludeHelper {
|
||||||
|
private final boolean isValid;
|
||||||
|
private final List<TypeElement> classTypeElements;
|
||||||
|
private final Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues;
|
||||||
|
|
||||||
|
IncludeHelper(ProcessingEnvironment processingEnv, Element element, AnnotationMirror annotationMirror, boolean packagesSupported) {
|
||||||
|
annotationValues = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
|
||||||
|
var value = ElementUtils.getAnnotationValue(annotationValues, "value");
|
||||||
|
var classes = ElementUtils.getAnnotationValue(annotationValues, "classes");
|
||||||
|
var packages = ElementUtils.getAnnotationValue(annotationValues, "packages");
|
||||||
|
var isValid = true;
|
||||||
|
var classTypeElements = new ArrayList<TypeElement>();
|
||||||
|
if (value.isPresent() || classes.isPresent() || packages.isPresent()) {
|
||||||
|
var valueList = value.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
|
||||||
|
var classesList = classes.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
|
||||||
|
var packagesList = packages.map(ElementUtils::getAttributeStringList).orElseGet(List::of);
|
||||||
|
if (valueList.isEmpty() && classesList.isEmpty() && packagesList.isEmpty()) {
|
||||||
|
if (packagesSupported) {
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\", \"classes\" or \"packages\" required", element);
|
||||||
|
} else {
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\" or \"classes\" required", element);
|
||||||
|
}
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
isValid = processList(processingEnv, isValid, element, valueList, classTypeElements);
|
||||||
|
isValid = processList(processingEnv, isValid, element, classesList, classTypeElements);
|
||||||
|
packages.ifPresent(annotationValue -> processPackages(processingEnv, classTypeElements, packagesList));
|
||||||
|
} else {
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not read attribute for annotation", element);
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
this.isValid = isValid;
|
||||||
|
this.classTypeElements = List.copyOf(classTypeElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<? extends ExecutableElement, ? extends AnnotationValue> getAnnotationValues() {
|
||||||
|
return annotationValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isValid() {
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeElement> getClassTypeElements() {
|
||||||
|
return classTypeElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processList(ProcessingEnvironment processingEnv, boolean isValid, Element element, List<TypeMirror> list, ArrayList<TypeElement> classTypeElements) {
|
||||||
|
for (var typeMirror : list) {
|
||||||
|
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
|
||||||
|
if (typeElement == null) {
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + typeMirror, element);
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
classTypeElements.add(typeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPackages(ProcessingEnvironment processingEnv, List<TypeElement> classTypeElements, List<String> packagesList) {
|
||||||
|
for (var packageName : packagesList) {
|
||||||
|
var packageElement = processingEnv.getElementUtils().getPackageElement(packageName);
|
||||||
|
for (var child : packageElement.getEnclosedElements()) {
|
||||||
|
if (child.getKind() == ElementKind.RECORD) {
|
||||||
|
classTypeElements.add((TypeElement) child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,21 +15,26 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.processor;
|
package io.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
import com.squareup.javapoet.*;
|
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.EXCLUDE_WILDCARD_TYPES;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.STANDARD_FOR_SETTER;
|
||||||
|
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||||
|
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||||
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||||
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||||
|
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.*;
|
import javax.lang.model.element.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.regex.Pattern;
|
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;
|
||||||
|
|
||||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
import com.squareup.javapoet.*;
|
||||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
|
||||||
|
|
||||||
class InternalRecordBuilderProcessor {
|
class InternalRecordBuilderProcessor {
|
||||||
private final RecordBuilder.Options metaData;
|
private final RecordBuilder.Options metaData;
|
||||||
@@ -45,17 +50,15 @@ class InternalRecordBuilderProcessor {
|
|||||||
private final CollectionBuilderUtils collectionBuilderUtils;
|
private final CollectionBuilderUtils collectionBuilderUtils;
|
||||||
|
|
||||||
private static final TypeName overrideType = TypeName.get(Override.class);
|
private static final TypeName overrideType = TypeName.get(Override.class);
|
||||||
private static final TypeName optionalType = TypeName.get(Optional.class);
|
private static final TypeName validType = ClassName.get("javax.validation", "Valid");
|
||||||
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 static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator", "RecordBuilderValidator");
|
||||||
|
private static final TypeVariableName rType = TypeVariableName.get("R");
|
||||||
private final ProcessingEnvironment processingEnv;
|
private final ProcessingEnvironment processingEnv;
|
||||||
|
|
||||||
InternalRecordBuilderProcessor(ProcessingEnvironment processingEnv, TypeElement record, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
InternalRecordBuilderProcessor(ProcessingEnvironment processingEnv, TypeElement record, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||||
this.processingEnv = processingEnv;
|
this.processingEnv = processingEnv;
|
||||||
var recordActualPackage = ElementUtils.getPackageName(record);
|
var recordActualPackage = ElementUtils.getPackageName(record);
|
||||||
this.metaData = getMetaData(record, metaData);
|
this.metaData = metaData;
|
||||||
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
||||||
packageName = packageNameOpt.orElse(recordActualPackage);
|
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());
|
||||||
@@ -63,20 +66,33 @@ class InternalRecordBuilderProcessor {
|
|||||||
recordComponents = buildRecordComponents(record);
|
recordComponents = buildRecordComponents(record);
|
||||||
uniqueVarName = getUniqueVarName();
|
uniqueVarName = getUniqueVarName();
|
||||||
notNullPattern = Pattern.compile(metaData.interpretNotNullsPattern());
|
notNullPattern = Pattern.compile(metaData.interpretNotNullsPattern());
|
||||||
collectionBuilderUtils = new CollectionBuilderUtils(recordComponents, this.metaData.useImmutableCollections());
|
collectionBuilderUtils = new CollectionBuilderUtils(recordComponents, this.metaData);
|
||||||
|
|
||||||
builder = TypeSpec.classBuilder(builderClassType.name())
|
builder = TypeSpec.classBuilder(builderClassType.name())
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.addTypeVariables(typeVariables);
|
.addTypeVariables(typeVariables);
|
||||||
|
if (metaData.addClassRetainedGenerated()) {
|
||||||
|
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||||
|
}
|
||||||
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
|
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
|
||||||
addWithNestedClass();
|
if (metaData.enableWither()) {
|
||||||
|
addWithNestedClass();
|
||||||
|
}
|
||||||
|
if (!metaData.beanClassName().isEmpty()) {
|
||||||
|
addBeanNestedClass();
|
||||||
|
}
|
||||||
addDefaultConstructor();
|
addDefaultConstructor();
|
||||||
addStaticBuilder();
|
if (metaData.addStaticBuilder()) {
|
||||||
|
addStaticBuilder();
|
||||||
|
}
|
||||||
if (recordComponents.size() > 0) {
|
if (recordComponents.size() > 0) {
|
||||||
addAllArgsConstructor();
|
addAllArgsConstructor();
|
||||||
}
|
}
|
||||||
addStaticDefaultBuilderMethod();
|
addStaticDefaultBuilderMethod();
|
||||||
addStaticCopyBuilderMethod();
|
addStaticCopyBuilderMethod();
|
||||||
|
if (metaData.enableWither()) {
|
||||||
|
addStaticFromWithMethod();
|
||||||
|
}
|
||||||
addStaticComponentsMethod();
|
addStaticComponentsMethod();
|
||||||
addBuildMethod();
|
addBuildMethod();
|
||||||
addToStringMethod();
|
addToStringMethod();
|
||||||
@@ -85,9 +101,17 @@ class InternalRecordBuilderProcessor {
|
|||||||
recordComponents.forEach(component -> {
|
recordComponents.forEach(component -> {
|
||||||
add1Field(component);
|
add1Field(component);
|
||||||
add1SetterMethod(component);
|
add1SetterMethod(component);
|
||||||
add1GetterMethod(component);
|
if (metaData.enableGetters()) {
|
||||||
|
add1GetterMethod(component);
|
||||||
|
}
|
||||||
|
if (metaData.addConcreteSettersForOptional()) {
|
||||||
|
add1ConcreteOptionalSetterMethod(component);
|
||||||
|
}
|
||||||
|
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, EXCLUDE_WILDCARD_TYPES);
|
||||||
|
collectionMetaData.ifPresent(meta -> add1CollectionBuilders(meta, component));
|
||||||
});
|
});
|
||||||
collectionBuilderUtils.addShims(builder);
|
collectionBuilderUtils.addShims(builder);
|
||||||
|
collectionBuilderUtils.addMutableMakers(builder);
|
||||||
builderType = builder.build();
|
builderType = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +133,7 @@ class InternalRecordBuilderProcessor {
|
|||||||
builder.addModifiers(Modifier.PUBLIC); // builders are top level classes - can only be public or package-private
|
builder.addModifiers(Modifier.PUBLIC); // builders are top level classes - can only be public or package-private
|
||||||
}
|
}
|
||||||
// is package-private
|
// is package-private
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
builder.addModifiers(Modifier.PUBLIC);
|
builder.addModifiers(Modifier.PUBLIC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,11 +151,6 @@ class InternalRecordBuilderProcessor {
|
|||||||
.collect(Collectors.toList());
|
.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() {
|
private void addWithNestedClass() {
|
||||||
/*
|
/*
|
||||||
Adds a nested interface that adds withers similar to:
|
Adds a nested interface that adds withers similar to:
|
||||||
@@ -148,10 +166,44 @@ 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));
|
if (metaData.addClassRetainedGenerated()) {
|
||||||
|
classBuilder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||||
|
}
|
||||||
|
recordComponents.forEach(component -> addNestedGetterMethod(classBuilder, component, prefixedName(component, true)));
|
||||||
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));
|
||||||
|
if (metaData.addFunctionalMethodsToWith()) {
|
||||||
|
classBuilder.addType(buildFunctionalInterface("Function", true))
|
||||||
|
.addType(buildFunctionalInterface("Consumer", false))
|
||||||
|
.addMethod(buildFunctionalHandler("Function", "map", true))
|
||||||
|
.addMethod(buildFunctionalHandler("Consumer", "accept", false));
|
||||||
|
}
|
||||||
|
builder.addType(classBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBeanNestedClass() {
|
||||||
|
/*
|
||||||
|
Adds a nested interface that adds getters similar to:
|
||||||
|
|
||||||
|
public class MyRecordBuilder {
|
||||||
|
public interface Bean {
|
||||||
|
// getter methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var classBuilder = TypeSpec.interfaceBuilder(metaData.beanClassName())
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addJavadoc("Add getters to {@code $L}\n", recordClassType.name())
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addTypeVariables(typeVariables);
|
||||||
|
recordComponents.forEach(component -> {
|
||||||
|
if (prefixedName(component, true).equals(component.name())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addNestedGetterMethod(classBuilder, component, component.name());
|
||||||
|
add1PrefixedGetterMethod(classBuilder, component);
|
||||||
|
});
|
||||||
builder.addType(classBuilder.build());
|
builder.addType(classBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +243,7 @@ class InternalRecordBuilderProcessor {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var codeBlockBuilder = CodeBlock.builder()
|
var codeBlockBuilder = CodeBlock.builder()
|
||||||
.add("return new $L(", builderClassType.name());
|
.add("return new $L$L(", builderClassType.name(), typeVariables.isEmpty() ? "" : "<>");
|
||||||
addComponentCallsAsArguments(-1, codeBlockBuilder);
|
addComponentCallsAsArguments(-1, codeBlockBuilder);
|
||||||
codeBlockBuilder.add(");");
|
codeBlockBuilder.add(");");
|
||||||
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
|
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
|
||||||
@@ -252,6 +304,28 @@ class InternalRecordBuilderProcessor {
|
|||||||
classBuilder.addMethod(methodSpec);
|
classBuilder.addMethod(methodSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void add1PrefixedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component) {
|
||||||
|
/*
|
||||||
|
Adds a get method for the component similar to:
|
||||||
|
|
||||||
|
default MyRecord getName() {
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var codeBlockBuilder = CodeBlock.builder();
|
||||||
|
codeBlockBuilder.add("$[return $L()$];", component.name());
|
||||||
|
|
||||||
|
var methodName = prefixedName(component, true);
|
||||||
|
var methodSpec = MethodSpec.methodBuilder(methodName)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addJavadoc("Returns the value of {@code $L}\n", component.name())
|
||||||
|
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||||
|
.addCode(codeBlockBuilder.build())
|
||||||
|
.returns(component.typeName())
|
||||||
|
.build();
|
||||||
|
classBuilder.addMethod(methodSpec);
|
||||||
|
}
|
||||||
|
|
||||||
private void addComponentCallsAsArguments(int index, CodeBlock.Builder codeBlockBuilder) {
|
private void addComponentCallsAsArguments(int index, CodeBlock.Builder codeBlockBuilder) {
|
||||||
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
|
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
|
||||||
if (parameterIndex > 0) {
|
if (parameterIndex > 0) {
|
||||||
@@ -259,9 +333,9 @@ class InternalRecordBuilderProcessor {
|
|||||||
}
|
}
|
||||||
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
|
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||||
if (parameterIndex == index) {
|
if (parameterIndex == index) {
|
||||||
collectionBuilderUtils.add(codeBlockBuilder, parameterComponent);
|
collectionBuilderUtils.addShimCall(codeBlockBuilder, parameterComponent);
|
||||||
} else {
|
} else {
|
||||||
codeBlockBuilder.add("$L()", parameterComponent.name());
|
codeBlockBuilder.add("$L()", prefixedName(parameterComponent, true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -315,8 +389,10 @@ class InternalRecordBuilderProcessor {
|
|||||||
private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) {
|
private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) {
|
||||||
if (metaData.interpretNotNulls()) {
|
if (metaData.interpretNotNulls()) {
|
||||||
var component = recordComponents.get(index);
|
var component = recordComponents.get(index);
|
||||||
if (!component.typeName().isPrimitive() && isNullAnnotated(component)) {
|
if (!collectionBuilderUtils.isImmutableCollection(component)) {
|
||||||
builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required");
|
if (!component.typeName().isPrimitive() && isNullAnnotated(component)) {
|
||||||
|
builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,8 +417,7 @@ class InternalRecordBuilderProcessor {
|
|||||||
.addAnnotation(generatedRecordBuilderAnnotation);
|
.addAnnotation(generatedRecordBuilderAnnotation);
|
||||||
recordComponents.forEach(component -> {
|
recordComponents.forEach(component -> {
|
||||||
constructorBuilder.addParameter(component.typeName(), component.name());
|
constructorBuilder.addParameter(component.typeName(), component.name());
|
||||||
var codeBuilder = CodeBlock.builder().add("this.$L = $L", component.name(), component.name());
|
constructorBuilder.addStatement("this.$L = $L", component.name(), component.name());
|
||||||
constructorBuilder.addStatement(codeBuilder.build());
|
|
||||||
});
|
});
|
||||||
builder.addMethod(constructorBuilder.build());
|
builder.addMethod(constructorBuilder.build());
|
||||||
}
|
}
|
||||||
@@ -417,7 +492,12 @@ class InternalRecordBuilderProcessor {
|
|||||||
*/
|
*/
|
||||||
var codeBuilder = CodeBlock.builder();
|
var codeBuilder = CodeBlock.builder();
|
||||||
codeBuilder.add("return (this == o) || (");
|
codeBuilder.add("return (this == o) || (");
|
||||||
codeBuilder.add("(o instanceof $L $L)", builderClassType.name(), uniqueVarName);
|
if (typeVariables.isEmpty()) {
|
||||||
|
codeBuilder.add("(o instanceof $L $L)", builderClassType.name(), uniqueVarName);
|
||||||
|
} else {
|
||||||
|
String wildcardList = typeVariables.stream().map(__ -> "?").collect(Collectors.joining(","));
|
||||||
|
codeBuilder.add("(o instanceof $L<$L> $L)", builderClassType.name(), wildcardList, uniqueVarName);
|
||||||
|
}
|
||||||
recordComponents.forEach(recordComponent -> {
|
recordComponents.forEach(recordComponent -> {
|
||||||
String name = recordComponent.name();
|
String name = recordComponent.name();
|
||||||
if (recordComponent.typeName().isPrimitive()) {
|
if (recordComponent.typeName().isPrimitive()) {
|
||||||
@@ -464,6 +544,16 @@ class InternalRecordBuilderProcessor {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var codeBuilder = CodeBlock.builder();
|
var codeBuilder = CodeBlock.builder();
|
||||||
|
|
||||||
|
IntStream.range(0, recordComponents.size()).forEach(index -> {
|
||||||
|
var recordComponent = recordComponents.get(index);
|
||||||
|
if (collectionBuilderUtils.isImmutableCollection(recordComponent)) {
|
||||||
|
codeBuilder.add("$[$L = ", recordComponent.name());
|
||||||
|
collectionBuilderUtils.addShimCall(codeBuilder, recordComponents.get(index));
|
||||||
|
codeBuilder.add(";\n$]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
addNullCheckCodeBlock(codeBuilder);
|
addNullCheckCodeBlock(codeBuilder);
|
||||||
codeBuilder.add("$[return ");
|
codeBuilder.add("$[return ");
|
||||||
if (metaData.useValidationApi()) {
|
if (metaData.useValidationApi()) {
|
||||||
@@ -474,7 +564,7 @@ class InternalRecordBuilderProcessor {
|
|||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
codeBuilder.add(", ");
|
codeBuilder.add(", ");
|
||||||
}
|
}
|
||||||
collectionBuilderUtils.add(codeBuilder, recordComponents.get(index));
|
codeBuilder.add("$L", recordComponents.get(index).name());
|
||||||
});
|
});
|
||||||
codeBuilder.add(")");
|
codeBuilder.add(")");
|
||||||
if (metaData.useValidationApi()) {
|
if (metaData.useValidationApi()) {
|
||||||
@@ -484,6 +574,89 @@ class InternalRecordBuilderProcessor {
|
|||||||
return codeBuilder.build();
|
return codeBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TypeName buildWithTypeName()
|
||||||
|
{
|
||||||
|
ClassName rawTypeName = ClassName.get(packageName, builderClassType.name() + "." + metaData.withClassName());
|
||||||
|
if (typeVariables.isEmpty()) {
|
||||||
|
return rawTypeName;
|
||||||
|
}
|
||||||
|
return ParameterizedTypeName.get(rawTypeName, typeVariables.toArray(new TypeName[]{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFromWithClass() {
|
||||||
|
/*
|
||||||
|
Adds static private class that implements/proxies the Wither
|
||||||
|
|
||||||
|
private static final class _FromWith implements MyRecordBuilder.With {
|
||||||
|
private final MyRecord from;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String p1() {
|
||||||
|
return from.p1();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String p2() {
|
||||||
|
return from.p2();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fromWithClassBuilder = TypeSpec.classBuilder(metaData.fromWithClassName())
|
||||||
|
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addTypeVariables(typeVariables)
|
||||||
|
.addSuperinterface(buildWithTypeName());
|
||||||
|
if (metaData.addClassRetainedGenerated()) {
|
||||||
|
fromWithClassBuilder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromWithClassBuilder.addField(recordClassType.typeName(), "from", Modifier.PRIVATE, Modifier.FINAL);
|
||||||
|
MethodSpec constructorSpec = MethodSpec.constructorBuilder()
|
||||||
|
.addParameter(recordClassType.typeName(), "from")
|
||||||
|
.addStatement("this.from = from")
|
||||||
|
.addModifiers(Modifier.PRIVATE)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.build();
|
||||||
|
fromWithClassBuilder.addMethod(constructorSpec);
|
||||||
|
|
||||||
|
IntStream.range(0, recordComponents.size()).forEach(index -> {
|
||||||
|
var component = recordComponents.get(index);
|
||||||
|
MethodSpec methodSpec = MethodSpec.methodBuilder(prefixedName(component, true))
|
||||||
|
.returns(component.typeName())
|
||||||
|
.addAnnotation(Override.class)
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addStatement("return from.$L()", component.name())
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.build();
|
||||||
|
fromWithClassBuilder.addMethod(methodSpec);
|
||||||
|
});
|
||||||
|
this.builder.addType(fromWithClassBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStaticFromWithMethod() {
|
||||||
|
/*
|
||||||
|
Adds static method that returns a "with"er view of an existing record.
|
||||||
|
|
||||||
|
public static With from(MyRecord from) {
|
||||||
|
return new _FromWith(from);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
addFromWithClass();
|
||||||
|
|
||||||
|
var methodSpec = MethodSpec.methodBuilder(metaData.fromMethodName())
|
||||||
|
.addJavadoc("Return a \"with\"er for an existing record instance\n")
|
||||||
|
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addTypeVariables(typeVariables)
|
||||||
|
.addParameter(recordClassType.typeName(), metaData.fromMethodName())
|
||||||
|
.returns(buildWithTypeName())
|
||||||
|
.addStatement("return new $L$L(from)", metaData.fromWithClassName(), typeVariables.isEmpty() ? "" : "<>")
|
||||||
|
.build();
|
||||||
|
builder.addMethod(methodSpec);
|
||||||
|
}
|
||||||
|
|
||||||
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:
|
||||||
@@ -537,10 +710,8 @@ class InternalRecordBuilderProcessor {
|
|||||||
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
|
||||||
|
|
||||||
public static Stream<Map.Entry<String, Object>> stream(MyRecord record) {
|
public static Stream<Map.Entry<String, Object>> stream(MyRecord record) {
|
||||||
return Stream.of(
|
return Stream.of(new AbstractMap.SimpleImmutableEntry<>("p1", record.p1()),
|
||||||
new AbstractMap.SimpleEntry<>("p1", record.p1()),
|
new AbstractMap.SimpleImmutableEntry<>("p2", record.p2()));
|
||||||
new AbstractMap.SimpleEntry<>("p2", record.p2())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var codeBuilder = CodeBlock.builder().add("return $T.of(", Stream.class);
|
var codeBuilder = CodeBlock.builder().add("return $T.of(", Stream.class);
|
||||||
@@ -549,7 +720,7 @@ class InternalRecordBuilderProcessor {
|
|||||||
codeBuilder.add(",\n ");
|
codeBuilder.add(",\n ");
|
||||||
}
|
}
|
||||||
var name = recordComponents.get(index).name();
|
var name = recordComponents.get(index).name();
|
||||||
codeBuilder.add("new $T<>($S, record.$L())", AbstractMap.SimpleEntry.class, name, name);
|
codeBuilder.add("new $T<>($S, record.$L())", AbstractMap.SimpleImmutableEntry.class, name, name);
|
||||||
});
|
});
|
||||||
codeBuilder.add(")");
|
codeBuilder.add(")");
|
||||||
var mapEntryTypeVariables = ParameterizedTypeName.get(Map.Entry.class, String.class, Object.class);
|
var mapEntryTypeVariables = ParameterizedTypeName.get(Map.Entry.class, String.class, Object.class);
|
||||||
@@ -574,43 +745,29 @@ class InternalRecordBuilderProcessor {
|
|||||||
*/
|
*/
|
||||||
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
|
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
|
||||||
if (metaData.emptyDefaultForOptional()) {
|
if (metaData.emptyDefaultForOptional()) {
|
||||||
TypeName thisOptionalType = null;
|
Optional<OptionalType> thisOptionalType = OptionalType.fromClassType(component);
|
||||||
if (isOptional(component)) {
|
if (thisOptionalType.isPresent()) {
|
||||||
thisOptionalType = optionalType;
|
var codeBlock = CodeBlock.builder()
|
||||||
} else if (component.typeName().equals(optionalIntType)) {
|
.add("$T.empty()", thisOptionalType.get().typeName())
|
||||||
thisOptionalType = optionalIntType;
|
.build();
|
||||||
} 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);
|
fieldSpecBuilder.initializer(codeBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.addField(fieldSpecBuilder.build());
|
builder.addField(fieldSpecBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOptional(ClassType component) {
|
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component, String methodName) {
|
||||||
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:
|
For a single record component, add a getter similar to:
|
||||||
|
|
||||||
T p();
|
T p();
|
||||||
*/
|
*/
|
||||||
var methodSpecBuilder = MethodSpec.methodBuilder(component.name())
|
var methodSpecBuilder = MethodSpec.methodBuilder(methodName)
|
||||||
.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.ABSTRACT, Modifier.PUBLIC)
|
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.returns(component.typeName());
|
.returns(component.typeName());
|
||||||
addAccessorAnnotations(component, methodSpecBuilder);
|
addAccessorAnnotations(component, methodSpecBuilder, this::filterOutValid);
|
||||||
classBuilder.addMethod(methodSpecBuilder.build());
|
classBuilder.addMethod(methodSpecBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,6 +775,10 @@ class InternalRecordBuilderProcessor {
|
|||||||
return !annotationSpec.type.equals(overrideType);
|
return !annotationSpec.type.equals(overrideType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean filterOutValid(AnnotationSpec annotationSpec) {
|
||||||
|
return !annotationSpec.type.equals(validType);
|
||||||
|
}
|
||||||
|
|
||||||
private void addConstructorAnnotations(RecordClassType component, ParameterSpec.Builder parameterSpecBuilder) {
|
private void addConstructorAnnotations(RecordClassType component, ParameterSpec.Builder parameterSpecBuilder) {
|
||||||
if (metaData.inheritComponentAnnotations()) {
|
if (metaData.inheritComponentAnnotations()) {
|
||||||
component.getCanonicalConstructorAnnotations()
|
component.getCanonicalConstructorAnnotations()
|
||||||
@@ -628,16 +789,141 @@ class InternalRecordBuilderProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAccessorAnnotations(RecordClassType component, MethodSpec.Builder methodSpecBuilder) {
|
private void addAccessorAnnotations(RecordClassType component, MethodSpec.Builder methodSpecBuilder, Predicate<AnnotationSpec> additionalFilter) {
|
||||||
if (metaData.inheritComponentAnnotations()) {
|
if (metaData.inheritComponentAnnotations()) {
|
||||||
component.getAccessorAnnotations()
|
component.getAccessorAnnotations()
|
||||||
.stream()
|
.stream()
|
||||||
.map(AnnotationSpec::get)
|
.map(AnnotationSpec::get)
|
||||||
.filter(this::filterOutOverride)
|
.filter(this::filterOutOverride)
|
||||||
|
.filter(additionalFilter)
|
||||||
.forEach(methodSpecBuilder::addAnnotation);
|
.forEach(methodSpecBuilder::addAnnotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String capitalize(String s) {
|
||||||
|
return (s.length() < 2) ? s.toUpperCase(Locale.ROOT) : (Character.toUpperCase(s.charAt(0)) + s.substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add1CollectionBuilders(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||||
|
if (collectionBuilderUtils.isList(component) || collectionBuilderUtils.isSet(component)) {
|
||||||
|
add1ListBuilder(meta, component);
|
||||||
|
} else if (collectionBuilderUtils.isMap(component)) {
|
||||||
|
add1MapBuilder(meta, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add1MapBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||||
|
/*
|
||||||
|
For a single map record component, add a methods similar to:
|
||||||
|
|
||||||
|
public T addP(K key, V value) {
|
||||||
|
this.p = __ensureMapMutable(p);
|
||||||
|
this.p.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T addP(Stream<? extends Map.Entry<K, V> i) {
|
||||||
|
this.p = __ensureMapMutable(p);
|
||||||
|
i.forEach(this.p::put);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T addP(Iterable<? extends Map.Entry<K, V> i) {
|
||||||
|
this.p = __ensureMapMutable(p);
|
||||||
|
i.forEach(this.p::put);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
for (var i = 0; i < 3; ++i) {
|
||||||
|
var codeBlockBuilder = CodeBlock.builder();
|
||||||
|
if (collectionBuilderUtils.isImmutableCollection(component)) {
|
||||||
|
codeBlockBuilder
|
||||||
|
.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.mutableMakerName(component), component.name());
|
||||||
|
} else {
|
||||||
|
codeBlockBuilder
|
||||||
|
.beginControlFlow("if (this.$L == null)", component.name())
|
||||||
|
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||||
|
.endControlFlow();
|
||||||
|
}
|
||||||
|
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||||
|
.addJavadoc("Add to the internally allocated {@code HashMap} for {@code $L}\n", component.name())
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.returns(builderClassType.typeName());
|
||||||
|
if (i == 0) {
|
||||||
|
methodSpecBuilder.addParameter(meta.typeArguments().get(0), "key");
|
||||||
|
methodSpecBuilder.addParameter(meta.typeArguments().get(1), "value");
|
||||||
|
codeBlockBuilder.addStatement("this.$L.put(key, value)", component.name());
|
||||||
|
} else {
|
||||||
|
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
|
||||||
|
var entryType = ParameterizedTypeName.get(ClassName.get(Map.Entry.class), WildcardTypeName.subtypeOf(meta.typeArguments().get(0)), WildcardTypeName.subtypeOf(meta.typeArguments().get(1)));
|
||||||
|
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(parameterClass, WildcardTypeName.subtypeOf(entryType));
|
||||||
|
methodSpecBuilder.addParameter(parameterizedTypeName, "i");
|
||||||
|
codeBlockBuilder.addStatement("i.forEach(entry -> this.$L.put(entry.getKey(), entry.getValue()))", component.name());
|
||||||
|
}
|
||||||
|
codeBlockBuilder.addStatement("return this");
|
||||||
|
methodSpecBuilder.addCode(codeBlockBuilder.build());
|
||||||
|
builder.addMethod(methodSpecBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add1ListBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||||
|
/*
|
||||||
|
For a single list or set record component, add methods similar to:
|
||||||
|
|
||||||
|
public T addP(I i) {
|
||||||
|
this.list = __ensureListMutable(list);
|
||||||
|
this.p.add(i);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T addP(Stream<? extends I> i) {
|
||||||
|
this.list = __ensureListMutable(list);
|
||||||
|
this.p.addAll(i);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T addP(Iterable<? extends I> i) {
|
||||||
|
this.list = __ensureListMutable(list);
|
||||||
|
this.p.addAll(i);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
for (var i = 0; i < 3; ++i) {
|
||||||
|
var addClockBlock = CodeBlock.builder();
|
||||||
|
TypeName parameter;
|
||||||
|
if (i == 0) {
|
||||||
|
addClockBlock.addStatement("this.$L.add(i)", component.name());
|
||||||
|
parameter = meta.typeArguments().get(0);
|
||||||
|
} else {
|
||||||
|
addClockBlock.addStatement("i.forEach(this.$L::add)", component.name());
|
||||||
|
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
|
||||||
|
parameter = ParameterizedTypeName.get(parameterClass, WildcardTypeName.subtypeOf(meta.typeArguments().get(0)));
|
||||||
|
}
|
||||||
|
var codeBlockBuilder = CodeBlock.builder();
|
||||||
|
if (collectionBuilderUtils.isImmutableCollection(component)) {
|
||||||
|
codeBlockBuilder
|
||||||
|
.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.mutableMakerName(component), component.name());
|
||||||
|
} else {
|
||||||
|
codeBlockBuilder
|
||||||
|
.beginControlFlow("if (this.$L == null)", component.name())
|
||||||
|
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||||
|
.endControlFlow();
|
||||||
|
}
|
||||||
|
codeBlockBuilder
|
||||||
|
.add(addClockBlock.build())
|
||||||
|
.addStatement("return this");
|
||||||
|
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||||
|
.addJavadoc("Add to the internally allocated {@code $L} for {@code $L}\n", meta.singleItemCollectionClass().getSimpleName(), component.name())
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.returns(builderClassType.typeName())
|
||||||
|
.addParameter(parameter, "i")
|
||||||
|
.addCode(codeBlockBuilder.build());
|
||||||
|
builder.addMethod(methodSpecBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void add1GetterMethod(RecordClassType component) {
|
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:
|
||||||
@@ -646,13 +932,13 @@ class InternalRecordBuilderProcessor {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var methodSpecBuilder = MethodSpec.methodBuilder(component.name())
|
var methodSpecBuilder = MethodSpec.methodBuilder(prefixedName(component, true))
|
||||||
.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());
|
||||||
addAccessorAnnotations(component, methodSpecBuilder);
|
addAccessorAnnotations(component, methodSpecBuilder, __ -> true);
|
||||||
builder.addMethod(methodSpecBuilder.build());
|
builder.addMethod(methodSpecBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,19 +951,140 @@ class InternalRecordBuilderProcessor {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
var methodSpec = MethodSpec.methodBuilder(prefixedName(component, false))
|
||||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
|
||||||
|
|
||||||
var methodSpec = MethodSpec.methodBuilder(component.name())
|
|
||||||
.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
.addParameter(parameterSpecBuilder.build())
|
.returns(builderClassType.typeName());
|
||||||
.returns(builderClassType.typeName())
|
|
||||||
.addStatement("this.$L = $L", component.name(), component.name())
|
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD_FOR_SETTER);
|
||||||
.addStatement("return this")
|
var parameterSpecBuilder = collectionMetaData.map(meta -> {
|
||||||
|
CodeBlock.Builder codeSpec = CodeBlock.builder();
|
||||||
|
codeSpec.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.shimName(component), component.name());
|
||||||
|
methodSpec.addJavadoc("Re-create the internally allocated {@code $T} for {@code $L} by copying the argument\n", component.typeName(), component.name())
|
||||||
|
.addCode(codeSpec.build());
|
||||||
|
return ParameterSpec.builder(meta.wildType(), component.name());
|
||||||
|
}).orElseGet(() -> {
|
||||||
|
methodSpec.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||||
|
.addStatement("this.$L = $L", component.name(), component.name());
|
||||||
|
return ParameterSpec.builder(component.typeName(), component.name());
|
||||||
|
});
|
||||||
|
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||||
|
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
|
||||||
|
builder.addMethod(methodSpec.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add1ConcreteOptionalSetterMethod(RecordClassType component) {
|
||||||
|
/*
|
||||||
|
For a single optional record component, add a concrete setter similar to:
|
||||||
|
|
||||||
|
public MyRecordBuilder p(T p) {
|
||||||
|
this.p = p;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var optionalType = OptionalType.fromClassType(component);
|
||||||
|
if (optionalType.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var type = optionalType.get();
|
||||||
|
var methodSpec = MethodSpec.methodBuilder(prefixedName(component, false))
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.returns(builderClassType.typeName());
|
||||||
|
|
||||||
|
var parameterSpecBuilder = ParameterSpec.builder(type.valueType(), component.name());
|
||||||
|
methodSpec.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||||
|
.addStatement(getOptionalStatement(type), component.name(), type.typeName(), component.name());
|
||||||
|
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||||
|
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
|
||||||
|
builder.addMethod(methodSpec.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOptionalStatement(OptionalType type) {
|
||||||
|
|
||||||
|
if(type.isOptional()) {
|
||||||
|
return "this.$L = $T.ofNullable($L)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "this.$L = $T.of($L)";
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TypeVariableName> typeVariablesWithReturn() {
|
||||||
|
var variables = new ArrayList<TypeVariableName>();
|
||||||
|
variables.add(rType);
|
||||||
|
variables.addAll(typeVariables);
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodSpec buildFunctionalHandler(String className, String methodName, boolean isMap) {
|
||||||
|
/*
|
||||||
|
Build a Functional handler ala:
|
||||||
|
|
||||||
|
default <R> R map(Function<R, T> proc) {
|
||||||
|
return proc.apply(p());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var localTypeVariables = isMap ? typeVariablesWithReturn() : typeVariables;
|
||||||
|
var typeName = localTypeVariables.isEmpty() ? ClassName.get("", className) : ParameterizedTypeName.get(ClassName.get("", className), localTypeVariables.toArray(TypeName[]::new));
|
||||||
|
var methodBuilder = MethodSpec.methodBuilder(methodName)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
|
||||||
|
.addParameter(typeName, "proc");
|
||||||
|
var codeBlockBuilder = CodeBlock.builder();
|
||||||
|
if (isMap) {
|
||||||
|
methodBuilder.addJavadoc("Map record components into a new object");
|
||||||
|
methodBuilder.addTypeVariable(rType);
|
||||||
|
methodBuilder.returns(rType);
|
||||||
|
codeBlockBuilder.add("return ");
|
||||||
|
} else {
|
||||||
|
methodBuilder.addJavadoc("Perform an operation on record components");
|
||||||
|
}
|
||||||
|
codeBlockBuilder.add("proc.apply(");
|
||||||
|
addComponentCallsAsArguments(-1, codeBlockBuilder);
|
||||||
|
codeBlockBuilder.add(");");
|
||||||
|
methodBuilder.addCode(codeBlockBuilder.build());
|
||||||
|
return methodBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeSpec buildFunctionalInterface(String className, boolean isMap) {
|
||||||
|
/*
|
||||||
|
Build a Functional interface ala:
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface Function<R, T> {
|
||||||
|
R apply(T a);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var localTypeVariables = isMap ? typeVariablesWithReturn() : typeVariables;
|
||||||
|
var methodBuilder = MethodSpec.methodBuilder("apply").addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
|
||||||
|
recordComponents.forEach(component -> {
|
||||||
|
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||||
|
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||||
|
methodBuilder.addParameter(parameterSpecBuilder.build());
|
||||||
|
});
|
||||||
|
if (isMap) {
|
||||||
|
methodBuilder.returns(rType);
|
||||||
|
}
|
||||||
|
return TypeSpec.interfaceBuilder(className)
|
||||||
|
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||||
|
.addAnnotation(FunctionalInterface.class)
|
||||||
|
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||||
|
.addTypeVariables(localTypeVariables)
|
||||||
|
.addMethod(methodBuilder.build())
|
||||||
.build();
|
.build();
|
||||||
builder.addMethod(methodSpec);
|
}
|
||||||
|
|
||||||
|
private String prefixedName(RecordClassType component, boolean isGetter) {
|
||||||
|
BiFunction<String, String, String> prefixer = (p, s) -> p.isEmpty()
|
||||||
|
? s : p + Character.toUpperCase(s.charAt(0)) + s.substring(1);
|
||||||
|
boolean isBool = component.typeName().toString().toLowerCase(Locale.ROOT).equals("boolean");
|
||||||
|
if (isGetter) {
|
||||||
|
if (isBool) {
|
||||||
|
return prefixer.apply(metaData.booleanPrefix(), component.name());
|
||||||
|
}
|
||||||
|
return prefixer.apply(metaData.getterPrefix(), component.name());
|
||||||
|
}
|
||||||
|
return prefixer.apply(metaData.setterPrefix(), component.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.processor;
|
package io.soabase.recordbuilder.processor;
|
||||||
|
|
||||||
import com.squareup.javapoet.ClassName;
|
import com.squareup.javapoet.*;
|
||||||
import com.squareup.javapoet.MethodSpec;
|
|
||||||
import com.squareup.javapoet.ParameterSpec;
|
|
||||||
import com.squareup.javapoet.TypeSpec;
|
|
||||||
import com.squareup.javapoet.TypeVariableName;
|
|
||||||
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
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;
|
||||||
@@ -29,19 +26,14 @@ import javax.lang.model.element.Modifier;
|
|||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.type.TypeKind;
|
import javax.lang.model.type.TypeKind;
|
||||||
import javax.tools.Diagnostic;
|
import javax.tools.Diagnostic;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordInterfaceAnnotation;
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordInterfaceAnnotation;
|
||||||
|
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||||
|
|
||||||
class InternalRecordInterfaceProcessor {
|
class InternalRecordInterfaceProcessor {
|
||||||
private final ProcessingEnvironment processingEnv;
|
private final ProcessingEnvironment processingEnv;
|
||||||
@@ -56,9 +48,10 @@ class InternalRecordInterfaceProcessor {
|
|||||||
|
|
||||||
private static final Set<String> javaBeanPrefixes = Set.of("get", "is");
|
private static final Set<String> javaBeanPrefixes = Set.of("get", "is");
|
||||||
|
|
||||||
private record Component(ExecutableElement element, Optional<String> alternateName){}
|
private record Component(ExecutableElement element, Optional<String> alternateName) {
|
||||||
|
}
|
||||||
|
|
||||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt, boolean fromTemplate) {
|
||||||
this.processingEnv = processingEnv;
|
this.processingEnv = processingEnv;
|
||||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
||||||
recordComponents = getRecordComponents(iface);
|
recordComponents = getRecordComponents(iface);
|
||||||
@@ -76,11 +69,22 @@ class InternalRecordInterfaceProcessor {
|
|||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||||
.addTypeVariables(typeVariables);
|
.addTypeVariables(typeVariables);
|
||||||
|
if (metaData.addClassRetainedGenerated()) {
|
||||||
|
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
if (addRecordBuilder) {
|
if (addRecordBuilder) {
|
||||||
ClassType builderClassType = ElementUtils.getClassType(packageName, getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "." + metaData.withClassName(), iface.getTypeParameters());
|
ClassType builderClassType = ElementUtils.getClassType(packageName, getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "." + metaData.withClassName(), iface.getTypeParameters());
|
||||||
builder.addAnnotation(RecordBuilder.class);
|
builder.addAnnotation(RecordBuilder.class);
|
||||||
builder.addSuperinterface(builderClassType.typeName());
|
builder.addSuperinterface(builderClassType.typeName());
|
||||||
|
if (fromTemplate) {
|
||||||
|
builder.addAnnotation(AnnotationSpec.get(metaData));
|
||||||
|
} else {
|
||||||
|
var options = iface.getAnnotation(RecordBuilder.Options.class);
|
||||||
|
if (options != null) {
|
||||||
|
builder.addAnnotation(AnnotationSpec.get(options));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alternateMethods = buildAlternateMethods(recordComponents);
|
alternateMethods = buildAlternateMethods(recordComponents);
|
||||||
@@ -88,8 +92,7 @@ class InternalRecordInterfaceProcessor {
|
|||||||
recordType = builder.build();
|
recordType = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isValid()
|
boolean isValid() {
|
||||||
{
|
|
||||||
return !recordComponents.isEmpty();
|
return !recordComponents.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,8 +108,7 @@ class InternalRecordInterfaceProcessor {
|
|||||||
return recordClassType;
|
return recordClassType;
|
||||||
}
|
}
|
||||||
|
|
||||||
String toRecord(String classSource)
|
String toRecord(String classSource) {
|
||||||
{
|
|
||||||
// javapoet does yet support records - so a class was created and we can reshape it
|
// javapoet does yet support records - so a class was created and we can reshape it
|
||||||
// The class will look something like this:
|
// The class will look something like this:
|
||||||
/*
|
/*
|
||||||
@@ -139,8 +141,7 @@ class InternalRecordInterfaceProcessor {
|
|||||||
return fixedRecord.toString();
|
return fixedRecord.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodSpec generateArgumentList()
|
private MethodSpec generateArgumentList() {
|
||||||
{
|
|
||||||
MethodSpec.Builder builder = MethodSpec.methodBuilder(FAKE_METHOD_NAME);
|
MethodSpec.Builder builder = MethodSpec.methodBuilder(FAKE_METHOD_NAME);
|
||||||
recordComponents.forEach(component -> {
|
recordComponents.forEach(component -> {
|
||||||
String name = component.alternateName.orElseGet(() -> component.element.getSimpleName().toString());
|
String name = component.alternateName.orElseGet(() -> component.element.getSimpleName().toString());
|
||||||
@@ -153,18 +154,18 @@ class InternalRecordInterfaceProcessor {
|
|||||||
|
|
||||||
private List<String> buildAlternateMethods(List<Component> recordComponents) {
|
private List<String> buildAlternateMethods(List<Component> recordComponents) {
|
||||||
return recordComponents.stream()
|
return recordComponents.stream()
|
||||||
.filter(component -> component.alternateName.isPresent())
|
.filter(component -> component.alternateName.isPresent())
|
||||||
.map(component -> {
|
.map(component -> {
|
||||||
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
|
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
|
||||||
.addAnnotation(Override.class)
|
.addAnnotation(Override.class)
|
||||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||||
.returns(ClassName.get(component.element.getReturnType()))
|
.returns(ClassName.get(component.element.getReturnType()))
|
||||||
.addModifiers(Modifier.PUBLIC)
|
.addModifiers(Modifier.PUBLIC)
|
||||||
.addCode("return $L();", component.alternateName.get())
|
.addCode("return $L();", component.alternateName.get())
|
||||||
.build();
|
.build();
|
||||||
return method.toString();
|
return method.toString();
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Component> getRecordComponents(TypeElement iface) {
|
private List<Component> getRecordComponents(TypeElement iface) {
|
||||||
@@ -180,8 +181,8 @@ class InternalRecordInterfaceProcessor {
|
|||||||
}
|
}
|
||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
private static class IllegalInterface extends RuntimeException
|
|
||||||
{
|
private static class IllegalInterface extends RuntimeException {
|
||||||
public IllegalInterface(String message) {
|
public IllegalInterface(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
@@ -219,14 +220,13 @@ class InternalRecordInterfaceProcessor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<String> stripBeanPrefix(String name)
|
private Optional<String> stripBeanPrefix(String name) {
|
||||||
{
|
|
||||||
return javaBeanPrefixes.stream()
|
return javaBeanPrefixes.stream()
|
||||||
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
|
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(prefix -> {
|
.map(prefix -> {
|
||||||
var stripped = name.substring(prefix.length());
|
var stripped = name.substring(prefix.length());
|
||||||
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
|
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* 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 java.util.Optional;
|
||||||
|
import java.util.OptionalDouble;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
|
import com.squareup.javapoet.ParameterizedTypeName;
|
||||||
|
import com.squareup.javapoet.TypeName;
|
||||||
|
|
||||||
|
public record OptionalType(TypeName typeName, TypeName valueType) {
|
||||||
|
|
||||||
|
private static final TypeName optionalType = TypeName.get(Optional.class);
|
||||||
|
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
|
||||||
|
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
|
||||||
|
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
|
||||||
|
|
||||||
|
private static boolean isOptional(ClassType component) {
|
||||||
|
if (component.typeName().equals(optionalType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)
|
||||||
|
&& parameterizedTypeName.rawType.equals(optionalType);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<OptionalType> fromClassType(final ClassType component) {
|
||||||
|
if (isOptional(component)) {
|
||||||
|
if (!(component.typeName() instanceof ParameterizedTypeName parameterizedType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalType, TypeName.get(Object.class)));
|
||||||
|
}
|
||||||
|
final TypeName containingType = parameterizedType.typeArguments.isEmpty()
|
||||||
|
? TypeName.get(Object.class)
|
||||||
|
: parameterizedType.typeArguments.get(0);
|
||||||
|
return Optional.of(new OptionalType(optionalType, containingType));
|
||||||
|
}
|
||||||
|
if (component.typeName().equals(optionalIntType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalIntType, TypeName.get(int.class)));
|
||||||
|
}
|
||||||
|
if (component.typeName().equals(optionalLongType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalLongType, TypeName.get(long.class)));
|
||||||
|
}
|
||||||
|
if (component.typeName().equals(optionalDoubleType)) {
|
||||||
|
return Optional.of(new OptionalType(optionalDoubleType, TypeName.get(double.class)));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOptional() {
|
||||||
|
return typeName.equals(optionalType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ 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.RecordBuilderGenerated;
|
||||||
import io.soabase.recordbuilder.core.RecordInterface;
|
import io.soabase.recordbuilder.core.RecordInterface;
|
||||||
|
|
||||||
import javax.annotation.processing.AbstractProcessor;
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
@@ -29,10 +30,8 @@ import javax.lang.model.SourceVersion;
|
|||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
import javax.lang.model.element.PackageElement;
|
import javax.lang.model.element.PackageElement;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.type.TypeMirror;
|
|
||||||
import javax.tools.Diagnostic;
|
import javax.tools.Diagnostic;
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -40,8 +39,7 @@ import java.util.Set;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class RecordBuilderProcessor
|
public class RecordBuilderProcessor
|
||||||
extends AbstractProcessor
|
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();
|
||||||
@@ -49,23 +47,21 @@ public class RecordBuilderProcessor
|
|||||||
|
|
||||||
static final AnnotationSpec generatedRecordBuilderAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordBuilder.class.getName()).build();
|
static final AnnotationSpec generatedRecordBuilderAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordBuilder.class.getName()).build();
|
||||||
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordInterface.class.getName()).build();
|
static final AnnotationSpec generatedRecordInterfaceAnnotation = AnnotationSpec.builder(Generated.class).addMember("value", "$S", RecordInterface.class.getName()).build();
|
||||||
|
static final AnnotationSpec recordBuilderGeneratedAnnotation = AnnotationSpec.builder(RecordBuilderGenerated.class).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 false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getSupportedAnnotationTypes()
|
public Set<String> getSupportedAnnotationTypes() {
|
||||||
{
|
|
||||||
return Set.of("*");
|
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()
|
||||||
@@ -73,59 +69,51 @@ public class RecordBuilderProcessor
|
|||||||
return SourceVersion.latest();
|
return SourceVersion.latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void process(TypeElement annotation, Element element)
|
private void process(TypeElement annotation, Element element) {
|
||||||
{
|
|
||||||
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());
|
var typeElement = (TypeElement) element;
|
||||||
processRecordBuilder((TypeElement) element, metaData, Optional.empty());
|
processRecordBuilder(typeElement, getMetaData(typeElement), Optional.empty());
|
||||||
}
|
} else if (annotationClass.equals(RECORD_INTERFACE)) {
|
||||||
else if (annotationClass.equals(RECORD_INTERFACE)) {
|
var typeElement = (TypeElement) element;
|
||||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(), getMetaData(typeElement), Optional.empty(), false);
|
||||||
processRecordInterface((TypeElement) element, element.getAnnotation(RecordInterface.class).addRecordBuilder(), metaData, Optional.empty());
|
} else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||||
}
|
processIncludes(element, getMetaData(element), annotationClass);
|
||||||
else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
|
||||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
|
||||||
processIncludes(element, metaData, annotationClass);
|
|
||||||
} else {
|
} else {
|
||||||
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
|
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
|
||||||
if (recordBuilderTemplate != null) {
|
if (recordBuilderTemplate != null) {
|
||||||
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
if (recordBuilderTemplate.asRecordInterface()) {
|
||||||
|
processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(), Optional.empty(), true);
|
||||||
|
} else {
|
||||||
|
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass)
|
private RecordBuilder.Options getMetaData(Element element) {
|
||||||
{
|
var recordSpecificMetaData = element.getAnnotation(RecordBuilder.Options.class);
|
||||||
|
return (recordSpecificMetaData != null) ? recordSpecificMetaData : RecordBuilderOptions.build(processingEnv.getOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass) {
|
||||||
|
var isRecordBuilderInclude = annotationClass.equals(RECORD_BUILDER_INCLUDE);
|
||||||
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 includeHelper = new IncludeHelper(processingEnv, element, annotationMirrorOpt.get(), isRecordBuilderInclude);
|
||||||
var values = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirrorOpt.get());
|
if (includeHelper.isValid()) {
|
||||||
var classes = ElementUtils.getAnnotationValue(values, "value");
|
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "packagePattern").orElse(null), "*");
|
||||||
if (classes.isEmpty()) {
|
for (var typeElement : includeHelper.getClassTypeElements()) {
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation value for: " + annotationClass, element);
|
var packageName = buildPackageName(packagePattern, element, typeElement);
|
||||||
}
|
if (packageName != null) {
|
||||||
else {
|
if (isRecordBuilderInclude) {
|
||||||
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(values, "packagePattern").orElse(null), "*");
|
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
||||||
var classesMirrors = ElementUtils.getClassesAttribute(classes.get());
|
} else {
|
||||||
for (TypeMirror mirror : classesMirrors) {
|
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "addRecordBuilder");
|
||||||
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(mirror);
|
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
||||||
if (typeElement == null) {
|
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName), false);
|
||||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + mirror, element);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var packageName = buildPackageName(packagePattern, element, typeElement);
|
|
||||||
if (packageName != null) {
|
|
||||||
if (annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
|
||||||
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(values, "addRecordBuilder");
|
|
||||||
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
|
||||||
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,8 +121,7 @@ public class RecordBuilderProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass)
|
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass) {
|
||||||
{
|
|
||||||
PackageElement includedClassPackage = findPackageElement(includedClass, includedClass);
|
PackageElement includedClassPackage = findPackageElement(includedClass, includedClass);
|
||||||
if (includedClassPackage == null) {
|
if (includedClassPackage == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -146,8 +133,7 @@ public class RecordBuilderProcessor
|
|||||||
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;
|
||||||
@@ -158,21 +144,19 @@ public class RecordBuilderProcessor
|
|||||||
return findPackageElement(actualElement, includedClass.getEnclosingElement());
|
return findPackageElement(actualElement, includedClass.getEnclosingElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName)
|
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName, boolean fromTemplate) {
|
||||||
{
|
|
||||||
if (!element.getKind().isInterface()) {
|
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, fromTemplate);
|
||||||
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, RecordBuilder.Options metaData, Optional<String> packageName)
|
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageName) {
|
||||||
{
|
|
||||||
// we use string based name comparison for the element kind,
|
// 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
|
||||||
@@ -185,8 +169,7 @@ public class RecordBuilderProcessor
|
|||||||
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, RecordBuilder.Options 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();
|
||||||
@@ -196,14 +179,12 @@ public class RecordBuilderProcessor
|
|||||||
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, RecordBuilder.Options metaData, Function<String, String> toRecordProc)
|
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc) {
|
||||||
{
|
|
||||||
JavaFile javaFile = javaFileBuilder(packageName, type, metaData);
|
JavaFile javaFile = javaFileBuilder(packageName, type, metaData);
|
||||||
|
|
||||||
String classSourceCode = javaFile.toString();
|
String classSourceCode = javaFile.toString();
|
||||||
@@ -217,14 +198,12 @@ public class RecordBuilderProcessor
|
|||||||
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, RecordBuilder.Options metaData)
|
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData) {
|
||||||
{
|
|
||||||
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true).indent(metaData.fileIndent());
|
var 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()) {
|
||||||
@@ -233,8 +212,7 @@ public class RecordBuilderProcessor
|
|||||||
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();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>25-java15</version>
|
<version>34</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder-core</artifactId>
|
<artifactId>record-builder-processor</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -51,31 +51,6 @@
|
|||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<annotationProcessorPaths>
|
|
||||||
<annotationProcessorPath>
|
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
|
||||||
<artifactId>record-builder-processor</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</annotationProcessorPath>
|
|
||||||
</annotationProcessorPaths>
|
|
||||||
<annotationProcessors>
|
|
||||||
<annotationProcessor>io.soabase.recordbuilder.processor.RecordBuilderProcessor</annotationProcessor>
|
|
||||||
</annotationProcessors>
|
|
||||||
<release>${jdk-version}</release>
|
|
||||||
<compilerArgs>
|
|
||||||
<arg>${enable-preview}</arg>
|
|
||||||
</compilerArgs>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-deploy-plugin</artifactId>
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
@@ -91,6 +66,48 @@
|
|||||||
<skip>true</skip>
|
<skip>true</skip>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jacoco</groupId>
|
||||||
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>default-prepare-agent</id>
|
||||||
|
<goals>
|
||||||
|
<goal>prepare-agent</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>default-report</id>
|
||||||
|
<goals>
|
||||||
|
<goal>report</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>default-check</id>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includes>
|
||||||
|
<include>io/soabase/recordbuilder/test/jacoco/*</include>
|
||||||
|
</includes>
|
||||||
|
<rules>
|
||||||
|
<rule>
|
||||||
|
<element>BUNDLE</element>
|
||||||
|
<limits>
|
||||||
|
<limit>
|
||||||
|
<counter>COMPLEXITY</counter>
|
||||||
|
<value>COVEREDRATIO</value>
|
||||||
|
<minimum>0.60</minimum>
|
||||||
|
</limit>
|
||||||
|
</limits>
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
@RecordBuilder.Options(
|
||||||
|
addSingleItemCollectionBuilders = true,
|
||||||
|
useImmutableCollections = true,
|
||||||
|
mutableListClassName = "PersonalizedMutableList"
|
||||||
|
)
|
||||||
|
public record CollectionCopying<T>(List<String> list, Set<T> set, Map<Instant, T> map, Collection<T> collection,
|
||||||
|
int count) implements CollectionCopyingBuilder.With<T> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
import io.soabase.recordbuilder.core.RecordInterface;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RecordInterface
|
||||||
|
@RecordBuilder.Options(useImmutableCollections = true)
|
||||||
|
public interface CollectionInterface<T, X extends Point> {
|
||||||
|
List<T> l();
|
||||||
|
|
||||||
|
Set<T> s();
|
||||||
|
|
||||||
|
Map<T, X> m();
|
||||||
|
|
||||||
|
Collection<X> c();
|
||||||
|
}
|
||||||
@@ -17,12 +17,20 @@ package io.soabase.recordbuilder.test;
|
|||||||
|
|
||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@RecordBuilder
|
@RecordBuilder
|
||||||
@RecordBuilder.Options(useImmutableCollections = true)
|
@RecordBuilder.Options(useImmutableCollections = true, addFunctionalMethodsToWith = 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> {
|
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> {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var r = new CollectionRecord<>(List.of("hey"), Set.of("there"), Map.of("one", new Point(10, 20)), Set.of(new Point(30, 40)));
|
||||||
|
Instant now = r.map((l1, s1, m1, c1) -> Instant.now());
|
||||||
|
r.accept((l1, s1, m1, c1) -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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.util.List;
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
import io.soabase.recordbuilder.test.CustomMethodNamesBuilder.Bean;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
@RecordBuilder.Options(
|
||||||
|
setterPrefix = "set", getterPrefix = "get", booleanPrefix = "is", beanClassName = "Bean")
|
||||||
|
public record CustomMethodNames(
|
||||||
|
int theValue,
|
||||||
|
List<Integer> theList,
|
||||||
|
boolean theBoolean) implements Bean {
|
||||||
|
}
|
||||||
@@ -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.RecordBuilderFull;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RecordBuilderFull
|
||||||
|
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords, @NotNull String justAString) {
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
@RecordBuilder.Options(prefixEnclosingClassNames = false)
|
||||||
|
@RecordBuilder.Include(IncludeWithOption.Hey.class)
|
||||||
|
public class IncludeWithOption {
|
||||||
|
public static record Hey(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;
|
||||||
|
|
||||||
|
@MyInterfaceTemplate
|
||||||
|
public interface InterfaceTemplateTest {
|
||||||
|
String name();
|
||||||
|
|
||||||
|
int age();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
@RecordBuilder.Template(options = @RecordBuilder.Options(
|
||||||
|
fileComment = "This is a test",
|
||||||
|
withClassName = "Com"),
|
||||||
|
asRecordInterface = true
|
||||||
|
)
|
||||||
|
public @interface MyInterfaceTemplate {
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
@RecordBuilder.Options(addStaticBuilder = false)
|
||||||
|
@RecordBuilder
|
||||||
|
public record NoStaticBuilder(String foo) {
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.test;
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -22,6 +24,6 @@ import java.util.OptionalDouble;
|
|||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
@RecordBuilder.Options(emptyDefaultForOptional = true, addConcreteSettersForOptional = true)
|
||||||
@RecordBuilder
|
@RecordBuilder
|
||||||
public record RecordWithOptional(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
public record RecordWithOptional(@NotNull Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalDouble;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
||||||
|
@RecordBuilder
|
||||||
|
public record RecordWithOptional2(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble 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 javax.validation.Valid;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
@RecordBuilder.Options(useValidationApi = true)
|
||||||
|
public record RequestWithValid(@NotNull @Valid Part part) implements RequestWithValidBuilder.With {
|
||||||
|
public record Part(@NotBlank String name) {}
|
||||||
|
}
|
||||||
@@ -18,8 +18,9 @@ package io.soabase.recordbuilder.test;
|
|||||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@RecordBuilder.Options(interpretNotNulls = true)
|
@RecordBuilder.Options(interpretNotNulls = true)
|
||||||
@RecordBuilder
|
@RecordBuilder
|
||||||
public record RequiredRecord(@NotNull String hey, @NotNull int i) implements RequiredRecordBuilder.With {
|
public record RequiredRecord(@NotNull String hey, @NotNull int i, @NotNull List<String> l) implements RequiredRecordBuilder.With {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
@RecordBuilder.Options(
|
||||||
|
addSingleItemCollectionBuilders = true,
|
||||||
|
singleItemBuilderPrefix = "add1",
|
||||||
|
useImmutableCollections = true,
|
||||||
|
addFunctionalMethodsToWith = true
|
||||||
|
)
|
||||||
|
public record SingleItems<T>(List<String> strings, Set<List<T>> sets, Map<Instant, T> map, Collection<T> collection) implements SingleItemsBuilder.With<T> {
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
@RecordBuilder.Options(
|
||||||
|
enableGetters = false,
|
||||||
|
enableWither = false
|
||||||
|
)
|
||||||
|
public record StrippedFeaturesRecord(int aField) {}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RecordBuilder
|
||||||
|
@RecordBuilder.Options(
|
||||||
|
addSingleItemCollectionBuilders = true,
|
||||||
|
useImmutableCollections = true
|
||||||
|
)
|
||||||
|
public record WildcardSingleItems<T>(List<? extends String> strings, Set<? extends List<? extends T>> sets, Map<? extends Instant, ? extends T> map, Collection<? extends T> collection) implements WildcardSingleItemsBuilder.With<T> {
|
||||||
|
}
|
||||||
@@ -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.includes;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||||
|
|
||||||
|
@RecordBuilder.Include(
|
||||||
|
packages = "io.soabase.recordbuilder.test.includes.pack",
|
||||||
|
classes = JustATest.class
|
||||||
|
)
|
||||||
|
public class IncludeFactory {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test.includes;
|
||||||
|
|
||||||
|
public record JustATest(int i) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test.includes.pack;
|
||||||
|
|
||||||
|
public interface AlsoIgnoreMe {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test.includes.pack;
|
||||||
|
|
||||||
|
public class IgnoreMe {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test.includes.pack;
|
||||||
|
|
||||||
|
public record PackRecord1(String name) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test.includes.pack;
|
||||||
|
|
||||||
|
public record PackRecord2(String name, int age) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test.includes.pack;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public record PackRecord3(Instant time, int age) {
|
||||||
|
}
|
||||||
@@ -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.jacoco;
|
||||||
|
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilderFull;
|
||||||
|
import io.soabase.recordbuilder.core.RecordBuilderGenerated;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RecordBuilderFull
|
||||||
|
@RecordBuilderGenerated
|
||||||
|
public record FullRecordForJacoco(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecordForJacoco> fullRecords, @NotNull String justAString) {
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.test;
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -23,6 +24,15 @@ import static io.soabase.recordbuilder.test.foo.PointBuilder.Point;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class TestCollections {
|
class TestCollections {
|
||||||
|
@Test
|
||||||
|
void testRecordBuilderOptionsCopied() {
|
||||||
|
try {
|
||||||
|
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", Collection.class));
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
Assertions.fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCollectionRecordDefaultValues() {
|
void testCollectionRecordDefaultValues() {
|
||||||
var defaultValues = CollectionRecordBuilder.builder().build();
|
var defaultValues = CollectionRecordBuilder.builder().build();
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class TestImmutableCollections {
|
||||||
|
@Test
|
||||||
|
public void testImmutableListNotCopiedWhenNotChanged() {
|
||||||
|
var item = CollectionCopyingBuilder.<String>builder()
|
||||||
|
.addList("a")
|
||||||
|
.addList("b")
|
||||||
|
.addList("c")
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(item.list(), List.of("a", "b", "c"));
|
||||||
|
|
||||||
|
var oldList = item.list();
|
||||||
|
|
||||||
|
var copy = item.with()
|
||||||
|
.count(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldList, copy.list());
|
||||||
|
|
||||||
|
var otherCopy = item.with()
|
||||||
|
.count(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldList, otherCopy.list());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImmutableSetNotCopiedWhenNotChanged() {
|
||||||
|
var item = CollectionCopyingBuilder.<String>builder()
|
||||||
|
.addSet(Arrays.asList("1", "2", "3"))
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(item.set(), Set.of("1", "2", "3"));
|
||||||
|
|
||||||
|
var oldSet = item.set();
|
||||||
|
|
||||||
|
var copy = item.with()
|
||||||
|
.count(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldSet, copy.set());
|
||||||
|
|
||||||
|
var otherCopy = item.with()
|
||||||
|
.count(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldSet, otherCopy.set());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImmutableCollectionNotCopiedWhenNotChanged() {
|
||||||
|
var item = CollectionCopyingBuilder.<String>builder()
|
||||||
|
.collection(List.of("foo", "bar", "baz"))
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(item.collection(), List.of("foo", "bar", "baz"));
|
||||||
|
|
||||||
|
var oldCollection = item.collection();
|
||||||
|
|
||||||
|
var copy = item.with()
|
||||||
|
.count(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldCollection, copy.collection());
|
||||||
|
|
||||||
|
var otherCopy = item.with()
|
||||||
|
.count(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldCollection, otherCopy.collection());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImmutableMapNotCopiedWhenNotChanged() {
|
||||||
|
var item = CollectionCopyingBuilder.<String>builder()
|
||||||
|
.addMap(Instant.MAX, "future")
|
||||||
|
.addMap(Instant.MIN, "before")
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(item.map(), Map.of(Instant.MAX, "future", Instant.MIN, "before"));
|
||||||
|
|
||||||
|
var oldMap = item.map();
|
||||||
|
|
||||||
|
var copy = item.with()
|
||||||
|
.count(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldMap, copy.map());
|
||||||
|
|
||||||
|
var otherCopy = item.with()
|
||||||
|
.count(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertSame(oldMap, otherCopy.map());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSourceListNotModified() {
|
||||||
|
var item = new CollectionCopying<>(new ArrayList<>(), null, null, null, 0);
|
||||||
|
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||||
|
.addList("a")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertEquals(modifiedItem.list(), List.of("a"));
|
||||||
|
Assertions.assertTrue(item.list().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSourceSetNotModified() {
|
||||||
|
var item = new CollectionCopying<>(null, new HashSet<>(), null, null, 0);
|
||||||
|
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||||
|
.addSet("a")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertEquals(modifiedItem.set(), Set.of("a"));
|
||||||
|
Assertions.assertTrue(item.set().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSourceMapNotModified() {
|
||||||
|
var item = new CollectionCopying<>(null, null, new HashMap<>(), null, 0);
|
||||||
|
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||||
|
.addMap(Instant.MIN, "a")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Assertions.assertEquals(modifiedItem.map(), Map.of(Instant.MIN, "a"));
|
||||||
|
Assertions.assertTrue(item.map().isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class TestIncludes {
|
||||||
|
@Test
|
||||||
|
void testOptionsOnInclude() {
|
||||||
|
// assert it's not prefixed with the enclosing class name
|
||||||
|
IncludeWithOption.Hey hey = io.soabase.recordbuilder.test.HeyBuilder.builder().s("this is s").build();
|
||||||
|
Assertions.assertEquals("this is s", hey.s());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,14 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.test;
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.OptionalDouble;
|
import java.util.OptionalDouble;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
import java.util.OptionalLong;
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class TestOptional {
|
class TestOptional {
|
||||||
@Test
|
@Test
|
||||||
void testDefaultEmpty() {
|
void testDefaultEmpty() {
|
||||||
@@ -33,4 +33,51 @@ class TestOptional {
|
|||||||
Assertions.assertEquals(OptionalLong.empty(), record.l());
|
Assertions.assertEquals(OptionalLong.empty(), record.l());
|
||||||
Assertions.assertEquals(OptionalDouble.empty(), record.d());
|
Assertions.assertEquals(OptionalDouble.empty(), record.d());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRawSetters() {
|
||||||
|
var record = RecordWithOptionalBuilder.builder()
|
||||||
|
.value("value")
|
||||||
|
.raw("rawValue")
|
||||||
|
.i(42)
|
||||||
|
.l(424242L)
|
||||||
|
.d(42.42)
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(Optional.of("value"), record.value());
|
||||||
|
Assertions.assertEquals(Optional.of("rawValue"), record.raw());
|
||||||
|
Assertions.assertEquals(OptionalInt.of(42), record.i());
|
||||||
|
Assertions.assertEquals(OptionalLong.of(424242L), record.l());
|
||||||
|
Assertions.assertEquals(OptionalDouble.of(42.42), record.d());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOptionalSetters() {
|
||||||
|
var record = RecordWithOptional2Builder.builder()
|
||||||
|
.value(Optional.of("value"))
|
||||||
|
.raw(Optional.of("rawValue"))
|
||||||
|
.i(OptionalInt.of(42))
|
||||||
|
.l(OptionalLong.of(424242L))
|
||||||
|
.d(OptionalDouble.of(42.42))
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(Optional.of("value"), record.value());
|
||||||
|
Assertions.assertEquals(Optional.of("rawValue"), record.raw());
|
||||||
|
Assertions.assertEquals(OptionalInt.of(42), record.i());
|
||||||
|
Assertions.assertEquals(OptionalLong.of(424242L), record.l());
|
||||||
|
Assertions.assertEquals(OptionalDouble.of(42.42), record.d());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAcceptNullForOptionalRawSetter() {
|
||||||
|
// given
|
||||||
|
String value = null;
|
||||||
|
|
||||||
|
// when
|
||||||
|
var record = RecordWithOptionalBuilder.builder()
|
||||||
|
.value(value)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assertions.assertEquals(Optional.empty(), record.value());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class TestRecordBuilderFull {
|
||||||
|
@Test
|
||||||
|
void testNonNull() {
|
||||||
|
var record = FullRecordBuilder.builder().justAString("").build();
|
||||||
|
Assertions.assertEquals(List.of(), record.numbers());
|
||||||
|
Assertions.assertEquals(Map.of(), record.fullRecords());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testImmutable() {
|
||||||
|
var record = FullRecordBuilder.builder()
|
||||||
|
.fullRecords(new HashMap<>())
|
||||||
|
.numbers(new ArrayList<>())
|
||||||
|
.justAString("")
|
||||||
|
.build();
|
||||||
|
Assertions.assertThrows(UnsupportedOperationException.class, () -> record.fullRecords().put(1, record));
|
||||||
|
Assertions.assertThrows(UnsupportedOperationException.class, () -> record.numbers().add(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package io.soabase.recordbuilder.test;
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import java.util.AbstractMap.SimpleImmutableEntry;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -48,4 +51,28 @@ public class TestRecordInterface
|
|||||||
Assertions.assertEquals(generic.i(), 101);
|
Assertions.assertEquals(generic.i(), 101);
|
||||||
Assertions.assertEquals(generic.s(), now);
|
Assertions.assertEquals(generic.s(), now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuilderStreamWithValues()
|
||||||
|
{
|
||||||
|
var stream = SimpleRecordBuilder.stream(SimpleRecordBuilder.builder()
|
||||||
|
.i(19)
|
||||||
|
.s("value")
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
Assertions.assertEquals(stream, List.of(
|
||||||
|
Map.entry("i", 19),
|
||||||
|
Map.entry("s", "value")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuilderStreamWithNulls()
|
||||||
|
{
|
||||||
|
var stream = SimpleRecordBuilder.stream(SimpleRecordBuilder.builder()
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
Assertions.assertEquals(stream, List.of(
|
||||||
|
new SimpleImmutableEntry<>("i", 0),
|
||||||
|
new SimpleImmutableEntry<>("s", null)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2019 Jordan Zimmerman
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.soabase.recordbuilder.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class TestSingleItems {
|
||||||
|
@Test
|
||||||
|
public void testInternalCollections()
|
||||||
|
{
|
||||||
|
var now = Instant.now();
|
||||||
|
var item = SingleItemsBuilder.<String>builder()
|
||||||
|
.add1Map(now, "now")
|
||||||
|
.add1Map(Instant.MIN, "before")
|
||||||
|
.add1Sets(Arrays.asList("1", "2"))
|
||||||
|
.add1Sets(List.of("3"))
|
||||||
|
.add1Strings("a")
|
||||||
|
.add1Strings("b")
|
||||||
|
.add1Strings("c")
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(item.map(), Map.of(now, "now", Instant.MIN, "before"));
|
||||||
|
Assertions.assertEquals(item.sets(), Set.of(List.of("1", "2"), List.of("3")));
|
||||||
|
Assertions.assertEquals(item.strings(), List.of("a", "b", "c"));
|
||||||
|
|
||||||
|
var copy = item.with()
|
||||||
|
.add1Strings("new")
|
||||||
|
.add1Map(Instant.MAX, "after")
|
||||||
|
.add1Sets(List.of("10", "20", "30"))
|
||||||
|
.build();
|
||||||
|
Assertions.assertNotEquals(item, copy);
|
||||||
|
Assertions.assertEquals(copy.map(), Map.of(now, "now", Instant.MIN, "before", Instant.MAX, "after"));
|
||||||
|
Assertions.assertEquals(copy.sets(), Set.of(List.of("1", "2"), List.of("3"), List.of("10", "20", "30")));
|
||||||
|
Assertions.assertEquals(copy.strings(), List.of("a", "b", "c", "new"));
|
||||||
|
|
||||||
|
var stringsToAdd = Arrays.asList("x", "y", "z");
|
||||||
|
var listToAdd = Arrays.asList(List.of("aa", "bb"), List.of("cc"));
|
||||||
|
var mapToAdd = Map.of(now.plusMillis(1), "now+1", now.plusMillis(2), "now+2");
|
||||||
|
var streamed = SingleItemsBuilder.builder(item)
|
||||||
|
.add1Strings(stringsToAdd.stream())
|
||||||
|
.add1Sets(listToAdd.stream())
|
||||||
|
.add1Map(mapToAdd.entrySet().stream())
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(streamed.map(), Map.of(now, "now", Instant.MIN, "before", now.plusMillis(1), "now+1", now.plusMillis(2), "now+2"));
|
||||||
|
Assertions.assertEquals(streamed.sets(), Set.of(List.of("1", "2"), List.of("3"), List.of("aa", "bb"), List.of("cc")));
|
||||||
|
Assertions.assertEquals(streamed.strings(), Arrays.asList("a", "b", "c", "x", "y", "z"));
|
||||||
|
|
||||||
|
var nulls = SingleItemsBuilder.builder(item)
|
||||||
|
.strings(null)
|
||||||
|
.sets(null)
|
||||||
|
.map(null)
|
||||||
|
.build();
|
||||||
|
Assertions.assertEquals(nulls.map(), Map.of());
|
||||||
|
Assertions.assertEquals(nulls.sets(), Set.of());
|
||||||
|
Assertions.assertEquals(nulls.strings(), List.of());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import org.junit.jupiter.api.Assertions;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import javax.validation.ValidationException;
|
import javax.validation.ValidationException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
class TestValidation {
|
class TestValidation {
|
||||||
@Test
|
@Test
|
||||||
@@ -33,7 +34,7 @@ class TestValidation {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNotNullsWithNewProperty() {
|
void testNotNullsWithNewProperty() {
|
||||||
var valid = RequiredRecordBuilder.builder().hey("hey").i(1).build();
|
var valid = RequiredRecordBuilder.builder().hey("hey").i(1).l(List.of()).build();
|
||||||
Assertions.assertThrows(NullPointerException.class, () -> valid.withHey(null));
|
Assertions.assertThrows(NullPointerException.class, () -> valid.withHey(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,4 +43,14 @@ class TestValidation {
|
|||||||
var valid = RequiredRecord2Builder.builder().hey("hey").i(1).build();
|
var valid = RequiredRecord2Builder.builder().hey("hey").i(1).build();
|
||||||
Assertions.assertThrows(ValidationException.class, () -> valid.withHey(null));
|
Assertions.assertThrows(ValidationException.class, () -> valid.withHey(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRequestWithValid() {
|
||||||
|
Assertions.assertDoesNotThrow(() -> RequestWithValidBuilder.builder()
|
||||||
|
.part(new RequestWithValid.Part("jsfjsf"))
|
||||||
|
.build());
|
||||||
|
Assertions.assertThrows(ValidationException.class, () -> RequestWithValidBuilder.builder()
|
||||||
|
.part(new RequestWithValid.Part(""))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 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.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class TestVariousOptions {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builderGetsCustomSetterAndGetterNames() {
|
||||||
|
var obj = CustomMethodNamesBuilder.builder()
|
||||||
|
.setTheValue(1)
|
||||||
|
.setTheList(List.of(2))
|
||||||
|
.setTheBoolean(true);
|
||||||
|
assertEquals(1, obj.getTheValue());
|
||||||
|
assertEquals(List.of(2), obj.getTheList());
|
||||||
|
assertTrue(obj.isTheBoolean());
|
||||||
|
assertEquals(new CustomMethodNames(1, List.of(2), true), obj.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withBuilderGetsCustomSetterAndGetterNames() {
|
||||||
|
var obj = CustomMethodNamesBuilder.from(CustomMethodNamesBuilder.builder()
|
||||||
|
.setTheValue(1)
|
||||||
|
.setTheList(List.of(2))
|
||||||
|
.setTheBoolean(true)
|
||||||
|
.build());
|
||||||
|
assertEquals(1, obj.getTheValue());
|
||||||
|
assertEquals(List.of(2), obj.getTheList());
|
||||||
|
assertTrue(obj.isTheBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void recordHasPrefixedGetters() {
|
||||||
|
var obj = new CustomMethodNames(1, List.of(2), true);
|
||||||
|
assertEquals(1, obj.getTheValue());
|
||||||
|
assertEquals(List.of(2), obj.getTheList());
|
||||||
|
assertTrue(obj.isTheBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noStaticBuilder() {
|
||||||
|
boolean hasStaticBuilder = Stream.of(NoStaticBuilderBuilder.class.getDeclaredMethods())
|
||||||
|
.anyMatch(method -> method.getName().equals("NoStaticBuilder"));
|
||||||
|
assertFalse(hasStaticBuilder);
|
||||||
|
|
||||||
|
hasStaticBuilder = Stream.of(SimpleRecordBuilder.class.getDeclaredMethods())
|
||||||
|
.anyMatch(method -> method.getName().equals("SimpleRecord"));
|
||||||
|
assertTrue(hasStaticBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,17 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class TestWithers {
|
class TestWithers {
|
||||||
|
@Test
|
||||||
|
void testFromWithers() {
|
||||||
|
var r1 = new SimpleGenericRecord<>(10, List.of("1", "2", "3"));
|
||||||
|
var r2 = SimpleGenericRecordBuilder.from(r1).withS(List.of("4", "5"));
|
||||||
|
var r3 = SimpleGenericRecordBuilder.from(r1).with(b -> b.i(20).s(List.of("6", "7")));
|
||||||
|
Assertions.assertEquals(List.of("1", "2", "3"), r1.s());
|
||||||
|
Assertions.assertEquals(List.of("4", "5"), r2.s());
|
||||||
|
Assertions.assertEquals(List.of("6", "7"), r3.s());
|
||||||
|
Assertions.assertEquals(20, r3.i());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testWithers() {
|
void testWithers() {
|
||||||
var r1 = new SimpleGenericRecord<>(10, List.of("1", "2", "3"));
|
var r1 = new SimpleGenericRecord<>(10, List.of("1", "2", "3"));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>io.soabase.record-builder</groupId>
|
<groupId>io.soabase.record-builder</groupId>
|
||||||
<artifactId>record-builder</artifactId>
|
<artifactId>record-builder</artifactId>
|
||||||
<version>25-java15</version>
|
<version>34</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user