Compare commits
33 Commits
record-bui
...
jordanz/ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfedb0a5e0 | ||
|
|
4924f7b3ea | ||
|
|
3e1d7d69d0 | ||
|
|
22f827d81a | ||
|
|
91c6090ace | ||
|
|
098a5c8bfd | ||
|
|
e2f17d4087 | ||
|
|
2b3e895cf6 | ||
|
|
117c789593 | ||
|
|
4ff80fb20c | ||
|
|
abfc12bdb0 | ||
|
|
6c0fac0dff | ||
|
|
ae527cd8e5 | ||
|
|
73ba62057a | ||
|
|
87998aba68 | ||
|
|
04a0904d3f | ||
|
|
aa072af8e1 | ||
|
|
b435b5d3fd | ||
|
|
d3c1bb36f3 | ||
|
|
c3719326c9 | ||
|
|
661d0818c0 | ||
|
|
79bc8396f2 | ||
|
|
b2149622e4 | ||
|
|
0718e37f76 | ||
|
|
d112a1b352 | ||
|
|
86093b6bad | ||
|
|
7b6ad4d7ba | ||
|
|
cd059f1207 | ||
|
|
642dd01421 | ||
|
|
efd1a6b0d4 | ||
|
|
b525eddc76 | ||
|
|
d3828eda74 | ||
|
|
fef69af183 |
2
.github/workflows/maven_java16.yml
vendored
2
.github/workflows/maven_java16.yml
vendored
@@ -21,4 +21,4 @@ jobs:
|
||||
with:
|
||||
java-version: 16
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
run: ./mvnw -B package --file pom.xml
|
||||
|
||||
2
.github/workflows/maven_java17.yml
vendored
2
.github/workflows/maven_java17.yml
vendored
@@ -21,4 +21,4 @@ jobs:
|
||||
with:
|
||||
java-version: 17
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
run: ./mvnw -B package --file pom.xml
|
||||
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Default ignored files
|
||||
.idea
|
||||
**/target/**
|
||||
|
||||
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
18
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
18
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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
|
||||
#
|
||||
# https://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.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
|
||||
266
README.md
266
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://github.com/Randgalt/record-builder/actions)
|
||||
[](https://github.com/Randgalt/record-builder/actions/workflows/maven_java17.yml)
|
||||
[](https://search.maven.org/search?q=g:io.soabase.record-builder%20a:record-builder)
|
||||
|
||||
# RecordBuilder
|
||||
@@ -89,168 +89,176 @@ _Hat tip to [Benji Weber](https://benjiweber.co.uk/blog/2020/09/19/fun-with-java
|
||||
|
||||
## Builder Class Definition
|
||||
|
||||
(Note: you can see a builder class built using `@RecordBuilderFull` here: [SingleItemsBuilder.java](https://gist.github.com/Randgalt/8aa487a847ea2acdd76d702f7cf17d6a))
|
||||
(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
|
||||
public class NameAndAgeBuilder {
|
||||
private String name;
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
private int age;
|
||||
|
||||
private NameAndAgeBuilder() {
|
||||
}
|
||||
private NameAndAgeBuilder() {
|
||||
}
|
||||
|
||||
private NameAndAgeBuilder(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
private NameAndAgeBuilder(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static constructor/builder. Can be used instead of new NameAndAge(...)
|
||||
*/
|
||||
public static NameAndAge NameAndAge(String name, int age) {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
/**
|
||||
* Static constructor/builder. Can be used instead of new NameAndAge(...)
|
||||
*/
|
||||
public static NameAndAge NameAndAge(String name, int age) {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new builder with all fields set to default Java values
|
||||
*/
|
||||
public static NameAndAgeBuilder builder() {
|
||||
return new NameAndAgeBuilder();
|
||||
}
|
||||
/**
|
||||
* Return a new builder with all fields set to default Java values
|
||||
*/
|
||||
public static NameAndAgeBuilder builder() {
|
||||
return new NameAndAgeBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new builder with all fields set to the values taken from the given record instance
|
||||
*/
|
||||
public static NameAndAgeBuilder builder(NameAndAge from) {
|
||||
return new NameAndAgeBuilder(from.name(), from.age());
|
||||
}
|
||||
/**
|
||||
* Return a new builder with all fields set to the values taken from the given record instance
|
||||
*/
|
||||
public static NameAndAgeBuilder builder(NameAndAge from) {
|
||||
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 NameAndAgeBuilder.With() {
|
||||
@Override
|
||||
public String name() {
|
||||
return from.name();
|
||||
}
|
||||
/**
|
||||
* Return a "with"er for an existing record instance
|
||||
*/
|
||||
public static NameAndAgeBuilder.With from(NameAndAge from) {
|
||||
return new _FromWith(from);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int age() {
|
||||
return from.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.SimpleImmutableEntry<>("name", record.name()),
|
||||
new AbstractMap.SimpleImmutableEntry<>("age", record.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(Map.entry("name", record.name()),
|
||||
Map.entry("age", record.age()));
|
||||
}
|
||||
/**
|
||||
* Return a new record instance with all fields set to the current values in this builder
|
||||
*/
|
||||
public NameAndAge build() {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new record instance with all fields set to the current values in this builder
|
||||
*/
|
||||
public NameAndAge build() {
|
||||
return new NameAndAge(name, age);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NameAndAgeBuilder[name=" + name + ", age=" + age + "]";
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, 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));
|
||||
}
|
||||
|
||||
@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
|
||||
*/
|
||||
public NameAndAgeBuilder name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for the {@code name} record component in the builder
|
||||
*/
|
||||
public NameAndAgeBuilder name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current value for the {@code name} record component in the builder
|
||||
*/
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for the {@code age} record component in the builder
|
||||
*/
|
||||
public NameAndAgeBuilder age(int age) {
|
||||
this.age = age;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current value for the {@code age} record component in the builder
|
||||
*/
|
||||
public int age() {
|
||||
return age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add withers to {@code NameAndAge}
|
||||
*/
|
||||
public interface With {
|
||||
/**
|
||||
* Return the current value for the {@code name} record component in the builder
|
||||
*/
|
||||
String name();
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new value for the {@code age} record component in the builder
|
||||
*/
|
||||
public NameAndAgeBuilder age(int age) {
|
||||
this.age = age;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
public int age() {
|
||||
return age;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new record built from the builder passed to the given consumer
|
||||
* Add withers to {@code NameAndAge}
|
||||
*/
|
||||
default NameAndAge with(Consumer<NameAndAgeBuilder> consumer) {
|
||||
NameAndAgeBuilder builder = with();
|
||||
consumer.accept(builder);
|
||||
return builder.build();
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new record built from the builder passed to the given consumer
|
||||
*/
|
||||
default NameAndAge with(Consumer<NameAndAgeBuilder> consumer) {
|
||||
NameAndAgeBuilder builder = with();
|
||||
consumer.accept(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
|
||||
*/
|
||||
default NameAndAge withName(String name) {
|
||||
return new NameAndAge(name, 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of {@code NameAndAge} with a new value for {@code name}
|
||||
*/
|
||||
default NameAndAge withName(String name) {
|
||||
return new NameAndAge(name, age());
|
||||
}
|
||||
private static final class _FromWith implements NameAndAgeBuilder.With {
|
||||
private final NameAndAge from;
|
||||
|
||||
/**
|
||||
* 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 _FromWith(NameAndAge from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return from.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int age() {
|
||||
return from.age();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
287
mvnw
vendored
Executable file
287
mvnw
vendored
Executable file
@@ -0,0 +1,287 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.1.1
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||
. /usr/local/etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME
|
||||
else
|
||||
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`\\unset -f command; \\command -v java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
printf '%s' "$(cd "$basedir"; pwd)"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=$(find_maven_basedir "$(dirname $0)")
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
|
||||
else
|
||||
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $wrapperUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if $cygwin; then
|
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
QUIET="--quiet"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
QUIET=""
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath"
|
||||
else
|
||||
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath"
|
||||
fi
|
||||
[ $? -eq 0 ] || rm -f "$wrapperJarPath"
|
||||
elif command -v curl > /dev/null; then
|
||||
QUIET="--silent"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
QUIET=""
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L
|
||||
else
|
||||
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L
|
||||
fi
|
||||
[ $? -eq 0 ] || rm -f "$wrapperJarPath"
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaSource=`cygpath --path --windows "$javaSource"`
|
||||
javaClass=`cygpath --path --windows "$javaClass"`
|
||||
fi
|
||||
if [ -e "$javaSource" ]; then
|
||||
if [ ! -e "$javaClass" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaSource")
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
$MAVEN_DEBUG_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
187
mvnw.cmd
vendored
Normal file
187
mvnw.cmd
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.1.1
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
|
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %WRAPPER_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% ^
|
||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||
%MAVEN_OPTS% ^
|
||||
%MAVEN_DEBUG_OPTS% ^
|
||||
-classpath %WRAPPER_JAR% ^
|
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||
|
||||
cmd /C exit /B %ERROR_CODE%
|
||||
26
pom.xml
26
pom.xml
@@ -5,7 +5,7 @@
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>32</version>
|
||||
<version>36-SNAPSHOT</version>
|
||||
|
||||
<modules>
|
||||
<module>record-builder-core</module>
|
||||
@@ -32,6 +32,9 @@
|
||||
<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>
|
||||
|
||||
<license-file-path>src/etc/header.txt</license-file-path>
|
||||
|
||||
@@ -77,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-32</tag>
|
||||
<tag>record-builder-1.16</tag>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
@@ -153,6 +156,12 @@
|
||||
<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>
|
||||
@@ -218,6 +227,8 @@
|
||||
<exclude>**/.travis.yml</exclude>
|
||||
<exclude>**/gradlew</exclude>
|
||||
<exclude>**/.github/**</exclude>
|
||||
<exclude>**/.mvn/**</exclude>
|
||||
<exclude>**/mvnw*</exclude>
|
||||
</excludes>
|
||||
<strictCheck>true</strictCheck>
|
||||
</configuration>
|
||||
@@ -319,6 +330,12 @@
|
||||
<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>
|
||||
|
||||
@@ -352,6 +369,11 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>32</version>
|
||||
<version>36-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.core;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@@ -99,6 +100,11 @@ public @interface RecordBuilder {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -139,6 +145,11 @@ 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).
|
||||
@@ -195,6 +206,11 @@ public @interface RecordBuilder {
|
||||
*/
|
||||
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
|
||||
@@ -219,6 +235,49 @@ public @interface RecordBuilder {
|
||||
* this option does nothing.
|
||||
*/
|
||||
String beanClassName() default "";
|
||||
|
||||
/**
|
||||
* If true, generated classes are annotated with {@code RecordBuilderGenerated} which has a retention
|
||||
* policy of {@code CLASS}. This ensures that analyzers such as Jacoco will ignore the generated class.
|
||||
*/
|
||||
boolean addClassRetainedGenerated() default false;
|
||||
|
||||
/**
|
||||
* The {@link #fromMethodName} method instantiates an internal private class. This is the
|
||||
* name of that class.
|
||||
*/
|
||||
String fromWithClassName() default "_FromWith";
|
||||
|
||||
/**
|
||||
* If true, a functional-style builder is added so that record instances can be instantiated
|
||||
* without {@code new}.
|
||||
*/
|
||||
boolean addStaticBuilder() default true;
|
||||
|
||||
/**
|
||||
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||
* uses an internal class to track changes to lists. This is the name of that class.
|
||||
*/
|
||||
String mutableListClassName() default "_MutableList";
|
||||
|
||||
/**
|
||||
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||
* uses an internal class to track changes to sets. This is the name of that class.
|
||||
*/
|
||||
String mutableSetClassName() default "_MutableSet";
|
||||
|
||||
/**
|
||||
* If {@link #addSingleItemCollectionBuilders()} and {@link #useImmutableCollections()} are enabled the builder
|
||||
* uses an internal class to track changes to maps. This is the name of that class.
|
||||
*/
|
||||
String mutableMapClassName() default "_MutableMap";
|
||||
|
||||
|
||||
/**
|
||||
* Any additional {@link javax.lang.model.element.Modifier} you wish to apply to the builder. For example to
|
||||
* make the builder public when the record is package protect.
|
||||
*/
|
||||
Modifier[] builderClassModifiers() default {};
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
|
||||
@@ -21,7 +21,8 @@ import java.lang.annotation.*;
|
||||
interpretNotNulls = true,
|
||||
useImmutableCollections = true,
|
||||
addSingleItemCollectionBuilders = true,
|
||||
addFunctionalMethodsToWith = true
|
||||
addFunctionalMethodsToWith = true,
|
||||
addClassRetainedGenerated = true
|
||||
))
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.core;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
|
||||
/**
|
||||
* Jacoco ignores classes and methods annotated with `*Generated`
|
||||
*/
|
||||
@Target({PACKAGE, TYPE, METHOD, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, PARAMETER})
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface RecordBuilderGenerated {
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>32</version>
|
||||
<version>36-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -22,24 +22,38 @@ import javax.lang.model.element.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||
|
||||
class CollectionBuilderUtils {
|
||||
private final boolean useImmutableCollections;
|
||||
private final boolean addSingleItemCollectionBuilders;
|
||||
private final boolean addClassRetainedGenerated;
|
||||
private final String listShimName;
|
||||
private final String mapShimName;
|
||||
private final String setShimName;
|
||||
private final String collectionShimName;
|
||||
|
||||
private final String listMakerMethodName;
|
||||
private final String mapMakerMethodName;
|
||||
private final String setMakerMethodName;
|
||||
|
||||
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 boolean needsListMutableMaker;
|
||||
private boolean needsMapMutableMaker;
|
||||
private boolean needsSetMutableMaker;
|
||||
|
||||
private static final Class<?> listType = List.class;
|
||||
private static final Class<?> mapType = Map.class;
|
||||
private static final Class<?> setType = Set.class;
|
||||
private static final Class<?> collectionType = Collection.class;
|
||||
private static final TypeName listTypeName = TypeName.get(listType);
|
||||
private static final TypeName mapTypeName = TypeName.get(mapType);
|
||||
private static final TypeName setTypeName = TypeName.get(setType);
|
||||
private static final TypeName collectionTypeName = TypeName.get(collectionType);
|
||||
|
||||
private static final TypeVariableName tType = TypeVariableName.get("T");
|
||||
private static final TypeVariableName kType = TypeVariableName.get("K");
|
||||
@@ -49,14 +63,33 @@ class CollectionBuilderUtils {
|
||||
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName.get(ClassName.get(Set.class), tType);
|
||||
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName.get(ClassName.get(Collection.class), tType);
|
||||
|
||||
private static final Class<?> mutableListType = ArrayList.class;
|
||||
private static final Class<?> mutableMapType = HashMap.class;
|
||||
private static final Class<?> mutableSetType = HashSet.class;
|
||||
private static final ClassName mutableListTypeName = ClassName.get(mutableListType);
|
||||
private static final ClassName mutableMapTypeName = ClassName.get(mutableMapType);
|
||||
private static final ClassName mutableSetTypeName = ClassName.get(mutableSetType);
|
||||
private final TypeSpec mutableListSpec;
|
||||
private final TypeSpec mutableSetSpec;
|
||||
private final TypeSpec mutableMapSpec;
|
||||
|
||||
CollectionBuilderUtils(List<RecordClassType> recordComponents, RecordBuilder.Options metaData) {
|
||||
useImmutableCollections = metaData.useImmutableCollections();
|
||||
addSingleItemCollectionBuilders = metaData.addSingleItemCollectionBuilders();
|
||||
addClassRetainedGenerated = metaData.addClassRetainedGenerated();
|
||||
|
||||
listShimName = adjustShimName(recordComponents, "__list", 0);
|
||||
mapShimName = adjustShimName(recordComponents, "__map", 0);
|
||||
setShimName = adjustShimName(recordComponents, "__set", 0);
|
||||
collectionShimName = adjustShimName(recordComponents, "__collection", 0);
|
||||
listShimName = disambiguateGeneratedMethodName(recordComponents, "__list", 0);
|
||||
mapShimName = disambiguateGeneratedMethodName(recordComponents, "__map", 0);
|
||||
setShimName = disambiguateGeneratedMethodName(recordComponents, "__set", 0);
|
||||
collectionShimName = disambiguateGeneratedMethodName(recordComponents, "__collection", 0);
|
||||
|
||||
listMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureListMutable", 0);
|
||||
setMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureSetMutable", 0);
|
||||
mapMakerMethodName = disambiguateGeneratedMethodName(recordComponents, "__ensureMapMutable", 0);
|
||||
|
||||
mutableListSpec = buildMutableCollectionSubType(metaData.mutableListClassName(), mutableListTypeName, parameterizedListType, tType);
|
||||
mutableSetSpec = buildMutableCollectionSubType(metaData.mutableSetClassName(), mutableSetTypeName, parameterizedSetType, tType);
|
||||
mutableMapSpec = buildMutableCollectionSubType(metaData.mutableMapClassName(), mutableMapTypeName, parameterizedMapType, kType, vType);
|
||||
}
|
||||
|
||||
enum SingleItemsMetaDataMode {
|
||||
@@ -65,7 +98,8 @@ class CollectionBuilderUtils {
|
||||
EXCLUDE_WILDCARD_TYPES
|
||||
}
|
||||
|
||||
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {}
|
||||
record SingleItemsMetaData(Class<?> singleItemCollectionClass, List<TypeName> typeArguments, TypeName wildType) {
|
||||
}
|
||||
|
||||
Optional<SingleItemsMetaData> singleItemsMetaData(RecordClassType component, SingleItemsMetaDataMode mode) {
|
||||
if (addSingleItemCollectionBuilders && (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)) {
|
||||
@@ -73,15 +107,15 @@ class CollectionBuilderUtils {
|
||||
ClassName wildcardClass = null;
|
||||
int typeArgumentQty = 0;
|
||||
if (isList(component)) {
|
||||
collectionClass = ArrayList.class;
|
||||
collectionClass = mutableListType;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isSet(component)) {
|
||||
collectionClass = HashSet.class;
|
||||
collectionClass = mutableSetType;
|
||||
wildcardClass = ClassName.get(Collection.class);
|
||||
typeArgumentQty = 1;
|
||||
} else if (isMap(component)) {
|
||||
collectionClass = HashMap.class;
|
||||
collectionClass = mutableMapType;
|
||||
wildcardClass = (ClassName) component.rawTypeName();
|
||||
typeArgumentQty = 2;
|
||||
}
|
||||
@@ -109,30 +143,37 @@ class CollectionBuilderUtils {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
boolean isImmutableCollection(RecordClassType component) {
|
||||
return useImmutableCollections && (isList(component) || isMap(component) || isSet(component) || component.rawTypeName().equals(collectionTypeName));
|
||||
}
|
||||
|
||||
boolean isList(RecordClassType component) {
|
||||
return component.rawTypeName().equals(listType);
|
||||
return component.rawTypeName().equals(listTypeName);
|
||||
}
|
||||
|
||||
boolean isMap(RecordClassType component) {
|
||||
return component.rawTypeName().equals(mapType);
|
||||
return component.rawTypeName().equals(mapTypeName);
|
||||
}
|
||||
|
||||
boolean isSet(RecordClassType component) {
|
||||
return component.rawTypeName().equals(setType);
|
||||
return component.rawTypeName().equals(setTypeName);
|
||||
}
|
||||
|
||||
void add(CodeBlock.Builder builder, RecordClassType component) {
|
||||
void addShimCall(CodeBlock.Builder builder, RecordClassType component) {
|
||||
if (useImmutableCollections) {
|
||||
if (isList(component)) {
|
||||
needsListShim = true;
|
||||
needsListMutableMaker = true;
|
||||
builder.add("$L($L)", listShimName, component.name());
|
||||
} else if (isMap(component)) {
|
||||
needsMapShim = true;
|
||||
needsMapMutableMaker = true;
|
||||
builder.add("$L($L)", mapShimName, component.name());
|
||||
} else if (isSet(component)) {
|
||||
needsSetShim = true;
|
||||
needsSetMutableMaker = true;
|
||||
builder.add("$L($L)", setShimName, component.name());
|
||||
} else if (component.rawTypeName().equals(collectionType)) {
|
||||
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||
needsCollectionShim = true;
|
||||
builder.add("$L($L)", collectionShimName, component.name());
|
||||
} else {
|
||||
@@ -143,22 +184,67 @@ class CollectionBuilderUtils {
|
||||
}
|
||||
}
|
||||
|
||||
String shimName(RecordClassType component) {
|
||||
if (isList(component)) {
|
||||
return listShimName;
|
||||
} else if (isMap(component)) {
|
||||
return mapShimName;
|
||||
} else if (isSet(component)) {
|
||||
return setShimName;
|
||||
} else if (component.rawTypeName().equals(collectionTypeName)) {
|
||||
return collectionShimName;
|
||||
} else {
|
||||
throw new IllegalArgumentException(component + " is not a supported collection type");
|
||||
}
|
||||
}
|
||||
|
||||
String mutableMakerName(RecordClassType component) {
|
||||
if (isList(component)) {
|
||||
return listMakerMethodName;
|
||||
} else if (isMap(component)) {
|
||||
return mapMakerMethodName;
|
||||
} else if (isSet(component)) {
|
||||
return setMakerMethodName;
|
||||
} else {
|
||||
throw new IllegalArgumentException(component + " is not a supported collection type");
|
||||
}
|
||||
}
|
||||
|
||||
void addShims(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsListShim) {
|
||||
builder.addMethod(buildMethod(listShimName, listType, parameterizedListType, tType));
|
||||
builder.addMethod(buildShimMethod(listShimName, listTypeName, collectionType, parameterizedListType, tType));
|
||||
}
|
||||
if (needsSetShim) {
|
||||
builder.addMethod(buildMethod(setShimName, setType, parameterizedSetType, tType));
|
||||
builder.addMethod(buildShimMethod(setShimName, setTypeName, collectionType, parameterizedSetType, tType));
|
||||
}
|
||||
if (needsMapShim) {
|
||||
builder.addMethod(buildMethod(mapShimName, mapType, parameterizedMapType, kType, vType));
|
||||
builder.addMethod(buildShimMethod(mapShimName, mapTypeName, mapType, parameterizedMapType, kType, vType));
|
||||
}
|
||||
if (needsCollectionShim) {
|
||||
builder.addMethod(buildCollectionsMethod());
|
||||
builder.addMethod(buildCollectionsShimMethod());
|
||||
}
|
||||
}
|
||||
|
||||
void addMutableMakers(TypeSpec.Builder builder) {
|
||||
if (!useImmutableCollections) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsListMutableMaker) {
|
||||
builder.addMethod(buildMutableMakerMethod(listMakerMethodName, mutableListSpec.name, parameterizedListType, tType));
|
||||
builder.addType(mutableListSpec);
|
||||
}
|
||||
if (needsSetMutableMaker) {
|
||||
builder.addMethod(buildMutableMakerMethod(setMakerMethodName, mutableSetSpec.name, parameterizedSetType, tType));
|
||||
builder.addType(mutableSetSpec);
|
||||
}
|
||||
if (needsMapMutableMaker) {
|
||||
builder.addMethod(buildMutableMakerMethod(mapMakerMethodName, mutableMapSpec.name, parameterizedMapType, kType, vType));
|
||||
builder.addType(mutableMapSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,34 +269,71 @@ class CollectionBuilderUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
private String adjustShimName(List<RecordClassType> recordComponents, String baseName, int index) {
|
||||
private String disambiguateGeneratedMethodName(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 disambiguateGeneratedMethodName(recordComponents, baseName, index + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private MethodSpec buildMethod(String name, TypeName mainType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
private MethodSpec buildShimMethod(String name, TypeName mainType, Class<?> abstractType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
var code = CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
|
||||
TypeName[] wildCardTypeArguments = parameterizedType.typeArguments.stream().map(WildcardTypeName::subtypeOf).toList().toArray(new TypeName[0]);
|
||||
var extendedParameterizedType = ParameterizedTypeName.get(ClassName.get(abstractType), wildCardTypeArguments);
|
||||
return MethodSpec.methodBuilder(name)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.returns(parameterizedType)
|
||||
.addParameter(extendedParameterizedType, "o")
|
||||
.addStatement(code)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildMutableMakerMethod(String name, String mutableCollectionType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
var nullCase = CodeBlock.of("if (o == null) return new $L<>()", mutableCollectionType);
|
||||
var isMutableCase = CodeBlock.of("if (o instanceof $L) return o", mutableCollectionType);
|
||||
var defaultCase = CodeBlock.of("return new $L<>(o)", mutableCollectionType);
|
||||
return MethodSpec.methodBuilder(name)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.returns(parameterizedType)
|
||||
.addParameter(parameterizedType, "o")
|
||||
.addStatement(code)
|
||||
.addStatement(nullCase)
|
||||
.addStatement(isMutableCase)
|
||||
.addStatement(defaultCase)
|
||||
.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCollectionsMethod() {
|
||||
private TypeSpec buildMutableCollectionSubType(String className, ClassName mutableCollectionType, ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
|
||||
TypeName[] typeArguments = new TypeName[]{};
|
||||
typeArguments = Arrays.stream(typeVariables).toList().toArray(typeArguments);
|
||||
|
||||
TypeSpec.Builder builder = TypeSpec.classBuilder(className)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.superclass(ParameterizedTypeName.get(mutableCollectionType, typeArguments))
|
||||
.addTypeVariables(Arrays.asList(typeVariables))
|
||||
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation).addStatement("super()").build())
|
||||
.addMethod(MethodSpec.constructorBuilder().addAnnotation(generatedRecordBuilderAnnotation).addParameter(parameterizedType, "o").addStatement("super(o)").build());
|
||||
|
||||
if (addClassRetainedGenerated) {
|
||||
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private MethodSpec buildCollectionsShimMethod() {
|
||||
var code = CodeBlock.builder()
|
||||
.add("if (o instanceof Set) {\n")
|
||||
.indent()
|
||||
.addStatement("return $T.copyOf(o)", setType)
|
||||
.addStatement("return $T.copyOf(o)", setTypeName)
|
||||
.unindent()
|
||||
.addStatement("}")
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listType, listType)
|
||||
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listTypeName, listTypeName)
|
||||
.build();
|
||||
return MethodSpec.methodBuilder(collectionShimName)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
|
||||
@@ -15,23 +15,26 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.EXCLUDE_WILDCARD_TYPES;
|
||||
import static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.STANDARD_FOR_SETTER;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.recordBuilderGeneratedAnnotation;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.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 static io.soabase.recordbuilder.processor.CollectionBuilderUtils.SingleItemsMetaDataMode.*;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getBuilderName;
|
||||
import static io.soabase.recordbuilder.processor.ElementUtils.getWithMethodName;
|
||||
import static io.soabase.recordbuilder.processor.RecordBuilderProcessor.generatedRecordBuilderAnnotation;
|
||||
import com.squareup.javapoet.*;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
class InternalRecordBuilderProcessor {
|
||||
private final RecordBuilder.Options metaData;
|
||||
@@ -47,10 +50,7 @@ class InternalRecordBuilderProcessor {
|
||||
private final CollectionBuilderUtils collectionBuilderUtils;
|
||||
|
||||
private static final TypeName overrideType = TypeName.get(Override.class);
|
||||
private static final TypeName optionalType = TypeName.get(Optional.class);
|
||||
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
|
||||
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
|
||||
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
|
||||
private static final TypeName validType = ClassName.get("javax.validation", "Valid");
|
||||
private static final TypeName validatorTypeName = ClassName.get("io.soabase.recordbuilder.validator", "RecordBuilderValidator");
|
||||
private static final TypeVariableName rType = TypeVariableName.get("R");
|
||||
private final ProcessingEnvironment processingEnv;
|
||||
@@ -70,20 +70,30 @@ class InternalRecordBuilderProcessor {
|
||||
|
||||
builder = TypeSpec.classBuilder(builderClassType.name())
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.addModifiers(metaData.builderClassModifiers())
|
||||
.addTypeVariables(typeVariables);
|
||||
if (metaData.addClassRetainedGenerated()) {
|
||||
builder.addAnnotation(recordBuilderGeneratedAnnotation);
|
||||
}
|
||||
addVisibility(recordActualPackage.equals(packageName), record.getModifiers());
|
||||
addWithNestedClass();
|
||||
if (metaData.enableWither()) {
|
||||
addWithNestedClass();
|
||||
}
|
||||
if (!metaData.beanClassName().isEmpty()) {
|
||||
addBeanNestedClass();
|
||||
}
|
||||
addDefaultConstructor();
|
||||
addStaticBuilder();
|
||||
if (metaData.addStaticBuilder()) {
|
||||
addStaticBuilder();
|
||||
}
|
||||
if (recordComponents.size() > 0) {
|
||||
addAllArgsConstructor();
|
||||
}
|
||||
addStaticDefaultBuilderMethod();
|
||||
addStaticCopyBuilderMethod();
|
||||
addStaticFromWithMethod();
|
||||
if (metaData.enableWither()) {
|
||||
addStaticFromWithMethod();
|
||||
}
|
||||
addStaticComponentsMethod();
|
||||
addBuildMethod();
|
||||
addToStringMethod();
|
||||
@@ -92,11 +102,17 @@ class InternalRecordBuilderProcessor {
|
||||
recordComponents.forEach(component -> {
|
||||
add1Field(component);
|
||||
add1SetterMethod(component);
|
||||
add1GetterMethod(component);
|
||||
if (metaData.enableGetters()) {
|
||||
add1GetterMethod(component);
|
||||
}
|
||||
if (metaData.addConcreteSettersForOptional()) {
|
||||
add1ConcreteOptionalSetterMethod(component);
|
||||
}
|
||||
var collectionMetaData = collectionBuilderUtils.singleItemsMetaData(component, EXCLUDE_WILDCARD_TYPES);
|
||||
collectionMetaData.ifPresent(meta -> add1CollectionBuilders(meta, component));
|
||||
});
|
||||
collectionBuilderUtils.addShims(builder);
|
||||
collectionBuilderUtils.addMutableMakers(builder);
|
||||
builderType = builder.build();
|
||||
}
|
||||
|
||||
@@ -124,7 +140,7 @@ class InternalRecordBuilderProcessor {
|
||||
}
|
||||
|
||||
private List<RecordClassType> buildRecordComponents(TypeElement record) {
|
||||
var accessorAnnotations = record.getRecordComponents().stream().map(e -> e.getAccessor().getAnnotationMirrors()).collect(Collectors.toList());
|
||||
var accessorAnnotations = record.getRecordComponents().stream().map(e -> (e.getAccessor() != null) ? e.getAccessor().getAnnotationMirrors() : List.<AnnotationMirror>of()).collect(Collectors.toList());
|
||||
var canonicalConstructorAnnotations = ElementUtils.findCanonicalConstructor(record).map(constructor -> ((ExecutableElement) constructor).getParameters().stream().map(Element::getAnnotationMirrors).collect(Collectors.toList())).orElse(List.of());
|
||||
var recordComponents = record.getRecordComponents();
|
||||
return IntStream.range(0, recordComponents.size())
|
||||
@@ -151,6 +167,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);
|
||||
@@ -202,7 +221,7 @@ class InternalRecordBuilderProcessor {
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.add("$T builder = with();\n", builderClassType.typeName())
|
||||
.add("consumer.accept(builder);\n")
|
||||
.add("return builder.build();\n");
|
||||
.add("return builder.$L();\n", metaData.buildMethodName());
|
||||
var consumerType = ParameterizedTypeName.get(ClassName.get(Consumer.class), builderClassType.typeName());
|
||||
var parameter = ParameterSpec.builder(consumerType, "consumer").build();
|
||||
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
|
||||
@@ -225,7 +244,7 @@ class InternalRecordBuilderProcessor {
|
||||
}
|
||||
*/
|
||||
var codeBlockBuilder = CodeBlock.builder()
|
||||
.add("return new $L(", builderClassType.name());
|
||||
.add("return new $L$L(", builderClassType.name(), typeVariables.isEmpty() ? "" : "<>");
|
||||
addComponentCallsAsArguments(-1, codeBlockBuilder);
|
||||
codeBlockBuilder.add(");");
|
||||
var methodSpec = MethodSpec.methodBuilder(metaData.withClassMethodPrefix())
|
||||
@@ -315,7 +334,7 @@ class InternalRecordBuilderProcessor {
|
||||
}
|
||||
RecordClassType parameterComponent = recordComponents.get(parameterIndex);
|
||||
if (parameterIndex == index) {
|
||||
collectionBuilderUtils.add(codeBlockBuilder, parameterComponent);
|
||||
collectionBuilderUtils.addShimCall(codeBlockBuilder, parameterComponent);
|
||||
} else {
|
||||
codeBlockBuilder.add("$L()", prefixedName(parameterComponent, true));
|
||||
}
|
||||
@@ -371,8 +390,10 @@ class InternalRecordBuilderProcessor {
|
||||
private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) {
|
||||
if (metaData.interpretNotNulls()) {
|
||||
var component = recordComponents.get(index);
|
||||
if (!component.typeName().isPrimitive() && isNullAnnotated(component)) {
|
||||
builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required");
|
||||
if (!collectionBuilderUtils.isImmutableCollection(component)) {
|
||||
if (!component.typeName().isPrimitive() && isNullAnnotated(component)) {
|
||||
builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,9 +418,7 @@ 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()));
|
||||
constructorBuilder.addStatement("this.$L = $L", component.name(), component.name());
|
||||
});
|
||||
builder.addMethod(constructorBuilder.build());
|
||||
}
|
||||
@@ -474,7 +493,12 @@ class InternalRecordBuilderProcessor {
|
||||
*/
|
||||
var codeBuilder = CodeBlock.builder();
|
||||
codeBuilder.add("return (this == o) || (");
|
||||
codeBuilder.add("(o instanceof $L $L)", builderClassType.name(), uniqueVarName);
|
||||
if (typeVariables.isEmpty()) {
|
||||
codeBuilder.add("(o instanceof $L $L)", builderClassType.name(), uniqueVarName);
|
||||
} else {
|
||||
String wildcardList = typeVariables.stream().map(__ -> "?").collect(Collectors.joining(","));
|
||||
codeBuilder.add("(o instanceof $L<$L> $L)", builderClassType.name(), wildcardList, uniqueVarName);
|
||||
}
|
||||
recordComponents.forEach(recordComponent -> {
|
||||
String name = recordComponent.name();
|
||||
if (recordComponent.typeName().isPrimitive()) {
|
||||
@@ -521,6 +545,16 @@ 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.addShimCall(codeBuilder, recordComponents.get(index));
|
||||
codeBuilder.add(";\n$]");
|
||||
}
|
||||
});
|
||||
|
||||
addNullCheckCodeBlock(codeBuilder);
|
||||
codeBuilder.add("$[return ");
|
||||
if (metaData.useValidationApi()) {
|
||||
@@ -531,7 +565,7 @@ class InternalRecordBuilderProcessor {
|
||||
if (index > 0) {
|
||||
codeBuilder.add(", ");
|
||||
}
|
||||
collectionBuilderUtils.add(codeBuilder, recordComponents.get(index));
|
||||
codeBuilder.add("$L", recordComponents.get(index).name());
|
||||
});
|
||||
codeBuilder.add(")");
|
||||
if (metaData.useValidationApi()) {
|
||||
@@ -541,63 +575,85 @@ 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 MyRecordBuilder.With() {
|
||||
@Override
|
||||
public String p1() {
|
||||
return from.p1();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String p2() {
|
||||
return from.p2();
|
||||
}
|
||||
};
|
||||
return new _FromWith(from);
|
||||
}
|
||||
*/
|
||||
var witherClassNameBuilder = CodeBlock.builder()
|
||||
.add("$L.$L", builderClassType.name(), metaData.withClassName());
|
||||
if (!typeVariables.isEmpty()) {
|
||||
witherClassNameBuilder.add("<");
|
||||
IntStream.range(0, typeVariables.size()).forEach(index -> {
|
||||
if (index > 0) {
|
||||
witherClassNameBuilder.add(", ");
|
||||
}
|
||||
witherClassNameBuilder.add(typeVariables.get(index).name);
|
||||
});
|
||||
witherClassNameBuilder.add(">");
|
||||
}
|
||||
var witherClassName = witherClassNameBuilder.build().toString();
|
||||
var codeBuilder = CodeBlock.builder()
|
||||
.add("return new $L", witherClassName)
|
||||
.add("() {\n").indent();
|
||||
IntStream.range(0, recordComponents.size()).forEach(index -> {
|
||||
var component = recordComponents.get(index);
|
||||
if (index > 0) {
|
||||
codeBuilder.add("\n");
|
||||
}
|
||||
codeBuilder.add("@Override\n")
|
||||
.add("public $T $L() {\n", component.typeName(), prefixedName(component, true))
|
||||
.indent()
|
||||
.addStatement("return from.$L()", component.name())
|
||||
.unindent()
|
||||
.add("}\n");
|
||||
});
|
||||
codeBuilder.unindent().addStatement("}");
|
||||
|
||||
var withType = ClassName.get("", witherClassName);
|
||||
var methodSpec = MethodSpec.methodBuilder("from")//metaData.copyMethodName())
|
||||
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(withType)
|
||||
.addCode(codeBuilder.build())
|
||||
.returns(buildWithTypeName())
|
||||
.addStatement("return new $L$L(from)", metaData.fromWithClassName(), typeVariables.isEmpty() ? "" : "<>")
|
||||
.build();
|
||||
builder.addMethod(methodSpec);
|
||||
}
|
||||
@@ -690,31 +746,17 @@ class InternalRecordBuilderProcessor {
|
||||
*/
|
||||
var fieldSpecBuilder = FieldSpec.builder(component.typeName(), component.name(), Modifier.PRIVATE);
|
||||
if (metaData.emptyDefaultForOptional()) {
|
||||
TypeName thisOptionalType = null;
|
||||
if (isOptional(component)) {
|
||||
thisOptionalType = optionalType;
|
||||
} else if (component.typeName().equals(optionalIntType)) {
|
||||
thisOptionalType = optionalIntType;
|
||||
} else if (component.typeName().equals(optionalLongType)) {
|
||||
thisOptionalType = optionalLongType;
|
||||
} else if (component.typeName().equals(optionalDoubleType)) {
|
||||
thisOptionalType = optionalDoubleType;
|
||||
}
|
||||
if (thisOptionalType != null) {
|
||||
var codeBlock = CodeBlock.builder().add("$T.empty()", thisOptionalType).build();
|
||||
Optional<OptionalType> thisOptionalType = OptionalType.fromClassType(component);
|
||||
if (thisOptionalType.isPresent()) {
|
||||
var codeBlock = CodeBlock.builder()
|
||||
.add("$T.empty()", thisOptionalType.get().typeName())
|
||||
.build();
|
||||
fieldSpecBuilder.initializer(codeBlock);
|
||||
}
|
||||
}
|
||||
builder.addField(fieldSpecBuilder.build());
|
||||
}
|
||||
|
||||
private boolean isOptional(ClassType component) {
|
||||
if (component.typeName().equals(optionalType)) {
|
||||
return true;
|
||||
}
|
||||
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName) && parameterizedTypeName.rawType.equals(optionalType);
|
||||
}
|
||||
|
||||
private void addNestedGetterMethod(TypeSpec.Builder classBuilder, RecordClassType component, String methodName) {
|
||||
/*
|
||||
For a single record component, add a getter similar to:
|
||||
@@ -726,7 +768,7 @@ class InternalRecordBuilderProcessor {
|
||||
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(component.typeName());
|
||||
addAccessorAnnotations(component, methodSpecBuilder);
|
||||
addAccessorAnnotations(component, methodSpecBuilder, this::filterOutValid);
|
||||
classBuilder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
|
||||
@@ -734,6 +776,10 @@ class InternalRecordBuilderProcessor {
|
||||
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()
|
||||
@@ -744,12 +790,13 @@ class InternalRecordBuilderProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private void addAccessorAnnotations(RecordClassType component, MethodSpec.Builder methodSpecBuilder) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -771,34 +818,34 @@ class InternalRecordBuilderProcessor {
|
||||
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 = __ensureMapMutable(p);
|
||||
this.p.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
this.p = __ensureMapMutable(p);
|
||||
i.forEach(this.p::put);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends Map.Entry<K, V> i) {
|
||||
if (p == null) {
|
||||
p = new HashMap<>();
|
||||
}
|
||||
this.p = __ensureMapMutable(p);
|
||||
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 codeBlockBuilder = CodeBlock.builder();
|
||||
if (collectionBuilderUtils.isImmutableCollection(component)) {
|
||||
codeBlockBuilder
|
||||
.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.mutableMakerName(component), component.name());
|
||||
} else {
|
||||
codeBlockBuilder
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||
.endControlFlow();
|
||||
}
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
.addJavadoc("Add to the internally allocated {@code HashMap} for {@code $L}\n", component.name())
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
@@ -826,25 +873,19 @@ class InternalRecordBuilderProcessor {
|
||||
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.list = __ensureListMutable(list);
|
||||
this.p.add(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Stream<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.list = __ensureListMutable(list);
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T addP(Iterable<? extends I> i) {
|
||||
if (this.p == null) {
|
||||
this.p = new ArrayList<>();
|
||||
}
|
||||
this.list = __ensureListMutable(list);
|
||||
this.p.addAll(i);
|
||||
return this;
|
||||
}
|
||||
@@ -860,10 +901,17 @@ class InternalRecordBuilderProcessor {
|
||||
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()
|
||||
var codeBlockBuilder = CodeBlock.builder();
|
||||
if (collectionBuilderUtils.isImmutableCollection(component)) {
|
||||
codeBlockBuilder
|
||||
.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.mutableMakerName(component), component.name());
|
||||
} else {
|
||||
codeBlockBuilder
|
||||
.beginControlFlow("if (this.$L == null)", component.name())
|
||||
.addStatement("this.$L = new $T<>()", component.name(), meta.singleItemCollectionClass())
|
||||
.endControlFlow();
|
||||
}
|
||||
codeBlockBuilder
|
||||
.add(addClockBlock.build())
|
||||
.addStatement("return this");
|
||||
var methodSpecBuilder = MethodSpec.methodBuilder(metaData.singleItemBuilderPrefix() + capitalize(component.name()))
|
||||
@@ -890,11 +938,23 @@ class InternalRecordBuilderProcessor {
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(generatedRecordBuilderAnnotation)
|
||||
.returns(component.typeName())
|
||||
.addStatement("return $L", component.name());
|
||||
addAccessorAnnotations(component, methodSpecBuilder);
|
||||
.addCode(checkReturnShim(component));
|
||||
addAccessorAnnotations(component, methodSpecBuilder, __ -> true);
|
||||
builder.addMethod(methodSpecBuilder.build());
|
||||
}
|
||||
|
||||
private CodeBlock checkReturnShim(RecordClassType component) {
|
||||
var codeBuilder = CodeBlock.builder();
|
||||
if (collectionBuilderUtils.isImmutableCollection(component)) {
|
||||
codeBuilder.add("return ");
|
||||
collectionBuilderUtils.addShimCall(codeBuilder, component);
|
||||
codeBuilder.add(";");
|
||||
} else {
|
||||
codeBuilder.addStatement("return $L", component.name());
|
||||
}
|
||||
return codeBuilder.build();
|
||||
}
|
||||
|
||||
private void add1SetterMethod(RecordClassType component) {
|
||||
/*
|
||||
For a single record component, add a setter similar to:
|
||||
@@ -912,8 +972,8 @@ class InternalRecordBuilderProcessor {
|
||||
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())
|
||||
codeSpec.addStatement("this.$L = $L($L)", component.name(), collectionBuilderUtils.shimName(component), component.name());
|
||||
methodSpec.addJavadoc("Re-create the internally allocated {@code $T} for {@code $L} by copying the argument\n", component.typeName(), component.name())
|
||||
.addCode(codeSpec.build());
|
||||
return ParameterSpec.builder(meta.wildType(), component.name());
|
||||
}).orElseGet(() -> {
|
||||
@@ -926,6 +986,42 @@ class InternalRecordBuilderProcessor {
|
||||
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);
|
||||
@@ -993,7 +1089,7 @@ class InternalRecordBuilderProcessor {
|
||||
|
||||
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);
|
||||
? s : p + Character.toUpperCase(s.charAt(0)) + s.substring(1);
|
||||
boolean isBool = component.typeName().toString().toLowerCase(Locale.ROOT).equals("boolean");
|
||||
if (isGetter) {
|
||||
if (isBool) {
|
||||
|
||||
@@ -33,6 +33,7 @@ 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;
|
||||
@@ -68,6 +69,9 @@ 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());
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.processor;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import com.squareup.javapoet.ParameterizedTypeName;
|
||||
import com.squareup.javapoet.TypeName;
|
||||
|
||||
public record OptionalType(TypeName typeName, TypeName valueType) {
|
||||
|
||||
private static final TypeName optionalType = TypeName.get(Optional.class);
|
||||
private static final TypeName optionalIntType = TypeName.get(OptionalInt.class);
|
||||
private static final TypeName optionalLongType = TypeName.get(OptionalLong.class);
|
||||
private static final TypeName optionalDoubleType = TypeName.get(OptionalDouble.class);
|
||||
|
||||
private static boolean isOptional(ClassType component) {
|
||||
if (component.typeName().equals(optionalType)) {
|
||||
return true;
|
||||
}
|
||||
return (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)
|
||||
&& parameterizedTypeName.rawType.equals(optionalType);
|
||||
}
|
||||
|
||||
static Optional<OptionalType> fromClassType(final ClassType component) {
|
||||
if (isOptional(component)) {
|
||||
if (!(component.typeName() instanceof ParameterizedTypeName parameterizedType)) {
|
||||
return Optional.of(new OptionalType(optionalType, TypeName.get(Object.class)));
|
||||
}
|
||||
final TypeName containingType = parameterizedType.typeArguments.isEmpty()
|
||||
? TypeName.get(Object.class)
|
||||
: parameterizedType.typeArguments.get(0);
|
||||
return Optional.of(new OptionalType(optionalType, containingType));
|
||||
}
|
||||
if (component.typeName().equals(optionalIntType)) {
|
||||
return Optional.of(new OptionalType(optionalIntType, TypeName.get(int.class)));
|
||||
}
|
||||
if (component.typeName().equals(optionalLongType)) {
|
||||
return Optional.of(new OptionalType(optionalLongType, TypeName.get(long.class)));
|
||||
}
|
||||
if (component.typeName().equals(optionalDoubleType)) {
|
||||
return Optional.of(new OptionalType(optionalDoubleType, TypeName.get(double.class)));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean isOptional() {
|
||||
return typeName.equals(optionalType);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import com.squareup.javapoet.AnnotationSpec;
|
||||
import com.squareup.javapoet.JavaFile;
|
||||
import com.squareup.javapoet.TypeSpec;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
import io.soabase.recordbuilder.core.RecordBuilderGenerated;
|
||||
import io.soabase.recordbuilder.core.RecordInterface;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
@@ -46,6 +47,7 @@ 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) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>32</version>
|
||||
<version>36-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -66,6 +66,48 @@
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
addSingleItemCollectionBuilders = true,
|
||||
useImmutableCollections = true,
|
||||
mutableListClassName = "PersonalizedMutableList"
|
||||
)
|
||||
public record CollectionCopying<T>(List<String> list, Set<T> set, Map<Instant, T> map, Collection<T> collection,
|
||||
int count) implements CollectionCopyingBuilder.With<T> {
|
||||
}
|
||||
@@ -22,5 +22,5 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RecordBuilderFull
|
||||
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords) {
|
||||
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords, @NotNull String justAString) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@RecordBuilder.Options(addStaticBuilder = false)
|
||||
@RecordBuilder
|
||||
public record NoStaticBuilder(String foo) {
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -22,6 +24,6 @@ import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
||||
@RecordBuilder.Options(emptyDefaultForOptional = true, addConcreteSettersForOptional = true)
|
||||
@RecordBuilder
|
||||
public record RecordWithOptional(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||
public record RecordWithOptional(@NotNull Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder.Options(emptyDefaultForOptional = true)
|
||||
@RecordBuilder
|
||||
public record RecordWithOptional2(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l, OptionalDouble d) {}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(useValidationApi = true)
|
||||
public record RequestWithValid(@NotNull @Valid Part part) implements RequestWithValidBuilder.With {
|
||||
public record Part(@NotBlank String name) {}
|
||||
}
|
||||
@@ -18,8 +18,9 @@ package io.soabase.recordbuilder.test;
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@RecordBuilder.Options(interpretNotNulls = true)
|
||||
@RecordBuilder
|
||||
public record RequiredRecord(@NotNull String hey, @NotNull int i) implements RequiredRecordBuilder.With {
|
||||
public record RequiredRecord(@NotNull String hey, @NotNull int i, @NotNull List<String> l) implements RequiredRecordBuilder.With {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
@RecordBuilder
|
||||
@RecordBuilder.Options(
|
||||
enableGetters = false,
|
||||
enableWither = false
|
||||
)
|
||||
public record StrippedFeaturesRecord(int aField) {}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.jacoco;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilderFull;
|
||||
import io.soabase.recordbuilder.core.RecordBuilderGenerated;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RecordBuilderFull
|
||||
@RecordBuilderGenerated
|
||||
public record FullRecordForJacoco(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecordForJacoco> fullRecords, @NotNull String justAString) {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test.visibility;
|
||||
|
||||
import io.soabase.recordbuilder.core.RecordBuilder;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
|
||||
@RecordBuilder.Options(builderClassModifiers = {Modifier.PUBLIC})
|
||||
@RecordBuilder
|
||||
record PackagePrivateRecordWithPublicBuilder(String value) {
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class TestCollections {
|
||||
@Test
|
||||
void testRecordBuilderOptionsCopied() {
|
||||
try {
|
||||
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", List.class));
|
||||
assertNotNull(CollectionInterfaceRecordBuilder.class.getDeclaredMethod("__list", Collection.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
Assertions.fail(e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TestCollectionsBuilder {
|
||||
|
||||
@Test
|
||||
void testCollectionsBuilderReturnNonNullEmptyCollections() {
|
||||
CollectionRecordBuilder<Object, Point> builder = CollectionRecordBuilder.builder();
|
||||
|
||||
Assertions.assertNotNull(builder.l());
|
||||
Assertions.assertTrue(builder.l().isEmpty());
|
||||
Assertions.assertNotNull(builder.c());
|
||||
Assertions.assertTrue(builder.c().isEmpty());
|
||||
Assertions.assertNotNull(builder.m());
|
||||
Assertions.assertTrue(builder.m().isEmpty());
|
||||
Assertions.assertNotNull(builder.s());
|
||||
Assertions.assertTrue(builder.s().isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright 2019 Jordan Zimmerman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
public class TestImmutableCollections {
|
||||
@Test
|
||||
public void testImmutableListNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.addList("a")
|
||||
.addList("b")
|
||||
.addList("c")
|
||||
.build();
|
||||
Assertions.assertEquals(item.list(), List.of("a", "b", "c"));
|
||||
|
||||
var oldList = item.list();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldList, copy.list());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldList, otherCopy.list());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmutableSetNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.addSet(Arrays.asList("1", "2", "3"))
|
||||
.build();
|
||||
Assertions.assertEquals(item.set(), Set.of("1", "2", "3"));
|
||||
|
||||
var oldSet = item.set();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldSet, copy.set());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldSet, otherCopy.set());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmutableCollectionNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.collection(List.of("foo", "bar", "baz"))
|
||||
.build();
|
||||
Assertions.assertEquals(item.collection(), List.of("foo", "bar", "baz"));
|
||||
|
||||
var oldCollection = item.collection();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldCollection, copy.collection());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldCollection, otherCopy.collection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImmutableMapNotCopiedWhenNotChanged() {
|
||||
var item = CollectionCopyingBuilder.<String>builder()
|
||||
.addMap(Instant.MAX, "future")
|
||||
.addMap(Instant.MIN, "before")
|
||||
.build();
|
||||
Assertions.assertEquals(item.map(), Map.of(Instant.MAX, "future", Instant.MIN, "before"));
|
||||
|
||||
var oldMap = item.map();
|
||||
|
||||
var copy = item.with()
|
||||
.count(1)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldMap, copy.map());
|
||||
|
||||
var otherCopy = item.with()
|
||||
.count(2)
|
||||
.build();
|
||||
|
||||
Assertions.assertSame(oldMap, otherCopy.map());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSourceListNotModified() {
|
||||
var item = new CollectionCopying<>(new ArrayList<>(), null, null, null, 0);
|
||||
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||
.addList("a")
|
||||
.build();
|
||||
|
||||
Assertions.assertEquals(modifiedItem.list(), List.of("a"));
|
||||
Assertions.assertTrue(item.list().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSourceSetNotModified() {
|
||||
var item = new CollectionCopying<>(null, new HashSet<>(), null, null, 0);
|
||||
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||
.addSet("a")
|
||||
.build();
|
||||
|
||||
Assertions.assertEquals(modifiedItem.set(), Set.of("a"));
|
||||
Assertions.assertTrue(item.set().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSourceMapNotModified() {
|
||||
var item = new CollectionCopying<>(null, null, new HashMap<>(), null, 0);
|
||||
var modifiedItem = CollectionCopyingBuilder.builder(item)
|
||||
.addMap(Instant.MIN, "a")
|
||||
.build();
|
||||
|
||||
Assertions.assertEquals(modifiedItem.map(), Map.of(Instant.MIN, "a"));
|
||||
Assertions.assertTrue(item.map().isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -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,4 +33,51 @@ 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,15 @@ 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() {
|
||||
Assertions.assertThrows(NullPointerException.class, () -> FullRecordBuilder.builder().build());
|
||||
var record = FullRecordBuilder.builder().justAString("").build();
|
||||
Assertions.assertEquals(List.of(), record.numbers());
|
||||
Assertions.assertEquals(Map.of(), record.fullRecords());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -32,6 +36,7 @@ class TestRecordBuilderFull {
|
||||
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));
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.validation.ValidationException;
|
||||
import java.util.List;
|
||||
|
||||
class TestValidation {
|
||||
@Test
|
||||
@@ -33,7 +34,7 @@ class TestValidation {
|
||||
|
||||
@Test
|
||||
void testNotNullsWithNewProperty() {
|
||||
var valid = RequiredRecordBuilder.builder().hey("hey").i(1).build();
|
||||
var valid = RequiredRecordBuilder.builder().hey("hey").i(1).l(List.of()).build();
|
||||
Assertions.assertThrows(NullPointerException.class, () -> valid.withHey(null));
|
||||
}
|
||||
|
||||
@@ -42,4 +43,14 @@ class TestValidation {
|
||||
var valid = RequiredRecord2Builder.builder().hey("hey").i(1).build();
|
||||
Assertions.assertThrows(ValidationException.class, () -> valid.withHey(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRequestWithValid() {
|
||||
Assertions.assertDoesNotThrow(() -> RequestWithValidBuilder.builder()
|
||||
.part(new RequestWithValid.Part("jsfjsf"))
|
||||
.build());
|
||||
Assertions.assertThrows(ValidationException.class, () -> RequestWithValidBuilder.builder()
|
||||
.part(new RequestWithValid.Part(""))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
package io.soabase.recordbuilder.test;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TestCustomMethodNames {
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestVariousOptions {
|
||||
|
||||
@Test
|
||||
public void builderGetsCustomSetterAndGetterNames() {
|
||||
@@ -54,4 +55,15 @@ public class TestCustomMethodNames {
|
||||
assertEquals(List.of(2), obj.getTheList());
|
||||
assertTrue(obj.isTheBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noStaticBuilder() {
|
||||
boolean hasStaticBuilder = Stream.of(NoStaticBuilderBuilder.class.getDeclaredMethods())
|
||||
.anyMatch(method -> method.getName().equals("NoStaticBuilder"));
|
||||
assertFalse(hasStaticBuilder);
|
||||
|
||||
hasStaticBuilder = Stream.of(SimpleRecordBuilder.class.getDeclaredMethods())
|
||||
.anyMatch(method -> method.getName().equals("SimpleRecord"));
|
||||
assertTrue(hasStaticBuilder);
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,15 @@ class TestVisibility {
|
||||
|
||||
Assertions.assertTrue(Modifier.isPublic(WrapperProtectedRecordBuilder.class.getModifiers()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testMatchesWithModifers() {
|
||||
Assertions.assertFalse(Modifier.isPublic(PackagePrivateRecordWithPublicBuilder.class.getModifiers()));
|
||||
Assertions.assertFalse(Modifier.isPrivate(PackagePrivateRecordWithPublicBuilder.class.getModifiers()));
|
||||
Assertions.assertFalse(Modifier.isProtected(PackagePrivateRecordWithPublicBuilder.class.getModifiers()));
|
||||
|
||||
Assertions.assertTrue(Modifier.isPublic(PackagePrivateRecordWithPublicBuilderBuilder.class.getModifiers()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.soabase.record-builder</groupId>
|
||||
<artifactId>record-builder</artifactId>
|
||||
<version>32</version>
|
||||
<version>36-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user