Compare commits

..

26 Commits

Author SHA1 Message Date
Jordan Zimmerman
6781824f08 [maven-release-plugin] prepare for next development iteration 2023-07-03 09:57:59 +01:00
Jordan Zimmerman
2c34ffd4ca [maven-release-plugin] prepare release record-builder-37 2023-07-03 09:57:53 +01:00
Paweł Łabaj
c6cf23956f Fixes Randgalt/record-builder/#153 (#154) 2023-07-03 09:46:23 +01:00
Jordan Zimmerman
1b22341b58 [maven-release-plugin] prepare for next development iteration 2023-03-29 17:50:15 +01:00
Jordan Zimmerman
409fb883e4 [maven-release-plugin] prepare release record-builder-36 2023-03-29 17:50:09 +01:00
Jordan Zimmerman
141362845e With interface shouldn't use prefix option (#132) 2023-03-29 10:03:17 +01:00
Stefan Bischof
183ab67c1a handle RecordBuilder.Options on packages (#149)
Thanks for the PR
2023-03-29 08:53:06 +01:00
Jordan Zimmerman
685a31b56b Various clean ups (#150) 2023-03-29 08:07:59 +01:00
Varun Upadhyay
f3303c2386 Fix javadoc builder core (#144)
Thank you for the PR
2023-03-28 08:36:40 +01:00
tison
e12d359696 Upgrade license-maven-plugin version and reformat (#145) 2023-03-12 07:56:15 +00:00
Dmitrii Priporov
4924f7b3ea issue-122: updated add1GetterMethod logic in useImmutableCollections=true case (#138) 2023-02-07 07:45:32 +00:00
Jordan Zimmerman
3e1d7d69d0 Update README.md 2023-01-09 09:17:39 +00:00
Jordan Zimmerman
22f827d81a [maven-release-plugin] prepare for next development iteration 2023-01-09 09:12:02 +00:00
Jordan Zimmerman
91c6090ace [maven-release-plugin] prepare release record-builder-35 2023-01-09 09:11:57 +00:00
David Morris
098a5c8bfd Configurable modifiers on static builders (#135) 2023-01-07 13:28:22 +00:00
David Morris
e2f17d4087 Adding of maven wrapper (#136) 2023-01-07 13:25:29 +00:00
David Morris
2b3e895cf6 initial .gitignore (#137) 2023-01-06 08:10:49 +00:00
Sebastian Hoß
117c789593 use configured build method name instead of hard-coded name (#133)
Co-authored-by: Sebastian Hoß <seb@hoß.de>
2022-12-15 20:45:43 +00:00
Jordan Zimmerman
4ff80fb20c [maven-release-plugin] prepare for next development iteration 2022-08-02 05:05:19 +02:00
Jordan Zimmerman
abfc12bdb0 [maven-release-plugin] prepare release record-builder-34 2022-08-02 05:05:14 +02:00
Jordan Zimmerman
6c0fac0dff [maven-release-plugin] rollback the release of record-builder-34 2022-08-02 05:04:43 +02:00
Jordan Zimmerman
ae527cd8e5 [maven-release-plugin] prepare release record-builder-34 2022-08-02 05:04:20 +02:00
Jordan Zimmerman
73ba62057a [maven-release-plugin] rollback the release of record-builder-34 2022-08-02 05:03:00 +02:00
Jordan Zimmerman
87998aba68 [maven-release-plugin] prepare for next development iteration 2022-08-02 05:02:28 +02:00
Jordan Zimmerman
04a0904d3f [maven-release-plugin] prepare release record-builder-34 2022-08-02 05:02:23 +02:00
Johannes
aa072af8e1 Performance feature: copy collections only when they were changed. Fixes #114. (#118) 2022-06-19 21:17:09 +01:00
139 changed files with 2138 additions and 3812 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1,4 @@
# Default ignored files
.idea
**/target/**

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View 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

View File

@@ -1,4 +1,4 @@
[![Build Status](https://github.com/Randgalt/record-builder/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/Randgalt/record-builder/actions)
[![Maven Build - Java 17](https://github.com/Randgalt/record-builder/actions/workflows/maven_java17.yml/badge.svg)](https://github.com/Randgalt/record-builder/actions/workflows/maven_java17.yml)
[![Maven Central](https://img.shields.io/maven-central/v/io.soabase.record-builder/record-builder.svg?sort=date)](https://search.maven.org/search?q=g:io.soabase.record-builder%20a:record-builder)
# RecordBuilder
@@ -22,7 +22,6 @@ _Details:_
- [Generation Via Includes](#generation-via-includes)
- [Usage](#usage)
- [Customizing](customizing.md) (e.g. add immutable collections, etc.)
- **_NEW!!_** [RecordBuilder Enhancer](record-builder-enhancer/README.md) - Inject verification, defensive copying, null checks or custom code into your Java Record constructors during compilation!
## RecordBuilder Example
@@ -323,11 +322,6 @@ in the listed packages.
The target package for generation is the same as the package that contains the "Include"
annotation. Use `packagePattern` to change this (see Javadoc for details).
## Enhancer
[RecordBuilder Enhancer](record-builder-enhancer/README.md) - Inject verification, defensive copying, null
checks or custom code into your Java Record constructors during compilation.
## Usage
### Maven

287
mvnw vendored Executable file
View 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
View 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%

165
pom.xml
View File

@@ -1,20 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<packaging>pom</packaging>
<version>34-SNAPSHOT</version>
<version>38-SNAPSHOT</version>
<modules>
<module>record-builder-core</module>
<module>record-builder-processor</module>
<module>record-builder-test</module>
<module>record-builder-validator</module>
<module>record-builder-enhancer</module>
<module>record-builder-test-custom-enhancer</module> <!-- must be set to ignore in IntelliJ -->
<module>record-builder-enhancer-core</module> <!-- must be set to ignore in IntelliJ -->
</modules>
<properties>
@@ -28,28 +42,27 @@
<maven-source-plugin-version>3.2.0</maven-source-plugin-version>
<maven-install-plugin-version>2.5.2</maven-install-plugin-version>
<maven-deploy-plugin-version>2.8.2</maven-deploy-plugin-version>
<maven-license-plugin-version>1.9.0</maven-license-plugin-version>
<maven-license-plugin-version>4.1</maven-license-plugin-version>
<maven-gpg-plugin-version>1.6</maven-gpg-plugin-version>
<maven-javadoc-plugin-version>3.1.1</maven-javadoc-plugin-version>
<maven-clean-plugin-version>3.1.0</maven-clean-plugin-version>
<maven-shade-plugin-version>3.3.0</maven-shade-plugin-version>
<maven-shade-plugin-version>3.2.1</maven-shade-plugin-version>
<maven-release-plugin-version>2.5.3</maven-release-plugin-version>
<maven-jar-plugin-version>3.2.0</maven-jar-plugin-version>
<maven-surefire-plugin-version>3.0.0-M5</maven-surefire-plugin-version>
<jacoco-maven-plugin-version>0.8.7</jacoco-maven-plugin-version>
<formatter-maven-plugin-version>2.22.0</formatter-maven-plugin-version>
<license-file-path>src/etc/header.txt</license-file-path>
<javapoet-version>1.12.1</javapoet-version>
<junit-jupiter-version>5.5.2</junit-jupiter-version>
<asm-version>9.3</asm-version>
<assertj-core.version>3.24.2</assertj-core.version>
<asm-version>7.2</asm-version>
<validation-api-version>2.0.1.Final</validation-api-version>
<hibernate-validator-version>6.0.20.Final</hibernate-validator-version>
<javax-el-version>3.0.1-b09</javax-el-version>
<byte-buddy.version>1.12.10</byte-buddy.version>
<guava.version>31.1-jre</guava.version>
<picocli.version>4.6.3</picocli.version>
</properties>
<name>Record Builder</name>
@@ -109,18 +122,6 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${byte-buddy.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>${byte-buddy.version}</version>
</dependency>
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
@@ -133,30 +134,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-processor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-test-custom-enhancer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-validator</artifactId>
@@ -169,6 +152,12 @@
<version>${junit-jupiter-version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj-core.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
@@ -186,30 +175,6 @@
<artifactId>javax.el</artifactId>
<version>${javax-el-version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm-version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>${asm-version}</version>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>${picocli.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -255,8 +220,8 @@
</plugin>
<plugin>
<groupId>com.mycila.maven-license-plugin</groupId>
<artifactId>maven-license-plugin</artifactId>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>${maven-license-plugin-version}</version>
<configuration>
<header>${license-file-path}</header>
@@ -282,12 +247,13 @@
<exclude>**/io/soabase/com/google/**</exclude>
<exclude>**/com/company/**</exclude>
<exclude>**/META-INF/services/**</exclude>
<exclude>**/safe/**</exclude>
<exclude>**/jvm.config</exclude>
<exclude>**/.java-version</exclude>
<exclude>**/.travis.yml</exclude>
<exclude>**/gradlew</exclude>
<exclude>**/.github/**</exclude>
<exclude>**/.mvn/**</exclude>
<exclude>**/mvnw*</exclude>
</excludes>
<strictCheck>true</strictCheck>
</configuration>
@@ -328,6 +294,44 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin-version}</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<dependencyReducedPomLocation>${project.build.outputDirectory}/META-INF/reduced-pom.xml</dependencyReducedPomLocation>
<filters>
<filter>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm-version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>${asm-version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
@@ -357,6 +361,24 @@
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin-version}</version>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>${formatter-maven-plugin-version}</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>format</goal>
</goals>
<configuration>
<compilerCompliance>17</compilerCompliance>
<compilerSource>17</compilerSource>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
@@ -377,8 +399,8 @@
</plugin>
<plugin>
<groupId>com.mycila.maven-license-plugin</groupId>
<artifactId>maven-license-plugin</artifactId>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
</plugin>
<plugin>
@@ -395,6 +417,11 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

View File

@@ -1,9 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>34-SNAPSHOT</version>
<version>38-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,43 +15,47 @@
*/
package io.soabase.recordbuilder.core;
import java.lang.annotation.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.lang.model.element.Modifier;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface RecordBuilder {
@Target({ElementType.TYPE, ElementType.PACKAGE})
@Target({ ElementType.TYPE, ElementType.PACKAGE })
@Retention(RetentionPolicy.SOURCE)
@Inherited
@interface Include {
/**
* @return list of classes to include
* @return collection of classes to include
*/
Class<?>[] value() default {};
/**
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
* (i.e. a union of classes from both attributes).
* Synonym for {@code value()}. When using the other attributes it maybe clearer to use {@code classes()}
* instead of {@code value()}. Note: both attributes are applied (i.e. a union of classes from both attributes).
*
* @return list of classes
* @return collection of classes
*/
Class<?>[] classes() default {};
/**
* Optional list of package names. All records in the packages will get processed as
* if they were listed as classes to include.
* Optional list of package names. All records in the packages will get processed as if they were listed as
* classes to include.
*
* @return list of package names
* @return collection of package names
*/
String[] packages() default {};
/**
* Pattern used to generate the package for the generated class. The value
* is the literal package name however two replacement values can be used. '@'
* is replaced with the package of the {@code Include} annotation. '*' is replaced with
* the package of the included class.
* Pattern used to generate the package for the generated class. The value is the literal package name however
* two replacement values can be used. '@' is replaced with the package of the {@code Include} annotation. '*'
* is replaced with the package of the included class.
*
* @return package pattern
*/
@@ -59,18 +63,18 @@ public @interface RecordBuilder {
}
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Target({ ElementType.TYPE, ElementType.PACKAGE })
@Inherited
@interface Options {
/**
* The builder class name will be the name of the record (prefixed with any enclosing class) plus this suffix. E.g.
* if the record name is "Foo", the builder will be named "FooBuilder".
* The builder class name will be the name of the record (prefixed with any enclosing class) plus this suffix.
* E.g. if the record name is "Foo", the builder will be named "FooBuilder".
*/
String suffix() default "Builder";
/**
* Used by {@code RecordInterface}. The generated record will have the same name as the annotated interface
* plus this suffix. E.g. if the interface name is "Foo", the record will be named "FooRecord".
* Used by {@code RecordInterface}. The generated record will have the same name as the annotated interface plus
* this suffix. E.g. if the interface name is "Foo", the record will be named "FooRecord".
*/
String interfaceSuffix() default "Record";
@@ -125,22 +129,20 @@ public @interface RecordBuilder {
String fileIndent() default " ";
/**
* If the record is declared inside of another class, the outer class's name will
* be prefixed to the builder name if this returns true.
* If the record is declared inside another class, the outer class's name will be prefixed to the builder name
* if this returns true.
*/
boolean prefixEnclosingClassNames() default true;
/**
* If true, any annotations (if applicable) on record components are copied
* to the builder methods
* If true, any annotations (if applicable) on record components are copied to the builder methods
*
* @return true/false
*/
boolean inheritComponentAnnotations() default true;
/**
* Set the default value of {@code Optional} record components to
* {@code Optional.empty()}
* Set the default value of {@code Optional} record components to {@code Optional.empty()}
*/
boolean emptyDefaultForOptional() default true;
@@ -150,8 +152,8 @@ public @interface RecordBuilder {
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).
* Add not-null checks for record components annotated with any annotation named either "NotNull", "NoNull", or
* "NonNull" (see {@link #interpretNotNullsPattern()} for the actual regex matching pattern).
*/
boolean interpretNotNulls() default false;
@@ -162,29 +164,51 @@ public @interface RecordBuilder {
String interpretNotNullsPattern() default "(?i)((notnull)|(nonnull)|(nonull))";
/**
* <p>Pass built records through the Java Validation API if it's available in the classpath.</p>
* <p>
* Pass built records through the Java Validation API if it's available in the classpath.
* </p>
*
* <p>IMPORTANT:
* if this option is enabled you must include the {@code record-builder-validator} dependency in addition
* to {@code record-builder-core}. {@code record-builder-validator} is implemented completely via reflection and
* does not require other dependencies. Alternatively, you can define your own class with the package {@code package io.soabase.recordbuilder.validator;}
* named {@code RecordBuilderValidator} which has a public static method: {@code public static <T> T validate(T o)}.</p>
* <p>
* IMPORTANT: if this option is enabled you must include the {@code record-builder-validator} dependency in
* addition to {@code record-builder-core}. {@code record-builder-validator} is implemented completely via
* reflection and does not require other dependencies. Alternatively, you can define your own class with the
* package {@code package io.soabase.recordbuilder.validator;} named {@code RecordBuilderValidator} which has a
* public static method: {@code public static <T> T validate(T o)}.
* </p>
*/
boolean useValidationApi() default false;
/**
* Adds special handling for record components of type {@link java.util.List}, {@link java.util.Set},
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components
* of these types are passed through an added shim method that uses the corresponding immutable collection
* (e.g. {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components of these
* types are passed through an added shim method that uses the corresponding immutable collection (e.g.
* {@code List.copyOf(o)}) or an empty immutable collection if the component is {@code null}.
*
* @see #useUnmodifiableCollections()
*/
boolean useImmutableCollections() default false;
/**
* When enabled, collection types ({@code List}, {@code Set} and {@code Map}) are handled specially.
* The setters for these types now create an internal collection and items are added to that
* collection. Additionally, "adder" methods prefixed with {@link #singleItemBuilderPrefix()} are created
* to add single items to these collections.
* Adds special handling for record components of type: {@link java.util.List}, {@link java.util.Set},
* {@link java.util.Map} and {@link java.util.Collection}. When the record is built, any components of these
* types are passed through an added shim method that uses the corresponding unmodifiable collection (e.g.
* {@code Collections.unmodifiableList(o)}) or an empty immutable collection if the component is {@code null}.
*
* <p>
* For backward compatibility, when {@link #useImmutableCollections()} returns {@code true}, this property is
* ignored.
*
* @see #useImmutableCollections()
*
* @since 37
*/
boolean useUnmodifiableCollections() default false;
/**
* When enabled, collection types ({@code List}, {@code Set} and {@code Map}) are handled specially. The setters
* for these types now create an internal collection and items are added to that collection. Additionally,
* "adder" methods prefixed with {@link #singleItemBuilderPrefix()} are created to add single items to these
* collections.
*/
boolean addSingleItemCollectionBuilders() default false;
@@ -194,14 +218,14 @@ public @interface RecordBuilder {
String singleItemBuilderPrefix() default "add";
/**
* When enabled, adds functional methods to the nested "With" class (such as {@code map()} and {@code accept()}).
* When enabled, adds functional methods to the nested "With" class (such as {@code map()} and
* {@code accept()}).
*/
boolean addFunctionalMethodsToWith() default false;
/**
* If set, all builder setter methods will be prefixed with this string. Camel-casing will
* still be enforced, so if this option is set to "set" a field named "myField" will get
* a corresponding setter named "setMyField".
* If set, all builder setter methods will be prefixed with this string. Camel-casing will still be enforced, so
* if this option is set to "set" a field named "myField" will get a corresponding setter named "setMyField".
*/
String setterPrefix() default "";
@@ -211,47 +235,67 @@ public @interface RecordBuilder {
boolean enableGetters() default true;
/**
* If set, all builder getter methods will be prefixed with this string. Camel-casing will
* still be enforced, so if this option is set to "get", a field named "myField" will get
* a corresponding getter named "getMyField".
* If set, all builder getter methods will be prefixed with this string. Camel-casing will still be enforced, so
* if this option is set to "get", a field named "myField" will get a corresponding getter named "getMyField".
*/
String getterPrefix() default "";
/**
* If set, all boolean builder getter methods will be prefixed with this string.
* Camel-casing will still be enforced, so if this option is set to "is", a field named
* "myField" will get a corresponding getter named "isMyField".
* If set, all boolean builder getter methods will be prefixed with this string. Camel-casing will still be
* enforced, so if this option is set to "is", a field named "myField" will get a corresponding getter named
* "isMyField".
*/
String booleanPrefix() default "";
/**
* If set, the Builder will contain an internal interface with this name. This interface
* contains getters for all the fields in the Record prefixed with the value supplied in
* {@link this.getterPrefix} and {@link this.booleanPrefix}. This interface can be
* implemented by the original Record to have proper bean-style prefixed getters.
*
* Please note that unless either of the aforementioned prefixes are set,
* this option does nothing.
* If set, the Builder will contain an internal interface with this name. This interface contains getters for
* all the fields in the Record prefixed with the value supplied in {@link this.getterPrefix} and
* {@link this.booleanPrefix}. This interface can be implemented by the original Record to have proper
* bean-style prefixed getters. Please note that unless either of the aforementioned prefixes are set, this
* option does nothing.
*/
String beanClassName() default "";
/**
* If true, generated classes are annotated with {@code RecordBuilderGenerated} which has a retention
* policy of {@code CLASS}. This ensures that analyzers such as Jacoco will ignore the generated class.
* 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.
* 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}.
* 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)

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,19 +17,12 @@ package io.soabase.recordbuilder.core;
import java.lang.annotation.*;
@RecordBuilder.Template(options = @RecordBuilder.Options(
interpretNotNulls = true,
useImmutableCollections = true,
addSingleItemCollectionBuilders = true,
addFunctionalMethodsToWith = true,
addClassRetainedGenerated = true
))
/**
* An alternate form of {@code @RecordBuilder} that has most optional features turned on
*/
@RecordBuilder.Template(options = @RecordBuilder.Options(interpretNotNulls = true, useImmutableCollections = true, addSingleItemCollectionBuilders = true, addFunctionalMethodsToWith = true, addClassRetainedGenerated = true))
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
/**
* An alternate form of {@code @RecordBuilder} that has most
* optional features turned on
*/
public @interface RecordBuilderFull {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import static java.lang.annotation.ElementType.*;
/**
* Jacoco ignores classes and methods annotated with `*Generated`
*/
@Target({PACKAGE, TYPE, METHOD, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, PARAMETER})
@Target({ PACKAGE, TYPE, METHOD, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, PARAMETER })
@Retention(RetentionPolicy.CLASS)
public @interface RecordBuilderGenerated {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,21 +23,20 @@ import java.lang.annotation.*;
public @interface RecordInterface {
boolean addRecordBuilder() default true;
@Target({ElementType.TYPE, ElementType.PACKAGE})
@Target({ ElementType.TYPE, ElementType.PACKAGE })
@Retention(RetentionPolicy.SOURCE)
@Inherited
@interface Include {
/**
* @return list of classes to include
* @return collection of classes to include
*/
Class<?>[] value() default {};
/**
* Synonym for {@code value()}. When using the other attributes it maybe more clear to
* use {@code classes()} instead of {@code value()}. Note: both attributes are applied
* (i.e. a union of classes from both attributes).
* Synonym for {@code value()}. When using the other attributes it maybe clearer to use {@code classes()}
* instead of {@code value()}. Note: both attributes are applied (i.e. a union of classes from both attributes).
*
* @return list of classes
* @return collection of classes
*/
Class<?>[] classes() default {};
@@ -49,10 +48,9 @@ public @interface RecordInterface {
boolean addRecordBuilder() default true;
/**
* Pattern used to generate the package for the generated class. The value
* is the literal package name however two replacement values can be used. '@'
* is replaced with the package of the {@code Include} annotation. '*' is replaced with
* the package of the included class.
* Pattern used to generate the package for the generated class. The value is the literal package name however
* two replacement values can be used. '@' is replaced with the package of the {@code Include} annotation. '*'
* is replaced with the package of the included class.
*
* @return package pattern
*/

View File

@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>34-SNAPSHOT</version>
</parent>
<artifactId>record-builder-enhancer-core</artifactId>
<properties>
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
</properties>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
<useDependencyReducedPomInJar>true</useDependencyReducedPomInJar>
<relocations>
<relocation>
<pattern>org.objectweb</pattern>
<shadedPattern>recordbuilder.org.objectweb</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
<filters>
<filter>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,38 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface RecordBuilderEnhance {
Class<? extends RecordBuilderEnhancer>[] enhancers();
RecordBuilderEnhanceArguments[] arguments() default {};
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.ANNOTATION_TYPE)
@Inherited
@interface Template {
Class<? extends RecordBuilderEnhancer>[] enhancers();
RecordBuilderEnhanceArguments[] arguments() default {};
}
}

View File

@@ -1,29 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface RecordBuilderEnhanceArguments {
Class<? extends RecordBuilderEnhancer> enhancer();
String[] arguments();
}

View File

@@ -1,22 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.spi;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.type.TypeMirror;
public record Entry(int parameterIndex, RecordComponentElement element, TypeMirror erasedType) {
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.spi;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.List;
public interface Processor {
Elements elements();
Types types();
boolean hasEnhancer(Class<? extends RecordBuilderEnhancer> enhancer);
boolean verboseRequested();
void logInfo(CharSequence msg);
void logWarning(CharSequence msg);
void logError(CharSequence msg);
List<Entry> asEntries(TypeElement element);
}

View File

@@ -1,454 +0,0 @@
# RecordBuilder Enhancer
## What is RecordBuilder Enhancer
Inject verification, defensive copying, null checks or custom code into your Java Record constructors during compilation.
#### Features:
- [Builtin enhancers](#builtin-enhancers) to help with null checks and defensive copying
- [SPI](#write-your-own-custom-enhancer) for writing your own [custom enhancers](#write-your-own-custom-enhancer)
- [Create a custom annotation](#create-a-custom-annotation) that specifies a custom set of enhancers
#### Is it safe? Does it use undocumented features of Java?
- The Enhancer modifies your Java class files. There are some inherent safety concerns with this. However:
- The industry standard [ASM](https://asm.ow2.io) library is used to do the modifications. ASM is even used in the JDK itself.
- The Enhancer only inserts code in the default constructor of Java records. The code is inserted just after the
call to `super()` and before any existing code in the constructor.
- It's implemented as a standard [javac plugin](https://docs.oracle.com/en/java/javase/16/docs/api/jdk.compiler/com/sun/source/util/Plugin.html) and uses no undocumented features
- If you don't like the builtin enhancers you can [write your own](#write-your-own-custom-enhancer)
#### How does it relate to [RecordBuilder](../README.md)?
They aren't directly related but they share some code and both work on java records.
#### Why RecordBuilder Enhancer?
Java Records are fantastic data carriers and solve much of the pain of the lack of these types of classes
in older versions of Java. [RecordBuilder](../README.md) adds builders and withers to records. However,
records are still missing simple `null` checks and a few other niceties. This means boilerplate
for every record. RecordBuilder Enhancer is targeted at being able to write Java records without having to add
any additional code.
## How to Use RecordBuilder Enhancer
First, configure your build environment: see [details below](#javac-plugin). Then, use the Enhancer's
annotations to specify Java records that you want to be enhanced.
```java
@RecordBuilderEnhance(enhancers = RequireNonNull.class)
public record MyRecord(String s, List<String> l) {
}
```
Enhancer inserts code into the default constructor as if you wrote this:
```java
public record MyRecord(String s, List<String> l) {
public MyRecord {
Objects.requireNonNull(s, "s is null");
Objects.requireNonNull(l, "l is null");
}
}
```
The class file will be updated like this:
![](disassembly1.png)
Enhancers are applied in the order listed in the annotation. E.g.
```java
// will apply RequireNonNull and then CopyCollection
@RecordBuilderEnhance(enhancers = {RequireNonNull.class, CopyCollection.class})
public record MyRecord(List<String> l) {}
```
_becomes_
```java
public record MyRecord(List<String> l) {
Objects.requireNonNull(l);
l = List.copyOf(l);
}
```
#### Arguments
Enhancers can optional receive arguments (the builtin [NotNullAnnotations](#notnullannotations) does). Use `@RecordBuilderEnhanceArguments`
to pass arguments. E.g.
```java
@RecordBuilderEnhance(enhancers = NotNullAnnotations.class,
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = "altnonnull"))
public record MyRecord(@AltNonNull List<String> l) {}
```
## Builtin Enhancers
| ID | Arguments | Description |
|-----------------------------------------------------------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [EmptyNullOptional](#emptynulloptional) | - | Use `empty()` for null Optional/OptionalInt/OptionalLong/OptionalDouble record components |
| [EmptyNullString](#emptynullstring) | - | Use `""` for null String record components |
| [CopyCollection](#copycollection) | - | Make defensive copies of Collection, List and Map record components |
| [CopyCollectionNullableEmpty](#copycollectionnullableempty) | - | Make defensive copies of Collection, List and Map record components or empty collections when `null` |
| [GuavaCopyCollection](#guavacopycollection) | - | Same as CopyCollection but uses Google Guava collections |
| [GuavaCopyCollectionNullableEmpty](#guavacopycollectionnullableempty) | - | Same as CopyCollectionNullableEmpty but uses Google Guava collections |
| [RequireNonNull](#requirenonnull) | - | Call `requireNonNull()` on all non-primitive record components - note: checks other enhancers and doesn't apply to Strings if EmptyNullString is being used, etc. |
| [NotNullAnnotations](#notnullannotations) | expression (optional) | Any parameter with an annotation whose name matches this enhancer's regular expression argument will be passed to `requireNonNull()`. The argument is optional. If not supplied then _(notnull)&#124;(nonnull)_ is used. Matching is always case insensitive. |
### Examples
#### EmptyNullOptional
```java
@RecordBuilderEnhance(enhancers = EmptyNullOptional.class)
public record MyRecord(Optional<String> s, OptionalInt i) {}
```
_becomes_
```java
public record MyRecord(Optional<String> s, OptionalInt i) {
public MyRecord {
s = (s != null) ? s : Optional.empty();
i = (i != null) ? i : OptionalInt.empty();
}
}
```
-------
#### EmptyNullString
```java
@RecordBuilderEnhance(enhancers = EmptyNullString.class)
public record MyRecord(String s) {}
```
_becomes_
```java
public record MyRecord(String s) {
public MyRecord {
s = (s != null) ? s : "";
}
}
```
-------
#### CopyCollection
```java
@RecordBuilderEnhance(enhancers = CopyCollection.class)
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
```
_becomes_
```java
public record MyRecord(String s) {
public MyRecord {
c = Set.copyOf(c);
s = Set.copyOf(s);
l = List.copyOf(l);
m = Map.copyOf(m);
}
}
```
-------
#### CopyCollectionNullableEmpty
```java
@RecordBuilderEnhance(enhancers = CopyCollectionNullableEmpty.class)
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
```
_becomes_
```java
public record MyRecord(String s) {
public MyRecord {
c = (c != null) ? Set.copyOf(c) : Set.of();
s = (s != null) ? Set.copyOf(s) : Set.of();
l = (l != null) ? List.copyOf(l) : List.of();
m = (m != null) ? Map.copyOf(m) : Map.of();
}
}
```
-------
#### GuavaCopyCollection
```java
@RecordBuilderEnhance(enhancers = GuavaCopyCollection.class)
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
```
_becomes_
```java
public record MyRecord(String s) {
public MyRecord {
c = ImmutableSet.copyOf(c);
s = ImmutableSet.copyOf(s);
l = ImmutableList.copyOf(l);
m = Map.copyOf(m);
}
}
```
-------
#### GuavaCopyCollectionNullableEmpty
```java
@RecordBuilderEnhance(enhancers = GuavaCopyCollectionNullableEmpty.class)
public record MyRecord(Collection<String> c, Set<String> s, List<String> l, Map<String, String> m) {}
```
_becomes_
```java
public record MyRecord(String s) {
public MyRecord {
c = (c != null) ? ImmutableSet.copyOf(c) : ImmutableSet.of();
s = (s != null) ? ImmutableSet.copyOf(s) : ImmutableSet.of();
l = (l != null) ? ImmutableList.copyOf(l) : ImmutableList.of();
m = (m != null) ? ImmutableMap.copyOf(m) : ImmutableMap.of();
}
}
```
-------
#### RequireNonNull
```java
@RecordBuilderEnhance(enhancers = RequireNonNull.class)
public record MyRecord(String s, Instant t) {}
```
_becomes_
```java
public record MyRecord(String s, Instant t) {
public MyRecord {
Objects.requireNonNull(s, "s is null");
Objects.requireNonNull(t, "t is null");
}
}
```
-------
#### NotNullAnnotations
```java
@RecordBuilderEnhance(enhancers = NotNullAnnotations.class,
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = "(notnull)|(mynil)"))
public record MyRecord(String s, @NotNull Instant t, @MyNil Thing thing) {}
```
_becomes_
```java
public record MyRecord(String s, @NotNull Instant t, @MyNil Thing thing) {
public MyRecord {
Objects.requireNonNull(t, "t is null");
Objects.requireNonNull(thing, "thing is null");
}
}
```
## Create A Custom Annotation
Using `@RecordBuilderEnhance.Template` you can create your own RecordBuilderEnhance annotation that always uses the set of enhancers that you want.
```java
@RecordBuilderEnhance.Template(enhancers = {RequireNonNull.class, NotNullAnnotations.class},
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = "altnonnull.*"))
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface MyCoEnhance {
}
```
Now, you can use `@MyCoEnhance` instead of `@RecordBuilderEnhance` and the record will be enhanced with the enhancers specified.
## Javac Plugin
Add a dependency that contains the discoverable javac plugin to your build tool (see below for [Maven](#maven) or [Gradle](#gradle)). javac will
auto discover the Enhancer plugin. By default the enhancer assumes the standard directory layout used by
most Java build systems. i.e. if a Java source file is at `/foo/bar/myproject/src/main/java/my/package/MyClass.java`
the Enhancer will assume that the compiled class file for that source file will be found at
`/foo/bar/myproject/target/classes/my/package/MyClass.class`. If your build system does not use this method then
the Enhancer will need additional configuration ([see below](#options)). You can also
configure some of the behavior of the Enhancer ([see below](#options)).
### Maven
```xml
<!-- only needed during compilation -->
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer</artifactId>
<version>${record.builder.version}</version>
<scope>provided</scope>
</dependency>
<!-- contains the annotations -->
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer-core</artifactId>
<version>${record.builder.version}</version>
</dependency>
```
### Gradle
```groovy
dependencies {
compileOnly 'io.soabase.record-builder:record-builder-enhancer:$version-goes-here'
api 'io.soabase.record-builder:record-enhancer-core:$version-goes-here'
}
```
### Options
For normal usage you won't need to set any options for the Enhancer. The following options are available if you need them:
```text
[-hv] [--disable] [--dryRun] [--outputDirectory=<outputTo>] [DIRECTORY]
[DIRECTORY] The build's output directory - i.e. where javac writes generated classes.
The value can be a full path or a relative path. If not provided the Enhancer
plugin will attempt to use standard directories.
--disable Deactivate/disable the plugin
--dryRun Dry run only - doesn't modify any classes. You should enable verbose as well via: -v
-h, --help Outputs this help
--outputDirectory=<outputTo>
Optional alternate output directory for enhanced class files
-v, --verbose Verbose output during compilation
```
javac plugin options are specifed on the javac command line or as part of your build tool. On the command line:
```shell
javac -Xplugin:"recordbuilderenhancer ...arguments..."
```
In Maven:
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-Xplugin:recordbuilderenhancer ...arguments...</compilerArgument>
</configuration>
</plugin>
```
## Write Your Own Custom Enhancer
Notes on writing your own enhancer:
- Custom Enhancers must be built as separate modules from the code base you want to enhance. This is because
the enhancers must be available during compilation.
- You should somewhat be familiar with Java's AST classes though they are not hard to understand for newcomers
- Enhancers use the [ASM library](https://asm.ow2.io) to produce a list of statements to be inserted into Java record constructors
- You will need some knowledge of Java bytecode specifics and the Java Language specification. However, it's pretty simple
to use javac and javap to show you what bytecodes you need to specify ([see below](#how-to-get-the-bytecodes-for-your-enhancer))
- **IMPORTANT** - the ASM library has been shaded into the RecordBuilder Enhancer JAR. Make sure your custom enhancer uses the ASM classes
that are in the package `recordbuilder.org.objectweb.asm.*`. Many libraries use ASM and you may see the same classes in multiple packages.
For reference look at the implementation of the builtin enhancers to see exactly how to write one. [EmptyNullString](src/main/java/io/soabase/recordbuilder/enhancer/enhancers/EmptyNullString.java)
is a simple one to use as an example.
#### Add the SPI to your build
The RecordBuilder Enhancer SPI must be added to the module for your custom enhancer. It's artifact ID is `record-builder-enhancer-core`.
Your enhancer module can contain as many enhancers as you want. Each enhancer must implement the following interface:
```java
public interface RecordBuilderEnhancer {
InsnList enhance(Processor processor, TypeElement element, List<String> arguments);
}
```
Your custom enhancer is called for Java records that are annotated to list your enhancer class.
Your enhancer is created once during the start of the build process and will be called multiple times for each Java record
that is annotated with your enhancer. Your enhancer must return a list of instructions to insert or an empty list.
The `element` parameter refers to the Java record being currently compiled. `arguments` are any arguments specified in the annotation
for the Java record. `Processor` holds utilities useful during enhancing. Look at the builtin enhancers to see details on how to write
them - [EmptyNullString](src/main/java/io/soabase/recordbuilder/enhancer/enhancers/EmptyNullString.java) is a good one to start with.
Build and install your enhancer JAR and you can then use your new custom enhancer as a dependency in other projects and it will be available as an enhancer.
Alternatively, if you have a multi-module build your enhancer can be a module and used to enhance the other modules in the
project. The [record-builder-test-custom-enhancer](../record-builder-test-custom-enhancer) does this.
#### How to get the bytecodes for your enhancer
The trick for getting the bytecodes for your enhancer is to write a simple Java source file that does what you want and then
use the java tools to get the bytecodes. For example, let's say you want a custom enhancer that outputs the current date and time to standard
out.
Create a text file called "Temp.java" ala:
```java
import java.time.Instant;
public class Temp {
public Temp() {
System.out.println(Instant.now());
}
}
```
From a terminal compile the class:
```shell
javac Temp.java
```
Then dump the java bytecodes from the compiled class:
```shell
javap -c Temp.class
```
You will see:
```text
Compiled from "Temp.java"
public class Temp {
public Temp();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
7: invokestatic #13 // Method java/time/Instant.now:()Ljava/time/Instant;
10: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
13: return
}
```
The first two lines are the call to `super()`. The lines labeld `4`, `7`, and `10` are the bytecodes that you want for your enhancer.
Your enhance() implementation would look like this:
```java
InsnList insnList = new InsnList();
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/time/Instant", "now", "()Ljava/time/Instant;"));
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V"));
return insnList;
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -1,86 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>34-SNAPSHOT</version>
</parent>
<artifactId>record-builder-enhancer</artifactId>
<properties>
<license-file-path>${project.parent.basedir}/src/etc/header.txt</license-file-path>
</properties>
<dependencies>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer-core</artifactId>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
<useDependencyReducedPomInJar>true</useDependencyReducedPomInJar>
<relocations>
<relocation>
<pattern>picocli</pattern>
<shadedPattern>recordbuilder.picocli</shadedPattern>
</relocation>
<relocation>
<pattern>safe</pattern>
<shadedPattern>META-INF.services</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>io.soabase.recordbuilder.enhancer.Main</Main-Class>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
<filters>
<filter>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,131 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.soabase.recordbuilder.enhancer.RecordBuilderEnhancerPlugin.adjustedClassName;
class EnhancersController {
private final Map<String, Optional<RecordBuilderEnhancer>> enhancers = new ConcurrentHashMap<>();
record EnhancerAndArgs(RecordBuilderEnhancer enhancer, List<String> arguments) {}
List<EnhancerAndArgs> getEnhancers(ProcessorImpl processor, TypeElement typeElement) {
return internalGetEnhancers(processor, typeElement).flatMap(spec -> toEnhancer(processor, spec)).toList();
}
private Stream<EnhancerSpec> internalGetEnhancers(ProcessorImpl processor, TypeElement typeElement) {
Optional<? extends AnnotationMirror> recordBuilderEnhance = getAnnotationMirror(processor, typeElement);
Optional<? extends AnnotationMirror> recordBuilderEnhanceTemplate = getTemplateAnnotationMirror(processor, typeElement);
if (recordBuilderEnhance.isPresent() && recordBuilderEnhanceTemplate.isPresent()) {
processor.logError("RecordBuilderEnhance and RecordBuilderEnhance.Template cannot be combined.");
return Stream.of();
}
return Stream.concat(recordBuilderEnhance.stream().flatMap(this::getEnhancersAnnotationValue), recordBuilderEnhanceTemplate.stream().flatMap(this::getEnhancersAnnotationValue));
}
private Map<String, Object> getAnnotationValueMap(AnnotationMirror annotationMirror)
{
return annotationMirror.getElementValues().entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey().getSimpleName().toString(), entry -> entry.getValue().getValue()));
}
private record EnhancerSpec(String enhancerClass, List<String> arguments) {}
@SuppressWarnings("unchecked")
private Stream<EnhancerSpec> getEnhancersAnnotationValue(AnnotationMirror annotationMirror)
{
Map<String, Object> annotationValueMap = getAnnotationValueMap(annotationMirror);
List<? extends AnnotationValue> argumentsValue = (List<? extends AnnotationValue>) annotationValueMap.getOrDefault("arguments", List.of()); // list of RecordBuilderEnhanceArguments mirrors
Map<String, List<String>> argumentsMap = argumentsValue.stream()
.flatMap(argumentMirror -> {
Map<String, Object> argumentMap = getAnnotationValueMap((AnnotationMirror) argumentMirror.getValue());
Object enhancer = argumentMap.get("enhancer");
Object arguments = argumentMap.get("arguments");
if ((enhancer != null) && (arguments != null)) {
List<String> argumentList = ((List<? extends AnnotationValue>) arguments).stream().map(value -> value.getValue().toString()).toList();
return Stream.of(Map.entry(enhancer.toString(), argumentList));
}
return Stream.of();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
List<? extends AnnotationValue> enhancers = (List<? extends AnnotationValue>) annotationValueMap.get("enhancers");
if (enhancers != null) {
return enhancers.stream().map(annotationValue -> {
TypeMirror typeMirror = (TypeMirror) annotationValue.getValue();
return new EnhancerSpec(typeMirror.toString(), argumentsMap.getOrDefault(typeMirror.toString(), List.of()));
});
}
return Stream.of();
}
@SuppressWarnings("unchecked")
private Map<String, ? extends AnnotationValue> getArgumentsAnnotations(AnnotationMirror annotationMirror)
{
return annotationMirror.getElementValues().entrySet().stream()
.filter(entry -> entry.getKey().getSimpleName().contentEquals("arguments"))
.flatMap(entry -> ((List<? extends AnnotationValue>) entry.getValue().getValue()).stream())
.flatMap(annotationValue -> ((AnnotationMirror) annotationValue.getValue()).getElementValues().entrySet().stream()) // now as RecordBuilderEnhanceArguments
.collect(Collectors.toMap(entry -> entry.getKey().getSimpleName().toString(), Map.Entry::getValue));
}
private Optional<? extends AnnotationMirror> getAnnotationMirror(ProcessorImpl processor, TypeElement typeElement) {
return processor.elements().getAllAnnotationMirrors(typeElement).stream()
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(adjustedClassName(RecordBuilderEnhance.class)))
.findFirst();
}
private Optional<? extends AnnotationMirror> getTemplateAnnotationMirror(ProcessorImpl processor, TypeElement typeElement) {
return processor.elements().getAllAnnotationMirrors(typeElement).stream()
.flatMap(annotationMirror -> processor.elements().getAllAnnotationMirrors(annotationMirror.getAnnotationType().asElement()).stream())
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(adjustedClassName(RecordBuilderEnhance.Template.class)))
.findFirst();
}
private Stream<EnhancerAndArgs> toEnhancer(ProcessorImpl processor, EnhancerSpec spec)
{
return enhancers.computeIfAbsent(spec.enhancerClass(), __ -> newEnhancer(processor, spec.enhancerClass()))
.stream()
.map(enhancer -> new EnhancerAndArgs(enhancer, spec.arguments()));
}
private Optional<RecordBuilderEnhancer> newEnhancer(ProcessorImpl processor, String enhancerClass)
{
try {
Class<?> clazz = Class.forName(enhancerClass, true, RecordBuilderEnhancerPlugin.class.getClassLoader());
Object enhancer = clazz.getConstructor().newInstance();
return Optional.of((RecordBuilderEnhancer) enhancer);
} catch (Exception e) {
processor.logError("Could not create enhancer instance. type=%s exception=%s message=%s".formatted(enhancerClass, e.getClass().getSimpleName(), e.getMessage()));
return Optional.empty();
}
}
}

View File

@@ -1,45 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.File;
@Command(name = "RecordBuilder Enhancer",
description = "Enhances Java record class files with validations, preconditions, etc. See https://github.com/Randgalt/record-builder for details",
usageHelpAutoWidth = true)
class PluginOptions {
@Parameters(paramLabel = "DIRECTORY", arity = "0..1", description = "The build's output directory - i.e. where javac writes generated classes. The value can be a full path or a relative path. If not provided the Enhancer plugin will attempt to use standard directories.")
String directory = "";
@Option(names = {"-h", "--help"}, description = "Outputs this help")
boolean helpRequested = false;
@Option(names = {"-v", "--verbose"}, description = "Verbose output during compilation")
boolean verbose = false;
@Option(names = {"--disable"}, description = "Deactivate/disable the plugin")
boolean disable = false;
@Option(names = {"--dryRun"}, description = "Dry run only - doesn't modify any classes. You should enable verbose as well via: -v")
boolean dryRun = false;
@Option(names = {"--outputDirectory"}, description = "Optional alternate output directory for enhanced class files")
File outputTo = null;
}

View File

@@ -1,110 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.Trees;
import io.soabase.recordbuilder.enhancer.EnhancersController.EnhancerAndArgs;
import io.soabase.recordbuilder.enhancer.spi.Entry;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
class ProcessorImpl
implements Processor {
private final Collection<? extends Class<? extends RecordBuilderEnhancer>> enhancers;
private final Elements elements;
private final Types types;
private final Trees trees;
private final CompilationUnitTree compilationUnit;
private final boolean verboseRequested;
ProcessorImpl(Elements elements, Types types, Trees trees, CompilationUnitTree compilationUnit, boolean verboseRequested) {
this(Set.of(), elements, types, trees, compilationUnit, verboseRequested);
}
private ProcessorImpl(Collection<? extends Class<? extends RecordBuilderEnhancer>> enhancers, Elements elements, Types types, Trees trees, CompilationUnitTree compilationUnit, boolean verboseRequested) {
this.enhancers = enhancers;
this.elements = elements;
this.types = types;
this.trees = trees;
this.compilationUnit = compilationUnit;
this.verboseRequested = verboseRequested;
}
ProcessorImpl withEnhancers(List<EnhancerAndArgs> enhancers)
{
Collection<? extends Class<? extends RecordBuilderEnhancer>> enhancersList = enhancers.stream().map(enhancerAndArgs -> enhancerAndArgs.enhancer().getClass()).toList();
return new ProcessorImpl(enhancersList, elements, types, trees, compilationUnit, verboseRequested);
}
@Override
public boolean verboseRequested() {
return verboseRequested;
}
@Override
public List<Entry> asEntries(TypeElement element) {
List<? extends RecordComponentElement> recordComponents = element.getRecordComponents();
return IntStream.range(0, recordComponents.size())
.mapToObj(index -> new Entry(index + 1, recordComponents.get(index), types().erasure(recordComponents.get(index).asType())))
.toList();
}
@Override
public boolean hasEnhancer(Class<? extends RecordBuilderEnhancer> enhancer) {
return enhancers.contains(enhancer);
}
@Override
public void logInfo(CharSequence msg) {
printMessage(Diagnostic.Kind.NOTE, msg);
}
@Override
public void logWarning(CharSequence msg) {
printMessage(Diagnostic.Kind.MANDATORY_WARNING, msg);
}
@Override
public void logError(CharSequence msg) {
msg += " - Use -h for help in your -Xplugin arguments";
printMessage(Diagnostic.Kind.ERROR, msg);
}
@Override
public Elements elements() {
return elements;
}
@Override
public Types types() {
return types;
}
private void printMessage(Diagnostic.Kind kind, CharSequence msg) {
trees.printMessage(kind, "[RecordBuilder Enhancer] " + msg, compilationUnit, compilationUnit);
}
}

View File

@@ -1,139 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import io.soabase.recordbuilder.enhancer.EnhancersController.EnhancerAndArgs;
import io.soabase.recordbuilder.enhancer.Session.FileStreams;
import picocli.CommandLine;
import recordbuilder.org.objectweb.asm.ClassReader;
import recordbuilder.org.objectweb.asm.ClassWriter;
import recordbuilder.org.objectweb.asm.Opcodes;
import recordbuilder.org.objectweb.asm.tree.*;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.ListIterator;
import java.util.Optional;
import java.util.stream.Collectors;
public class RecordBuilderEnhancerPlugin
implements Plugin, TaskListener {
private volatile Session session;
@Override
public String getName() {
return "recordbuilderenhancer";
}
@Override
public boolean autoStart() {
return true;
}
@Override
public void init(JavacTask task, String... args) {
PluginOptions pluginOptions = new PluginOptions();
CommandLine commandLine = new CommandLine(pluginOptions);
commandLine.parseArgs(args);
if (!pluginOptions.disable) {
session = new Session(task, this, pluginOptions, commandLine);
}
}
@Override
public void finished(TaskEvent taskEvent) {
if (taskEvent.getKind() == TaskEvent.Kind.GENERATE) {
TypeElement typeElement = taskEvent.getTypeElement();
ProcessorImpl processor = session.newProcessor(taskEvent);
session.checkPrintHelp(processor);
List<EnhancerAndArgs> enhancers = session.enhancersController().getEnhancers(processor, typeElement);
if (!enhancers.isEmpty()) {
if (typeElement.getRecordComponents().isEmpty()) {
processor.logError(typeElement.getQualifiedName() + " is not a record");
} else {
if (processor.verboseRequested()) {
processor.logWarning("Enhancing %s with %s".formatted(typeElement.getSimpleName(), enhancers.stream().map(enhancer -> enhancer.getClass().getName()).collect(Collectors.joining(","))));
}
session.getFileStreams(processor, typeElement, taskEvent.getCompilationUnit().getSourceFile().toUri()).ifPresent(fileStreams -> enhance(typeElement, processor.withEnhancers(enhancers), enhancers, fileStreams));
}
}
}
}
private boolean removeAndSaveSuperCall(MethodNode constructor, InsnList insnList) {
ListIterator<AbstractInsnNode> iterator = constructor.instructions.iterator();
while (iterator.hasNext()) {
AbstractInsnNode node = iterator.next();
iterator.remove();
insnList.add(node);
if ((node.getOpcode() == Opcodes.INVOKESPECIAL) && ((MethodInsnNode) node).owner.equals("java/lang/Record") && ((MethodInsnNode) node).name.equals("<init>")) {
return true;
}
}
return false;
}
private void enhance(TypeElement typeElement, ProcessorImpl processor, List<EnhancerAndArgs> specs, FileStreams fileStreams) {
try {
ClassNode classNode = new ClassNode();
try (InputStream in = fileStreams.openInputStream()) {
ClassReader classReader = new ClassReader(in);
classReader.accept(classNode, 0);
}
InsnList insnList = new InsnList();
MethodNode constructor = findConstructor(classNode).orElseThrow(() -> new IllegalStateException("Could not find default constructor"));
if (!removeAndSaveSuperCall(constructor, insnList)) {
processor.logError("Unrecognized constructor - missing super() call.");
return;
}
specs.stream()
.map(spec -> spec.enhancer().enhance(processor, typeElement, spec.arguments()))
.forEach(insnList::add);
constructor.instructions.insert(insnList);
ClassWriter classWriter = new ClassWriter(Opcodes.ASM9 | ClassWriter.COMPUTE_FRAMES);
classNode.accept(classWriter);
if (!session.isDryRun()) {
try (OutputStream out = fileStreams.openOutputStream()) {
out.write(classWriter.toByteArray());
}
}
} catch (IOException e) {
processor.logError("Could not process " + typeElement.getQualifiedName() + " - " + e.getMessage());
}
}
static String adjustedClassName(Class<?> clazz) {
return clazz.getName().replace('$', '.');
}
private static Optional<MethodNode> findConstructor(ClassNode classNode) {
String defaultConstructorDescription = classNode.recordComponents.stream()
.map(recordComponentNode -> recordComponentNode.descriptor)
.collect(Collectors.joining("", "(", ")V"));
return classNode.methods.stream()
.filter(methodNode -> methodNode.name.equals("<init>") && methodNode.desc.equals(defaultConstructorDescription))
.findFirst();
}
}

View File

@@ -1,186 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.Trees;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import picocli.CommandLine;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.*;
import java.io.*;
import java.net.URI;
import java.util.List;
import java.util.Optional;
public class Session implements SessionFlag {
private final JavacTask task;
private final StandardJavaFileManager fileManager;
private final Trees trees;
private final EnhancersController enhancersController;
private final PluginOptions pluginOptions;
private final CommandLine commandLine;
public Session(JavacTask task, TaskListener taskListener, PluginOptions pluginOptions, CommandLine commandLine) {
this.task = task;
this.pluginOptions = pluginOptions;
this.commandLine = commandLine;
task.addTaskListener(taskListener);
enhancersController = new EnhancersController();
//PluginOptions.setupCommandLine(this.commandLine, enhancersController.enhancers(), true);
fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
try {
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(new File(this.pluginOptions.directory).getAbsoluteFile()));
} catch (IOException e) {
throw new RuntimeException("Could not set the class output path", e);
}
trees = Trees.instance(task);
if (pluginOptions.outputTo != null) {
if (!pluginOptions.outputTo.isDirectory() && !pluginOptions.outputTo.exists() && !pluginOptions.outputTo.mkdirs()) {
throw new RuntimeException("Could not create directory: " + pluginOptions.outputTo);
}
}
}
public boolean enabled()
{
return !pluginOptions.disable;
}
public boolean isDryRun()
{
return pluginOptions.dryRun;
}
public JavacTask task() {
return task;
}
public interface FileStreams
{
InputStream openInputStream() throws IOException;
OutputStream openOutputStream() throws IOException;
}
public Optional<FileStreams> getFileStreams(Processor processor, TypeElement element, URI fileUri) {
String directory = pluginOptions.directory;
if (directory.isEmpty()) {
String pathToFile = fileUri.getPath();
int srcMainJavaIndex = pathToFile.indexOf("src/main/java");
if (srcMainJavaIndex >= 0) {
directory = pathToFile.substring(0, srcMainJavaIndex) + "target/classes";
}
else {
directory = ".";
}
}
String className = getClassName(element);
File absoluteDirectory = new File(directory).getAbsoluteFile();
if (pluginOptions.verbose) {
processor.logWarning("Using classPath %s for class %s".formatted(absoluteDirectory, className));
}
try {
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(absoluteDirectory));
JavaFileObject inputJavaFile = fileManager.getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, className, JavaFileObject.Kind.CLASS, null);
JavaFileObject outputJavaFile;
if (pluginOptions.outputTo != null) {
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(pluginOptions.outputTo));
outputJavaFile = fileManager.getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, className, JavaFileObject.Kind.CLASS, null);
}
else {
outputJavaFile = inputJavaFile;
}
FileStreams fileStreams = new FileStreams() {
@Override
public InputStream openInputStream() throws IOException {
return inputJavaFile.openInputStream();
}
@Override
public OutputStream openOutputStream() throws IOException {
return outputJavaFile.openOutputStream();
}
};
return Optional.of(fileStreams);
} catch (IOException e) {
processor.logError("Could not set classpath to %s for class %s: %s".formatted(absoluteDirectory, className, e.getMessage()));
return Optional.empty();
}
}
public EnhancersController enhancersController() {
return enhancersController;
}
public ProcessorImpl newProcessor(TaskEvent taskEvent)
{
return new ProcessorImpl(task.getElements(), task.getTypes(), trees, taskEvent.getCompilationUnit(), pluginOptions.verbose);
}
public void checkPrintHelp(Processor processor)
{
if (pluginOptions.helpRequested && getAndSetFlag("show-help")) {
StringWriter help = new StringWriter();
help.write('\n');
commandLine.usage(new PrintWriter(help));
processor.logError(help.toString());
}
}
@Override
public boolean getAndSetFlag(String name)
{
boolean wasSet = isSet(name);
if (!wasSet) {
set(name);
return true;
}
return false;
}
private String getClassName(TypeElement typeElement) {
return task.getElements().getPackageOf(typeElement).getQualifiedName() + "." + getClassName(typeElement, "");
}
private static String getClassName(Element element, String separator) {
// prefix enclosing class names if nested in a class
if (element instanceof TypeElement) {
return getClassName(element.getEnclosingElement(), "$") + element.getSimpleName().toString() + separator;
}
return "";
}
private boolean isSet(String name)
{
return Boolean.parseBoolean(System.getProperty(toKey(name)));
}
private void set(String name)
{
System.setProperty(toKey(name), "true");
}
private String toKey(String name) {
return Session.class.getName() + ":" + name;
}
}

View File

@@ -1,21 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer;
@FunctionalInterface
public interface SessionFlag {
boolean getAndSetFlag(String name);
}

View File

@@ -1,53 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
public class CopyCollection extends CopyCollectionBase {
@Override
protected boolean isInterface() {
return true;
}
@Override
protected String mapMethod() {
return "(Ljava/util/Map;)Ljava/util/Map;";
}
@Override
protected String setMethod() {
return "(Ljava/util/Collection;)Ljava/util/Set;";
}
@Override
protected String listMethod() {
return "(Ljava/util/Collection;)Ljava/util/List;";
}
@Override
protected String map() {
return "java/util/Map";
}
@Override
protected String set() {
return "java/util/Set";
}
@Override
protected String list() {
return "java/util/List";
}
}

View File

@@ -1,70 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import recordbuilder.org.objectweb.asm.Opcodes;
import recordbuilder.org.objectweb.asm.tree.InsnList;
import recordbuilder.org.objectweb.asm.tree.MethodInsnNode;
import recordbuilder.org.objectweb.asm.tree.VarInsnNode;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.List;
public abstract class CopyCollectionBase
implements RecordBuilderEnhancer {
@Override
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
TypeMirror collectionType = processor.elements().getTypeElement("java.util.Collection").asType();
TypeMirror listType = processor.elements().getTypeElement("java.util.List").asType();
TypeMirror mapType = processor.elements().getTypeElement("java.util.Map").asType();
InsnList insnList = new InsnList();
processor.asEntries(element).stream()
.filter(entry -> processor.types().isAssignable(entry.erasedType(), collectionType) || processor.types().isAssignable(entry.erasedType(), mapType))
.forEach(entry -> {
// aload_1
// invokestatic #7 // InterfaceMethod java/util/List.copyOf:(Ljava/util/Collection;)Ljava/util/Set;
// astore_1
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
if (processor.types().isAssignable(entry.erasedType(), listType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, list(), "copyOf", listMethod(), isInterface()));
} else if (processor.types().isAssignable(entry.erasedType(), collectionType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, set(), "copyOf", setMethod(), isInterface()));
} else {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, map(), "copyOf", mapMethod(), isInterface()));
}
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
});
return insnList;
}
abstract protected boolean isInterface();
abstract protected String mapMethod();
abstract protected String setMethod();
abstract protected String listMethod();
abstract protected String map();
abstract protected String set();
abstract protected String list();
}

View File

@@ -1,68 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
public class CopyCollectionNullableEmpty extends CopyCollectionNullableEmptyBase {
@Override
protected boolean isInterface() {
return true;
}
@Override
protected String mapEmptyMethod() {
return "()Ljava/util/Map;";
}
@Override
protected String setEmptyMethod() {
return "()Ljava/util/Set;";
}
@Override
protected String listEmptyMethod() {
return "()Ljava/util/List;";
}
@Override
protected String mapCopyMethod() {
return "(Ljava/util/Map;)Ljava/util/Map;";
}
@Override
protected String setCopyMethod() {
return "(Ljava/util/Collection;)Ljava/util/Set;";
}
@Override
protected String listCopyMethod() {
return "(Ljava/util/Collection;)Ljava/util/List;";
}
@Override
protected String map() {
return "java/util/Map";
}
@Override
protected String set() {
return "java/util/Set";
}
@Override
protected String list() {
return "java/util/List";
}
}

View File

@@ -1,96 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import recordbuilder.org.objectweb.asm.Label;
import recordbuilder.org.objectweb.asm.Opcodes;
import recordbuilder.org.objectweb.asm.tree.*;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.List;
public abstract class CopyCollectionNullableEmptyBase
implements RecordBuilderEnhancer {
@Override
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
TypeMirror collectionType = processor.elements().getTypeElement("java.util.Collection").asType();
TypeMirror listType = processor.elements().getTypeElement("java.util.List").asType();
TypeMirror mapType = processor.elements().getTypeElement("java.util.Map").asType();
InsnList insnList = new InsnList();
processor.asEntries(element).stream()
.filter(entry -> processor.types().isAssignable(entry.erasedType(), collectionType) || processor.types().isAssignable(entry.erasedType(), mapType))
.forEach(entry -> {
/*
4: aload_1
5: ifnull 15
8: aload_1
9: invokestatic #7 // InterfaceMethod java/util/Set.copyOf:(Ljava/util/Collection;)Ljava/util/Set;
12: goto 18
15: invokestatic #13 // InterfaceMethod java/util/Set.of:()Ljava/util/Set;
18: astore_1
*/
LabelNode doEmptylabel = new LabelNode(new Label());
LabelNode doCopylabel = new LabelNode(new Label());
LabelNode doAssignlabel = new LabelNode(new Label());
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
insnList.add(new JumpInsnNode(Opcodes.IFNULL, doEmptylabel));
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
if (processor.types().isAssignable(entry.erasedType(), listType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, list(), "copyOf", listCopyMethod(), isInterface()));
} else if (processor.types().isAssignable(entry.erasedType(), collectionType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, set(), "copyOf", setCopyMethod(), isInterface()));
} else {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, map(), "copyOf", mapCopyMethod(), isInterface()));
}
insnList.add(new JumpInsnNode(Opcodes.GOTO, doAssignlabel));
insnList.add(doEmptylabel);
if (processor.types().isAssignable(entry.erasedType(), listType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, list(), "of", listEmptyMethod(), isInterface()));
} else if (processor.types().isAssignable(entry.erasedType(), collectionType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, set(), "of", setEmptyMethod(), isInterface()));
} else {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, map(), "of", mapEmptyMethod(), isInterface()));
}
insnList.add(doAssignlabel);
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
});
return insnList;
}
abstract protected boolean isInterface();
abstract protected String mapEmptyMethod();
abstract protected String setEmptyMethod();
abstract protected String listEmptyMethod();
abstract protected String mapCopyMethod();
abstract protected String setCopyMethod();
abstract protected String listCopyMethod();
abstract protected String map();
abstract protected String set();
abstract protected String list();
}

View File

@@ -1,74 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import recordbuilder.org.objectweb.asm.Label;
import recordbuilder.org.objectweb.asm.Opcodes;
import recordbuilder.org.objectweb.asm.tree.*;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.List;
public class EmptyNullOptional
implements RecordBuilderEnhancer {
@Override
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
TypeMirror optionalType = processor.elements().getTypeElement("java.util.Optional").asType();
TypeMirror optionalIntType = processor.elements().getTypeElement("java.util.OptionalInt").asType();
TypeMirror optionalLongType = processor.elements().getTypeElement("java.util.OptionalLong").asType();
TypeMirror optionalDouble = processor.elements().getTypeElement("java.util.OptionalDouble").asType();
InsnList insnList = new InsnList();
processor.asEntries(element)
.stream()
.filter(entry -> processor.types().isAssignable(entry.erasedType(), optionalType) || processor.types().isAssignable(entry.erasedType(), optionalIntType) || processor.types().isAssignable(entry.erasedType(), optionalLongType) || processor.types().isAssignable(entry.erasedType(), optionalDouble))
.forEach(entry -> {
/*
4: aload 5
6: ifnull 14
9: aload 5
11: goto 17
14: invokestatic #7 // Method java/util/Optional.empty:()Ljava/util/Optional;
17: astore 5
*/
LabelNode doEmptylabel = new LabelNode(new Label());
LabelNode doAssignlabel = new LabelNode(new Label());
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
insnList.add(new JumpInsnNode(Opcodes.IFNULL, doEmptylabel));
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
insnList.add(new JumpInsnNode(Opcodes.GOTO, doAssignlabel));
insnList.add(doEmptylabel);
if (processor.types().isAssignable(entry.erasedType(), optionalIntType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/OptionalInt", "empty", "()Ljava/util/OptionalInt;"));
}
else if (processor.types().isAssignable(entry.erasedType(), optionalLongType)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/OptionalLong", "empty", "()Ljava/util/OptionalLong;"));
}
else if (processor.types().isAssignable(entry.erasedType(), optionalDouble)) {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/OptionalDouble", "empty", "()Ljava/util/OptionalDouble;"));
}
else {
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/Optional", "empty", "()Ljava/util/Optional;"));
}
insnList.add(doAssignlabel);
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
});
return insnList;
}
}

View File

@@ -1,59 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import recordbuilder.org.objectweb.asm.Label;
import recordbuilder.org.objectweb.asm.Opcodes;
import recordbuilder.org.objectweb.asm.tree.*;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.List;
public class EmptyNullString
implements RecordBuilderEnhancer {
@Override
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
TypeMirror stringType = processor.elements().getTypeElement("java.lang.String").asType();
InsnList insnList = new InsnList();
processor.asEntries(element)
.stream()
.filter(entry -> processor.types().isAssignable(entry.erasedType(), stringType))
.forEach(entry -> {
/*
4: aload_1
5: ifnull 12
8: aload_1
9: goto 14
12: ldc #7 // String
14: astore_1
*/
LabelNode doEmptylabel = new LabelNode(new Label());
LabelNode doAssignlabel = new LabelNode(new Label());
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
insnList.add(new JumpInsnNode(Opcodes.IFNULL, doEmptylabel));
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
insnList.add(new JumpInsnNode(Opcodes.GOTO, doAssignlabel));
insnList.add(doEmptylabel);
insnList.add(new LdcInsnNode(""));
insnList.add(doAssignlabel);
insnList.add(new VarInsnNode(Opcodes.ASTORE, entry.parameterIndex()));
});
return insnList;
}
}

View File

@@ -1,53 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
public class GuavaCopyCollection extends CopyCollectionBase {
@Override
protected boolean isInterface() {
return false;
}
@Override
protected String mapMethod() {
return "(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;";
}
@Override
protected String setMethod() {
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableSet;";
}
@Override
protected String listMethod() {
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;";
}
@Override
protected String map() {
return "com/google/common/collect/ImmutableMap";
}
@Override
protected String set() {
return "com/google/common/collect/ImmutableSet";
}
@Override
protected String list() {
return "com/google/common/collect/ImmutableList";
}
}

View File

@@ -1,68 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
public class GuavaCopyCollectionNullableEmpty extends CopyCollectionNullableEmptyBase {
@Override
protected boolean isInterface() {
return false;
}
@Override
protected String mapEmptyMethod() {
return "()Lcom/google/common/collect/ImmutableMap;";
}
@Override
protected String setEmptyMethod() {
return "()Lcom/google/common/collect/ImmutableSet;";
}
@Override
protected String listEmptyMethod() {
return "()Lcom/google/common/collect/ImmutableList;";
}
@Override
protected String mapCopyMethod() {
return "(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;";
}
@Override
protected String setCopyMethod() {
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableSet;";
}
@Override
protected String listCopyMethod() {
return "(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;";
}
@Override
protected String map() {
return "com/google/common/collect/ImmutableMap";
}
@Override
protected String set() {
return "com/google/common/collect/ImmutableSet";
}
@Override
protected String list() {
return "com/google/common/collect/ImmutableList";
}
}

View File

@@ -1,63 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
import io.soabase.recordbuilder.enhancer.spi.Entry;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import recordbuilder.org.objectweb.asm.tree.InsnList;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.TypeElement;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;
public class NotNullAnnotations
implements RecordBuilderEnhancer
{
public static final String DEFAULT_EXPRESSION = "(notnull)|(nonnull)";
@Override
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
InsnList insnList = new InsnList();
if (arguments.size() > 1) {
processor.logError("Too many arguments to NotNullAnnotations.");
} else {
String expression = arguments.isEmpty() ? DEFAULT_EXPRESSION : arguments.get(0);
try {
Pattern pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE);
processor.asEntries(element)
.stream()
.filter(entry -> !entry.erasedType().getKind().isPrimitive())
.filter(entry -> hasMatchingAnnotation(entry, pattern))
.forEach(entry -> RequireNonNull.enhance(insnList, entry));
} catch (PatternSyntaxException e) {
processor.logError("Bad argument to NotNullAnnotations: " + e.getMessage());
}
}
return insnList;
}
private boolean hasMatchingAnnotation(Entry entry, Pattern pattern)
{
Stream<? extends AnnotationMirror> typeMirrors = entry.element().asType().getAnnotationMirrors().stream();
Stream<? extends AnnotationMirror> elementMirrors = entry.element().getAnnotationMirrors().stream();
return Stream.concat(typeMirrors, elementMirrors)
.anyMatch(mirror -> pattern.matcher(mirror.getAnnotationType().asElement().getSimpleName().toString()).matches());
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
import io.soabase.recordbuilder.enhancer.spi.Entry;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import javax.lang.model.type.TypeMirror;
import java.util.stream.Stream;
class ProcessorUtil {
private ProcessorUtil() {
}
static boolean isNotHandledByOthers(Class<? extends RecordBuilderEnhancer> enhancer, Processor processor, Entry entry, TypeMirror... types)
{
if (!processor.hasEnhancer(enhancer)) {
return true;
}
if (types == null) {
return true;
}
return Stream.of(types).noneMatch(type -> processor.types().isAssignable(entry.erasedType(), type));
}
}

View File

@@ -1,67 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.enhancers;
import io.soabase.recordbuilder.enhancer.spi.Entry;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import recordbuilder.org.objectweb.asm.Opcodes;
import recordbuilder.org.objectweb.asm.tree.*;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.List;
import static io.soabase.recordbuilder.enhancer.enhancers.ProcessorUtil.isNotHandledByOthers;
public class RequireNonNull
implements RecordBuilderEnhancer {
@Override
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments) {
TypeMirror optionalType = processor.elements().getTypeElement("java.util.Optional").asType();
TypeMirror stringType = processor.elements().getTypeElement("java.lang.String").asType();
TypeMirror collectionType = processor.elements().getTypeElement("java.util.Collection").asType();
TypeMirror listType = processor.elements().getTypeElement("java.util.List").asType();
TypeMirror mapType = processor.elements().getTypeElement("java.util.Map").asType();
InsnList insnList = new InsnList();
processor.asEntries(element)
.stream()
.filter(entry -> !entry.erasedType().getKind().isPrimitive())
.filter(entry -> isNotHandledByOthers(EmptyNullOptional.class, processor, entry, optionalType))
.filter(entry -> isNotHandledByOthers(EmptyNullString.class, processor, entry, stringType))
.filter(entry -> isNotHandledByOthers(CopyCollectionNullableEmpty.class, processor, entry, collectionType, listType, mapType))
.filter(entry -> isNotHandledByOthers(CopyCollection.class, processor, entry, collectionType, listType, mapType))
.filter(entry -> isNotHandledByOthers(GuavaCopyCollectionNullableEmpty.class, processor, entry, collectionType, listType, mapType))
.filter(entry -> isNotHandledByOthers(GuavaCopyCollection.class, processor, entry, collectionType, listType, mapType))
.forEach(entry -> enhance(insnList, entry));
return insnList;
}
static void enhance(InsnList insnList, Entry entry) {
// java.util.Objects.requireNonNull(var1, "<name> is null");
/*
ALOAD 1
LDC "X is null"
INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
POP
*/
insnList.add(new VarInsnNode(Opcodes.ALOAD, entry.parameterIndex()));
insnList.add(new LdcInsnNode("%s is null".formatted(entry.element().getSimpleName())));
insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/Objects", "requireNonNull", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"));
insnList.add(new InsnNode(Opcodes.POP));
}
}

View File

@@ -1 +0,0 @@
io.soabase.recordbuilder.enhancer.RecordBuilderEnhancerPlugin

View File

@@ -1,9 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>34-SNAPSHOT</version>
<version>38-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,87 +22,135 @@ 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 useUnmodifiableCollections;
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 Class<?> collectionsType = Collections.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 TypeName collectionsTypeName = TypeName.get(collectionsType);
private static final TypeVariableName tType = TypeVariableName.get("T");
private static final TypeVariableName kType = TypeVariableName.get("K");
private static final TypeVariableName vType = TypeVariableName.get("V");
private static final ParameterizedTypeName parameterizedListType = ParameterizedTypeName.get(ClassName.get(List.class), tType);
private static final ParameterizedTypeName parameterizedMapType = ParameterizedTypeName.get(ClassName.get(Map.class), kType, vType);
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName.get(ClassName.get(Set.class), tType);
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName.get(ClassName.get(Collection.class), tType);
private static final ParameterizedTypeName parameterizedListType = ParameterizedTypeName
.get(ClassName.get(List.class), tType);
private static final ParameterizedTypeName parameterizedMapType = ParameterizedTypeName
.get(ClassName.get(Map.class), kType, vType);
private static final ParameterizedTypeName parameterizedSetType = ParameterizedTypeName
.get(ClassName.get(Set.class), tType);
private static final ParameterizedTypeName parameterizedCollectionType = ParameterizedTypeName
.get(ClassName.get(Collection.class), tType);
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();
useUnmodifiableCollections = !useImmutableCollections && metaData.useUnmodifiableCollections();
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 {
STANDARD,
STANDARD_FOR_SETTER,
EXCLUDE_WILDCARD_TYPES
STANDARD, STANDARD_FOR_SETTER, 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)) {
if (addSingleItemCollectionBuilders
&& (component.typeName() instanceof ParameterizedTypeName parameterizedTypeName)) {
Class<?> collectionClass = null;
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;
}
var hasWildcardTypeArguments = hasWildcardTypeArguments(parameterizedTypeName, typeArgumentQty);
if (collectionClass != null) {
return switch (mode) {
case STANDARD -> singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
case STANDARD -> singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass,
typeArgumentQty);
case STANDARD_FOR_SETTER -> {
if (hasWildcardTypeArguments) {
yield Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, component.typeName()));
}
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
case STANDARD_FOR_SETTER -> {
if (hasWildcardTypeArguments) {
yield Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments,
component.typeName()));
}
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass,
typeArgumentQty);
}
case EXCLUDE_WILDCARD_TYPES -> {
if (hasWildcardTypeArguments) {
yield Optional.empty();
}
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass, typeArgumentQty);
case EXCLUDE_WILDCARD_TYPES -> {
if (hasWildcardTypeArguments) {
yield Optional.empty();
}
yield singleItemsMetaDataWithWildType(parameterizedTypeName, collectionClass, wildcardClass,
typeArgumentQty);
}
};
}
}
@@ -110,33 +158,41 @@ class CollectionBuilderUtils {
}
boolean isImmutableCollection(RecordClassType component) {
return useImmutableCollections && (isList(component) || isMap(component) || isSet(component) || component.rawTypeName().equals(collectionType));
return (useImmutableCollections || useUnmodifiableCollections)
&& (isList(component) || isMap(component) || isSet(component) || isCollection(component));
}
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) {
if (useImmutableCollections) {
private boolean isCollection(RecordClassType component) {
return component.rawTypeName().equals(collectionTypeName);
}
void addShimCall(CodeBlock.Builder builder, RecordClassType component) {
if (useImmutableCollections || useUnmodifiableCollections) {
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 (isCollection(component)) {
needsCollectionShim = true;
builder.add("$L($L)", collectionShimName, component.name());
} else {
@@ -147,31 +203,84 @@ 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 (isCollection(component)) {
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) {
if (!useImmutableCollections && !useUnmodifiableCollections) {
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());
}
}
private Optional<SingleItemsMetaData> singleItemsMetaDataWithWildType(ParameterizedTypeName parameterizedTypeName, Class<?> collectionClass, ClassName wildcardClass, int typeArgumentQty) {
void addMutableMakers(TypeSpec.Builder builder) {
if (!useImmutableCollections && !useUnmodifiableCollections) {
return;
}
if (needsListMutableMaker) {
builder.addMethod(
buildMutableMakerMethod(listMakerMethodName, mutableListSpec.name, parameterizedListType, tType));
builder.addType(mutableListSpec);
}
if (needsSetMutableMaker) {
builder.addMethod(
buildMutableMakerMethod(setMakerMethodName, mutableSetSpec.name, parameterizedSetType, tType));
builder.addType(mutableSetSpec);
}
if (needsMapMutableMaker) {
builder.addMethod(buildMutableMakerMethod(mapMakerMethodName, mutableMapSpec.name, parameterizedMapType,
kType, vType));
builder.addType(mutableMapSpec);
}
}
private Optional<SingleItemsMetaData> singleItemsMetaDataWithWildType(ParameterizedTypeName parameterizedTypeName,
Class<?> collectionClass, ClassName wildcardClass, int typeArgumentQty) {
TypeName wildType;
if (typeArgumentQty == 1) {
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)));
wildType = ParameterizedTypeName.get(wildcardClass,
WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)));
} else { // if (typeArgumentQty == 2)
wildType = ParameterizedTypeName.get(wildcardClass, WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)), WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(1)));
wildType = ParameterizedTypeName.get(wildcardClass,
WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(0)),
WildcardTypeName.subtypeOf(parameterizedTypeName.typeArguments.get(1)));
}
return Optional.of(new SingleItemsMetaData(collectionClass, parameterizedTypeName.typeArguments, wildType));
}
@@ -187,42 +296,101 @@ 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) {
var code = CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
return MethodSpec.methodBuilder(name)
.addAnnotation(generatedRecordBuilderAnnotation)
private MethodSpec buildShimMethod(String name, TypeName mainType, Class<?> abstractType,
ParameterizedTypeName parameterizedType, TypeVariableName... typeVariables) {
var code = buildShimMethodBody(mainType, parameterizedType);
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 CodeBlock buildShimMethodBody(TypeName mainType, ParameterizedTypeName parameterizedType) {
if (!useUnmodifiableCollections) {
return CodeBlock.of("return (o != null) ? $T.copyOf(o) : $T.of()", mainType, mainType);
}
if (mainType.equals(listTypeName)) {
return CodeBlock.of("return (o != null) ? $T.<$T>unmodifiableList(($T) o) : $T.<$T>emptyList()",
collectionsTypeName, tType, parameterizedType, collectionsTypeName, tType);
}
if (mainType.equals(setTypeName)) {
return CodeBlock.of("return (o != null) ? $T.<$T>unmodifiableSet(($T) o) : $T.<$T>emptySet()",
collectionsTypeName, tType, parameterizedType, collectionsTypeName, tType);
}
if (mainType.equals(mapTypeName)) {
return CodeBlock.of("return (o != null) ? $T.<$T, $T>unmodifiableMap(($T) o) : $T.<$T, $T>emptyMap()",
collectionsTypeName, kType, vType, parameterizedType, collectionsTypeName, kType, vType);
}
throw new IllegalStateException("Cannot build shim method for" + mainType);
}
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(nullCase)
.addStatement(isMutableCase).addStatement(defaultCase).build();
}
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))
.returns(parameterizedType)
.addParameter(parameterizedType, "o")
.addStatement(code)
.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 = buildCollectionShimMethodBody();
return MethodSpec.methodBuilder(collectionShimName).addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC).addTypeVariable(tType)
.returns(parameterizedCollectionType).addParameter(parameterizedCollectionType, "o").addCode(code)
.build();
}
private MethodSpec buildCollectionsMethod() {
var code = CodeBlock.builder()
.add("if (o instanceof Set) {\n")
.indent()
.addStatement("return $T.copyOf(o)", setType)
.unindent()
.addStatement("}")
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listType, listType)
.build();
return MethodSpec.methodBuilder(collectionShimName)
.addAnnotation(generatedRecordBuilderAnnotation)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.addTypeVariable(tType)
.returns(parameterizedCollectionType)
.addParameter(parameterizedCollectionType, "o")
.addCode(code)
.build();
private CodeBlock buildCollectionShimMethodBody() {
if (!useUnmodifiableCollections) {
return CodeBlock.builder().add("if (o instanceof Set) {\n").indent()
.addStatement("return $T.copyOf(o)", setTypeName).unindent().addStatement("}")
.addStatement("return (o != null) ? $T.copyOf(o) : $T.of()", listTypeName, listTypeName).build();
}
return CodeBlock.builder().beginControlFlow("if (o instanceof $T)", listType)
.addStatement("return $T.<$T>unmodifiableList(($T) o)", collectionsTypeName, tType,
parameterizedListType)
.endControlFlow().beginControlFlow("if (o instanceof $T)", setType)
.addStatement("return $T.<$T>unmodifiableSet(($T) o)", collectionsTypeName, tType, parameterizedSetType)
.endControlFlow().addStatement("return $T.<$T>emptyList()", collectionsTypeName, tType).build();
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,49 +37,43 @@ import java.util.Optional;
import java.util.stream.Collectors;
public class ElementUtils {
public static Optional<? extends AnnotationMirror> findAnnotationMirror(ProcessingEnvironment processingEnv, Element element, String annotationClass) {
public static Optional<? extends AnnotationMirror> findAnnotationMirror(ProcessingEnvironment processingEnv,
Element element, String annotationClass) {
return processingEnv.getElementUtils().getAllAnnotationMirrors(element).stream()
.filter(e -> e.getAnnotationType().toString().equals(annotationClass))
.findFirst();
.filter(e -> e.getAnnotationType().toString().equals(annotationClass)).findFirst();
}
public static Optional<? extends AnnotationValue> getAnnotationValue(Map<? extends ExecutableElement, ? extends AnnotationValue> values, String name) {
return values.entrySet()
.stream()
.filter(e -> e.getKey().getSimpleName().toString().equals(name))
.map(Map.Entry::getValue)
.findFirst();
public static Optional<? extends AnnotationValue> getAnnotationValue(
Map<? extends ExecutableElement, ? extends AnnotationValue> values, String name) {
return values.entrySet().stream().filter(e -> e.getKey().getSimpleName().toString().equals(name))
.map(Map.Entry::getValue).findFirst();
}
@SuppressWarnings("unchecked")
public static List<TypeMirror> getAttributeTypeMirrorList(AnnotationValue attribute)
{
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
return values.stream().map(v -> (TypeMirror)v.getValue()).collect(Collectors.toList());
public static List<TypeMirror> getAttributeTypeMirrorList(AnnotationValue attribute) {
List<? extends AnnotationValue> values = (attribute != null)
? (List<? extends AnnotationValue>) attribute.getValue() : Collections.emptyList();
return values.stream().map(v -> (TypeMirror) v.getValue()).collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
public static List<String> getAttributeStringList(AnnotationValue attribute)
{
List<? extends AnnotationValue> values = (attribute != null) ? (List<? extends AnnotationValue>)attribute.getValue() : Collections.emptyList();
return values.stream().map(v -> (String)v.getValue()).collect(Collectors.toList());
public static List<String> getAttributeStringList(AnnotationValue attribute) {
List<? extends AnnotationValue> values = (attribute != null)
? (List<? extends AnnotationValue>) attribute.getValue() : Collections.emptyList();
return values.stream().map(v -> (String) v.getValue()).collect(Collectors.toList());
}
public static boolean getBooleanAttribute(AnnotationValue attribute)
{
public static boolean getBooleanAttribute(AnnotationValue attribute) {
Object value = (attribute != null) ? attribute.getValue() : null;
if ( value != null )
{
if (value != null) {
return Boolean.parseBoolean(String.valueOf(value));
}
return false;
}
public static String getStringAttribute(AnnotationValue attribute, String defaultValue)
{
public static String getStringAttribute(AnnotationValue attribute, String defaultValue) {
Object value = (attribute != null) ? attribute.getValue() : null;
if ( value != null )
{
if (value != null) {
return String.valueOf(value);
}
return defaultValue;
@@ -99,7 +93,8 @@ public class ElementUtils {
return (index > -1) ? name.substring(0, index) : "";
}
public static ClassType getClassType(String packageName, String simpleName, List<? extends TypeParameterElement> typeParameters) {
public static ClassType getClassType(String packageName, String simpleName,
List<? extends TypeParameterElement> typeParameters) {
return getClassType(ClassName.get(packageName, simpleName), typeParameters);
}
@@ -107,7 +102,8 @@ public class ElementUtils {
return getClassType(ClassName.get(typeElement), typeParameters);
}
public static ClassType getClassType(ClassName builderClassName, List<? extends TypeParameterElement> typeParameters) {
public static ClassType getClassType(ClassName builderClassName,
List<? extends TypeParameterElement> typeParameters) {
if (typeParameters.isEmpty()) {
return new ClassType(builderClassName, builderClassName.simpleName());
}
@@ -115,10 +111,13 @@ public class ElementUtils {
return new ClassType(ParameterizedTypeName.get(builderClassName, typeNames), builderClassName.simpleName());
}
public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv, RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
public static RecordClassType getRecordClassType(ProcessingEnvironment processingEnv,
RecordComponentElement recordComponent, List<? extends AnnotationMirror> accessorAnnotations,
List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
var typeName = TypeName.get(recordComponent.asType());
var rawTypeName = TypeName.get(processingEnv.getTypeUtils().erasure(recordComponent.asType()));
return new RecordClassType(typeName, rawTypeName, recordComponent.getSimpleName().toString(), accessorAnnotations, canonicalConstructorAnnotations);
return new RecordClassType(typeName, rawTypeName, recordComponent.getSimpleName().toString(),
accessorAnnotations, canonicalConstructorAnnotations);
}
public static String getWithMethodName(ClassType component, String prefix) {
@@ -129,27 +128,30 @@ public class ElementUtils {
return prefix + Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
public static String getBuilderName(TypeElement element, RecordBuilder.Options metaData, ClassType classType, String suffix) {
public static String getBuilderName(TypeElement element, RecordBuilder.Options metaData, ClassType classType,
String suffix) {
// generate the class name
var baseName = classType.name() + suffix;
return metaData.prefixEnclosingClassNames() ? (getBuilderNamePrefix(element.getEnclosingElement()) + baseName) : baseName;
return metaData.prefixEnclosingClassNames() ? (getBuilderNamePrefix(element.getEnclosingElement()) + baseName)
: baseName;
}
public static Optional<? extends Element> findCanonicalConstructor(TypeElement record) {
if ( record.getKind() != ElementKind.RECORD ) {
if (record.getKind() != ElementKind.RECORD) {
return Optional.empty();
}
// based on https://github.com/openjdk/jdk/pull/3556/files#diff-a6270f4b50989abe733607c69038b2036306d13f77276af005d023b7fc57f1a2R2368
var componentList = record.getRecordComponents().stream().map(e -> e.asType().toString()).collect(Collectors.toList());
return record.getEnclosedElements().stream()
.filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
.filter(element -> {
var parameters = ((ExecutableElement)element).getParameters();
var parametersList = parameters.stream().map(e -> e.asType().toString()).collect(Collectors.toList());
return componentList.equals(parametersList);
})
.findFirst();
// based on
// https://github.com/openjdk/jdk/pull/3556/files#diff-a6270f4b50989abe733607c69038b2036306d13f77276af005d023b7fc57f1a2R2368
var componentList = record.getRecordComponents().stream().map(e -> e.asType().toString())
.collect(Collectors.toList());
return record.getEnclosedElements().stream().filter(element -> element.getKind() == ElementKind.CONSTRUCTOR)
.filter(element -> {
var parameters = ((ExecutableElement) element).getParameters();
var parametersList = parameters.stream().map(e -> e.asType().toString())
.collect(Collectors.toList());
return componentList.equals(parametersList);
}).findFirst();
}
private static String getBuilderNamePrefix(Element element) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,7 +28,8 @@ class IncludeHelper {
private final List<TypeElement> classTypeElements;
private final Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues;
IncludeHelper(ProcessingEnvironment processingEnv, Element element, AnnotationMirror annotationMirror, boolean packagesSupported) {
IncludeHelper(ProcessingEnvironment processingEnv, Element element, AnnotationMirror annotationMirror,
boolean packagesSupported) {
annotationValues = processingEnv.getElementUtils().getElementValuesWithDefaults(annotationMirror);
var value = ElementUtils.getAnnotationValue(annotationValues, "value");
var classes = ElementUtils.getAnnotationValue(annotationValues, "classes");
@@ -41,9 +42,11 @@ class IncludeHelper {
var packagesList = packages.map(ElementUtils::getAttributeStringList).orElseGet(List::of);
if (valueList.isEmpty() && classesList.isEmpty() && packagesList.isEmpty()) {
if (packagesSupported) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\", \"classes\" or \"packages\" required", element);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"At least one of \"value\", \"classes\" or \"packages\" required", element);
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "At least one of \"value\" or \"classes\" required", element);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"At least one of \"value\" or \"classes\" required", element);
}
isValid = false;
}
@@ -51,7 +54,8 @@ class IncludeHelper {
isValid = processList(processingEnv, isValid, element, classesList, classTypeElements);
packages.ifPresent(annotationValue -> processPackages(processingEnv, classTypeElements, packagesList));
} else {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not read attribute for annotation", element);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not read attribute for annotation",
element);
isValid = false;
}
this.isValid = isValid;
@@ -70,11 +74,13 @@ class IncludeHelper {
return classTypeElements;
}
private boolean processList(ProcessingEnvironment processingEnv, boolean isValid, Element element, List<TypeMirror> list, ArrayList<TypeElement> classTypeElements) {
private boolean processList(ProcessingEnvironment processingEnv, boolean isValid, Element element,
List<TypeMirror> list, ArrayList<TypeElement> classTypeElements) {
for (var typeMirror : list) {
TypeElement typeElement = (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror);
if (typeElement == null) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get element for: " + typeMirror, element);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Could not get element for: " + typeMirror, element);
isValid = false;
} else {
classTypeElements.add(typeElement);
@@ -83,7 +89,8 @@ class IncludeHelper {
return isValid;
}
private void processPackages(ProcessingEnvironment processingEnv, List<TypeElement> classTypeElements, List<String> packagesList) {
private void processPackages(ProcessingEnvironment processingEnv, List<TypeElement> classTypeElements,
List<String> packagesList) {
for (var packageName : packagesList) {
var packageElement = processingEnv.getElementUtils().getPackageElement(packageName);
for (var child : packageElement.getEnclosedElements()) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,30 +51,33 @@ class InternalRecordInterfaceProcessor {
private record Component(ExecutableElement element, Optional<String> alternateName) {
}
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageNameOpt, boolean fromTemplate) {
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder,
RecordBuilder.Options metaData, Optional<String> packageNameOpt, boolean fromTemplate) {
this.processingEnv = processingEnv;
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
recordComponents = getRecordComponents(iface);
this.iface = iface;
ClassType ifaceClassType = ElementUtils.getClassType(iface, iface.getTypeParameters());
recordClassType = ElementUtils.getClassType(packageName, getBuilderName(iface, metaData, ifaceClassType, metaData.interfaceSuffix()), iface.getTypeParameters());
List<TypeVariableName> typeVariables = iface.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList());
recordClassType = ElementUtils.getClassType(packageName,
getBuilderName(iface, metaData, ifaceClassType, metaData.interfaceSuffix()), iface.getTypeParameters());
List<TypeVariableName> typeVariables = iface.getTypeParameters().stream().map(TypeVariableName::get)
.collect(Collectors.toList());
MethodSpec methodSpec = generateArgumentList();
TypeSpec.Builder builder = TypeSpec.classBuilder(recordClassType.name())
.addSuperinterface(iface.asType())
.addMethod(methodSpec)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(generatedRecordInterfaceAnnotation)
TypeSpec.Builder builder = TypeSpec.classBuilder(recordClassType.name()).addSuperinterface(iface.asType())
.addMethod(methodSpec).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());
ClassType builderClassType = ElementUtils.getClassType(packageName,
getBuilderName(iface, metaData, recordClassType, metaData.suffix()) + "."
+ metaData.withClassName(),
iface.getTypeParameters());
builder.addAnnotation(RecordBuilder.class);
builder.addSuperinterface(builderClassType.typeName());
if (fromTemplate) {
@@ -112,30 +115,29 @@ class InternalRecordInterfaceProcessor {
// javapoet does yet support records - so a class was created and we can reshape it
// The class will look something like this:
/*
// Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import javax.annotation.processing.Generated;
@Generated("io.soabase.recordbuilder.core.RecordInterface")
@RecordBuilder
public class MyRecord implements MyInterface {
void __FAKE__(String name, int age) {
}
}
*/
Pattern pattern = Pattern.compile("(.*)(implements.*)(\\{)(.*" + FAKE_METHOD_NAME + ")(\\(.*\\))(.*)", Pattern.MULTILINE | Pattern.DOTALL);
* // Auto generated by io.soabase.recordbuilder.core.RecordBuilder: https://github.com/Randgalt/record-builder
* package io.soabase.recordbuilder.test;
*
* import io.soabase.recordbuilder.core.RecordBuilder; import javax.annotation.processing.Generated;
*
* @Generated("io.soabase.recordbuilder.core.RecordInterface")
*
* @RecordBuilder public class MyRecord implements MyInterface { void __FAKE__(String name, int age) { } }
*/
Pattern pattern = Pattern.compile("(.*)(implements.*)(\\{)(.*" + FAKE_METHOD_NAME + ")(\\(.*\\))(.*)",
Pattern.MULTILINE | Pattern.DOTALL);
Matcher matcher = pattern.matcher(classSource);
if (!matcher.find() || matcher.groupCount() != 6) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Internal error generating record. Group count: " + matcher.groupCount(), iface);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Internal error generating record. Group count: " + matcher.groupCount(), iface);
}
String declaration = matcher.group(1).trim().replace("class", "record");
String implementsSection = matcher.group(2).trim();
String argumentList = matcher.group(5).trim();
StringBuilder fixedRecord = new StringBuilder(declaration).append(argumentList).append(' ').append(implementsSection).append(" {");
StringBuilder fixedRecord = new StringBuilder(declaration).append(argumentList).append(' ')
.append(implementsSection).append(" {");
alternateMethods.forEach(method -> fixedRecord.append('\n').append(method));
fixedRecord.append('}');
return fixedRecord.toString();
@@ -145,27 +147,23 @@ class InternalRecordInterfaceProcessor {
MethodSpec.Builder builder = MethodSpec.methodBuilder(FAKE_METHOD_NAME);
recordComponents.forEach(component -> {
String name = component.alternateName.orElseGet(() -> component.element.getSimpleName().toString());
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(component.element.getReturnType()), name).build();
builder.addTypeVariables(component.element.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()));
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(component.element.getReturnType()), name)
.build();
builder.addTypeVariables(component.element.getTypeParameters().stream().map(TypeVariableName::get)
.collect(Collectors.toList()));
builder.addParameter(parameterSpec);
});
return builder.build();
}
private List<String> buildAlternateMethods(List<Component> recordComponents) {
return recordComponents.stream()
.filter(component -> component.alternateName.isPresent())
.map(component -> {
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
.addAnnotation(Override.class)
.addAnnotation(generatedRecordInterfaceAnnotation)
.returns(ClassName.get(component.element.getReturnType()))
.addModifiers(Modifier.PUBLIC)
.addCode("return $L();", component.alternateName.get())
.build();
return method.toString();
})
.collect(Collectors.toList());
return recordComponents.stream().filter(component -> component.alternateName.isPresent()).map(component -> {
var method = MethodSpec.methodBuilder(component.element.getSimpleName().toString())
.addAnnotation(Override.class).addAnnotation(generatedRecordInterfaceAnnotation)
.returns(ClassName.get(component.element.getReturnType())).addModifiers(Modifier.PUBLIC)
.addCode("return $L();", component.alternateName.get()).build();
return method.toString();
}).collect(Collectors.toList());
}
private List<Component> getRecordComponents(TypeElement iface) {
@@ -173,7 +171,8 @@ class InternalRecordInterfaceProcessor {
try {
getRecordComponents(iface, components, new HashSet<>(), new HashSet<>());
if (components.isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Annotated interface has no component methods", iface);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Annotated interface has no component methods", iface);
}
} catch (IllegalInterface e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), iface);
@@ -189,29 +188,32 @@ class InternalRecordInterfaceProcessor {
}
private void getRecordComponents(TypeElement iface, Collection<Component> components, Set<String> visitedSet, Set<String> usedNames) {
private void getRecordComponents(TypeElement iface, Collection<Component> components, Set<String> visitedSet,
Set<String> usedNames) {
if (!visitedSet.add(iface.getQualifiedName().toString())) {
return;
}
iface.getEnclosedElements().stream()
.filter(element -> (element.getKind() == ElementKind.METHOD) && !(element.getModifiers().contains(Modifier.STATIC)))
.map(element -> ((ExecutableElement) element))
.filter(element -> {
.filter(element -> (element.getKind() == ElementKind.METHOD)
&& !(element.getModifiers().contains(Modifier.STATIC)))
.map(element -> ((ExecutableElement) element)).filter(element -> {
if (element.isDefault()) {
return element.getAnnotation(IgnoreDefaultMethod.class) == null;
}
return true;
})
.peek(element -> {
}).peek(element -> {
if (!element.getParameters().isEmpty() || element.getReturnType().getKind() == TypeKind.VOID) {
throw new IllegalInterface(String.format("Non-static, non-default methods must take no arguments and must return a value. Bad method: %s.%s()", iface.getSimpleName(), element.getSimpleName()));
throw new IllegalInterface(String.format(
"Non-static, non-default methods must take no arguments and must return a value. Bad method: %s.%s()",
iface.getSimpleName(), element.getSimpleName()));
}
if (!element.getTypeParameters().isEmpty()) {
throw new IllegalInterface(String.format("Interface methods cannot have type parameters. Bad method: %s.%s()", iface.getSimpleName(), element.getSimpleName()));
throw new IllegalInterface(
String.format("Interface methods cannot have type parameters. Bad method: %s.%s()",
iface.getSimpleName(), element.getSimpleName()));
}
})
.filter(element -> usedNames.add(element.getSimpleName().toString()))
}).filter(element -> usedNames.add(element.getSimpleName().toString()))
.map(element -> new Component(element, stripBeanPrefix(element.getSimpleName().toString())))
.collect(Collectors.toCollection(() -> components));
iface.getInterfaces().forEach(parentIface -> {
@@ -221,10 +223,8 @@ class InternalRecordInterfaceProcessor {
}
private Optional<String> stripBeanPrefix(String name) {
return javaBeanPrefixes.stream()
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
.findFirst()
.map(prefix -> {
return javaBeanPrefixes.stream().filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
.findFirst().map(prefix -> {
var stripped = name.substring(prefix.length());
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,8 +43,7 @@ public record OptionalType(TypeName typeName, TypeName valueType) {
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)
final TypeName containingType = parameterizedType.typeArguments.isEmpty() ? TypeName.get(Object.class)
: parameterizedType.typeArguments.get(0);
return Optional.of(new OptionalType(optionalType, containingType));
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,35 +25,36 @@ class RecordBuilderOptions {
private static final Map<String, Object> defaultValues = buildDefaultValues();
static RecordBuilder.Options build(Map<String, String> options) {
return (RecordBuilder.Options)Proxy.newProxyInstance(RecordBuilderOptions.class.getClassLoader(), new Class[]{RecordBuilder.Options.class}, (proxy, method, args) -> {
var name = method.getName();
var defaultValue = defaultValues.get(name);
var option = options.get(name);
if (option != null) {
if (defaultValue instanceof String) {
return option;
}
if (defaultValue instanceof Boolean) {
return Boolean.parseBoolean(option);
}
if (defaultValue instanceof Integer) {
return Integer.parseInt(option);
}
if (defaultValue instanceof Long) {
return Long.parseLong(option);
}
if (defaultValue instanceof Double) {
return Double.parseDouble(option);
}
throw new IllegalArgumentException("Unhandled option type: " + defaultValue.getClass());
}
return defaultValue;
});
return (RecordBuilder.Options) Proxy.newProxyInstance(RecordBuilderOptions.class.getClassLoader(),
new Class[] { RecordBuilder.Options.class }, (proxy, method, args) -> {
var name = method.getName();
var defaultValue = defaultValues.get(name);
var option = options.get(name);
if (option != null) {
if (defaultValue instanceof String) {
return option;
}
if (defaultValue instanceof Boolean) {
return Boolean.parseBoolean(option);
}
if (defaultValue instanceof Integer) {
return Integer.parseInt(option);
}
if (defaultValue instanceof Long) {
return Long.parseLong(option);
}
if (defaultValue instanceof Double) {
return Double.parseDouble(option);
}
throw new IllegalArgumentException("Unhandled option type: " + defaultValue.getClass());
}
return defaultValue;
});
}
private static Map<String, Object> buildDefaultValues() {
var workMap = new HashMap<String, Object>();
for ( Method method : RecordBuilder.Options.class.getDeclaredMethods()) {
for (Method method : RecordBuilder.Options.class.getDeclaredMethods()) {
workMap.put(method.getName(), method.getDefaultValue());
}
workMap.put("toString", "Generated RecordBuilder.Options");

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,20 +38,23 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
public class RecordBuilderProcessor
extends AbstractProcessor {
public class RecordBuilderProcessor extends AbstractProcessor {
private static final String RECORD_BUILDER = RecordBuilder.class.getName();
private static final String RECORD_BUILDER_INCLUDE = RecordBuilder.Include.class.getName().replace('$', '.');
private static final String RECORD_INTERFACE = RecordInterface.class.getName();
private static final String RECORD_INTERFACE_INCLUDE = RecordInterface.Include.class.getName().replace('$', '.');
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();
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) {
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> process(annotation, element)));
annotations.forEach(annotation -> roundEnv.getElementsAnnotatedWith(annotation)
.forEach(element -> process(annotation, element)));
return false;
}
@@ -62,7 +65,7 @@ public class RecordBuilderProcessor
@Override
public SourceVersion getSupportedSourceVersion() {
// we don't directly return RELEASE_14 as that may
// we don't directly return RELEASE_14 as that may
// not exist in prior releases
// if we're running on an older release, returning latest()
// is fine as we won't encounter any records anyway
@@ -76,14 +79,16 @@ public class RecordBuilderProcessor
processRecordBuilder(typeElement, getMetaData(typeElement), Optional.empty());
} else if (annotationClass.equals(RECORD_INTERFACE)) {
var typeElement = (TypeElement) element;
processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(), getMetaData(typeElement), Optional.empty(), false);
processRecordInterface(typeElement, element.getAnnotation(RecordInterface.class).addRecordBuilder(),
getMetaData(typeElement), Optional.empty(), false);
} else if (annotationClass.equals(RECORD_BUILDER_INCLUDE) || annotationClass.equals(RECORD_INTERFACE_INCLUDE)) {
processIncludes(element, getMetaData(element), annotationClass);
} else {
var recordBuilderTemplate = annotation.getAnnotation(RecordBuilder.Template.class);
if (recordBuilderTemplate != null) {
if (recordBuilderTemplate.asRecordInterface()) {
processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(), Optional.empty(), true);
processRecordInterface((TypeElement) element, true, recordBuilderTemplate.options(),
Optional.empty(), true);
} else {
processRecordBuilder((TypeElement) element, recordBuilderTemplate.options(), Optional.empty());
}
@@ -93,27 +98,34 @@ public class RecordBuilderProcessor
private RecordBuilder.Options getMetaData(Element element) {
var recordSpecificMetaData = element.getAnnotation(RecordBuilder.Options.class);
return (recordSpecificMetaData != null) ? recordSpecificMetaData : RecordBuilderOptions.build(processingEnv.getOptions());
return (recordSpecificMetaData != null) ? recordSpecificMetaData
: RecordBuilderOptions.build(processingEnv.getOptions());
}
private void processIncludes(Element element, RecordBuilder.Options metaData, String annotationClass) {
var isRecordBuilderInclude = annotationClass.equals(RECORD_BUILDER_INCLUDE);
var annotationMirrorOpt = ElementUtils.findAnnotationMirror(processingEnv, element, annotationClass);
if (annotationMirrorOpt.isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not get annotation mirror for: " + annotationClass, element);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Could not get annotation mirror for: " + annotationClass, element);
} else {
var includeHelper = new IncludeHelper(processingEnv, element, annotationMirrorOpt.get(), isRecordBuilderInclude);
var includeHelper = new IncludeHelper(processingEnv, element, annotationMirrorOpt.get(),
isRecordBuilderInclude);
if (includeHelper.isValid()) {
var packagePattern = ElementUtils.getStringAttribute(ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "packagePattern").orElse(null), "*");
var packagePattern = ElementUtils.getStringAttribute(ElementUtils
.getAnnotationValue(includeHelper.getAnnotationValues(), "packagePattern").orElse(null), "*");
for (var typeElement : includeHelper.getClassTypeElements()) {
var packageName = buildPackageName(packagePattern, element, typeElement);
if (packageName != null) {
if (isRecordBuilderInclude) {
processRecordBuilder(typeElement, metaData, Optional.of(packageName));
} else {
var addRecordBuilderOpt = ElementUtils.getAnnotationValue(includeHelper.getAnnotationValues(), "addRecordBuilder");
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute).orElse(true);
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName), false);
var addRecordBuilderOpt = ElementUtils
.getAnnotationValue(includeHelper.getAnnotationValues(), "addRecordBuilder");
var addRecordBuilder = addRecordBuilderOpt.map(ElementUtils::getBooleanAttribute)
.orElse(true);
processRecordInterface(typeElement, addRecordBuilder, metaData, Optional.of(packageName),
false);
}
}
}
@@ -130,7 +142,8 @@ public class RecordBuilderProcessor
if (builderElement instanceof PackageElement) {
return replaced.replace("@", ((PackageElement) builderElement).getQualifiedName().toString());
}
return replaced.replace("@", ((PackageElement) builderElement.getEnclosingElement()).getQualifiedName().toString());
return replaced.replace("@",
((PackageElement) builderElement.getEnclosingElement()).getQualifiedName().toString());
}
private PackageElement findPackageElement(Element actualElement, Element includedClass) {
@@ -144,37 +157,63 @@ public class RecordBuilderProcessor
return findPackageElement(actualElement, includedClass.getEnclosingElement());
}
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData, Optional<String> packageName, boolean fromTemplate) {
private void processRecordInterface(TypeElement element, boolean addRecordBuilder, RecordBuilder.Options metaData,
Optional<String> packageName, boolean fromTemplate) {
if (!element.getKind().isInterface()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordInterface only valid for interfaces.", element);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"RecordInterface only valid for interfaces.", element);
return;
}
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData, packageName, fromTemplate);
validateMetaData(metaData, element);
var internalProcessor = new InternalRecordInterfaceProcessor(processingEnv, element, addRecordBuilder, metaData,
packageName, fromTemplate);
if (!internalProcessor.isValid()) {
return;
}
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(), internalProcessor.recordType(), metaData, internalProcessor::toRecord);
writeRecordInterfaceJavaFile(element, internalProcessor.packageName(), internalProcessor.recordClassType(),
internalProcessor.recordType(), metaData, internalProcessor::toRecord);
}
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData, Optional<String> packageName) {
private void processRecordBuilder(TypeElement record, RecordBuilder.Options metaData,
Optional<String> packageName) {
// we use string based name comparison for the element kind,
// as the ElementKind.RECORD enum doesn't exist on JRE releases
// older than Java 14, and we don't want to throw unexpected
// NoSuchFieldErrors
if (!"RECORD".equals(record.getKind().name())) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.", record);
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "RecordBuilder only valid for records.",
record);
return;
}
validateMetaData(metaData, record);
var internalProcessor = new InternalRecordBuilderProcessor(processingEnv, record, metaData, packageName);
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(), internalProcessor.builderType(), metaData);
writeRecordBuilderJavaFile(record, internalProcessor.packageName(), internalProcessor.builderClassType(),
internalProcessor.builderType(), metaData);
}
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType, TypeSpec builderType, RecordBuilder.Options metaData) {
private void validateMetaData(RecordBuilder.Options metaData, Element record) {
var useImmutableCollections = metaData.useImmutableCollections();
var useUnmodifiableCollections = metaData.useUnmodifiableCollections();
if (useImmutableCollections && useUnmodifiableCollections) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.MANDATORY_WARNING,
"Options.useUnmodifiableCollections property is ignored as Options.useImmutableCollections is set to true",
record);
}
}
private void writeRecordBuilderJavaFile(TypeElement record, String packageName, ClassType builderClassType,
TypeSpec builderType, RecordBuilder.Options metaData) {
// produces the Java file
JavaFile javaFile = javaFileBuilder(packageName, builderType, metaData);
Filer filer = processingEnv.getFiler();
try {
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name() : (packageName + "." + builderClassType.name());
String fullyQualifiedName = packageName.isEmpty() ? builderClassType.name()
: (packageName + "." + builderClassType.name());
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
try (Writer writer = sourceFile.openWriter()) {
javaFile.writeTo(writer);
@@ -184,7 +223,8 @@ public class RecordBuilderProcessor
}
}
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType, TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc) {
private void writeRecordInterfaceJavaFile(TypeElement element, String packageName, ClassType classType,
TypeSpec type, RecordBuilder.Options metaData, Function<String, String> toRecordProc) {
JavaFile javaFile = javaFileBuilder(packageName, type, metaData);
String classSourceCode = javaFile.toString();
@@ -193,7 +233,8 @@ public class RecordBuilderProcessor
Filer filer = processingEnv.getFiler();
try {
String fullyQualifiedName = packageName.isEmpty() ? classType.name() : (packageName + "." + classType.name());
String fullyQualifiedName = packageName.isEmpty() ? classType.name()
: (packageName + "." + classType.name());
JavaFileObject sourceFile = filer.createSourceFile(fullyQualifiedName);
try (Writer writer = sourceFile.openWriter()) {
writer.write(recordSourceCode);
@@ -204,7 +245,8 @@ public class RecordBuilderProcessor
}
private JavaFile javaFileBuilder(String packageName, TypeSpec type, RecordBuilder.Options metaData) {
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true).indent(metaData.fileIndent());
var javaFileBuilder = JavaFile.builder(packageName, type).skipJavaLangImports(true)
.indent(metaData.fileIndent());
var comment = metaData.fileComment();
if ((comment != null) && !comment.isEmpty()) {
javaFileBuilder.addFileComment(comment);

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,9 @@ public class RecordClassType extends ClassType {
private final List<? extends AnnotationMirror> accessorAnnotations;
private final List<? extends AnnotationMirror> canonicalConstructorAnnotations;
public RecordClassType(TypeName typeName, TypeName rawTypeName, String name, List<? extends AnnotationMirror> accessorAnnotations, List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
public RecordClassType(TypeName typeName, TypeName rawTypeName, String name,
List<? extends AnnotationMirror> accessorAnnotations,
List<? extends AnnotationMirror> canonicalConstructorAnnotations) {
super(typeName, name);
this.rawTypeName = rawTypeName;
this.accessorAnnotations = accessorAnnotations;

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>34-SNAPSHOT</version>
</parent>
<artifactId>record-builder-test-custom-enhancer</artifactId>
<dependencies>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,50 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.spi.Processor;
import io.soabase.recordbuilder.enhancer.spi.RecordBuilderEnhancer;
import recordbuilder.org.objectweb.asm.Opcodes;
import recordbuilder.org.objectweb.asm.tree.FieldInsnNode;
import recordbuilder.org.objectweb.asm.tree.InsnList;
import recordbuilder.org.objectweb.asm.tree.InsnNode;
import recordbuilder.org.objectweb.asm.tree.MethodInsnNode;
import javax.lang.model.element.TypeElement;
import java.util.List;
public class TestEnhancer
implements RecordBuilderEnhancer
{
@Override
public InsnList enhance(Processor processor, TypeElement element, List<String> arguments)
{
/*
4: getstatic #7 // Field io/soabase/recordbuilder/enhancer/test/Counter.COUNTER:Ljava/util/concurrent/atomic/AtomicInteger;
7: invokevirtual #13 // Method java/util/concurrent/atomic/AtomicInteger.incrementAndGet:()I
10: pop
*/
InsnList insnList = new InsnList();
insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, "io/soabase/recordbuilder/enhancer/test/Counter", "COUNTER", "Ljava/util/concurrent/atomic/AtomicInteger;"));
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/util/concurrent/atomic/AtomicInteger", "incrementAndGet", "()I"));
insnList.add(new InsnNode(Opcodes.POP));
return insnList;
}
public String description() {
return "This is a test";
}
}

View File

@@ -1,9 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2019 The original author or authors
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>34-SNAPSHOT</version>
<version>38-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -19,43 +36,17 @@
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-core</artifactId>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer-core</artifactId>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-enhancer</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-validator</artifactId>
</dependency>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-test-custom-enhancer</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
@@ -73,6 +64,12 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -1,31 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.enhancers.CopyCollectionNullableEmpty;
import java.math.BigInteger;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@RecordBuilderEnhance(enhancers = CopyCollectionNullableEmpty.class)
public record CopyCollectionNullableEmptyTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
}

View File

@@ -1,25 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
public static final AtomicInteger COUNTER = new AtomicInteger();
private Counter() {
}
}

View File

@@ -1,31 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.enhancers.CopyCollectionNullableEmpty;
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullOptional;
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullString;
import io.soabase.recordbuilder.enhancer.enhancers.RequireNonNull;
import java.lang.annotation.*;
@RecordBuilderEnhance.Template(enhancers = {CopyCollectionNullableEmpty.class, EmptyNullString.class, EmptyNullOptional.class, RequireNonNull.class})
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface CustomEnhance {
}

View File

@@ -1,28 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
@CustomEnhance
public record CustomEnhanced(String s, Optional<String> o, Instant i, List<BigDecimal> l) {
public CustomEnhanced {
Counter.COUNTER.decrementAndGet();
}
}

View File

@@ -1,31 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.enhancers.GuavaCopyCollectionNullableEmpty;
import java.math.BigInteger;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@RecordBuilderEnhance(enhancers = GuavaCopyCollectionNullableEmpty.class)
public record GuavaCopyCollectionNullableEmptyTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
}

View File

@@ -1,27 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.RECORD_COMPONENT;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@Target(RECORD_COMPONENT)
@Retention(SOURCE)
public @interface MadeUpNotNullable {
}

View File

@@ -1,30 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhanceArguments;
import io.soabase.recordbuilder.enhancer.enhancers.NotNullAnnotations;
import javax.validation.constraints.NotNull;
@RecordBuilderEnhance(enhancers = NotNullAnnotations.class,
arguments = @RecordBuilderEnhanceArguments(enhancer = NotNullAnnotations.class, arguments = ".*notnull.*"))
public record NotNullAnnotation(String s, @NotNull Integer i, @MadeUpNotNullable Double d) {
public NotNullAnnotation() {
this(null, null, null);
}
}

View File

@@ -1,29 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.enhancers.CopyCollection;
import io.soabase.recordbuilder.enhancer.enhancers.RequireNonNull;
import java.math.BigInteger;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@RecordBuilderEnhance(enhancers = {RequireNonNull.class, CopyCollection.class})
public record NotNullableCopyCollectionTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
}

View File

@@ -1,28 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullOptional;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
@RecordBuilderEnhance(enhancers = EmptyNullOptional.class)
public record OptionalTest(OptionalInt i, OptionalLong l, OptionalDouble d, Optional<String> o) {
}

View File

@@ -1,22 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
@RecordBuilderEnhance(enhancers = TestEnhancer.class)
public record PluginTest(int i) {
}

View File

@@ -1,23 +0,0 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.enhancers.EmptyNullString;
@RecordBuilderEnhance(enhancers = EmptyNullString.class)
public record StringTest(String s) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,7 @@ package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
import io.soabase.recordbuilder.core.RecordInterface;
@RecordInterface.Include({
Thingy.class
})
@RecordBuilder.Include({
Nested.NestedRecord.class
})
@RecordInterface.Include({ Thingy.class })
@RecordBuilder.Include({ Nested.NestedRecord.class })
public class Builder {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,19 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.enhancer.test;
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.enhancer.RecordBuilderEnhance;
import io.soabase.recordbuilder.enhancer.enhancers.GuavaCopyCollection;
import io.soabase.recordbuilder.core.RecordBuilder;
import java.math.BigInteger;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@RecordBuilderEnhance(enhancers = GuavaCopyCollection.class)
public record GuavaCopyCollectionTest(Collection<Instant> c, List<BigInteger> l, Set<Boolean> s, Map<Map<String, Short>, AtomicBoolean> m) {
@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> {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,10 +25,11 @@ import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(useImmutableCollections = true, addFunctionalMethodsToWith = true)
public record CollectionRecord<T, X extends Point>(List<T> l, Set<T> s, Map<T, X> m,
Collection<X> c) implements CollectionRecordBuilder.With<T, X> {
public record CollectionRecord<T, X extends Point>(List<T> l, Set<T> s, Map<T, X> m, Collection<X> c)
implements CollectionRecordBuilder.With<T, X> {
public static void main(String[] args) {
var r = new CollectionRecord<>(List.of("hey"), Set.of("there"), Map.of("one", new Point(10, 20)), Set.of(new Point(30, 40)));
var r = new CollectionRecord<>(List.of("hey"), Set.of("there"), Map.of("one", new Point(10, 20)),
Set.of(new Point(30, 40)));
Instant now = r.map((l1, s1, m1, c1) -> Instant.now());
r.accept((l1, s1, m1, c1) -> {
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,5 +24,6 @@ import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(useImmutableCollections = true)
public record CollectionRecordConflicts(List<String> __list, Set<String> __set, Map<String, String> __map, Collection<String> __collection) implements CollectionRecordConflictsBuilder.With {
public record CollectionRecordConflicts(List<String> __list, Set<String> __set, Map<String, String> __map,
Collection<String> __collection) implements CollectionRecordConflictsBuilder.With {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,13 @@
package io.soabase.recordbuilder.test;
import java.util.List;
import java.util.Map;
import io.soabase.recordbuilder.core.RecordBuilder;
import io.soabase.recordbuilder.test.CustomMethodNamesBuilder.Bean;
@RecordBuilder
@RecordBuilder.Options(
setterPrefix = "set", getterPrefix = "get", booleanPrefix = "is", beanClassName = "Bean")
public record CustomMethodNames(
int theValue,
List<Integer> theList,
boolean theBoolean) implements Bean {
@RecordBuilder.Options(setterPrefix = "set", getterPrefix = "get", booleanPrefix = "is", beanClassName = "Bean")
public record CustomMethodNames<K, V>(Map<K, V> kvMap, int theValue, List<Integer> theList, boolean theBoolean)
implements Bean, CustomMethodNamesBuilder.With {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,10 +21,8 @@ import javax.lang.model.type.ErrorType;
import java.util.List;
@RecordBuilder
public record ExceptionDetails(
String internalMessage, String endUserMessage, String httpStatus,
ErrorType errorType, List<String> jsonProblems, Throwable cause
) {
public record ExceptionDetails(String internalMessage, String endUserMessage, String httpStatus, ErrorType errorType,
List<String> jsonProblems, Throwable cause) {
@Override
public List<String> jsonProblems() {
if (jsonProblems == null) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,5 +22,6 @@ import java.util.List;
import java.util.Map;
@RecordBuilderFull
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords, @NotNull String justAString) {
public record FullRecord(@NotNull List<Number> numbers, @NotNull Map<Number, FullRecord> fullRecords,
@NotNull String justAString) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,5 +20,6 @@ import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Options(prefixEnclosingClassNames = false)
@RecordBuilder.Include(IncludeWithOption.Hey.class)
public class IncludeWithOption {
public static record Hey(String s){}
public static record Hey(String s) {
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,6 @@ package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Template(options = @RecordBuilder.Options(
fileComment = "This is a test",
withClassName = "Com"),
asRecordInterface = true
)
@RecordBuilder.Template(options = @RecordBuilder.Options(fileComment = "This is a test", withClassName = "Com"), asRecordInterface = true)
public @interface MyInterfaceTemplate {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,6 @@ package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder.Template(options = @RecordBuilder.Options(
fileComment = "This is a test",
withClassName = "Com"
))
public @interface MyTemplate
{
@RecordBuilder.Template(options = @RecordBuilder.Options(fileComment = "This is a test", withClassName = "Com"))
public @interface MyTemplate {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,5 +16,6 @@
package io.soabase.recordbuilder.test;
public class Nested {
record NestedRecord(int x, int y){}
record NestedRecord(int x, int y) {
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,9 +1,5 @@
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +13,22 @@ import io.soabase.recordbuilder.core.RecordBuilder;
* 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;
/**
* 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) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,4 +15,5 @@
*/
package io.soabase.recordbuilder.test;
public record Pair<T, U>(T t, U u) {}
public record Pair<T, U>(T t, U u) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,4 +15,5 @@
*/
package io.soabase.recordbuilder.test;
public record Point(int x, int y) {}
public record Point(int x, int y) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,4 +18,5 @@ package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordBuilder;
@RecordBuilder
public record RecordWithAnR(int r, String b) {}
public record RecordWithAnR(int r, String b) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,4 +26,6 @@ import java.util.OptionalLong;
@RecordBuilder.Options(emptyDefaultForOptional = true, addConcreteSettersForOptional = true)
@RecordBuilder
public record RecordWithOptional(@NotNull 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) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,4 +23,6 @@ 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) {}
public record RecordWithOptional2(Optional<String> value, Optional raw, OptionalInt i, OptionalLong l,
OptionalDouble d) {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,5 +24,6 @@ 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) {}
public record Part(@NotBlank String name) {
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,5 +22,6 @@ import java.util.List;
@RecordBuilder.Options(interpretNotNulls = true)
@RecordBuilder
public record RequiredRecord(@NotNull String hey, @NotNull int i, @NotNull List<String> l) implements RequiredRecordBuilder.With {
public record RequiredRecord(@NotNull String hey, @NotNull int i, @NotNull List<String> l)
implements RequiredRecordBuilder.With {
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2019 Jordan Zimmerman
/*
* Copyright 2019 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,11 +24,7 @@ import java.util.Map;
import java.util.Set;
@RecordBuilder
@RecordBuilder.Options(
addSingleItemCollectionBuilders = true,
singleItemBuilderPrefix = "add1",
useImmutableCollections = true,
addFunctionalMethodsToWith = true
)
public record SingleItems<T>(List<String> strings, Set<List<T>> sets, Map<Instant, T> map, Collection<T> collection) implements SingleItemsBuilder.With<T> {
@RecordBuilder.Options(addSingleItemCollectionBuilders = true, singleItemBuilderPrefix = "add1", useImmutableCollections = true, addFunctionalMethodsToWith = true)
public record SingleItems<T>(List<String> strings, Set<List<T>> sets, Map<Instant, T> map, Collection<T> collection)
implements SingleItemsBuilder.With<T> {
}

Some files were not shown because too many files have changed in this diff Show More