Compare commits
21 Commits
jordanz/en
...
record-bui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0647b66bcd | ||
|
|
08a4471d15 | ||
|
|
a1acfb2863 | ||
|
|
82bc1c1625 | ||
|
|
2d029a2786 | ||
|
|
2625b3d849 | ||
|
|
cc5e189f94 | ||
|
|
9125ba0660 | ||
|
|
664809fc69 | ||
|
|
9d011b82aa | ||
|
|
35125f550d | ||
|
|
cb2bd68697 | ||
|
|
f40cfd48ee | ||
|
|
d9f2adc2f9 | ||
|
|
99832d50ae | ||
|
|
1203109108 | ||
|
|
7e4675f7c0 | ||
|
|
8ab9d8bdca | ||
|
|
24edc5e70c | ||
|
|
3832cb3881 | ||
|
|
802dd1f880 |
@@ -19,6 +19,6 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 16
|
||||
java-version: 16.0.0-ea
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
@@ -1,7 +1,7 @@
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Maven Build - Java 17
|
||||
name: Maven Build - Java 15
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -19,6 +19,10 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
java-version: 15
|
||||
- name: Create Maven Directory
|
||||
run: mkdir -p .mvn/
|
||||
- name: Create Maven JVM file
|
||||
run: echo "--enable-preview" > .mvn/jvm.config
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
run: mvn -P java15 -B package --file pom.xml
|
||||
229
README.md
229
README.md
@@ -21,8 +21,8 @@ _Details:_
|
||||
- [Record From Interface Details](#RecordInterface-Example)
|
||||
- [Generation Via Includes](#generation-via-includes)
|
||||
- [Usage](#usage)
|
||||
- [Customizing](customizing.md) (e.g. add immutable collections, etc.)
|
||||
- **_NEW!!_** [RecordBuilder Enhancer](record-builder-enhancer/README.md) - Inject verification, defensive copying, null checks or custom code into your Java Record constructors during compilation!
|
||||
- [Customizing](customizing.md)
|
||||
- [Java 15 Versions](#java-15-versions)
|
||||
|
||||
## RecordBuilder Example
|
||||
|
||||
@@ -80,18 +80,12 @@ NameAndAge r5 = r4.with(b -> {
|
||||
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._
|
||||
|
||||
## 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:
|
||||
|
||||
```java
|
||||
@@ -129,21 +123,6 @@ public class NameAndAgeBuilder {
|
||||
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
|
||||
*/
|
||||
@@ -151,23 +130,6 @@ public class NameAndAgeBuilder {
|
||||
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
|
||||
*/
|
||||
@@ -198,25 +160,53 @@ public class NameAndAgeBuilder {
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downcast to {@code NameAndAge}
|
||||
*/
|
||||
private static NameAndAge _downcast(Object obj) {
|
||||
try {
|
||||
return (NameAndAge)obj;
|
||||
}
|
||||
catch (ClassCastException dummy) {
|
||||
throw new RuntimeException("NameAndAgeBuilder.With can only be implemented for NameAndAge");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add withers to {@code NameAndAge}
|
||||
*/
|
||||
public interface With {
|
||||
/**
|
||||
* Return the current value for the {@code name} record component in the builder
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Return the current value for the {@code age} record component in the builder
|
||||
*/
|
||||
int age();
|
||||
|
||||
/**
|
||||
* Return a new record builder using the current values
|
||||
*/
|
||||
default NameAndAgeBuilder with() {
|
||||
return new NameAndAgeBuilder(name(), age());
|
||||
NameAndAge r = _downcast(this);
|
||||
return NameAndAgeBuilder.builder(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,32 +222,16 @@ public class NameAndAgeBuilder {
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
|
||||
*/
|
||||
default NameAndAge withName(String name) {
|
||||
return new NameAndAge(name, age());
|
||||
NameAndAge r = _downcast(this);
|
||||
return new NameAndAge(name, r.age());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code age}
|
||||
*/
|
||||
default NameAndAge withAge(int 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();
|
||||
NameAndAge r = _downcast(this);
|
||||
return new NameAndAge(r.name(), age);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,30 +291,48 @@ public void Placeholder {
|
||||
}
|
||||
```
|
||||
|
||||
`@RecordBuilder.Include` also supports a `packages` attribute that includes all records
|
||||
in the listed packages.
|
||||
|
||||
The target package for generation is the same as the package that contains the "Include"
|
||||
annotation. Use `packagePattern` to change this (see Javadoc for details).
|
||||
|
||||
## Enhancer
|
||||
|
||||
[RecordBuilder Enhancer](record-builder-enhancer/README.md) - Inject verification, defensive copying, null
|
||||
checks or custom code into your Java Record constructors during compilation.
|
||||
|
||||
## Usage
|
||||
|
||||
### Maven
|
||||
|
||||
Add a dependency that contains the discoverable annotation processor:
|
||||
1) Add the dependency that contains the `@RecordBuilder` annotation.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-processor</artifactId>
|
||||
<version>${record.builder.version}</version>
|
||||
<artifactId>record-builder-core</artifactId>
|
||||
<version>set-version-here</version>
|
||||
<scope>provided</scope>
|
||||
</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
|
||||
@@ -363,3 +355,76 @@ 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
|
||||
own custom RecordBuilder annotations. See [Customizing RecordBuilder](customizing.md)
|
||||
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,17 +1,10 @@
|
||||
[◀︎ RecordBuilder](README.md) • Customizing RecordBuilder
|
||||
|
||||
# RecordBuilderFull
|
||||
|
||||
Note: `@RecordBuilderFull` has most optional features enabled. It's an alternate
|
||||
form of `@RecordBuilder` that uses the [templating mechanism](#create-a-custom-annotation) to
|
||||
enable optional features.
|
||||
|
||||
# Customizing RecordBuilder
|
||||
|
||||
RecordBuilder can be customized in a number of ways. The types of customizations will change over time. See
|
||||
[@RecordBuilder.Options](record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java)
|
||||
for the current set of customizations and their default values. For example, the `useImmutableCollections` option
|
||||
adds special handling for record components of type `java.util.List`, `java.util.Set`, `java.util.Map` and `java.util.Collection`. When the record is built, any components of these types are passed through an added shim method that uses the corresponding immutable collection (e.g. `List.copyOf(o)`) or an empty immutable collection if the component is `null`.
|
||||
for the current set of customizations and their default values.
|
||||
|
||||
You can:
|
||||
|
||||
@@ -90,6 +83,3 @@ public @interface MyCoRecordBuilder {
|
||||
|
||||
Now, you can use `@MyCoRecordBuilder` instead of `@RecordBuilder` and the record
|
||||
will be built with options as specified.
|
||||
|
||||
Note: the template mechanism also supports `@RecordInterface` templates via the `asRecordInterface` attribute.
|
||||
When it is set a `@RecordInterface` template is created instead.
|
||||
|
||||
22
java15.sh
Executable file
22
java15.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/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
Executable file
21
java16.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/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
|
||||
156
pom.xml
156
pom.xml
@@ -5,16 +5,13 @@
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>34-SNAPSHOT</version>
|
||||
<version>21</version>
|
||||
|
||||
<modules>
|
||||
<module>record-builder-core</module>
|
||||
<module>record-builder-processor</module>
|
||||
<module>record-builder-test</module>
|
||||
<module>record-builder-validator</module>
|
||||
<module>record-builder-enhancer</module>
|
||||
<module>record-builder-test-custom-enhancer</module> <!-- must be set to ignore in IntelliJ -->
|
||||
<module>record-builder-enhancer-core</module> <!-- must be set to ignore in IntelliJ -->
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
@@ -22,6 +19,8 @@
|
||||
<project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<enable-preview />
|
||||
|
||||
<jdk-version>16</jdk-version>
|
||||
|
||||
<maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
|
||||
@@ -32,24 +31,19 @@
|
||||
<maven-gpg-plugin-version>1.6</maven-gpg-plugin-version>
|
||||
<maven-javadoc-plugin-version>3.1.1</maven-javadoc-plugin-version>
|
||||
<maven-clean-plugin-version>3.1.0</maven-clean-plugin-version>
|
||||
<maven-shade-plugin-version>3.3.0</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-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>
|
||||
<maven-jar-plugin-version>3.2.0</maven-jar-plugin-version>
|
||||
|
||||
<license-file-path>src/etc/header.txt</license-file-path>
|
||||
|
||||
<javapoet-version>1.12.1</javapoet-version>
|
||||
<junit-jupiter-version>5.5.2</junit-jupiter-version>
|
||||
<asm-version>9.3</asm-version>
|
||||
<asm-version>7.2</asm-version>
|
||||
<validation-api-version>2.0.1.Final</validation-api-version>
|
||||
<hibernate-validator-version>6.0.20.Final</hibernate-validator-version>
|
||||
<javax-el-version>3.0.1-b09</javax-el-version>
|
||||
<byte-buddy.version>1.12.10</byte-buddy.version>
|
||||
<guava.version>31.1-jre</guava.version>
|
||||
<picocli.version>4.6.3</picocli.version>
|
||||
</properties>
|
||||
|
||||
<name>Record Builder</name>
|
||||
@@ -86,7 +80,7 @@
|
||||
<url>https://github.com/randgalt/record-builder</url>
|
||||
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
|
||||
<tag>record-builder-1.16</tag>
|
||||
<tag>record-builder-21</tag>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
@@ -109,18 +103,6 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>${byte-buddy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy-agent</artifactId>
|
||||
<version>${byte-buddy.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup</groupId>
|
||||
<artifactId>javapoet</artifactId>
|
||||
@@ -133,30 +115,6 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-processor</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-test-custom-enhancer</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-validator</artifactId>
|
||||
@@ -186,48 +144,21 @@
|
||||
<artifactId>javax.el</artifactId>
|
||||
<version>${javax-el-version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>${asm-version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-tree</artifactId>
|
||||
<version>${asm-version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>${picocli.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${maven-surefire-plugin-version}</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin-version}</version>
|
||||
<configuration>
|
||||
<release>${jdk-version}</release>
|
||||
<compilerArgs>
|
||||
<arg>${enable-preview}</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -282,7 +213,6 @@
|
||||
<exclude>**/io/soabase/com/google/**</exclude>
|
||||
<exclude>**/com/company/**</exclude>
|
||||
<exclude>**/META-INF/services/**</exclude>
|
||||
<exclude>**/safe/**</exclude>
|
||||
<exclude>**/jvm.config</exclude>
|
||||
<exclude>**/.java-version</exclude>
|
||||
<exclude>**/.travis.yml</exclude>
|
||||
@@ -328,6 +258,44 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>${maven-shade-plugin-version}</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<dependencyReducedPomLocation>${project.build.outputDirectory}/META-INF/reduced-pom.xml</dependencyReducedPomLocation>
|
||||
<filters>
|
||||
<filter>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>${asm-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
<version>${asm-version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
@@ -340,6 +308,15 @@
|
||||
</configuration>
|
||||
</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>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
@@ -351,12 +328,6 @@
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>${maven-gpg-plugin-version}</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco-maven-plugin-version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
@@ -390,11 +361,6 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -422,5 +388,13 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>java15</id>
|
||||
<properties>
|
||||
<jdk-version>15</jdk-version>
|
||||
<enable-preview>--enable-preview</enable-preview>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>34-SNAPSHOT</version>
|
||||
<version>21</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -25,32 +25,12 @@ public @interface RecordBuilder {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Inherited
|
||||
@interface Include {
|
||||
/**
|
||||
* @return list of classes to include
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
|
||||
/**
|
||||
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
|
||||
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
|
||||
* (i.e. a union of classes from both attributes).
|
||||
*
|
||||
* @return list of classes
|
||||
*/
|
||||
Class<?>[] classes() default {};
|
||||
|
||||
/**
|
||||
* Optional list of package names. All records in the packages will get processed as
|
||||
* if they were listed as classes to include.
|
||||
*
|
||||
* @return list of package names
|
||||
*/
|
||||
String[] packages() default {};
|
||||
Class<?>[] value();
|
||||
|
||||
/**
|
||||
* Pattern used to generate the package for the generated class. The value
|
||||
* is the literal package name however two replacement values can be used. '@'
|
||||
* is replaced with the package of the {@code Include} annotation. '*' is replaced with
|
||||
* is replaced with the package of the Include annotation. '*' is replaced with
|
||||
* the package of the included class.
|
||||
*
|
||||
* @return package pattern
|
||||
@@ -90,20 +70,15 @@ public @interface RecordBuilder {
|
||||
String buildMethodName() default "build";
|
||||
|
||||
/**
|
||||
* The name to use for the from-to-wither method
|
||||
* The name to use for the downcast method
|
||||
*/
|
||||
String fromMethodName() default "from";
|
||||
String downCastMethodName() default "_downcast";
|
||||
|
||||
/**
|
||||
* The name to use for the method that returns the record components as a 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
|
||||
*/
|
||||
@@ -144,11 +119,6 @@ public @interface RecordBuilder {
|
||||
*/
|
||||
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",
|
||||
* "NoNull", or "NonNull" (see {@link #interpretNotNullsPattern()} for the actual regex matching pattern).
|
||||
@@ -171,95 +141,12 @@ public @interface RecordBuilder {
|
||||
* named {@code RecordBuilderValidator} which has a public static method: {@code public static <T> T validate(T o)}.</p>
|
||||
*/
|
||||
boolean useValidationApi() default false;
|
||||
|
||||
/**
|
||||
* Adds special handling for record components of type {@link java.util.List}, {@link java.util.Set},
|
||||
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components
|
||||
* of these types are passed through an added shim method that uses the corresponding immutable collection
|
||||
* (e.g. {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
|
||||
*/
|
||||
boolean useImmutableCollections() default false;
|
||||
|
||||
/**
|
||||
* When enabled, collection types ({@code List}, {@code Set} and {@code Map}) are handled specially.
|
||||
* The setters for these types now create an internal collection and items are added to that
|
||||
* collection. Additionally, "adder" methods prefixed with {@link #singleItemBuilderPrefix()} are created
|
||||
* to add single items to these collections.
|
||||
*/
|
||||
boolean addSingleItemCollectionBuilders() default false;
|
||||
|
||||
/**
|
||||
* The prefix for adder methods when {@link #addSingleItemCollectionBuilders()} is enabled
|
||||
*/
|
||||
String singleItemBuilderPrefix() default "add";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Inherited
|
||||
@interface Template {
|
||||
RecordBuilder.Options options();
|
||||
|
||||
boolean asRecordInterface() default false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.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 {
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.core;
|
||||
|
||||
import java.lang.annotation.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,7 +15,11 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.core;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
@@ -27,31 +31,14 @@ public @interface RecordInterface {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Inherited
|
||||
@interface Include {
|
||||
/**
|
||||
* @return list of classes to include
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
Class<?>[] value();
|
||||
|
||||
/**
|
||||
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
|
||||
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
|
||||
* (i.e. a union of classes from both attributes).
|
||||
*
|
||||
* @return list of classes
|
||||
*/
|
||||
Class<?>[] classes() default {};
|
||||
|
||||
/**
|
||||
* If true the generated record is annotated with {@code @RecordBuilder}
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
boolean addRecordBuilder() default true;
|
||||
|
||||
/**
|
||||
* Pattern used to generate the package for the generated class. The value
|
||||
* is the literal package name however two replacement values can be used. '@'
|
||||
* is replaced with the package of the {@code Include} annotation. '*' is replaced with
|
||||
* is replaced with the package of the Include annotation. '*' is replaced with
|
||||
* the package of the included class.
|
||||
*
|
||||
* @return package pattern
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>record-builder-enhancer-core</artifactId>
|
||||
|
||||
<properties>
|
||||
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-tree</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
|
||||
<useDependencyReducedPomInJar>true</useDependencyReducedPomInJar>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>org.objectweb</pattern>
|
||||
<shadedPattern>recordbuilder.org.objectweb</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface RecordBuilderEnhance {
|
||||
Class<? extends RecordBuilderEnhancer>[] enhancers();
|
||||
|
||||
RecordBuilderEnhanceArguments[] arguments() default {};
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Inherited
|
||||
@interface Template {
|
||||
Class<? extends RecordBuilderEnhancer>[] enhancers();
|
||||
|
||||
RecordBuilderEnhanceArguments[] arguments() default {};
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface RecordBuilderEnhanceArguments {
|
||||
Class<? extends RecordBuilderEnhancer> enhancer();
|
||||
|
||||
String[] arguments();
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.spi;
|
||||
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public record Entry(int parameterIndex, RecordComponentElement element, TypeMirror erasedType) {
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.spi;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import java.util.List;
|
||||
|
||||
public interface Processor {
|
||||
Elements elements();
|
||||
|
||||
Types types();
|
||||
|
||||
boolean hasEnhancer(Class<? extends RecordBuilderEnhancer> enhancer);
|
||||
|
||||
boolean verboseRequested();
|
||||
|
||||
void logInfo(CharSequence msg);
|
||||
|
||||
void logWarning(CharSequence msg);
|
||||
|
||||
void logError(CharSequence msg);
|
||||
|
||||
List<Entry> asEntries(TypeElement element);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.spi;
|
||||
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import java.util.List;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RecordBuilderEnhancer {
|
||||
InsnList enhance(Processor processor, TypeElement element, List<String> arguments);
|
||||
}
|
||||
@@ -1,454 +0,0 @@
|
||||
# RecordBuilder Enhancer
|
||||
|
||||
## What is RecordBuilder Enhancer
|
||||
|
||||
Inject verification, defensive copying, null checks or custom code into your Java Record constructors during compilation.
|
||||
|
||||
#### Features:
|
||||
|
||||
- [Builtin enhancers](#builtin-enhancers) to help with null checks and defensive copying
|
||||
- [SPI](#write-your-own-custom-enhancer) for writing your own [custom enhancers](#write-your-own-custom-enhancer)
|
||||
- [Create a custom annotation](#create-a-custom-annotation) that specifies a custom set of enhancers
|
||||
|
||||
#### Is it safe? Does it use undocumented features of Java?
|
||||
|
||||
- The Enhancer modifies your Java class files. There are some inherent safety concerns with this. However:
|
||||
- The industry standard [ASM](https://asm.ow2.io) library is used to do the modifications. ASM is even used in the JDK itself.
|
||||
- The Enhancer only inserts code in the default constructor of Java records. The code is inserted just after the
|
||||
call to `super()` and before any existing code in the constructor.
|
||||
- It's implemented as a standard [javac plugin](https://docs.oracle.com/en/java/javase/16/docs/api/jdk.compiler/com/sun/source/util/Plugin.html) and uses no undocumented features
|
||||
- If you don't like the builtin enhancers you can [write your own](#write-your-own-custom-enhancer)
|
||||
|
||||
#### How does it relate to [RecordBuilder](../README.md)?
|
||||
|
||||
They aren't directly related but they share some code and both work on java records.
|
||||
|
||||
#### Why RecordBuilder Enhancer?
|
||||
|
||||
Java Records are fantastic data carriers and solve much of the pain of the lack of these types of classes
|
||||
in older versions of Java. [RecordBuilder](../README.md) adds builders and withers to records. However,
|
||||
records are still missing simple `null` checks and a few other niceties. This means boilerplate
|
||||
for every record. RecordBuilder Enhancer is targeted at being able to write Java records without having to add
|
||||
any additional code.
|
||||
|
||||
## How to Use RecordBuilder Enhancer
|
||||
|
||||
First, configure your build environment: see [details below](#javac-plugin). Then, use the Enhancer's
|
||||
annotations to specify Java records that you want to be enhanced.
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = RequireNonNull.class)
|
||||
public record MyRecord(String s, List<String> l) {
|
||||
}
|
||||
```
|
||||
|
||||
Enhancer inserts code into the default constructor as if you wrote this:
|
||||
|
||||
```java
|
||||
public record MyRecord(String s, List<String> l) {
|
||||
public MyRecord {
|
||||
Objects.requireNonNull(s, "s is null");
|
||||
Objects.requireNonNull(l, "l is null");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The class file will be updated like this:
|
||||
|
||||

|
||||
|
||||
Enhancers are applied in the order listed in the annotation. E.g.
|
||||
|
||||
```java
|
||||
// will apply RequireNonNull and then CopyCollection
|
||||
@RecordBuilderEnhance(enhancers = {RequireNonNull.class, CopyCollection.class})
|
||||
public record MyRecord(List<String> l) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(List<String> l) {
|
||||
Objects.requireNonNull(l);
|
||||
l = List.copyOf(l);
|
||||
}
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
Enhancers can optional receive arguments (the builtin [NotNullAnnotations](#notnullannotations) does). Use `@RecordBuilderEnhanceArguments`
|
||||
to pass arguments. E.g.
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = NotNullAnnotations.class,
|
||||
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = "altnonnull"))
|
||||
public record MyRecord(@AltNonNull List<String> l) {}
|
||||
```
|
||||
|
||||
|
||||
## Builtin Enhancers
|
||||
|
||||
| ID | Arguments | Description |
|
||||
|-----------------------------------------------------------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [EmptyNullOptional](#emptynulloptional) | - | Use `empty()` for null Optional/OptionalInt/OptionalLong/OptionalDouble record components |
|
||||
| [EmptyNullString](#emptynullstring) | - | Use `""` for null String record components |
|
||||
| [CopyCollection](#copycollection) | - | Make defensive copies of Collection, List and Map record components |
|
||||
| [CopyCollectionNullableEmpty](#copycollectionnullableempty) | - | Make defensive copies of Collection, List and Map record components or empty collections when `null` |
|
||||
| [GuavaCopyCollection](#guavacopycollection) | - | Same as CopyCollection but uses Google Guava collections |
|
||||
| [GuavaCopyCollectionNullableEmpty](#guavacopycollectionnullableempty) | - | Same as CopyCollectionNullableEmpty but uses Google Guava collections |
|
||||
| [RequireNonNull](#requirenonnull) | - | Call `requireNonNull()` on all non-primitive record components - note: checks other enhancers and doesn't apply to Strings if EmptyNullString is being used, etc. |
|
||||
| [NotNullAnnotations](#notnullannotations) | expression (optional) | Any parameter with an annotation whose name matches this enhancer's regular expression argument will be passed to `requireNonNull()`. The argument is optional. If not supplied then _(notnull)|(nonnull)_ is used. Matching is always case insensitive. |
|
||||
|
||||
### Examples
|
||||
|
||||
#### EmptyNullOptional
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = EmptyNullOptional.class)
|
||||
public record MyRecord(Optional<String> s, OptionalInt i) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(Optional<String> s, OptionalInt i) {
|
||||
public MyRecord {
|
||||
s = (s != null) ? s : Optional.empty();
|
||||
i = (i != null) ? i : OptionalInt.empty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
#### EmptyNullString
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = EmptyNullString.class)
|
||||
public record MyRecord(String s) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(String s) {
|
||||
public MyRecord {
|
||||
s = (s != null) ? s : "";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
#### CopyCollection
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = CopyCollection.class)
|
||||
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(String s) {
|
||||
public MyRecord {
|
||||
c = Set.copyOf(c);
|
||||
s = Set.copyOf(s);
|
||||
l = List.copyOf(l);
|
||||
m = Map.copyOf(m);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
#### CopyCollectionNullableEmpty
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = CopyCollectionNullableEmpty.class)
|
||||
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(String s) {
|
||||
public MyRecord {
|
||||
c = (c != null) ? Set.copyOf(c) : Set.of();
|
||||
s = (s != null) ? Set.copyOf(s) : Set.of();
|
||||
l = (l != null) ? List.copyOf(l) : List.of();
|
||||
m = (m != null) ? Map.copyOf(m) : Map.of();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
#### GuavaCopyCollection
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = GuavaCopyCollection.class)
|
||||
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(String s) {
|
||||
public MyRecord {
|
||||
c = ImmutableSet.copyOf(c);
|
||||
s = ImmutableSet.copyOf(s);
|
||||
l = ImmutableList.copyOf(l);
|
||||
m = Map.copyOf(m);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
#### GuavaCopyCollectionNullableEmpty
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = GuavaCopyCollectionNullableEmpty.class)
|
||||
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(String s) {
|
||||
public MyRecord {
|
||||
c = (c != null) ? ImmutableSet.copyOf(c) : ImmutableSet.of();
|
||||
s = (s != null) ? ImmutableSet.copyOf(s) : ImmutableSet.of();
|
||||
l = (l != null) ? ImmutableList.copyOf(l) : ImmutableList.of();
|
||||
m = (m != null) ? ImmutableMap.copyOf(m) : ImmutableMap.of();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
#### RequireNonNull
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = RequireNonNull.class)
|
||||
public record MyRecord(String s, Instant t) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(String s, Instant t) {
|
||||
public MyRecord {
|
||||
Objects.requireNonNull(s, "s is null");
|
||||
Objects.requireNonNull(t, "t is null");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-------
|
||||
|
||||
#### NotNullAnnotations
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance(enhancers = NotNullAnnotations.class,
|
||||
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = "(notnull)|(mynil)"))
|
||||
public record MyRecord(String s, @NotNull Instant t, @MyNil Thing thing) {}
|
||||
```
|
||||
|
||||
_becomes_
|
||||
|
||||
```java
|
||||
public record MyRecord(String s, @NotNull Instant t, @MyNil Thing thing) {
|
||||
public MyRecord {
|
||||
Objects.requireNonNull(t, "t is null");
|
||||
Objects.requireNonNull(thing, "thing is null");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Create A Custom Annotation
|
||||
|
||||
Using `@RecordBuilderEnhance.Template` you can create your own RecordBuilderEnhance annotation that always uses the set of enhancers that you want.
|
||||
|
||||
```java
|
||||
@RecordBuilderEnhance.Template(enhancers = {RequireNonNull.class, NotNullAnnotations.class},
|
||||
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = "altnonnull.*"))
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface MyCoEnhance {
|
||||
}
|
||||
```
|
||||
|
||||
Now, you can use `@MyCoEnhance` instead of `@RecordBuilderEnhance` and the record will be enhanced with the enhancers specified.
|
||||
|
||||
## Javac Plugin
|
||||
|
||||
Add a dependency that contains the discoverable javac plugin to your build tool (see below for [Maven](#maven) or [Gradle](#gradle)). javac will
|
||||
auto discover the Enhancer plugin. By default the enhancer assumes the standard directory layout used by
|
||||
most Java build systems. i.e. if a Java source file is at `/foo/bar/myproject/src/main/java/my/package/MyClass.java`
|
||||
the Enhancer will assume that the compiled class file for that source file will be found at
|
||||
`/foo/bar/myproject/target/classes/my/package/MyClass.class`. If your build system does not use this method then
|
||||
the Enhancer will need additional configuration ([see below](#options)). You can also
|
||||
configure some of the behavior of the Enhancer ([see below](#options)).
|
||||
|
||||
### Maven
|
||||
|
||||
```xml
|
||||
<!-- only needed during compilation -->
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer</artifactId>
|
||||
<version>${record.builder.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- contains the annotations -->
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer-core</artifactId>
|
||||
<version>${record.builder.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
compileOnly 'io.soabase.record-builder:record-builder-enhancer:$version-goes-here'
|
||||
api 'io.soabase.record-builder:record-enhancer-core:$version-goes-here'
|
||||
}
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
For normal usage you won't need to set any options for the Enhancer. The following options are available if you need them:
|
||||
|
||||
```text
|
||||
[-hv] [--disable] [--dryRun] [--outputDirectory=<outputTo>] [DIRECTORY]
|
||||
|
||||
[DIRECTORY] The build's output directory - i.e. where javac writes generated classes.
|
||||
The value can be a full path or a relative path. If not provided the Enhancer
|
||||
plugin will attempt to use standard directories.
|
||||
--disable Deactivate/disable the plugin
|
||||
--dryRun Dry run only - doesn't modify any classes. You should enable verbose as well via: -v
|
||||
-h, --help Outputs this help
|
||||
--outputDirectory=<outputTo>
|
||||
Optional alternate output directory for enhanced class files
|
||||
-v, --verbose Verbose output during compilation
|
||||
```
|
||||
|
||||
javac plugin options are specifed on the javac command line or as part of your build tool. On the command line:
|
||||
|
||||
```shell
|
||||
javac -Xplugin:"recordbuilderenhancer ...arguments..."
|
||||
```
|
||||
|
||||
In Maven:
|
||||
|
||||
```xml
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<compilerArgument>-Xplugin:recordbuilderenhancer ...arguments...</compilerArgument>
|
||||
</configuration>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
## Write Your Own Custom Enhancer
|
||||
|
||||
Notes on writing your own enhancer:
|
||||
|
||||
- Custom Enhancers must be built as separate modules from the code base you want to enhance. This is because
|
||||
the enhancers must be available during compilation.
|
||||
- You should somewhat be familiar with Java's AST classes though they are not hard to understand for newcomers
|
||||
- Enhancers use the [ASM library](https://asm.ow2.io) to produce a list of statements to be inserted into Java record constructors
|
||||
- You will need some knowledge of Java bytecode specifics and the Java Language specification. However, it's pretty simple
|
||||
to use javac and javap to show you what bytecodes you need to specify ([see below](#how-to-get-the-bytecodes-for-your-enhancer))
|
||||
- **IMPORTANT** - the ASM library has been shaded into the RecordBuilder Enhancer JAR. Make sure your custom enhancer uses the ASM classes
|
||||
that are in the package `recordbuilder.org.objectweb.asm.*`. Many libraries use ASM and you may see the same classes in multiple packages.
|
||||
|
||||
For reference look at the implementation of the builtin enhancers to see exactly how to write one. [EmptyNullString](src/main/java/io/soabase/recordbuilder/enhancer/enhancers/EmptyNullString.java)
|
||||
is a simple one to use as an example.
|
||||
|
||||
#### Add the SPI to your build
|
||||
|
||||
The RecordBuilder Enhancer SPI must be added to the module for your custom enhancer. It's artifact ID is `record-builder-enhancer-core`.
|
||||
Your enhancer module can contain as many enhancers as you want. Each enhancer must implement the following interface:
|
||||
|
||||
```java
|
||||
public interface RecordBuilderEnhancer {
|
||||
InsnList enhance(Processor processor, TypeElement element, List<String> arguments);
|
||||
}
|
||||
```
|
||||
|
||||
Your custom enhancer is called for Java records that are annotated to list your enhancer class.
|
||||
Your enhancer is created once during the start of the build process and will be called multiple times for each Java record
|
||||
that is annotated with your enhancer. Your enhancer must return a list of instructions to insert or an empty list.
|
||||
The `element` parameter refers to the Java record being currently compiled. `arguments` are any arguments specified in the annotation
|
||||
for the Java record. `Processor` holds utilities useful during enhancing. Look at the builtin enhancers to see details on how to write
|
||||
them - [EmptyNullString](src/main/java/io/soabase/recordbuilder/enhancer/enhancers/EmptyNullString.java) is a good one to start with.
|
||||
|
||||
Build and install your enhancer JAR and you can then use your new custom enhancer as a dependency in other projects and it will be available as an enhancer.
|
||||
Alternatively, if you have a multi-module build your enhancer can be a module and used to enhance the other modules in the
|
||||
project. The [record-builder-test-custom-enhancer](../record-builder-test-custom-enhancer) does this.
|
||||
|
||||
#### How to get the bytecodes for your enhancer
|
||||
|
||||
The trick for getting the bytecodes for your enhancer is to write a simple Java source file that does what you want and then
|
||||
use the java tools to get the bytecodes. For example, let's say you want a custom enhancer that outputs the current date and time to standard
|
||||
out.
|
||||
|
||||
Create a text file called "Temp.java" ala:
|
||||
|
||||
```java
|
||||
import java.time.Instant;
|
||||
|
||||
public class Temp {
|
||||
public Temp() {
|
||||
System.out.println(Instant.now());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
From a terminal compile the class:
|
||||
|
||||
```shell
|
||||
javac Temp.java
|
||||
```
|
||||
|
||||
Then dump the java bytecodes from the compiled class:
|
||||
|
||||
```shell
|
||||
javap -c Temp.class
|
||||
```
|
||||
|
||||
You will see:
|
||||
|
||||
```text
|
||||
Compiled from "Temp.java"
|
||||
public class Temp {
|
||||
public Temp();
|
||||
Code:
|
||||
0: aload_0
|
||||
1: invokespecial #1 // Method java/lang/Object."<init>":()V
|
||||
4: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
|
||||
7: invokestatic #13 // Method java/time/Instant.now:()Ljava/time/Instant;
|
||||
10: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
|
||||
13: return
|
||||
}
|
||||
```
|
||||
|
||||
The first two lines are the call to `super()`. The lines labeld `4`, `7`, and `10` are the bytecodes that you want for your enhancer.
|
||||
|
||||
Your enhance() implementation would look like this:
|
||||
|
||||
```java
|
||||
InsnList insnList = new InsnList();
|
||||
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/time/Instant", "now", "()Ljava/time/Instant;"));
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V"));
|
||||
return insnList;
|
||||
```
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB |
@@ -1,86 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>record-builder-enhancer</artifactId>
|
||||
|
||||
<properties>
|
||||
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
|
||||
<useDependencyReducedPomInJar>true</useDependencyReducedPomInJar>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>picocli</pattern>
|
||||
<shadedPattern>recordbuilder.picocli</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>safe</pattern>
|
||||
<shadedPattern>META-INF.services</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Main-Class>io.soabase.recordbuilder.enhancer.Main</Main-Class>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.soabase.recordbuilder.enhancer.RecordBuilderEnhancerPlugin.adjustedClassName;
|
||||
|
||||
class EnhancersController {
|
||||
private final Map<String, Optional<RecordBuilderEnhancer>> enhancers = new ConcurrentHashMap<>();
|
||||
|
||||
record EnhancerAndArgs(RecordBuilderEnhancer enhancer, List<String> arguments) {}
|
||||
|
||||
List<EnhancerAndArgs> getEnhancers(ProcessorImpl processor, TypeElement typeElement) {
|
||||
return internalGetEnhancers(processor, typeElement).flatMap(spec -> toEnhancer(processor, spec)).toList();
|
||||
}
|
||||
|
||||
private Stream<EnhancerSpec> internalGetEnhancers(ProcessorImpl processor, TypeElement typeElement) {
|
||||
Optional<? extends AnnotationMirror> recordBuilderEnhance = getAnnotationMirror(processor, typeElement);
|
||||
Optional<? extends AnnotationMirror> recordBuilderEnhanceTemplate = getTemplateAnnotationMirror(processor, typeElement);
|
||||
if (recordBuilderEnhance.isPresent() && recordBuilderEnhanceTemplate.isPresent()) {
|
||||
processor.logError("RecordBuilderEnhance and RecordBuilderEnhance.Template cannot be combined.");
|
||||
return Stream.of();
|
||||
}
|
||||
return Stream.concat(recordBuilderEnhance.stream().flatMap(this::getEnhancersAnnotationValue), recordBuilderEnhanceTemplate.stream().flatMap(this::getEnhancersAnnotationValue));
|
||||
}
|
||||
|
||||
private Map<String, Object> getAnnotationValueMap(AnnotationMirror annotationMirror)
|
||||
{
|
||||
return annotationMirror.getElementValues().entrySet().stream()
|
||||
.collect(Collectors.toMap(entry -> entry.getKey().getSimpleName().toString(), entry -> entry.getValue().getValue()));
|
||||
}
|
||||
|
||||
private record EnhancerSpec(String enhancerClass, List<String> arguments) {}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Stream<EnhancerSpec> getEnhancersAnnotationValue(AnnotationMirror annotationMirror)
|
||||
{
|
||||
Map<String, Object> annotationValueMap = getAnnotationValueMap(annotationMirror);
|
||||
|
||||
List<? extends AnnotationValue> argumentsValue = (List<? extends AnnotationValue>) annotationValueMap.getOrDefault("arguments", List.of()); // list of RecordBuilderEnhanceArguments mirrors
|
||||
Map<String, List<String>> argumentsMap = argumentsValue.stream()
|
||||
.flatMap(argumentMirror -> {
|
||||
Map<String, Object> argumentMap = getAnnotationValueMap((AnnotationMirror) argumentMirror.getValue());
|
||||
Object enhancer = argumentMap.get("enhancer");
|
||||
Object arguments = argumentMap.get("arguments");
|
||||
if ((enhancer != null) && (arguments != null)) {
|
||||
List<String> argumentList = ((List<? extends AnnotationValue>) arguments).stream().map(value -> value.getValue().toString()).toList();
|
||||
return Stream.of(Map.entry(enhancer.toString(), argumentList));
|
||||
}
|
||||
return Stream.of();
|
||||
})
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
List<? extends AnnotationValue> enhancers = (List<? extends AnnotationValue>) annotationValueMap.get("enhancers");
|
||||
if (enhancers != null) {
|
||||
return enhancers.stream().map(annotationValue -> {
|
||||
TypeMirror typeMirror = (TypeMirror) annotationValue.getValue();
|
||||
return new EnhancerSpec(typeMirror.toString(), argumentsMap.getOrDefault(typeMirror.toString(), List.of()));
|
||||
});
|
||||
}
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, ? extends AnnotationValue> getArgumentsAnnotations(AnnotationMirror annotationMirror)
|
||||
{
|
||||
return annotationMirror.getElementValues().entrySet().stream()
|
||||
.filter(entry -> entry.getKey().getSimpleName().contentEquals("arguments"))
|
||||
.flatMap(entry -> ((List<? extends AnnotationValue>) entry.getValue().getValue()).stream())
|
||||
.flatMap(annotationValue -> ((AnnotationMirror) annotationValue.getValue()).getElementValues().entrySet().stream()) // now as RecordBuilderEnhanceArguments
|
||||
|
||||
.collect(Collectors.toMap(entry -> entry.getKey().getSimpleName().toString(), Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Optional<? extends AnnotationMirror> getAnnotationMirror(ProcessorImpl processor, TypeElement typeElement) {
|
||||
return processor.elements().getAllAnnotationMirrors(typeElement).stream()
|
||||
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(adjustedClassName(RecordBuilderEnhance.class)))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private Optional<? extends AnnotationMirror> getTemplateAnnotationMirror(ProcessorImpl processor, TypeElement typeElement) {
|
||||
return processor.elements().getAllAnnotationMirrors(typeElement).stream()
|
||||
.flatMap(annotationMirror -> processor.elements().getAllAnnotationMirrors(annotationMirror.getAnnotationType().asElement()).stream())
|
||||
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(adjustedClassName(RecordBuilderEnhance.Template.class)))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private Stream<EnhancerAndArgs> toEnhancer(ProcessorImpl processor, EnhancerSpec spec)
|
||||
{
|
||||
return enhancers.computeIfAbsent(spec.enhancerClass(), __ -> newEnhancer(processor, spec.enhancerClass()))
|
||||
.stream()
|
||||
.map(enhancer -> new EnhancerAndArgs(enhancer, spec.arguments()));
|
||||
}
|
||||
|
||||
private Optional<RecordBuilderEnhancer> newEnhancer(ProcessorImpl processor, String enhancerClass)
|
||||
{
|
||||
try {
|
||||
Class<?> clazz = Class.forName(enhancerClass, true, RecordBuilderEnhancerPlugin.class.getClassLoader());
|
||||
Object enhancer = clazz.getConstructor().newInstance();
|
||||
return Optional.of((RecordBuilderEnhancer) enhancer);
|
||||
} catch (Exception e) {
|
||||
processor.logError("Could not create enhancer instance. type=%s exception=%s message=%s".formatted(enhancerClass, e.getClass().getSimpleName(), e.getMessage()));
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
CommandLine commandLine = new CommandLine(new PluginOptions());
|
||||
commandLine.usage(System.out);
|
||||
}
|
||||
|
||||
private Main() {
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Command(name = "RecordBuilder Enhancer",
|
||||
description = "Enhances Java record class files with validations, preconditions, etc. See https://github.com/Randgalt/record-builder for details",
|
||||
usageHelpAutoWidth = true)
|
||||
class PluginOptions {
|
||||
@Parameters(paramLabel = "DIRECTORY", arity = "0..1", description = "The build's output directory - i.e. where javac writes generated classes. The value can be a full path or a relative path. If not provided the Enhancer plugin will attempt to use standard directories.")
|
||||
String directory = "";
|
||||
|
||||
@Option(names = {"-h", "--help"}, description = "Outputs this help")
|
||||
boolean helpRequested = false;
|
||||
|
||||
@Option(names = {"-v", "--verbose"}, description = "Verbose output during compilation")
|
||||
boolean verbose = false;
|
||||
|
||||
@Option(names = {"--disable"}, description = "Deactivate/disable the plugin")
|
||||
boolean disable = false;
|
||||
|
||||
@Option(names = {"--dryRun"}, description = "Dry run only - doesn't modify any classes. You should enable verbose as well via: -v")
|
||||
boolean dryRun = false;
|
||||
|
||||
@Option(names = {"--outputDirectory"}, description = "Optional alternate output directory for enhanced class files")
|
||||
File outputTo = null;
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.Trees;
|
||||
import io.soabase.recordbuilder.enhancer.EnhancersController.EnhancerAndArgs;
|
||||
import io.soabase.recordbuilder.enhancer.spi.Entry;
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
class ProcessorImpl
|
||||
implements Processor {
|
||||
private final Collection<? extends Class<? extends RecordBuilderEnhancer>> enhancers;
|
||||
private final Elements elements;
|
||||
private final Types types;
|
||||
private final Trees trees;
|
||||
private final CompilationUnitTree compilationUnit;
|
||||
private final boolean verboseRequested;
|
||||
|
||||
ProcessorImpl(Elements elements, Types types, Trees trees, CompilationUnitTree compilationUnit, boolean verboseRequested) {
|
||||
this(Set.of(), elements, types, trees, compilationUnit, verboseRequested);
|
||||
}
|
||||
|
||||
private ProcessorImpl(Collection<? extends Class<? extends RecordBuilderEnhancer>> enhancers, Elements elements, Types types, Trees trees, CompilationUnitTree compilationUnit, boolean verboseRequested) {
|
||||
this.enhancers = enhancers;
|
||||
this.elements = elements;
|
||||
this.types = types;
|
||||
this.trees = trees;
|
||||
this.compilationUnit = compilationUnit;
|
||||
this.verboseRequested = verboseRequested;
|
||||
}
|
||||
|
||||
ProcessorImpl withEnhancers(List<EnhancerAndArgs> enhancers)
|
||||
{
|
||||
Collection<? extends Class<? extends RecordBuilderEnhancer>> enhancersList = enhancers.stream().map(enhancerAndArgs -> enhancerAndArgs.enhancer().getClass()).toList();
|
||||
return new ProcessorImpl(enhancersList, elements, types, trees, compilationUnit, verboseRequested);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verboseRequested() {
|
||||
return verboseRequested;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Entry> asEntries(TypeElement element) {
|
||||
List<? extends RecordComponentElement> recordComponents = element.getRecordComponents();
|
||||
return IntStream.range(0, recordComponents.size())
|
||||
.mapToObj(index -> new Entry(index + 1, recordComponents.get(index), types().erasure(recordComponents.get(index).asType())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEnhancer(Class<? extends RecordBuilderEnhancer> enhancer) {
|
||||
return enhancers.contains(enhancer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(CharSequence msg) {
|
||||
printMessage(Diagnostic.Kind.NOTE, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarning(CharSequence msg) {
|
||||
printMessage(Diagnostic.Kind.MANDATORY_WARNING, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logError(CharSequence msg) {
|
||||
msg += " - Use -h for help in your -Xplugin arguments";
|
||||
printMessage(Diagnostic.Kind.ERROR, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Elements elements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Types types() {
|
||||
return types;
|
||||
}
|
||||
|
||||
private void printMessage(Diagnostic.Kind kind, CharSequence msg) {
|
||||
trees.printMessage(kind, "[RecordBuilder Enhancer] " + msg, compilationUnit, compilationUnit);
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.Plugin;
|
||||
import com.sun.source.util.TaskEvent;
|
||||
import com.sun.source.util.TaskListener;
|
||||
import io.soabase.recordbuilder.enhancer.EnhancersController.EnhancerAndArgs;
|
||||
import io.soabase.recordbuilder.enhancer.Session.FileStreams;
|
||||
import picocli.CommandLine;
|
||||
import recordbuilder.org.objectweb.asm.ClassReader;
|
||||
import recordbuilder.org.objectweb.asm.ClassWriter;
|
||||
import recordbuilder.org.objectweb.asm.Opcodes;
|
||||
import recordbuilder.org.objectweb.asm.tree.*;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RecordBuilderEnhancerPlugin
|
||||
implements Plugin, TaskListener {
|
||||
private volatile Session session;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "recordbuilderenhancer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean autoStart() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(JavacTask task, String... args) {
|
||||
PluginOptions pluginOptions = new PluginOptions();
|
||||
CommandLine commandLine = new CommandLine(pluginOptions);
|
||||
commandLine.parseArgs(args);
|
||||
if (!pluginOptions.disable) {
|
||||
session = new Session(task, this, pluginOptions, commandLine);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finished(TaskEvent taskEvent) {
|
||||
if (taskEvent.getKind() == TaskEvent.Kind.GENERATE) {
|
||||
TypeElement typeElement = taskEvent.getTypeElement();
|
||||
ProcessorImpl processor = session.newProcessor(taskEvent);
|
||||
session.checkPrintHelp(processor);
|
||||
List<EnhancerAndArgs> enhancers = session.enhancersController().getEnhancers(processor, typeElement);
|
||||
if (!enhancers.isEmpty()) {
|
||||
if (typeElement.getRecordComponents().isEmpty()) {
|
||||
processor.logError(typeElement.getQualifiedName() + " is not a record");
|
||||
} else {
|
||||
if (processor.verboseRequested()) {
|
||||
processor.logWarning("Enhancing %s with %s".formatted(typeElement.getSimpleName(), enhancers.stream().map(enhancer -> enhancer.getClass().getName()).collect(Collectors.joining(","))));
|
||||
}
|
||||
session.getFileStreams(processor, typeElement, taskEvent.getCompilationUnit().getSourceFile().toUri()).ifPresent(fileStreams -> enhance(typeElement, processor.withEnhancers(enhancers), enhancers, fileStreams));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeAndSaveSuperCall(MethodNode constructor, InsnList insnList) {
|
||||
ListIterator<AbstractInsnNode> iterator = constructor.instructions.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
AbstractInsnNode node = iterator.next();
|
||||
iterator.remove();
|
||||
insnList.add(node);
|
||||
if ((node.getOpcode() == Opcodes.INVOKESPECIAL) && ((MethodInsnNode) node).owner.equals("java/lang/Record") && ((MethodInsnNode) node).name.equals("<init>")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void enhance(TypeElement typeElement, ProcessorImpl processor, List<EnhancerAndArgs> specs, FileStreams fileStreams) {
|
||||
try {
|
||||
ClassNode classNode = new ClassNode();
|
||||
try (InputStream in = fileStreams.openInputStream()) {
|
||||
ClassReader classReader = new ClassReader(in);
|
||||
classReader.accept(classNode, 0);
|
||||
}
|
||||
InsnList insnList = new InsnList();
|
||||
MethodNode constructor = findConstructor(classNode).orElseThrow(() -> new IllegalStateException("Could not find default constructor"));
|
||||
if (!removeAndSaveSuperCall(constructor, insnList)) {
|
||||
processor.logError("Unrecognized constructor - missing super() call.");
|
||||
return;
|
||||
}
|
||||
specs.stream()
|
||||
.map(spec -> spec.enhancer().enhance(processor, typeElement, spec.arguments()))
|
||||
.forEach(insnList::add);
|
||||
constructor.instructions.insert(insnList);
|
||||
|
||||
ClassWriter classWriter = new ClassWriter(Opcodes.ASM9 | ClassWriter.COMPUTE_FRAMES);
|
||||
classNode.accept(classWriter);
|
||||
|
||||
if (!session.isDryRun()) {
|
||||
try (OutputStream out = fileStreams.openOutputStream()) {
|
||||
out.write(classWriter.toByteArray());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
processor.logError("Could not process " + typeElement.getQualifiedName() + " - " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static String adjustedClassName(Class<?> clazz) {
|
||||
return clazz.getName().replace('$', '.');
|
||||
}
|
||||
|
||||
private static Optional<MethodNode> findConstructor(ClassNode classNode) {
|
||||
String defaultConstructorDescription = classNode.recordComponents.stream()
|
||||
.map(recordComponentNode -> recordComponentNode.descriptor)
|
||||
.collect(Collectors.joining("", "(", ")V"));
|
||||
return classNode.methods.stream()
|
||||
.filter(methodNode -> methodNode.name.equals("<init>") && methodNode.desc.equals(defaultConstructorDescription))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.TaskEvent;
|
||||
import com.sun.source.util.TaskListener;
|
||||
import com.sun.source.util.Trees;
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.*;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class Session implements SessionFlag {
|
||||
private final JavacTask task;
|
||||
private final StandardJavaFileManager fileManager;
|
||||
private final Trees trees;
|
||||
private final EnhancersController enhancersController;
|
||||
private final PluginOptions pluginOptions;
|
||||
private final CommandLine commandLine;
|
||||
|
||||
public Session(JavacTask task, TaskListener taskListener, PluginOptions pluginOptions, CommandLine commandLine) {
|
||||
this.task = task;
|
||||
this.pluginOptions = pluginOptions;
|
||||
this.commandLine = commandLine;
|
||||
task.addTaskListener(taskListener);
|
||||
enhancersController = new EnhancersController();
|
||||
//PluginOptions.setupCommandLine(this.commandLine, enhancersController.enhancers(), true);
|
||||
fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
|
||||
try {
|
||||
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(new File(this.pluginOptions.directory).getAbsoluteFile()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not set the class output path", e);
|
||||
}
|
||||
|
||||
trees = Trees.instance(task);
|
||||
|
||||
if (pluginOptions.outputTo != null) {
|
||||
if (!pluginOptions.outputTo.isDirectory() && !pluginOptions.outputTo.exists() && !pluginOptions.outputTo.mkdirs()) {
|
||||
throw new RuntimeException("Could not create directory: " + pluginOptions.outputTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean enabled()
|
||||
{
|
||||
return !pluginOptions.disable;
|
||||
}
|
||||
|
||||
public boolean isDryRun()
|
||||
{
|
||||
return pluginOptions.dryRun;
|
||||
}
|
||||
|
||||
public JavacTask task() {
|
||||
return task;
|
||||
}
|
||||
|
||||
public interface FileStreams
|
||||
{
|
||||
InputStream openInputStream() throws IOException;
|
||||
|
||||
OutputStream openOutputStream() throws IOException;
|
||||
}
|
||||
|
||||
public Optional<FileStreams> getFileStreams(Processor processor, TypeElement element, URI fileUri) {
|
||||
String directory = pluginOptions.directory;
|
||||
if (directory.isEmpty()) {
|
||||
String pathToFile = fileUri.getPath();
|
||||
int srcMainJavaIndex = pathToFile.indexOf("src/main/java");
|
||||
if (srcMainJavaIndex >= 0) {
|
||||
directory = pathToFile.substring(0, srcMainJavaIndex) + "target/classes";
|
||||
}
|
||||
else {
|
||||
directory = ".";
|
||||
}
|
||||
}
|
||||
String className = getClassName(element);
|
||||
File absoluteDirectory = new File(directory).getAbsoluteFile();
|
||||
if (pluginOptions.verbose) {
|
||||
processor.logWarning("Using classPath %s for class %s".formatted(absoluteDirectory, className));
|
||||
}
|
||||
try {
|
||||
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(absoluteDirectory));
|
||||
JavaFileObject inputJavaFile = fileManager.getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, className, JavaFileObject.Kind.CLASS, null);
|
||||
JavaFileObject outputJavaFile;
|
||||
if (pluginOptions.outputTo != null) {
|
||||
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(pluginOptions.outputTo));
|
||||
outputJavaFile = fileManager.getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, className, JavaFileObject.Kind.CLASS, null);
|
||||
}
|
||||
else {
|
||||
outputJavaFile = inputJavaFile;
|
||||
}
|
||||
FileStreams fileStreams = new FileStreams() {
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return inputJavaFile.openInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return outputJavaFile.openOutputStream();
|
||||
}
|
||||
};
|
||||
return Optional.of(fileStreams);
|
||||
} catch (IOException e) {
|
||||
processor.logError("Could not set classpath to %s for class %s: %s".formatted(absoluteDirectory, className, e.getMessage()));
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public EnhancersController enhancersController() {
|
||||
return enhancersController;
|
||||
}
|
||||
|
||||
public ProcessorImpl newProcessor(TaskEvent taskEvent)
|
||||
{
|
||||
return new ProcessorImpl(task.getElements(), task.getTypes(), trees, taskEvent.getCompilationUnit(), pluginOptions.verbose);
|
||||
}
|
||||
|
||||
public void checkPrintHelp(Processor processor)
|
||||
{
|
||||
if (pluginOptions.helpRequested && getAndSetFlag("show-help")) {
|
||||
StringWriter help = new StringWriter();
|
||||
help.write('\n');
|
||||
commandLine.usage(new PrintWriter(help));
|
||||
processor.logError(help.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAndSetFlag(String name)
|
||||
{
|
||||
boolean wasSet = isSet(name);
|
||||
if (!wasSet) {
|
||||
set(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getClassName(TypeElement typeElement) {
|
||||
return task.getElements().getPackageOf(typeElement).getQualifiedName() + "." + getClassName(typeElement, "");
|
||||
}
|
||||
|
||||
private static String getClassName(Element element, String separator) {
|
||||
// prefix enclosing class names if nested in a class
|
||||
if (element instanceof TypeElement) {
|
||||
return getClassName(element.getEnclosingElement(), "$") + element.getSimpleName().toString() + separator;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private boolean isSet(String name)
|
||||
{
|
||||
return Boolean.parseBoolean(System.getProperty(toKey(name)));
|
||||
}
|
||||
|
||||
private void set(String name)
|
||||
{
|
||||
System.setProperty(toKey(name), "true");
|
||||
}
|
||||
|
||||
private String toKey(String name) {
|
||||
return Session.class.getName() + ":" + name;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SessionFlag {
|
||||
boolean getAndSetFlag(String name);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
public class CopyCollection extends CopyCollectionBase {
|
||||
@Override
|
||||
protected boolean isInterface() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String mapMethod() {
|
||||
return "(Ljava/util/Map;)Ljava/util/Map;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String setMethod() {
|
||||
return "(Ljava/util/Collection;)Ljava/util/Set;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String listMethod() {
|
||||
return "(Ljava/util/Collection;)Ljava/util/List;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String map() {
|
||||
return "java/util/Map";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String set() {
|
||||
return "java/util/Set";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String list() {
|
||||
return "java/util/List";
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
import recordbuilder.org.objectweb.asm.Opcodes;
|
||||
import recordbuilder.org.objectweb.asm.tree.InsnList;
|
||||
import recordbuilder.org.objectweb.asm.tree.MethodInsnNode;
|
||||
import recordbuilder.org.objectweb.asm.tree.VarInsnNode;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class CopyCollectionBase
|
||||
implements RecordBuilderEnhancer {
|
||||
@Override
|
||||
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
|
||||
TypeMirror collectionType = processor.elements().getTypeElement("java.util.Collection").asType();
|
||||
TypeMirror listType = processor.elements().getTypeElement("java.util.List").asType();
|
||||
TypeMirror mapType = processor.elements().getTypeElement("java.util.Map").asType();
|
||||
|
||||
InsnList insnList = new InsnList();
|
||||
processor.asEntries(element).stream()
|
||||
.filter(entry -> processor.types().isAssignable(entry.erasedType(), collectionType) || processor.types().isAssignable(entry.erasedType(), mapType))
|
||||
.forEach(entry -> {
|
||||
// aload_1
|
||||
// invokestatic #7 // InterfaceMethod java/util/List.copyOf:(Ljava/util/Collection;)Ljava/util/Set;
|
||||
// astore_1
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
if (processor.types().isAssignable(entry.erasedType(), listType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, list(), "copyOf", listMethod(), isInterface()));
|
||||
} else if (processor.types().isAssignable(entry.erasedType(), collectionType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, set(), "copyOf", setMethod(), isInterface()));
|
||||
} else {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, map(), "copyOf", mapMethod(), isInterface()));
|
||||
}
|
||||
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
|
||||
});
|
||||
return insnList;
|
||||
}
|
||||
|
||||
abstract protected boolean isInterface();
|
||||
|
||||
abstract protected String mapMethod();
|
||||
|
||||
abstract protected String setMethod();
|
||||
|
||||
abstract protected String listMethod();
|
||||
|
||||
abstract protected String map();
|
||||
|
||||
abstract protected String set();
|
||||
|
||||
abstract protected String list();
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
public class CopyCollectionNullableEmpty extends CopyCollectionNullableEmptyBase {
|
||||
@Override
|
||||
protected boolean isInterface() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String mapEmptyMethod() {
|
||||
return "()Ljava/util/Map;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String setEmptyMethod() {
|
||||
return "()Ljava/util/Set;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String listEmptyMethod() {
|
||||
return "()Ljava/util/List;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String mapCopyMethod() {
|
||||
return "(Ljava/util/Map;)Ljava/util/Map;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String setCopyMethod() {
|
||||
return "(Ljava/util/Collection;)Ljava/util/Set;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String listCopyMethod() {
|
||||
return "(Ljava/util/Collection;)Ljava/util/List;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String map() {
|
||||
return "java/util/Map";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String set() {
|
||||
return "java/util/Set";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String list() {
|
||||
return "java/util/List";
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
import recordbuilder.org.objectweb.asm.Label;
|
||||
import recordbuilder.org.objectweb.asm.Opcodes;
|
||||
import recordbuilder.org.objectweb.asm.tree.*;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class CopyCollectionNullableEmptyBase
|
||||
implements RecordBuilderEnhancer {
|
||||
@Override
|
||||
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
|
||||
TypeMirror collectionType = processor.elements().getTypeElement("java.util.Collection").asType();
|
||||
TypeMirror listType = processor.elements().getTypeElement("java.util.List").asType();
|
||||
TypeMirror mapType = processor.elements().getTypeElement("java.util.Map").asType();
|
||||
|
||||
InsnList insnList = new InsnList();
|
||||
processor.asEntries(element).stream()
|
||||
.filter(entry -> processor.types().isAssignable(entry.erasedType(), collectionType) || processor.types().isAssignable(entry.erasedType(), mapType))
|
||||
.forEach(entry -> {
|
||||
/*
|
||||
4: aload_1
|
||||
5: ifnull 15
|
||||
8: aload_1
|
||||
9: invokestatic #7 // InterfaceMethod java/util/Set.copyOf:(Ljava/util/Collection;)Ljava/util/Set;
|
||||
12: goto 18
|
||||
15: invokestatic #13 // InterfaceMethod java/util/Set.of:()Ljava/util/Set;
|
||||
18: astore_1
|
||||
*/
|
||||
LabelNode doEmptylabel = new LabelNode(new Label());
|
||||
LabelNode doCopylabel = new LabelNode(new Label());
|
||||
LabelNode doAssignlabel = new LabelNode(new Label());
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
insnList.add(new JumpInsnNode(Opcodes.IFNULL, doEmptylabel));
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
if (processor.types().isAssignable(entry.erasedType(), listType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, list(), "copyOf", listCopyMethod(), isInterface()));
|
||||
} else if (processor.types().isAssignable(entry.erasedType(), collectionType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, set(), "copyOf", setCopyMethod(), isInterface()));
|
||||
} else {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, map(), "copyOf", mapCopyMethod(), isInterface()));
|
||||
}
|
||||
insnList.add(new JumpInsnNode(Opcodes.GOTO, doAssignlabel));
|
||||
insnList.add(doEmptylabel);
|
||||
if (processor.types().isAssignable(entry.erasedType(), listType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, list(), "of", listEmptyMethod(), isInterface()));
|
||||
} else if (processor.types().isAssignable(entry.erasedType(), collectionType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, set(), "of", setEmptyMethod(), isInterface()));
|
||||
} else {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, map(), "of", mapEmptyMethod(), isInterface()));
|
||||
}
|
||||
insnList.add(doAssignlabel);
|
||||
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
|
||||
});
|
||||
return insnList;
|
||||
}
|
||||
|
||||
abstract protected boolean isInterface();
|
||||
|
||||
abstract protected String mapEmptyMethod();
|
||||
|
||||
abstract protected String setEmptyMethod();
|
||||
|
||||
abstract protected String listEmptyMethod();
|
||||
|
||||
abstract protected String mapCopyMethod();
|
||||
|
||||
abstract protected String setCopyMethod();
|
||||
|
||||
abstract protected String listCopyMethod();
|
||||
|
||||
abstract protected String map();
|
||||
|
||||
abstract protected String set();
|
||||
|
||||
abstract protected String list();
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
import recordbuilder.org.objectweb.asm.Label;
|
||||
import recordbuilder.org.objectweb.asm.Opcodes;
|
||||
import recordbuilder.org.objectweb.asm.tree.*;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
|
||||
public class EmptyNullOptional
|
||||
implements RecordBuilderEnhancer {
|
||||
@Override
|
||||
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
|
||||
TypeMirror optionalType = processor.elements().getTypeElement("java.util.Optional").asType();
|
||||
TypeMirror optionalIntType = processor.elements().getTypeElement("java.util.OptionalInt").asType();
|
||||
TypeMirror optionalLongType = processor.elements().getTypeElement("java.util.OptionalLong").asType();
|
||||
TypeMirror optionalDouble = processor.elements().getTypeElement("java.util.OptionalDouble").asType();
|
||||
|
||||
InsnList insnList = new InsnList();
|
||||
processor.asEntries(element)
|
||||
.stream()
|
||||
.filter(entry -> processor.types().isAssignable(entry.erasedType(), optionalType) || processor.types().isAssignable(entry.erasedType(), optionalIntType) || processor.types().isAssignable(entry.erasedType(), optionalLongType) || processor.types().isAssignable(entry.erasedType(), optionalDouble))
|
||||
.forEach(entry -> {
|
||||
/*
|
||||
4: aload 5
|
||||
6: ifnull 14
|
||||
9: aload 5
|
||||
11: goto 17
|
||||
14: invokestatic #7 // Method java/util/Optional.empty:()Ljava/util/Optional;
|
||||
17: astore 5
|
||||
*/
|
||||
LabelNode doEmptylabel = new LabelNode(new Label());
|
||||
LabelNode doAssignlabel = new LabelNode(new Label());
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
insnList.add(new JumpInsnNode(Opcodes.IFNULL, doEmptylabel));
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
insnList.add(new JumpInsnNode(Opcodes.GOTO, doAssignlabel));
|
||||
insnList.add(doEmptylabel);
|
||||
if (processor.types().isAssignable(entry.erasedType(), optionalIntType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/OptionalInt", "empty", "()Ljava/util/OptionalInt;"));
|
||||
}
|
||||
else if (processor.types().isAssignable(entry.erasedType(), optionalLongType)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/OptionalLong", "empty", "()Ljava/util/OptionalLong;"));
|
||||
}
|
||||
else if (processor.types().isAssignable(entry.erasedType(), optionalDouble)) {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/OptionalDouble", "empty", "()Ljava/util/OptionalDouble;"));
|
||||
}
|
||||
else {
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/Optional", "empty", "()Ljava/util/Optional;"));
|
||||
}
|
||||
insnList.add(doAssignlabel);
|
||||
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
|
||||
});
|
||||
return insnList;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
import recordbuilder.org.objectweb.asm.Label;
|
||||
import recordbuilder.org.objectweb.asm.Opcodes;
|
||||
import recordbuilder.org.objectweb.asm.tree.*;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
|
||||
public class EmptyNullString
|
||||
implements RecordBuilderEnhancer {
|
||||
@Override
|
||||
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
|
||||
TypeMirror stringType = processor.elements().getTypeElement("java.lang.String").asType();
|
||||
InsnList insnList = new InsnList();
|
||||
processor.asEntries(element)
|
||||
.stream()
|
||||
.filter(entry -> processor.types().isAssignable(entry.erasedType(), stringType))
|
||||
.forEach(entry -> {
|
||||
/*
|
||||
4: aload_1
|
||||
5: ifnull 12
|
||||
8: aload_1
|
||||
9: goto 14
|
||||
12: ldc #7 // String
|
||||
14: astore_1
|
||||
*/
|
||||
LabelNode doEmptylabel = new LabelNode(new Label());
|
||||
LabelNode doAssignlabel = new LabelNode(new Label());
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
insnList.add(new JumpInsnNode(Opcodes.IFNULL, doEmptylabel));
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
insnList.add(new JumpInsnNode(Opcodes.GOTO, doAssignlabel));
|
||||
insnList.add(doEmptylabel);
|
||||
insnList.add(new LdcInsnNode(""));
|
||||
insnList.add(doAssignlabel);
|
||||
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
|
||||
});
|
||||
return insnList;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
public class GuavaCopyCollection extends CopyCollectionBase {
|
||||
@Override
|
||||
protected boolean isInterface() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String mapMethod() {
|
||||
return "(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String setMethod() {
|
||||
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableSet;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String listMethod() {
|
||||
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String map() {
|
||||
return "com/google/common/collect/ImmutableMap";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String set() {
|
||||
return "com/google/common/collect/ImmutableSet";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String list() {
|
||||
return "com/google/common/collect/ImmutableList";
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
public class GuavaCopyCollectionNullableEmpty extends CopyCollectionNullableEmptyBase {
|
||||
@Override
|
||||
protected boolean isInterface() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String mapEmptyMethod() {
|
||||
return "()Lcom/google/common/collect/ImmutableMap;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String setEmptyMethod() {
|
||||
return "()Lcom/google/common/collect/ImmutableSet;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String listEmptyMethod() {
|
||||
return "()Lcom/google/common/collect/ImmutableList;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String mapCopyMethod() {
|
||||
return "(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String setCopyMethod() {
|
||||
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableSet;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String listCopyMethod() {
|
||||
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String map() {
|
||||
return "com/google/common/collect/ImmutableMap";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String set() {
|
||||
return "com/google/common/collect/ImmutableSet";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String list() {
|
||||
return "com/google/common/collect/ImmutableList";
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Entry;
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
import recordbuilder.org.objectweb.asm.tree.InsnList;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class NotNullAnnotations
|
||||
implements RecordBuilderEnhancer
|
||||
{
|
||||
public static final String DEFAULT_EXPRESSION = "(notnull)|(nonnull)";
|
||||
|
||||
@Override
|
||||
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
|
||||
InsnList insnList = new InsnList();
|
||||
if (arguments.size() > 1) {
|
||||
processor.logError("Too many arguments to NotNullAnnotations.");
|
||||
} else {
|
||||
String expression = arguments.isEmpty() ? DEFAULT_EXPRESSION : arguments.get(0);
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE);
|
||||
processor.asEntries(element)
|
||||
.stream()
|
||||
.filter(entry -> !entry.erasedType().getKind().isPrimitive())
|
||||
.filter(entry -> hasMatchingAnnotation(entry, pattern))
|
||||
.forEach(entry -> RequireNonNull.enhance(insnList, entry));
|
||||
} catch (PatternSyntaxException e) {
|
||||
processor.logError("Bad argument to NotNullAnnotations: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return insnList;
|
||||
}
|
||||
|
||||
private boolean hasMatchingAnnotation(Entry entry, Pattern pattern)
|
||||
{
|
||||
Stream<? extends AnnotationMirror> typeMirrors = entry.element().asType().getAnnotationMirrors().stream();
|
||||
Stream<? extends AnnotationMirror> elementMirrors = entry.element().getAnnotationMirrors().stream();
|
||||
return Stream.concat(typeMirrors, elementMirrors)
|
||||
.anyMatch(mirror -> pattern.matcher(mirror.getAnnotationType().asElement().getSimpleName().toString()).matches());
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Entry;
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class ProcessorUtil {
|
||||
private ProcessorUtil() {
|
||||
}
|
||||
|
||||
static boolean isNotHandledByOthers(Class<? extends RecordBuilderEnhancer> enhancer, Processor processor, Entry entry, TypeMirror... types)
|
||||
{
|
||||
if (!processor.hasEnhancer(enhancer)) {
|
||||
return true;
|
||||
}
|
||||
if (types == null) {
|
||||
return true;
|
||||
}
|
||||
return Stream.of(types).noneMatch(type -> processor.types().isAssignable(entry.erasedType(), type));
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.enhancers;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Entry;
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
import recordbuilder.org.objectweb.asm.Opcodes;
|
||||
import recordbuilder.org.objectweb.asm.tree.*;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
|
||||
import static io.soabase.recordbuilder.enhancer.enhancers.ProcessorUtil.isNotHandledByOthers;
|
||||
|
||||
public class RequireNonNull
|
||||
implements RecordBuilderEnhancer {
|
||||
@Override
|
||||
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
|
||||
TypeMirror optionalType = processor.elements().getTypeElement("java.util.Optional").asType();
|
||||
TypeMirror stringType = processor.elements().getTypeElement("java.lang.String").asType();
|
||||
TypeMirror collectionType = processor.elements().getTypeElement("java.util.Collection").asType();
|
||||
TypeMirror listType = processor.elements().getTypeElement("java.util.List").asType();
|
||||
TypeMirror mapType = processor.elements().getTypeElement("java.util.Map").asType();
|
||||
|
||||
InsnList insnList = new InsnList();
|
||||
processor.asEntries(element)
|
||||
.stream()
|
||||
.filter(entry -> !entry.erasedType().getKind().isPrimitive())
|
||||
.filter(entry -> isNotHandledByOthers(EmptyNullOptional.class, processor, entry, optionalType))
|
||||
.filter(entry -> isNotHandledByOthers(EmptyNullString.class, processor, entry, stringType))
|
||||
.filter(entry -> isNotHandledByOthers(CopyCollectionNullableEmpty.class, processor, entry, collectionType, listType, mapType))
|
||||
.filter(entry -> isNotHandledByOthers(CopyCollection.class, processor, entry, collectionType, listType, mapType))
|
||||
.filter(entry -> isNotHandledByOthers(GuavaCopyCollectionNullableEmpty.class, processor, entry, collectionType, listType, mapType))
|
||||
.filter(entry -> isNotHandledByOthers(GuavaCopyCollection.class, processor, entry, collectionType, listType, mapType))
|
||||
.forEach(entry -> enhance(insnList, entry));
|
||||
return insnList;
|
||||
}
|
||||
|
||||
static void enhance(InsnList insnList, Entry entry) {
|
||||
// java.util.Objects.requireNonNull(var1, "<name> is null");
|
||||
/*
|
||||
ALOAD 1
|
||||
LDC "X is null"
|
||||
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
|
||||
POP
|
||||
*/
|
||||
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
|
||||
insnList.add(new LdcInsnNode("%s is null".formatted(entry.element().getSimpleName())));
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/Objects", "requireNonNull", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"));
|
||||
insnList.add(new InsnNode(Opcodes.POP));
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
io.soabase.recordbuilder.enhancer.RecordBuilderEnhancerPlugin
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>34-SNAPSHOT</version>
|
||||
<version>21</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
|
||||
class CollectionBuilderUtils {
|
||||
private final boolean useImmutableCollections;
|
||||
private final boolean addSingleItemCollectionBuilders;
|
||||
private final String listShimName;
|
||||
private final String mapShimName;
|
||||
private final String setShimName;
|
||||
private final String collectionShimName;
|
||||
|
||||
private boolean needsListShim;
|
||||
private boolean needsMapShim;
|
||||
private boolean needsSetShim;
|
||||
private boolean needsCollectionShim;
|
||||
|
||||
private static final TypeName listType = TypeName.get(List.class);
|
||||
private static final TypeName mapType = TypeName.get(Map.class);
|
||||
private static final TypeName setType = TypeName.get(Set.class);
|
||||
private static final TypeName collectionType = TypeName.get(Collection.class);
|
||||
|
||||
private static final TypeVariableName tType = TypeVariableName.get("T");
|
||||
private static final TypeVariableName kType = TypeVariableName.get("K");
|
||||
private static final TypeVariableName vType = TypeVariableName.get("V");
|
||||
private static final ParameterizedTypeName parameterizedListType = ParameterizedTypeName.get(ClassName.get(List.class), tType);
|
||||
private static final ParameterizedTypeName parameterizedMapType = ParameterizedTypeName.get(ClassName.get(Map.class), kType, vType);
|
||||
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName.get(ClassName.get(Set.class), tType);
|
||||
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName.get(ClassName.get(Collection.class), tType);
|
||||
|
||||
CollectionBuilderUtils(List<RecordClassType> recordComponents, RecordBuilder.Options metaData) {
|
||||
useImmutableCollections = metaData.useImmutableCollections();
|
||||
addSingleItemCollectionBuilders = metaData.addSingleItemCollectionBuilders();
|
||||
|
||||
listShimName = adjustShimName(recordComponents, "__list", 0);
|
||||
mapShimName = adjustShimName(recordComponents, "__map", 0);
|
||||
setShimName = adjustShimName(recordComponents, "__set", 0);
|
||||
collectionShimName = adjustShimName(recordComponents, "__collection", 0);
|
||||
}
|
||||
|
||||
enum SingleItemsMetaDataMode {
|
||||
STANDARD,
|
||||
STANDARD_FOR_SETTER,
|
||||
EXCLUDE_WILDCARD_TYPES
|
||||
}
|
||||
|
||||
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {}
|
||||
|
||||
Optional<SingleItemsMetaData> singleItemsMetaData(RecordClassType component, SingleItemsMetaDataMode mode) {
|
||||
if (addSingleItemCollectionBuilders && (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)) {
|
||||
Class<?> collectionClass = null;
|
||||
ClassName wildcardClass = null;
|
||||
int typeArgumentQty = 0;
|
||||
if (isList(component)) {
|
||||
collectionClass = ArrayList.class;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isSet(component)) {
|
||||
collectionClass = HashSet.class;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isMap(component)) {
|
||||
collectionClass = HashMap.class;
|
||||
wildcardClass = (ClassName) component.rawTypeName();
|
||||
typeArgumentQty = 2;
|
||||
}
|
||||
var hasWildcardTypeArguments = hasWildcardTypeArguments(parameterizedTypeName, typeArgumentQty);
|
||||
if (collectionClass != null) {
|
||||
return switch (mode) {
|
||||
case STANDARD -> singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||
|
||||
case STANDARD_FOR_SETTER -> {
|
||||
if (hasWildcardTypeArguments) {
|
||||
yield Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, component.typeName()));
|
||||
}
|
||||
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||
}
|
||||
|
||||
case EXCLUDE_WILDCARD_TYPES -> {
|
||||
if (hasWildcardTypeArguments) {
|
||||
yield Optional.empty();
|
||||
}
|
||||
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
boolean isImmutableCollection(RecordClassType component) {
|
||||
return useImmutableCollections && (isList(component) || isMap(component) || isSet(component) || component.rawTypeName().equals(collectionType));
|
||||
}
|
||||
|
||||
boolean isList(RecordClassType component) {
|
||||
return component.rawTypeName().equals(listType);
|
||||
}
|
||||
|
||||
boolean isMap(RecordClassType component) {
|
||||
return component.rawTypeName().equals(mapType);
|
||||
}
|
||||
|
||||
boolean isSet(RecordClassType component) {
|
||||
return component.rawTypeName().equals(setType);
|
||||
}
|
||||
|
||||
void add(CodeBlock.Builder builder, RecordClassType component) {
|
||||
if (useImmutableCollections) {
|
||||
if (isList(component)) {
|
||||
needsListShim = true;
|
||||
builder.add("$L($L)", listShimName, component.name());
|
||||
} else if (isMap(component)) {
|
||||
needsMapShim = true;
|
||||
builder.add("$L($L)", mapShimName, component.name());
|
||||
} else if (isSet(component)) {
|
||||
needsSetShim = true;
|
||||
builder.add("$L($L)", setShimName, component.name());
|
||||
} else if (component.rawTypeName().equals(collectionType)) {
|
||||
needsCollectionShim = true;
|
||||
builder.add("$L($L)", collectionShimName, component.name());
|
||||
} else {
|
||||
builder.add("$L", component.name());
|
||||
}
|
||||
} else {
|
||||
builder.add("$L", component.name());
|
||||
}
|
||||
}
|
||||
|
||||
void addShims(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsListShim) {
|
||||
builder.addMethod(buildMethod(listShimName, listType, parameterizedListType, tType));
|
||||
}
|
||||
if (needsSetShim) {
|
||||
builder.addMethod(buildMethod(setShimName, setType, parameterizedSetType, tType));
|
||||
}
|
||||
if (needsMapShim) {
|
||||
builder.addMethod(buildMethod(mapShimName, mapType, parameterizedMapType, kType, vType));
|
||||
}
|
||||
if (needsCollectionShim) {
|
||||
builder.addMethod(buildCollectionsMethod());
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<SingleItemsMetaData> singleItemsMetaDataWithWildType(ParameterizedTypeName parameterizedTypeName, Class<?> collectionClass, ClassName wildcardClass, int typeArgumentQty) {
|
||||
TypeName wildType;
|
||||
if (typeArgumentQty == 1) {
|
||||
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)));
|
||||
} else { // if (typeArgumentQty == 2)
|
||||
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)), WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(1)));
|
||||
}
|
||||
return Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, wildType));
|
||||
}
|
||||
|
||||
private boolean hasWildcardTypeArguments(ParameterizedTypeName parameterizedTypeName, int argumentCount) {
|
||||
for (int i = 0; i < argumentCount; ++i) {
|
||||
if (parameterizedTypeName.typeArguments.size() > i) {
|
||||
if (parameterizedTypeName.typeArguments.get(i) instanceof WildcardTypeName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String adjustShimName(List<RecordClassType> recordComponents, String baseName, int index) {
|
||||
var name = (index == 0) ? baseName : (baseName + index);
|
||||
if (recordComponents.stream().anyMatch(component -> component.name().equals(name))) {
|
||||
return adjustShimName(recordComponents, baseName, index + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private MethodSpec buildMethod(String name, TypeName mainType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
var code = CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
|
||||
return MethodSpec.methodBuilder(name)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.returns(parameterizedType)
|
||||
.addParameter(parameterizedType, "o")
|
||||
.addStatement(code)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCollectionsMethod() {
|
||||
var code = CodeBlock.builder()
|
||||
.add("if (o instanceof Set) {\n")
|
||||
.indent()
|
||||
.addStatement("return $T.copyOf(o)", setType)
|
||||
.unindent()
|
||||
.addStatement("}")
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listType, listType)
|
||||
.build();
|
||||
return MethodSpec.methodBuilder(collectionShimName)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariable(tType)
|
||||
.returns(parameterizedCollectionType)
|
||||
.addParameter(parameterizedCollectionType, "o")
|
||||
.addCode(code)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -52,19 +52,12 @@ public class ElementUtils {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<TypeMirror> getAttributeTypeMirrorList(AnnotationValue attribute)
|
||||
public static List<TypeMirror> getClassesAttribute(AnnotationValue attribute)
|
||||
{
|
||||
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
|
||||
return values.stream().map(v -> (TypeMirror)v.getValue()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<String> getAttributeStringList(AnnotationValue attribute)
|
||||
{
|
||||
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
|
||||
return values.stream().map(v -> (String)v.getValue()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static boolean getBooleanAttribute(AnnotationValue attribute)
|
||||
{
|
||||
Object value = (attribute != null) ? attribute.getValue() : null;
|
||||
@@ -115,10 +108,8 @@ public class ElementUtils {
|
||||
return new ClassType(ParameterizedTypeName.get(builderClassName, typeNames), builderClassName.simpleName());
|
||||
}
|
||||
|
||||
public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv, RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||
var typeName = TypeName.get(recordComponent.asType());
|
||||
var rawTypeName = TypeName.get(processingEnv.getTypeUtils().erasure(recordComponent.asType()));
|
||||
return new RecordClassType(typeName, rawTypeName, recordComponent.getSimpleName().toString(), accessorAnnotations, canonicalConstructorAnnotations);
|
||||
public static RecordClassType getRecordClassType(RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||
return new RecordClassType(TypeName.get(recordComponent.asType()), recordComponent.getSimpleName().toString(), accessorAnnotations, canonicalConstructorAnnotations);
|
||||
}
|
||||
|
||||
public static String getWithMethodName(ClassType component, String prefix) {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class IncludeHelper {
|
||||
private final boolean isValid;
|
||||
private final List<TypeElement> classTypeElements;
|
||||
private final Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues;
|
||||
|
||||
IncludeHelper(ProcessingEnvironment processingEnv, Element element, AnnotationMirror annotationMirror, boolean packagesSupported) {
|
||||
annotationValues = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
|
||||
var value = ElementUtils.getAnnotationValue(annotationValues, "value");
|
||||
var classes = ElementUtils.getAnnotationValue(annotationValues, "classes");
|
||||
var packages = ElementUtils.getAnnotationValue(annotationValues, "packages");
|
||||
var isValid = true;
|
||||
var classTypeElements = new ArrayList<TypeElement>();
|
||||
if (value.isPresent() || classes.isPresent() || packages.isPresent()) {
|
||||
var valueList = value.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
|
||||
var classesList = classes.map(ElementUtils::getAttributeTypeMirrorList).orElseGet(List::of);
|
||||
var packagesList = packages.map(ElementUtils::getAttributeStringList).orElseGet(List::of);
|
||||
if (valueList.isEmpty() && classesList.isEmpty() && packagesList.isEmpty()) {
|
||||
if (packagesSupported) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\", \"classes\" or \"packages\" required", element);
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\" or \"classes\" required", element);
|
||||
}
|
||||
isValid = false;
|
||||
}
|
||||
isValid = processList(processingEnv, isValid, element, valueList, classTypeElements);
|
||||
isValid = processList(processingEnv, isValid, element, classesList, classTypeElements);
|
||||
packages.ifPresent(annotationValue -> processPackages(processingEnv, classTypeElements, packagesList));
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not read attribute for annotation", element);
|
||||
isValid = false;
|
||||
}
|
||||
this.isValid = isValid;
|
||||
this.classTypeElements = List.copyOf(classTypeElements);
|
||||
}
|
||||
|
||||
Map<? extends ExecutableElement, ? extends AnnotationValue> getAnnotationValues() {
|
||||
return annotationValues;
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
List<TypeElement> getClassTypeElements() {
|
||||
return classTypeElements;
|
||||
}
|
||||
|
||||
private boolean processList(ProcessingEnvironment processingEnv, boolean isValid, Element element, List<TypeMirror> list, ArrayList<TypeElement> classTypeElements) {
|
||||
for (var typeMirror : list) {
|
||||
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
|
||||
if (typeElement == null) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + typeMirror, element);
|
||||
isValid = false;
|
||||
} else {
|
||||
classTypeElements.add(typeElement);
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private void processPackages(ProcessingEnvironment processingEnv, List<TypeElement> classTypeElements, List<String> packagesList) {
|
||||
for (var packageName : packagesList) {
|
||||
var packageElement = processingEnv.getElementUtils().getPackageElement(packageName);
|
||||
for (var child : packageElement.getEnclosedElements()) {
|
||||
if (child.getKind() == ElementKind.RECORD) {
|
||||
classTypeElements.add((TypeElement) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,27 +15,20 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.EXCLUDE_WILDCARD_TYPES;
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.STANDARD;
|
||||
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 com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
|
||||
class InternalRecordBuilderProcessor {
|
||||
private final RecordBuilder.Options metaData;
|
||||
@@ -48,52 +41,35 @@ class InternalRecordBuilderProcessor {
|
||||
private final TypeSpec.Builder builder;
|
||||
private final String uniqueVarName;
|
||||
private final Pattern notNullPattern;
|
||||
private final CollectionBuilderUtils collectionBuilderUtils;
|
||||
|
||||
private static final TypeName overrideType = TypeName.get(Override.class);
|
||||
private static final TypeName validType = ClassName.get("javax.validation", "Valid");
|
||||
private static final TypeName optionalType = TypeName.get(Optional.class);
|
||||
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
|
||||
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
|
||||
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
|
||||
private static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator", "RecordBuilderValidator");
|
||||
private static final TypeVariableName rType = TypeVariableName.get("R");
|
||||
private final ProcessingEnvironment processingEnv;
|
||||
|
||||
InternalRecordBuilderProcessor(ProcessingEnvironment processingEnv, TypeElement record, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
this.processingEnv = processingEnv;
|
||||
var recordActualPackage = ElementUtils.getPackageName(record);
|
||||
this.metaData = metaData;
|
||||
InternalRecordBuilderProcessor(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
this.metaData = getMetaData(record, metaData);
|
||||
recordClassType = ElementUtils.getClassType(record, record.getTypeParameters());
|
||||
packageName = packageNameOpt.orElse(recordActualPackage);
|
||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(record));
|
||||
builderClassType = ElementUtils.getClassType(packageName, getBuilderName(record, metaData, recordClassType, metaData.suffix()), record.getTypeParameters());
|
||||
typeVariables = record.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
|
||||
recordComponents = buildRecordComponents(record);
|
||||
uniqueVarName = getUniqueVarName();
|
||||
notNullPattern = Pattern.compile(metaData.interpretNotNullsPattern());
|
||||
collectionBuilderUtils = new CollectionBuilderUtils(recordComponents, this.metaData);
|
||||
|
||||
builder = TypeSpec.classBuilder(builderClassType.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addTypeVariables(typeVariables);
|
||||
if (metaData.addClassRetainedGenerated()) {
|
||||
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||
}
|
||||
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
|
||||
if (metaData.enableWither()) {
|
||||
addWithNestedClass();
|
||||
}
|
||||
if (!metaData.beanClassName().isEmpty()) {
|
||||
addBeanNestedClass();
|
||||
}
|
||||
addWithNestedClass();
|
||||
addDefaultConstructor();
|
||||
if (metaData.addStaticBuilder()) {
|
||||
addStaticBuilder();
|
||||
}
|
||||
addStaticBuilder();
|
||||
if (recordComponents.size() > 0) {
|
||||
addAllArgsConstructor();
|
||||
}
|
||||
addStaticDefaultBuilderMethod();
|
||||
addStaticCopyBuilderMethod();
|
||||
if (metaData.enableWither()) {
|
||||
addStaticFromWithMethod();
|
||||
}
|
||||
addStaticComponentsMethod();
|
||||
addBuildMethod();
|
||||
addToStringMethod();
|
||||
@@ -102,16 +78,9 @@ class InternalRecordBuilderProcessor {
|
||||
recordComponents.forEach(component -> {
|
||||
add1Field(component);
|
||||
add1SetterMethod(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));
|
||||
add1GetterMethod(component);
|
||||
});
|
||||
collectionBuilderUtils.addShims(builder);
|
||||
addStaticDowncastMethod();
|
||||
builderType = builder.build();
|
||||
}
|
||||
|
||||
@@ -127,17 +96,6 @@ class InternalRecordBuilderProcessor {
|
||||
return builderType;
|
||||
}
|
||||
|
||||
private void addVisibility(boolean builderIsInRecordPackage, Set<Modifier> modifiers) {
|
||||
if (builderIsInRecordPackage) {
|
||||
if (modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
|
||||
builder.addModifiers(Modifier.PUBLIC); // builders are top level classes - can only be public or package-private
|
||||
}
|
||||
// is package-private
|
||||
} else {
|
||||
builder.addModifiers(Modifier.PUBLIC);
|
||||
}
|
||||
}
|
||||
|
||||
private List<RecordClassType> buildRecordComponents(TypeElement record) {
|
||||
var accessorAnnotations = record.getRecordComponents().stream().map(e -> e.getAccessor().getAnnotationMirrors()).collect(Collectors.toList());
|
||||
var canonicalConstructorAnnotations = ElementUtils.findCanonicalConstructor(record).map(constructor -> ((ExecutableElement) constructor).getParameters().stream().map(Element::getAnnotationMirrors).collect(Collectors.toList())).orElse(List.of());
|
||||
@@ -146,11 +104,16 @@ class InternalRecordBuilderProcessor {
|
||||
.mapToObj(index -> {
|
||||
var thisAccessorAnnotations = (accessorAnnotations.size() > index) ? accessorAnnotations.get(index) : List.<AnnotationMirror>of();
|
||||
var thisCanonicalConstructorAnnotations = (canonicalConstructorAnnotations.size() > index) ? canonicalConstructorAnnotations.get(index) : List.<AnnotationMirror>of();
|
||||
return ElementUtils.getRecordClassType(processingEnv, recordComponents.get(index), thisAccessorAnnotations, thisCanonicalConstructorAnnotations);
|
||||
return ElementUtils.getRecordClassType(recordComponents.get(index), thisAccessorAnnotations, thisCanonicalConstructorAnnotations);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private RecordBuilder.Options getMetaData(TypeElement record, RecordBuilder.Options metaData) {
|
||||
var recordSpecificMetaData = record.getAnnotation(RecordBuilder.Options.class);
|
||||
return (recordSpecificMetaData != null) ? recordSpecificMetaData : metaData;
|
||||
}
|
||||
|
||||
private void addWithNestedClass() {
|
||||
/*
|
||||
Adds a nested interface that adds withers similar to:
|
||||
@@ -166,44 +129,9 @@ class InternalRecordBuilderProcessor {
|
||||
.addJavadoc("Add withers to {@code $L}\n", recordClassType.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addTypeVariables(typeVariables);
|
||||
if (metaData.addClassRetainedGenerated()) {
|
||||
classBuilder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||
}
|
||||
recordComponents.forEach(component -> addNestedGetterMethod(classBuilder, component, prefixedName(component, true)));
|
||||
addWithBuilderMethod(classBuilder);
|
||||
addWithSuppliedBuilderMethod(classBuilder);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -239,13 +167,13 @@ class InternalRecordBuilderProcessor {
|
||||
Adds a method that returns a pre-filled copy builder similar to:
|
||||
|
||||
default MyRecordBuilder with() {
|
||||
MyRecord r = _downcast(this);
|
||||
return MyRecordBuilder.builder(r);
|
||||
}
|
||||
*/
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.add("return new $L$L(", builderClassType.name(), typeVariables.isEmpty() ? "" : "<>");
|
||||
addComponentCallsAsArguments(-1, codeBlockBuilder);
|
||||
codeBlockBuilder.add(");");
|
||||
.add("$T $L = $L(this);\n", recordClassType.typeName(), uniqueVarName, metaData.downCastMethodName())
|
||||
.add("return $L.$L($L);", builderClassType.name(), metaData.copyMethodName(), uniqueVarName);
|
||||
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addJavadoc("Return a new record builder using the current values")
|
||||
@@ -273,26 +201,31 @@ class InternalRecordBuilderProcessor {
|
||||
Adds a with method for the component similar to:
|
||||
|
||||
default MyRecord withName(String name) {
|
||||
MyRecord r = _downcast(this);
|
||||
return new MyRecord(name, r.age());
|
||||
}
|
||||
*/
|
||||
var codeBlockBuilder = CodeBlock.builder();
|
||||
addNullCheckCodeBlock(codeBlockBuilder, index);
|
||||
codeBlockBuilder.add("$[return ");
|
||||
if (metaData.useValidationApi()) {
|
||||
codeBlockBuilder.add("$T.validate(", validatorTypeName);
|
||||
if (recordComponents.size() > 1) {
|
||||
codeBlockBuilder.add("$T $L = $L(this);\n", recordClassType.typeName(), uniqueVarName, metaData.downCastMethodName());
|
||||
}
|
||||
codeBlockBuilder.add("new $T(", recordClassType.typeName());
|
||||
addComponentCallsAsArguments(index, codeBlockBuilder);
|
||||
codeBlockBuilder.add(")");
|
||||
if (metaData.useValidationApi()) {
|
||||
codeBlockBuilder.add(")");
|
||||
}
|
||||
codeBlockBuilder.add(";$]");
|
||||
codeBlockBuilder.add("return new $T(", recordClassType.typeName());
|
||||
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
|
||||
if (parameterIndex > 0) {
|
||||
codeBlockBuilder.add(", ");
|
||||
}
|
||||
ClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||
if (parameterIndex == index) {
|
||||
codeBlockBuilder.add(parameterComponent.name());
|
||||
} else {
|
||||
codeBlockBuilder.add("$L.$L()", uniqueVarName, parameterComponent.name());
|
||||
}
|
||||
});
|
||||
codeBlockBuilder.add(");");
|
||||
|
||||
var methodName = getWithMethodName(component, metaData.withClassMethodPrefix());
|
||||
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||
component.getCanonicalConstructorAnnotations().forEach(annotationMirror -> parameterSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
var methodSpec = MethodSpec.methodBuilder(methodName)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addJavadoc("Return a new instance of {@code $L} with a new value for {@code $L}\n", recordClassType.name(), component.name())
|
||||
@@ -304,42 +237,6 @@ class InternalRecordBuilderProcessor {
|
||||
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) {
|
||||
IntStream.range(0, recordComponents.size()).forEach(parameterIndex -> {
|
||||
if (parameterIndex > 0) {
|
||||
codeBlockBuilder.add(", ");
|
||||
}
|
||||
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||
if (parameterIndex == index) {
|
||||
collectionBuilderUtils.add(codeBlockBuilder, parameterComponent);
|
||||
} else {
|
||||
codeBlockBuilder.add("$L()", prefixedName(parameterComponent, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addDefaultConstructor() {
|
||||
/*
|
||||
Adds a default constructor similar to:
|
||||
@@ -372,7 +269,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addCode(codeBlock);
|
||||
recordComponents.forEach(component -> {
|
||||
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||
component.getCanonicalConstructorAnnotations().forEach(annotationMirror -> parameterSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
builder.addParameter(parameterSpecBuilder.build());
|
||||
});
|
||||
this.builder.addMethod(builder.build());
|
||||
@@ -380,20 +277,10 @@ class InternalRecordBuilderProcessor {
|
||||
|
||||
private void addNullCheckCodeBlock(CodeBlock.Builder builder) {
|
||||
if (metaData.interpretNotNulls()) {
|
||||
for (int i = 0; i < recordComponents.size(); ++i) {
|
||||
addNullCheckCodeBlock(builder, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) {
|
||||
if (metaData.interpretNotNulls()) {
|
||||
var component = recordComponents.get(index);
|
||||
if (!collectionBuilderUtils.isImmutableCollection(component)) {
|
||||
if (!component.typeName().isPrimitive() && isNullAnnotated(component)) {
|
||||
builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required");
|
||||
}
|
||||
}
|
||||
recordComponents.stream()
|
||||
.filter(component -> !component.typeName().isPrimitive())
|
||||
.filter(this::isNullAnnotated)
|
||||
.forEach(component -> builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,9 +304,8 @@ class InternalRecordBuilderProcessor {
|
||||
.addAnnotation(generatedRecordBuilderAnnotation);
|
||||
recordComponents.forEach(component -> {
|
||||
constructorBuilder.addParameter(component.typeName(), component.name());
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD);
|
||||
collectionMetaData.ifPresentOrElse(meta -> constructorBuilder.addStatement("this.$L = new $T<>($L)", component.name(), meta.singleItemCollectionClass(), component.name()),
|
||||
() -> constructorBuilder.addStatement("this.$L = $L", component.name(), component.name()));
|
||||
var codeBuilder = CodeBlock.builder().add("this.$L = $L", component.name(), component.name());
|
||||
constructorBuilder.addStatement(codeBuilder.build());
|
||||
});
|
||||
builder.addMethod(constructorBuilder.build());
|
||||
}
|
||||
@@ -494,12 +380,7 @@ class InternalRecordBuilderProcessor {
|
||||
*/
|
||||
var codeBuilder = CodeBlock.builder();
|
||||
codeBuilder.add("return (this == o) || (");
|
||||
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);
|
||||
}
|
||||
codeBuilder.add("(o instanceof $L $L)", builderClassType.name(), uniqueVarName);
|
||||
recordComponents.forEach(recordComponent -> {
|
||||
String name = recordComponent.name();
|
||||
if (recordComponent.typeName().isPrimitive()) {
|
||||
@@ -546,16 +427,6 @@ class InternalRecordBuilderProcessor {
|
||||
*/
|
||||
|
||||
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.add(codeBuilder, recordComponents.get(index));
|
||||
codeBuilder.add(";\n$]");
|
||||
}
|
||||
});
|
||||
|
||||
addNullCheckCodeBlock(codeBuilder);
|
||||
codeBuilder.add("$[return ");
|
||||
if (metaData.useValidationApi()) {
|
||||
@@ -576,89 +447,6 @@ class InternalRecordBuilderProcessor {
|
||||
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() {
|
||||
/*
|
||||
Adds a copy builder method that pre-fills the builder with existing values similar to:
|
||||
@@ -712,8 +500,10 @@ class InternalRecordBuilderProcessor {
|
||||
Adds a static method that converts a record instance into a stream of its component parts
|
||||
|
||||
public static Stream<Map.Entry<String, Object>> stream(MyRecord record) {
|
||||
return Stream.of(new AbstractMap.SimpleImmutableEntry<>("p1", record.p1()),
|
||||
new AbstractMap.SimpleImmutableEntry<>("p2", record.p2()));
|
||||
return Stream.of(
|
||||
new AbstractMap.SimpleEntry<>("p1", record.p1()),
|
||||
new AbstractMap.SimpleEntry<>("p2", record.p2())
|
||||
);
|
||||
}
|
||||
*/
|
||||
var codeBuilder = CodeBlock.builder().add("return $T.of(", Stream.class);
|
||||
@@ -722,7 +512,7 @@ class InternalRecordBuilderProcessor {
|
||||
codeBuilder.add(",\n ");
|
||||
}
|
||||
var name = recordComponents.get(index).name();
|
||||
codeBuilder.add("new $T<>($S, record.$L())", AbstractMap.SimpleImmutableEntry.class, name, name);
|
||||
codeBuilder.add("new $T<>($S, record.$L())", AbstractMap.SimpleEntry.class, name, name);
|
||||
});
|
||||
codeBuilder.add(")");
|
||||
var mapEntryTypeVariables = ParameterizedTypeName.get(Map.Entry.class, String.class, Object.class);
|
||||
@@ -739,6 +529,37 @@ class InternalRecordBuilderProcessor {
|
||||
builder.addMethod(methodSpec);
|
||||
}
|
||||
|
||||
private void addStaticDowncastMethod() {
|
||||
/*
|
||||
Adds a method that downcasts to the record type
|
||||
|
||||
private static MyRecord _downcast(Object this) {
|
||||
return (MyRecord)this;
|
||||
}
|
||||
*/
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.add("try {\n")
|
||||
.indent()
|
||||
.add("return ($T)obj;\n", recordClassType.typeName())
|
||||
.unindent()
|
||||
.add("}\n")
|
||||
.add("catch (ClassCastException dummy) {\n")
|
||||
.indent()
|
||||
.add("throw new RuntimeException($S);\n", builderClassType.name() + "." + metaData.withClassName() + " can only be implemented by " + recordClassType.name())
|
||||
.unindent()
|
||||
.add("}");
|
||||
var methodSpec = MethodSpec.methodBuilder(metaData.downCastMethodName())
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addJavadoc("Downcast to {@code $L}\n", recordClassType.name())
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addParameter(Object.class, "obj")
|
||||
.addTypeVariables(typeVariables)
|
||||
.returns(recordClassType.typeName())
|
||||
.addCode(codeBlockBuilder.build())
|
||||
.build();
|
||||
builder.addMethod(methodSpec);
|
||||
}
|
||||
|
||||
private void add1Field(ClassType component) {
|
||||
/*
|
||||
For a single record component, add a field similar to:
|
||||
@@ -747,182 +568,29 @@ class InternalRecordBuilderProcessor {
|
||||
*/
|
||||
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
|
||||
if (metaData.emptyDefaultForOptional()) {
|
||||
Optional<OptionalType> thisOptionalType = OptionalType.fromClassType(component);
|
||||
if (thisOptionalType.isPresent()) {
|
||||
var codeBlock = CodeBlock.builder()
|
||||
.add("$T.empty()", thisOptionalType.get().typeName())
|
||||
.build();
|
||||
TypeName thisOptionalType = null;
|
||||
if (isOptional(component)) {
|
||||
thisOptionalType = optionalType;
|
||||
} else if (component.typeName().equals(optionalIntType)) {
|
||||
thisOptionalType = optionalIntType;
|
||||
} else if (component.typeName().equals(optionalLongType)) {
|
||||
thisOptionalType = optionalLongType;
|
||||
} else if (component.typeName().equals(optionalDoubleType)) {
|
||||
thisOptionalType = optionalDoubleType;
|
||||
}
|
||||
if (thisOptionalType != null) {
|
||||
var codeBlock = CodeBlock.builder().add("$T.empty()", thisOptionalType).build();
|
||||
fieldSpecBuilder.initializer(codeBlock);
|
||||
}
|
||||
}
|
||||
builder.addField(fieldSpecBuilder.build());
|
||||
}
|
||||
|
||||
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component, String methodName) {
|
||||
/*
|
||||
For a single record component, add a getter similar to:
|
||||
|
||||
T p();
|
||||
*/
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(methodName)
|
||||
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(component.typeName());
|
||||
addAccessorAnnotations(component, methodSpecBuilder, this::filterOutValid);
|
||||
classBuilder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
|
||||
private boolean filterOutOverride(AnnotationSpec annotationSpec) {
|
||||
return !annotationSpec.type.equals(overrideType);
|
||||
}
|
||||
|
||||
private boolean filterOutValid(AnnotationSpec annotationSpec) {
|
||||
return !annotationSpec.type.equals(validType);
|
||||
}
|
||||
|
||||
private void addConstructorAnnotations(RecordClassType component, ParameterSpec.Builder parameterSpecBuilder) {
|
||||
if (metaData.inheritComponentAnnotations()) {
|
||||
component.getCanonicalConstructorAnnotations()
|
||||
.stream()
|
||||
.map(AnnotationSpec::get)
|
||||
.filter(this::filterOutOverride)
|
||||
.forEach(parameterSpecBuilder::addAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAccessorAnnotations(RecordClassType component, MethodSpec.Builder methodSpecBuilder, Predicate<AnnotationSpec> additionalFilter) {
|
||||
if (metaData.inheritComponentAnnotations()) {
|
||||
component.getAccessorAnnotations()
|
||||
.stream()
|
||||
.map(AnnotationSpec::get)
|
||||
.filter(this::filterOutOverride)
|
||||
.filter(additionalFilter)
|
||||
.forEach(methodSpecBuilder::addAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
private String capitalize(String s) {
|
||||
return (s.length() < 2) ? s.toUpperCase(Locale.ROOT) : (Character.toUpperCase(s.charAt(0)) + s.substring(1));
|
||||
}
|
||||
|
||||
private void add1CollectionBuilders(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||
if (collectionBuilderUtils.isList(component) || collectionBuilderUtils.isSet(component)) {
|
||||
add1ListBuilder(meta, component);
|
||||
} else if (collectionBuilderUtils.isMap(component)) {
|
||||
add1MapBuilder(meta, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void add1MapBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||
/*
|
||||
For a single map record component, add a methods similar to:
|
||||
|
||||
public T addP(K key, V value) {
|
||||
if (this.p == null) {
|
||||
this.p = new HashMap<>();
|
||||
}
|
||||
this.p.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
i.forEach(this.p::put);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
i.forEach(this.p::put);
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), HashMap.class)
|
||||
.endControlFlow();
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
.addJavadoc("Add to the internally allocated {@code HashMap} for {@code $L}\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(builderClassType.typeName());
|
||||
if (i == 0) {
|
||||
methodSpecBuilder.addParameter(meta.typeArguments().get(0), "key");
|
||||
methodSpecBuilder.addParameter(meta.typeArguments().get(1), "value");
|
||||
codeBlockBuilder.addStatement("this.$L.put(key, value)", component.name());
|
||||
} else {
|
||||
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
|
||||
var entryType = ParameterizedTypeName.get(ClassName.get(Map.Entry.class), WildcardTypeName.subtypeOf(meta.typeArguments().get(0)), WildcardTypeName.subtypeOf(meta.typeArguments().get(1)));
|
||||
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(parameterClass, WildcardTypeName.subtypeOf(entryType));
|
||||
methodSpecBuilder.addParameter(parameterizedTypeName, "i");
|
||||
codeBlockBuilder.addStatement("i.forEach(entry -> this.$L.put(entry.getKey(), entry.getValue()))", component.name());
|
||||
}
|
||||
codeBlockBuilder.addStatement("return this");
|
||||
methodSpecBuilder.addCode(codeBlockBuilder.build());
|
||||
builder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void add1ListBuilder(CollectionBuilderUtils.SingleItemsMetaData meta, RecordClassType component) {
|
||||
/*
|
||||
For a single list or set record component, add methods similar to:
|
||||
|
||||
public T addP(I i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.p.add(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
var addClockBlock = CodeBlock.builder();
|
||||
TypeName parameter;
|
||||
if (i == 0) {
|
||||
addClockBlock.addStatement("this.$L.add(i)", component.name());
|
||||
parameter = meta.typeArguments().get(0);
|
||||
} else {
|
||||
addClockBlock.addStatement("i.forEach(this.$L::add)", component.name());
|
||||
var parameterClass = ClassName.get((i == 1) ? Stream.class : Iterable.class);
|
||||
parameter = ParameterizedTypeName.get(parameterClass, WildcardTypeName.subtypeOf(meta.typeArguments().get(0)));
|
||||
}
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||
.endControlFlow()
|
||||
.add(addClockBlock.build())
|
||||
.addStatement("return this");
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
.addJavadoc("Add to the internally allocated {@code $L} for {@code $L}\n", meta.singleItemCollectionClass().getSimpleName(), component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(builderClassType.typeName())
|
||||
.addParameter(parameter, "i")
|
||||
.addCode(codeBlockBuilder.build());
|
||||
builder.addMethod(methodSpecBuilder.build());
|
||||
private boolean isOptional(ClassType component) {
|
||||
if (component.typeName().equals(optionalType)) {
|
||||
return true;
|
||||
}
|
||||
return (component.typeName() instanceof ParameterizedTypeName) && ((ParameterizedTypeName) component.typeName()).rawType.equals(optionalType);
|
||||
}
|
||||
|
||||
private void add1GetterMethod(RecordClassType component) {
|
||||
@@ -933,13 +601,13 @@ class InternalRecordBuilderProcessor {
|
||||
return p;
|
||||
}
|
||||
*/
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(prefixedName(component, true))
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(component.name())
|
||||
.addJavadoc("Return the current value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(component.typeName())
|
||||
.addStatement("return $L", component.name());
|
||||
addAccessorAnnotations(component, methodSpecBuilder, __ -> true);
|
||||
component.getAccessorAnnotations().forEach(annotationMirror -> methodSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
builder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
|
||||
@@ -952,140 +620,19 @@ class InternalRecordBuilderProcessor {
|
||||
return this;
|
||||
}
|
||||
*/
|
||||
var methodSpec = MethodSpec.methodBuilder(prefixedName(component, false))
|
||||
var parameterSpecBuilder = ParameterSpec.builder(component.typeName(), component.name());
|
||||
component.getCanonicalConstructorAnnotations().forEach(annotationMirror -> parameterSpecBuilder.addAnnotation(AnnotationSpec.get(annotationMirror)));
|
||||
|
||||
var methodSpec = MethodSpec.methodBuilder(component.name())
|
||||
.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(builderClassType.typeName());
|
||||
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, STANDARD_FOR_SETTER);
|
||||
var parameterSpecBuilder = collectionMetaData.map(meta -> {
|
||||
CodeBlock.Builder codeSpec = CodeBlock.builder();
|
||||
codeSpec.addStatement("this.$L = ($L != null) ? new $T<>($L) : null", component.name(), component.name(), meta.singleItemCollectionClass(), component.name());
|
||||
methodSpec.addJavadoc("Re-create the internally allocated {@code $L} for {@code $L} by copying the argument\n", meta.singleItemCollectionClass().getSimpleName(), component.name())
|
||||
.addCode(codeSpec.build());
|
||||
return ParameterSpec.builder(meta.wildType(), component.name());
|
||||
}).orElseGet(() -> {
|
||||
methodSpec.addJavadoc("Set a new value for the {@code $L} record component in the builder\n", component.name())
|
||||
.addStatement("this.$L = $L", component.name(), component.name());
|
||||
return ParameterSpec.builder(component.typeName(), component.name());
|
||||
});
|
||||
addConstructorAnnotations(component, parameterSpecBuilder);
|
||||
methodSpec.addStatement("return this").addParameter(parameterSpecBuilder.build());
|
||||
builder.addMethod(methodSpec.build());
|
||||
}
|
||||
|
||||
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())
|
||||
.addParameter(parameterSpecBuilder.build())
|
||||
.returns(builderClassType.typeName())
|
||||
.addStatement("this.$L = $L", component.name(), component.name())
|
||||
.addStatement("return this")
|
||||
.build();
|
||||
}
|
||||
|
||||
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());
|
||||
builder.addMethod(methodSpec);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import com.squareup.javapoet.ClassName;
|
||||
import com.squareup.javapoet.MethodSpec;
|
||||
import com.squareup.javapoet.ParameterSpec;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
import com.squareup.javapoet.TypeVariableName;
|
||||
import io.soabase.recordbuilder.core.IgnoreDefaultMethod;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@@ -26,14 +29,19 @@ import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordInterfaceAnnotation;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||
|
||||
class InternalRecordInterfaceProcessor {
|
||||
private final ProcessingEnvironment processingEnv;
|
||||
@@ -48,10 +56,9 @@ class InternalRecordInterfaceProcessor {
|
||||
|
||||
private static final Set<String> javaBeanPrefixes = Set.of("get", "is");
|
||||
|
||||
private record Component(ExecutableElement element, Optional<String> alternateName) {
|
||||
}
|
||||
private record Component(ExecutableElement element, Optional<String> alternateName){}
|
||||
|
||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt, boolean fromTemplate) {
|
||||
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt) {
|
||||
this.processingEnv = processingEnv;
|
||||
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
|
||||
recordComponents = getRecordComponents(iface);
|
||||
@@ -69,22 +76,11 @@ class InternalRecordInterfaceProcessor {
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||
.addTypeVariables(typeVariables);
|
||||
if (metaData.addClassRetainedGenerated()) {
|
||||
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||
}
|
||||
|
||||
if (addRecordBuilder) {
|
||||
ClassType builderClassType = ElementUtils.getClassType(packageName, getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "." + metaData.withClassName(), iface.getTypeParameters());
|
||||
builder.addAnnotation(RecordBuilder.class);
|
||||
builder.addSuperinterface(builderClassType.typeName());
|
||||
if (fromTemplate) {
|
||||
builder.addAnnotation(AnnotationSpec.get(metaData));
|
||||
} else {
|
||||
var options = iface.getAnnotation(RecordBuilder.Options.class);
|
||||
if (options != null) {
|
||||
builder.addAnnotation(AnnotationSpec.get(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alternateMethods = buildAlternateMethods(recordComponents);
|
||||
@@ -92,7 +88,8 @@ class InternalRecordInterfaceProcessor {
|
||||
recordType = builder.build();
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
boolean isValid()
|
||||
{
|
||||
return !recordComponents.isEmpty();
|
||||
}
|
||||
|
||||
@@ -108,7 +105,8 @@ class InternalRecordInterfaceProcessor {
|
||||
return recordClassType;
|
||||
}
|
||||
|
||||
String toRecord(String classSource) {
|
||||
String toRecord(String classSource)
|
||||
{
|
||||
// javapoet does yet support records - so a class was created and we can reshape it
|
||||
// The class will look something like this:
|
||||
/*
|
||||
@@ -141,7 +139,8 @@ class InternalRecordInterfaceProcessor {
|
||||
return fixedRecord.toString();
|
||||
}
|
||||
|
||||
private MethodSpec generateArgumentList() {
|
||||
private MethodSpec generateArgumentList()
|
||||
{
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder(FAKE_METHOD_NAME);
|
||||
recordComponents.forEach(component -> {
|
||||
String name = component.alternateName.orElseGet(() -> component.element.getSimpleName().toString());
|
||||
@@ -154,18 +153,18 @@ class InternalRecordInterfaceProcessor {
|
||||
|
||||
private List<String> buildAlternateMethods(List<Component> recordComponents) {
|
||||
return recordComponents.stream()
|
||||
.filter(component -> component.alternateName.isPresent())
|
||||
.map(component -> {
|
||||
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||
.returns(ClassName.get(component.element.getReturnType()))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addCode("return $L();", component.alternateName.get())
|
||||
.build();
|
||||
return method.toString();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
.filter(component -> component.alternateName.isPresent())
|
||||
.map(component -> {
|
||||
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
|
||||
.addAnnotation(Override.class)
|
||||
.addAnnotation(generatedRecordInterfaceAnnotation)
|
||||
.returns(ClassName.get(component.element.getReturnType()))
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addCode("return $L();", component.alternateName.get())
|
||||
.build();
|
||||
return method.toString();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Component> getRecordComponents(TypeElement iface) {
|
||||
@@ -181,8 +180,8 @@ class InternalRecordInterfaceProcessor {
|
||||
}
|
||||
return components;
|
||||
}
|
||||
|
||||
private static class IllegalInterface extends RuntimeException {
|
||||
private static class IllegalInterface extends RuntimeException
|
||||
{
|
||||
public IllegalInterface(String message) {
|
||||
super(message);
|
||||
}
|
||||
@@ -220,13 +219,14 @@ class InternalRecordInterfaceProcessor {
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<String> stripBeanPrefix(String name) {
|
||||
private Optional<String> stripBeanPrefix(String name)
|
||||
{
|
||||
return javaBeanPrefixes.stream()
|
||||
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
|
||||
.findFirst()
|
||||
.map(prefix -> {
|
||||
var stripped = name.substring(prefix.length());
|
||||
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
|
||||
});
|
||||
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
|
||||
.findFirst()
|
||||
.map(prefix -> {
|
||||
var stripped = name.substring(prefix.length());
|
||||
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import 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,7 +19,6 @@ import com.squareup.javapoet.AnnotationSpec;
|
||||
import com.squareup.javapoet.JavaFile;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import io.soabase.recordbuilder.core.RecordBuilderGenerated;
|
||||
import io.soabase.recordbuilder.core.RecordInterface;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
@@ -30,8 +29,10 @@ import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.PackageElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Optional;
|
||||
@@ -39,7 +40,8 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RecordBuilderProcessor
|
||||
extends AbstractProcessor {
|
||||
extends AbstractProcessor
|
||||
{
|
||||
private static final String RECORD_BUILDER = RecordBuilder.class.getName();
|
||||
private static final String RECORD_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.');
|
||||
private static final String RECORD_INTERFACE = RecordInterface.class.getName();
|
||||
@@ -47,21 +49,23 @@ public class RecordBuilderProcessor
|
||||
|
||||
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 recordBuilderGeneratedAnnotation = AnnotationSpec.builder(RecordBuilderGenerated.class).build();
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
|
||||
{
|
||||
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> process(annotation, element)));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSupportedAnnotationTypes() {
|
||||
public Set<String> getSupportedAnnotationTypes()
|
||||
{
|
||||
return Set.of("*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
public SourceVersion getSupportedSourceVersion()
|
||||
{
|
||||
// we don't directly return RELEASE_14 as that may
|
||||
// not exist in prior releases
|
||||
// if we're running on an older release, returning latest()
|
||||
@@ -69,51 +73,59 @@ public class RecordBuilderProcessor
|
||||
return SourceVersion.latest();
|
||||
}
|
||||
|
||||
private void process(TypeElement annotation, Element element) {
|
||||
private void process(TypeElement annotation, Element element)
|
||||
{
|
||||
String annotationClass = annotation.getQualifiedName().toString();
|
||||
if (annotationClass.equals(RECORD_BUILDER)) {
|
||||
var typeElement = (TypeElement) element;
|
||||
processRecordBuilder(typeElement, getMetaData(typeElement), Optional.empty());
|
||||
} else if (annotationClass.equals(RECORD_INTERFACE)) {
|
||||
var typeElement = (TypeElement) element;
|
||||
processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(), getMetaData(typeElement), Optional.empty(), false);
|
||||
} else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||
processIncludes(element, getMetaData(element), annotationClass);
|
||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
processRecordBuilder((TypeElement) element, metaData, Optional.empty());
|
||||
}
|
||||
else if (annotationClass.equals(RECORD_INTERFACE)) {
|
||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
processRecordInterface((TypeElement) element, element.getAnnotation(RecordInterface.class).addRecordBuilder(), metaData, Optional.empty());
|
||||
}
|
||||
else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||
var metaData = RecordBuilderOptions.build(processingEnv.getOptions());
|
||||
processIncludes(element, metaData, annotationClass);
|
||||
} else {
|
||||
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
|
||||
if (recordBuilderTemplate != null) {
|
||||
if (recordBuilderTemplate.asRecordInterface()) {
|
||||
processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(), Optional.empty(), true);
|
||||
} else {
|
||||
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
||||
}
|
||||
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RecordBuilder.Options getMetaData(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);
|
||||
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass)
|
||||
{
|
||||
var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass);
|
||||
if (annotationMirrorOpt.isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation mirror for: " + annotationClass, element);
|
||||
} else {
|
||||
var includeHelper = new IncludeHelper(processingEnv, element, annotationMirrorOpt.get(), isRecordBuilderInclude);
|
||||
if (includeHelper.isValid()) {
|
||||
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "packagePattern").orElse(null), "*");
|
||||
for (var typeElement : includeHelper.getClassTypeElements()) {
|
||||
var packageName = buildPackageName(packagePattern, element, typeElement);
|
||||
if (packageName != null) {
|
||||
if (isRecordBuilderInclude) {
|
||||
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
||||
} else {
|
||||
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "addRecordBuilder");
|
||||
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
||||
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName), false);
|
||||
}
|
||||
else {
|
||||
var values = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirrorOpt.get());
|
||||
var classes = ElementUtils.getAnnotationValue(values, "value");
|
||||
if (classes.isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation value for: " + annotationClass, element);
|
||||
}
|
||||
else {
|
||||
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(values, "packagePattern").orElse(null), "*");
|
||||
var classesMirrors = ElementUtils.getClassesAttribute(classes.get());
|
||||
for (TypeMirror mirror : classesMirrors) {
|
||||
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(mirror);
|
||||
if (typeElement == null) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + mirror, element);
|
||||
}
|
||||
else {
|
||||
var packageName = buildPackageName(packagePattern, element, typeElement);
|
||||
if (packageName != null) {
|
||||
if (annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
|
||||
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(values, "addRecordBuilder");
|
||||
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
|
||||
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName));
|
||||
}
|
||||
else {
|
||||
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +133,8 @@ public class RecordBuilderProcessor
|
||||
}
|
||||
}
|
||||
|
||||
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass) {
|
||||
private String buildPackageName(String packagePattern, Element builderElement, TypeElement includedClass)
|
||||
{
|
||||
PackageElement includedClassPackage = findPackageElement(includedClass, includedClass);
|
||||
if (includedClassPackage == null) {
|
||||
return null;
|
||||
@@ -133,7 +146,8 @@ public class RecordBuilderProcessor
|
||||
return replaced.replace("@", ((PackageElement) builderElement.getEnclosingElement()).getQualifiedName().toString());
|
||||
}
|
||||
|
||||
private PackageElement findPackageElement(Element actualElement, Element includedClass) {
|
||||
private PackageElement findPackageElement(Element actualElement, Element includedClass)
|
||||
{
|
||||
if (includedClass == null) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Element has not package", actualElement);
|
||||
return null;
|
||||
@@ -144,19 +158,21 @@ public class RecordBuilderProcessor
|
||||
return findPackageElement(actualElement, includedClass.getEnclosingElement());
|
||||
}
|
||||
|
||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName, boolean fromTemplate) {
|
||||
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName)
|
||||
{
|
||||
if (!element.getKind().isInterface()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordInterface only valid for interfaces.", element);
|
||||
return;
|
||||
}
|
||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName, fromTemplate);
|
||||
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName);
|
||||
if (!internalProcessor.isValid()) {
|
||||
return;
|
||||
}
|
||||
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord);
|
||||
}
|
||||
|
||||
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageName) {
|
||||
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageName)
|
||||
{
|
||||
// we use string based name comparison for the element kind,
|
||||
// as the ElementKind.RECORD enum doesn't exist on JRE releases
|
||||
// older than Java 14, and we don't want to throw unexpected
|
||||
@@ -165,11 +181,12 @@ public class RecordBuilderProcessor
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record);
|
||||
return;
|
||||
}
|
||||
var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName);
|
||||
var internalProcessor = new InternalRecordBuilderProcessor(record, metaData, packageName);
|
||||
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData);
|
||||
}
|
||||
|
||||
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, RecordBuilder.Options metaData) {
|
||||
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, RecordBuilder.Options metaData)
|
||||
{
|
||||
// produces the Java file
|
||||
JavaFile javaFile = javaFileBuilder(packageName, builderType, metaData);
|
||||
Filer filer = processingEnv.getFiler();
|
||||
@@ -179,12 +196,14 @@ public class RecordBuilderProcessor
|
||||
try (Writer writer = sourceFile.openWriter()) {
|
||||
javaFile.writeTo(writer);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
handleWriteError(record, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc) {
|
||||
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc)
|
||||
{
|
||||
JavaFile javaFile = javaFileBuilder(packageName, type, metaData);
|
||||
|
||||
String classSourceCode = javaFile.toString();
|
||||
@@ -198,12 +217,14 @@ public class RecordBuilderProcessor
|
||||
try (Writer writer = sourceFile.openWriter()) {
|
||||
writer.write(recordSourceCode);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
handleWriteError(element, e);
|
||||
}
|
||||
}
|
||||
|
||||
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData) {
|
||||
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData)
|
||||
{
|
||||
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true).indent(metaData.fileIndent());
|
||||
var comment = metaData.fileComment();
|
||||
if ((comment != null) && !comment.isEmpty()) {
|
||||
@@ -212,7 +233,8 @@ public class RecordBuilderProcessor
|
||||
return javaFileBuilder.build();
|
||||
}
|
||||
|
||||
private void handleWriteError(TypeElement element, IOException e) {
|
||||
private void handleWriteError(TypeElement element, IOException e)
|
||||
{
|
||||
String message = "Could not create source file";
|
||||
if (e.getMessage() != null) {
|
||||
message = message + ": " + e.getMessage();
|
||||
|
||||
@@ -21,21 +21,15 @@ import javax.lang.model.element.AnnotationMirror;
|
||||
import java.util.List;
|
||||
|
||||
public class RecordClassType extends ClassType {
|
||||
private final TypeName rawTypeName;
|
||||
private final List<? extends AnnotationMirror> accessorAnnotations;
|
||||
private final List<? extends AnnotationMirror> canonicalConstructorAnnotations;
|
||||
|
||||
public RecordClassType(TypeName typeName, TypeName rawTypeName, String name, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||
public RecordClassType(TypeName typeName, String name, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
|
||||
super(typeName, name);
|
||||
this.rawTypeName = rawTypeName;
|
||||
this.accessorAnnotations = accessorAnnotations;
|
||||
this.canonicalConstructorAnnotations = canonicalConstructorAnnotations;
|
||||
}
|
||||
|
||||
public TypeName rawTypeName() {
|
||||
return rawTypeName;
|
||||
}
|
||||
|
||||
public List<? extends AnnotationMirror> getAccessorAnnotations() {
|
||||
return accessorAnnotations;
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>34-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>record-builder-test-custom-enhancer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.spi.Processor;
|
||||
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
|
||||
import recordbuilder.org.objectweb.asm.Opcodes;
|
||||
import recordbuilder.org.objectweb.asm.tree.FieldInsnNode;
|
||||
import recordbuilder.org.objectweb.asm.tree.InsnList;
|
||||
import recordbuilder.org.objectweb.asm.tree.InsnNode;
|
||||
import recordbuilder.org.objectweb.asm.tree.MethodInsnNode;
|
||||
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import java.util.List;
|
||||
|
||||
public class TestEnhancer
|
||||
implements RecordBuilderEnhancer
|
||||
{
|
||||
@Override
|
||||
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments)
|
||||
{
|
||||
/*
|
||||
4: getstatic #7 // Field io/soabase/recordbuilder/enhancer/test/Counter.COUNTER:Ljava/util/concurrent/atomic/AtomicInteger;
|
||||
7: invokevirtual #13 // Method java/util/concurrent/atomic/AtomicInteger.incrementAndGet:()I
|
||||
10: pop
|
||||
*/
|
||||
InsnList insnList = new InsnList();
|
||||
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, "io/soabase/recordbuilder/enhancer/test/Counter", "COUNTER", "Ljava/util/concurrent/atomic/AtomicInteger;"));
|
||||
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/util/concurrent/atomic/AtomicInteger", "incrementAndGet", "()I"));
|
||||
insnList.add(new InsnNode(Opcodes.POP));
|
||||
return insnList;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return "This is a test";
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>34-SNAPSHOT</version>
|
||||
<version>21</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -22,22 +22,6 @@
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-processor</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-enhancer</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -46,16 +30,6 @@
|
||||
<artifactId>record-builder-validator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder-test-custom-enhancer</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
@@ -77,6 +51,31 @@
|
||||
|
||||
<build>
|
||||
<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>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
@@ -92,48 +91,6 @@
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</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>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.CopyCollectionNullableEmpty;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = CopyCollectionNullableEmpty.class)
|
||||
public record CopyCollectionNullableEmptyTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Counter {
|
||||
public static final AtomicInteger COUNTER = new AtomicInteger();
|
||||
|
||||
private Counter() {
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.CopyCollectionNullableEmpty;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullOptional;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullString;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.RequireNonNull;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@RecordBuilderEnhance.Template(enhancers = {CopyCollectionNullableEmpty.class, EmptyNullString.class, EmptyNullOptional.class, RequireNonNull.class})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface CustomEnhance {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@CustomEnhance
|
||||
public record CustomEnhanced(String s, Optional<String> o, Instant i, List<BigDecimal> l) {
|
||||
public CustomEnhanced {
|
||||
Counter.COUNTER.decrementAndGet();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.GuavaCopyCollectionNullableEmpty;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = GuavaCopyCollectionNullableEmpty.class)
|
||||
public record GuavaCopyCollectionNullableEmptyTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.GuavaCopyCollection;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = GuavaCopyCollection.class)
|
||||
public record GuavaCopyCollectionTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.RECORD_COMPONENT;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
@Target(RECORD_COMPONENT)
|
||||
@Retention(SOURCE)
|
||||
public @interface MadeUpNotNullable {
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhanceArguments;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.NotNullAnnotations;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = NotNullAnnotations.class,
|
||||
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = ".*notnull.*"))
|
||||
public record NotNullAnnotation(String s, @NotNull Integer i, @MadeUpNotNullable Double d) {
|
||||
public NotNullAnnotation() {
|
||||
this(null, null, null);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.CopyCollection;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.RequireNonNull;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = {RequireNonNull.class, CopyCollection.class})
|
||||
public record NotNullableCopyCollectionTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullOptional;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = EmptyNullOptional.class)
|
||||
public record OptionalTest(OptionalInt i, OptionalLong l, OptionalDouble d, Optional<String> o) {
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = TestEnhancer.class)
|
||||
public record PluginTest(int i) {
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
|
||||
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullString;
|
||||
|
||||
@RecordBuilderEnhance(enhancers = EmptyNullString.class)
|
||||
public record StringTest(String s) {
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import io.soabase.recordbuilder.core.RecordInterface;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordInterface
|
||||
@RecordBuilder.Options(useImmutableCollections = true)
|
||||
public interface CollectionInterface<T, X extends Point> {
|
||||
List<T> l();
|
||||
|
||||
Set<T> s();
|
||||
|
||||
Map<T, X> m();
|
||||
|
||||
Collection<X> c();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@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 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) -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(useImmutableCollections = true)
|
||||
public record CollectionRecordConflicts(List<String> __list, Set<String> __set, Map<String, String> __map, Collection<String> __collection) implements CollectionRecordConflictsBuilder.With {
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import 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 {
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.lang.model.type.ErrorType;
|
||||
import java.util.List;
|
||||
|
||||
@RecordBuilder
|
||||
public record ExceptionDetails(
|
||||
String internalMessage, String endUserMessage, String httpStatus,
|
||||
ErrorType errorType, List<String> jsonProblems, Throwable cause
|
||||
) {
|
||||
@Override
|
||||
public List<String> jsonProblems() {
|
||||
if (jsonProblems == null) {
|
||||
return List.of();
|
||||
}
|
||||
return jsonProblems;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilderFull;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RecordBuilderFull
|
||||
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords, @NotNull String justAString) {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@RecordBuilder()
|
||||
@RecordBuilder.Options(inheritComponentAnnotations = false)
|
||||
public record IgnoreAnnotated(@NotNull String s) {
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder.Options(prefixEnclosingClassNames = false)
|
||||
@RecordBuilder.Include(IncludeWithOption.Hey.class)
|
||||
public class IncludeWithOption {
|
||||
public static record Hey(String s){}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
@MyInterfaceTemplate
|
||||
public interface InterfaceTemplateTest {
|
||||
String name();
|
||||
|
||||
int age();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RecordBuilder
|
||||
public record MutableCollectionRecord(List<String> l) implements MutableCollectionRecordBuilder.With {
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder.Template(options = @RecordBuilder.Options(
|
||||
fileComment = "This is a test",
|
||||
withClassName = "Com"),
|
||||
asRecordInterface = true
|
||||
)
|
||||
public @interface MyInterfaceTemplate {
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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,8 +15,6 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -24,6 +22,6 @@ import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
@RecordBuilder.Options(emptyDefaultForOptional = true, addConcreteSettersForOptional = true)
|
||||
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
||||
@RecordBuilder
|
||||
public record RecordWithOptional(@NotNull Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||
public record RecordWithOptional(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import 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) {}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import 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,9 +18,8 @@ package io.soabase.recordbuilder.test;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@RecordBuilder.Options(interpretNotNulls = true)
|
||||
@RecordBuilder
|
||||
public record RequiredRecord(@NotNull String hey, @NotNull int i, @NotNull List<String> l) implements RequiredRecordBuilder.With {
|
||||
public record RequiredRecord(@NotNull String hey, @NotNull int i) {
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@ import javax.validation.constraints.NotNull;
|
||||
|
||||
@RecordBuilder.Options(useValidationApi = true)
|
||||
@RecordBuilder
|
||||
public record RequiredRecord2(@NotNull String hey, @NotNull int i) implements RequiredRecord2Builder.With {
|
||||
public record RequiredRecord2(@NotNull String hey, @NotNull int i) {
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
addSingleItemCollectionBuilders = true,
|
||||
singleItemBuilderPrefix = "add1",
|
||||
useImmutableCollections = true,
|
||||
addFunctionalMethodsToWith = true
|
||||
)
|
||||
public record SingleItems<T>(List<String> strings, Set<List<T>> sets, Map<Instant, T> map, Collection<T> collection) implements SingleItemsBuilder.With<T> {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
enableGetters = false,
|
||||
enableWither = false
|
||||
)
|
||||
public record StrippedFeaturesRecord(int aField) {}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
addSingleItemCollectionBuilders = true,
|
||||
useImmutableCollections = true
|
||||
)
|
||||
public record WildcardSingleItems<T>(List<? extends String> strings, Set<? extends List<? extends T>> sets, Map<? extends Instant, ? extends T> map, Collection<? extends T> collection) implements WildcardSingleItemsBuilder.With<T> {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.includes;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder.Include(
|
||||
packages = "io.soabase.recordbuilder.test.includes.pack",
|
||||
classes = JustATest.class
|
||||
)
|
||||
public class IncludeFactory {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.includes;
|
||||
|
||||
public record JustATest(int i) {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.includes.pack;
|
||||
|
||||
public interface AlsoIgnoreMe {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.includes.pack;
|
||||
|
||||
public class IgnoreMe {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.includes.pack;
|
||||
|
||||
public record PackRecord1(String name) {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.includes.pack;
|
||||
|
||||
public record PackRecord2(String name, int age) {
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.includes.pack;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record PackRecord3(Instant time, int age) {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.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) {
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.visibility;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder
|
||||
record PackagePrivateRecord(int i) {
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.visibility;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
class Wrapper {
|
||||
@RecordBuilder
|
||||
protected record ProtectedRecord(int i) {
|
||||
}
|
||||
|
||||
@RecordBuilder
|
||||
record PackagePrivateRecord(int i) {
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.enhancer.test;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
class TestEnhanced {
|
||||
@Test
|
||||
void testOptional()
|
||||
{
|
||||
OptionalTest optionalTest = new OptionalTest(null, null, null, null);
|
||||
Assertions.assertTrue(optionalTest.d().isEmpty());
|
||||
Assertions.assertTrue(optionalTest.i().isEmpty());
|
||||
Assertions.assertTrue(optionalTest.l().isEmpty());
|
||||
Assertions.assertTrue(optionalTest.o().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testString()
|
||||
{
|
||||
StringTest stringTest = new StringTest(null);
|
||||
Assertions.assertNotNull(stringTest.s());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyCollectionNullableEmptyTest()
|
||||
{
|
||||
CopyCollectionNullableEmptyTest test = new CopyCollectionNullableEmptyTest(null, null, null, null);
|
||||
Assertions.assertTrue(test.c().isEmpty());
|
||||
Assertions.assertTrue(test.l().isEmpty());
|
||||
Assertions.assertTrue(test.s().isEmpty());
|
||||
Assertions.assertTrue(test.m().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomEnhanced()
|
||||
{
|
||||
Instant now = Instant.now();
|
||||
int current = Counter.COUNTER.get();
|
||||
CustomEnhanced customEnhanced = new CustomEnhanced(null, null, now, null);
|
||||
Assertions.assertTrue(customEnhanced.o().isEmpty());
|
||||
Assertions.assertEquals(customEnhanced.s(), "");
|
||||
Assertions.assertTrue(customEnhanced.l().isEmpty());
|
||||
Assertions.assertEquals(customEnhanced.i(), now);
|
||||
Assertions.assertEquals(current - 1, Counter.COUNTER.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGuavaCopyCollectionNullableEmptyTest()
|
||||
{
|
||||
GuavaCopyCollectionNullableEmptyTest test = new GuavaCopyCollectionNullableEmptyTest(null, null, null, null);
|
||||
Assertions.assertTrue(test.l() instanceof ImmutableList<BigInteger>);
|
||||
Assertions.assertTrue(test.s() instanceof ImmutableSet<Boolean>);
|
||||
Assertions.assertTrue(test.c() instanceof ImmutableSet<Instant>);
|
||||
Assertions.assertTrue(test.m() instanceof ImmutableMap<Map<String, Short>, AtomicBoolean>);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPlugin()
|
||||
{
|
||||
int previous = Counter.COUNTER.get();
|
||||
new PluginTest(0);
|
||||
Assertions.assertEquals(previous + 1, Counter.COUNTER.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotNullAnnotations()
|
||||
{
|
||||
NotNullAnnotation notNullAnnotation = new NotNullAnnotation(null, 10, 10.0);
|
||||
Assertions.assertNull(notNullAnnotation.s());
|
||||
Assertions.assertNotNull(notNullAnnotation.i());
|
||||
Assertions.assertNotNull(notNullAnnotation.d());
|
||||
Assertions.assertThrows(NullPointerException.class, () -> new NotNullAnnotation("s", null, 10.0));
|
||||
Assertions.assertThrows(NullPointerException.class, () -> new NotNullAnnotation("s", 100, null));
|
||||
Assertions.assertThrows(NullPointerException.class, NotNullAnnotation::new);
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,6 @@ import javax.validation.constraints.Null;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
class TestAnnotated {
|
||||
@Test
|
||||
void testInheritComponentAnnotationsFalse() throws NoSuchMethodException {
|
||||
var method = IgnoreAnnotatedBuilder.class.getMethod("s");
|
||||
Assertions.assertNull(method.getAnnotation(NotNull.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStaticConstructor() throws NoSuchMethodException {
|
||||
var method = AnnotatedBuilder.class.getMethod("Annotated", String.class, Integer.TYPE, Double.TYPE);
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static io.soabase.recordbuilder.test.foo.PointBuilder.Point;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TestCollections {
|
||||
@Test
|
||||
void testRecordBuilderOptionsCopied() {
|
||||
try {
|
||||
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", List.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
Assertions.fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionRecordDefaultValues() {
|
||||
var defaultValues = CollectionRecordBuilder.builder().build();
|
||||
assertNotNull(defaultValues.l());
|
||||
assertNotNull(defaultValues.s());
|
||||
assertNotNull(defaultValues.m());
|
||||
assertNotNull(defaultValues.c());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionRecordImmutable() {
|
||||
var list = new ArrayList<String>();
|
||||
list.add("one");
|
||||
var set = new HashSet<String>();
|
||||
set.add("one");
|
||||
var map = new HashMap<String, Point>();
|
||||
map.put("one", Point(10, 20));
|
||||
var collectionAsSet = new HashSet<Point>();
|
||||
collectionAsSet.add(Point(30, 40));
|
||||
var r = CollectionRecordBuilder.<String, Point>builder()
|
||||
.l(list)
|
||||
.s(set)
|
||||
.m(map)
|
||||
.c(collectionAsSet)
|
||||
.build();
|
||||
|
||||
assertValues(r, list, set, map, collectionAsSet);
|
||||
assertValueChanges(r, list, set, map, collectionAsSet);
|
||||
assertImmutable(r);
|
||||
|
||||
var collectionAsList = new ArrayList<Point>();
|
||||
var x = CollectionRecordBuilder.<String, Point>builder()
|
||||
.l(list)
|
||||
.s(set)
|
||||
.m(map)
|
||||
.c(collectionAsList)
|
||||
.build();
|
||||
assertTrue(x.c() instanceof List);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCollectionRecordImmutableWithers() {
|
||||
var r = CollectionRecordBuilder.<String, Point>builder().build();
|
||||
|
||||
var list = new ArrayList<String>();
|
||||
list.add("one");
|
||||
var set = new HashSet<String>();
|
||||
set.add("one");
|
||||
var map = new HashMap<String, Point>();
|
||||
map.put("one", Point(10, 20));
|
||||
var collectionAsSet = new HashSet<Point>();
|
||||
collectionAsSet.add(Point(30, 40));
|
||||
var x = r.withL(list).withS(set).withM(map).withC(collectionAsSet);
|
||||
|
||||
assertValues(x, list, set, map, collectionAsSet);
|
||||
assertValueChanges(x, list, set, map, collectionAsSet);
|
||||
assertImmutable(x);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMutableCollectionRecord() {
|
||||
var r = MutableCollectionRecordBuilder.builder().build();
|
||||
assertNull(r.l());
|
||||
|
||||
var list = new ArrayList<String>();
|
||||
list.add("one");
|
||||
r = MutableCollectionRecordBuilder.builder().l(list).build();
|
||||
|
||||
assertEquals(r.l(), list);
|
||||
list.add("two");
|
||||
assertEquals(r.l(), list);
|
||||
}
|
||||
|
||||
private void assertImmutable(CollectionRecord<String, Point> r) {
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.l().add("hey"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.s().add("hey"));
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.m().put("hey", Point(1, 2)));
|
||||
assertThrows(UnsupportedOperationException.class, () -> r.c().add(Point(1, 2)));
|
||||
}
|
||||
|
||||
private void assertValueChanges(CollectionRecord<String, Point> r, ArrayList<String> list, HashSet<String> set, HashMap<String, Point> map, HashSet<Point> collectionAsSet) {
|
||||
list.add("two");
|
||||
set.add("two");
|
||||
map.put("two", Point(50, 60));
|
||||
collectionAsSet.add(Point(70, 80));
|
||||
|
||||
assertNotEquals(r.l(), list);
|
||||
assertNotEquals(r.s(), set);
|
||||
assertNotEquals(r.m(), map);
|
||||
assertNotEquals(r.c(), collectionAsSet);
|
||||
}
|
||||
|
||||
private void assertValues(CollectionRecord<String, Point> r, ArrayList<String> list, HashSet<String> set, HashMap<String, Point> map, HashSet<Point> collectionAsSet) {
|
||||
assertEquals(r.l(), list);
|
||||
assertEquals(r.s(), set);
|
||||
assertEquals(r.m(), map);
|
||||
assertEquals(r.c(), collectionAsSet);
|
||||
assertTrue(r.c() instanceof Set);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import 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;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class TestOptional {
|
||||
@Test
|
||||
void testDefaultEmpty() {
|
||||
@@ -33,51 +33,4 @@ class TestOptional {
|
||||
Assertions.assertEquals(OptionalLong.empty(), record.l());
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
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));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user