Compare commits

..

14 Commits

Author SHA1 Message Date
Jordan Zimmerman
c0b78ec1db [maven-release-plugin] prepare release record-builder-1.15.jdk14 2021-01-13 21:59:19 -05:00
Jordan Zimmerman
d26c87c666 Java 14 version 2021-01-13 21:11:11 -05:00
Jordan Zimmerman
f8e56a40f3 Merge pull request #19 from Randgalt/use-with-to-get-builder
Have the consumer version of with() use the other with()
2020-12-21 08:10:32 -05:00
Jordan Zimmerman
5a07cf23f7 Have the consumer version of with() use the other with() to get the builder. This will ensure better testing and is more logical 2020-12-21 08:08:44 -05:00
Jordan Zimmerman
78ba53f96d Merge pull request #18 from Randgalt/patch-1
Use provided/compileOnly instead of implementation
2020-12-21 07:36:38 -05:00
Jordan Zimmerman
0153f65121 you're right - we only need provided/compileOnly. I've made the updates to Maven as well. 2020-12-21 07:12:28 -05:00
Marc Philipp
c1e771bbbe Use compileOnly instead of implementation
Since most users only need the annotations.
2020-12-21 11:43:56 +01:00
Jordan Zimmerman
c9ba514773 [maven-release-plugin] prepare for next development iteration 2020-12-11 19:27:42 -05:00
Jordan Zimmerman
491ed4f6e0 [maven-release-plugin] prepare release record-builder-1.14.ea 2020-12-11 19:27:34 -05:00
Jordan Zimmerman
16751508cc Update README.md 2020-12-11 13:54:05 -05:00
Jordan Zimmerman
ee7f81c7b8 Switch to Github Actions 2020-12-11 13:52:58 -05:00
Jordan Zimmerman
015287608b Update maven.yml 2020-12-11 13:51:51 -05:00
Jordan Zimmerman
467bcc9041 Create maven.yml 2020-12-11 13:50:22 -05:00
Jordan Zimmerman
666bc334ad Merge pull request #16 from mbarbero/master
Stop relying on toString() to detect package name.
2020-12-11 13:39:30 -05:00
14 changed files with 81 additions and 240 deletions

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +0,0 @@
# These are supported funding model platforms
github: Randgalt

View File

@@ -1,7 +1,7 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Maven Build - Java 16
name: Java CI with Maven
on:
push:
@@ -19,6 +19,6 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 16.0.0-ea
java-version: 15
- name: Build with Maven
run: mvn -B package --file pom.xml

View File

@@ -1,24 +0,0 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Maven Build - Java 15
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 15
- name: Build with Maven
run: mvn -P java15 -B package --file pom.xml

1
.mvn/jvm.config Normal file
View File

@@ -0,0 +1 @@
--enable-preview

120
README.md
View File

@@ -1,13 +1,13 @@
[![Build Status](https://github.com/Randgalt/record-builder/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/Randgalt/record-builder/actions)
[![Maven Central](https://img.shields.io/maven-central/v/io.soabase.record-builder/record-builder.svg)](https://search.maven.org/search?q=g:io.soabase.record-builder%20a:record-builder)
# RecordBuilder
# RecordBuilder - Early Access
## What is RecordBuilder
Java 16 introduces [Records](https://openjdk.java.net/jeps/395). While this version of records is fantastic,
it's currently missing some important features normally found in data classes: a builder
and "with"ers. This project is an annotation processor that creates:
Java 15 introduced [Records](https://cr.openjdk.java.net/~briangoetz/amber/datum.html) as a preview feature. Since Java 9,
features in Java are being released in stages. While the Java 15 version of records is fantastic, it's currently missing important features
for data classes: a builder and "with"ers. This project is an annotation processor that creates:
- a companion builder class for Java records
- an interface that adds "with" copy methods
@@ -22,7 +22,6 @@ _Details:_
- [Generation Via Includes](#generation-via-includes)
- [Usage](#usage)
- [Customizing](#customizing)
- [Java 15 Versions](#java-15-versions)
## RecordBuilder Example
@@ -257,8 +256,6 @@ Notes:
- ...cannot have type parameters
- Methods with default implementations are used in the generation unless they are annotated with `@IgnoreDefaultMethod`
- If you do not want a record builder generated, annotate your interface as `@RecordInterface(addRecordBuilder = false)`
- If your interface is a JavaBean (e.g. `getThing()`, `isThing()`) the "get" and "is" prefixes are
stripped and forwarding methods are added.
## Generation Via Includes
@@ -289,7 +286,7 @@ annotation. Use `packagePattern` to change this (see Javadoc for details).
### Maven
1) Add the dependency that contains the `@RecordBuilder` annotation.
1\. Add the dependency that contains the `@RecordBuilder` annotation.
```
<dependency>
@@ -301,7 +298,7 @@ annotation. Use `packagePattern` to change this (see Javadoc for details).
```
2) Enable the annotation processing for the Maven Compiler Plugin:
2\. Enable the annotation processing for the Maven Compiler Plugin:
```
<plugin>
@@ -321,11 +318,21 @@ annotation. Use `packagePattern` to change this (see Javadoc for details).
</annotationProcessors>
<!-- "release" and "enable-preview" are required while records are preview features -->
<release>15</release>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
... any other options here ...
</configuration>
</plugin>
```
3\. Enable Preview for Maven
Create a file in your project's root named `.mvn/jvm.config`. The file should have 1 line with the value: `--enable-preview`. (see: https://stackoverflow.com/questions/58023240)
### Gradle
Add the following to your build.gradle file:
@@ -335,12 +342,31 @@ dependencies {
annotationProcessor 'io.soabase.record-builder:record-builder-processor:$version-goes-here'
compileOnly 'io.soabase.record-builder:record-builder-core:$version-goes-here'
}
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.jvmArgs += '--enable-preview'
options.compilerArgs += '--enable-preview'
}
tasks.withType(Test) {
jvmArgs += "--enable-preview"
}
```
### IDE
Depending on your IDE you are likely to need to enable Annotation Processing in your IDE settings.
## Enable Preview
Note: records are a preview feature only. You'll need take a number of steps in order to try RecordBuilder:
- Install and make active Java 15 or later
- Make sure your development tool is using Java 15 or later and is configured to enable preview features (for Maven I've documented how to do this here: [https://stackoverflow.com/a/59363152/2048051](https://stackoverflow.com/a/59363152/2048051))
- Bear in mind that this is not yet meant for production and there are numerous bugs in the tools and JDKs.
Note: I've seen some very odd compilation bugs with the current Java 15 and Maven. If you get internal Javac errors I suggest rebuilding with `mvn clean package` and/or `mvn clean install`.
## Customizing
The names of the generated methods, etc. are determined by [RecordBuilderMetaData](https://github.com/Randgalt/record-builder/blob/master/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilderMetaData.java). If you want to use your own meta data instance:
@@ -362,79 +388,3 @@ Alternatively, you can provide values for each individual meta data (or combinat
- `javac ... -AfileComment=foo`
- `javac ... -AfileIndent=foo`
- `javac ... -AprefixEnclosingClassNames=foo`
## Java 15 Versions
Artifacts compiled wth Java 15 are available. The artifact IDs for these are:
- core: `record-builder-core-java15`
- processor: `record-builder-processor-java15`
Note: records are a preview feature only in Java 15. You'll need take a number of steps in order to try RecordBuilder:
- Install and make active Java 15 or later
- Make sure your development tool is using Java 15 or later and is configured to enable preview features (for Maven I've documented how to do this here: [https://stackoverflow.com/a/59363152/2048051](https://stackoverflow.com/a/59363152/2048051))
- Bear in mind that this is not yet meant for production and there are numerous bugs in the tools and JDKs.
Note: I've seen some very odd compilation bugs with the current Java 15 and Maven. If you get internal Javac errors I suggest rebuilding with `mvn clean package` and/or `mvn clean install`.
You will need to enable preview in your build tools:
### Maven
```
<dependencies>
<dependency>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-core-java15</artifactId>
<version>set-version-here</version>
</dependency>
</dependencies>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>set-version-here</version>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder-processor-java15</artifactId>
<version>set-version-here</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>io.soabase.recordbuilder.processor.RecordBuilderProcessor</annotationProcessor>
</annotationProcessors>
<!-- "release" and "enable-preview" are required while records are preview features -->
<release>15</release>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
... any other options here ...
</configuration>
</plugin>
```
Create a file in your project's root named `.mvn/jvm.config`. The file should have 1 line with the value: `--enable-preview`. (see: https://stackoverflow.com/questions/58023240)
### Gradle
```
dependencies {
annotationProcessor 'io.soabase.record-builder:record-builder-processor-java15:$version-goes-here'
compileOnly 'io.soabase.record-builder:record-builder-core-java15:$version-goes-here'
}
tasks.withType(JavaCompile) {
options.fork = true
options.forkOptions.jvmArgs += '--enable-preview'
options.compilerArgs += '--enable-preview'
}
tasks.withType(Test) {
jvmArgs += "--enable-preview"
}
```

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env bash
#
# Copyright 2019 Jordan Zimmerman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
jenv local 15
javahome
mkdir -p .mvn/
echo "--enable-preview" > .mvn/jvm.config

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env bash
#
# Copyright 2019 Jordan Zimmerman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
jenv local 16-ea
javahome
rm -fr .mvn

20
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<packaging>pom</packaging>
<version>1.18-java15</version>
<version>1.15.jdk14</version>
<modules>
<module>record-builder-core</module>
@@ -18,9 +18,7 @@
<project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<enable-preview />
<jdk-version>16</jdk-version>
<jdk-version>14</jdk-version>
<maven-compiler-plugin-version>3.8.1</maven-compiler-plugin-version>
<maven-source-plugin-version>3.2.0</maven-source-plugin-version>
@@ -73,7 +71,7 @@
<url>https://github.com/randgalt/record-builder</url>
<connection>scm:git:https://github.com/randgalt/record-builder.git</connection>
<developerConnection>scm:git:git@github.com:randgalt/record-builder.git</developerConnection>
<tag>record-builder-1.18-java15</tag>
<tag>record-builder-1.15.jdk14</tag>
</scm>
<issueManagement>
@@ -126,7 +124,7 @@
<configuration>
<release>${jdk-version}</release>
<compilerArgs>
<arg>${enable-preview}</arg>
<arg>--enable-preview</arg>
</compilerArgs>
</configuration>
</plugin>
@@ -280,7 +278,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin-version}</version>
<configuration>
<argLine>${enable-preview}</argLine>
<argLine>--enable-preview</argLine>
</configuration>
</plugin>
</plugins>
@@ -344,13 +342,5 @@
</plugins>
</build>
</profile>
<profile>
<id>java15</id>
<properties>
<jdk-version>15</jdk-version>
<enable-preview>--enable-preview</enable-preview>
</properties>
</profile>
</profiles>
</project>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>1.18-java15</version>
<version>1.15.jdk14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -13,16 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.soabase.recordbuilder.test;
import io.soabase.recordbuilder.core.RecordInterface;
import java.time.Instant;
@RecordInterface
public interface BeanStyle {
String getName();
Instant getDate();
boolean isSomething();
module io.soabase.record.builder.core {
exports io.soabase.recordbuilder.core;
opens io.soabase.recordbuilder.core;
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>1.18-java15</version>
<version>1.15.jdk14</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -48,17 +48,12 @@ class InternalRecordInterfaceProcessor {
private final ProcessingEnvironment processingEnv;
private final String packageName;
private final TypeSpec recordType;
private final List<Component> recordComponents;
private final List<ExecutableElement> recordComponents;
private final TypeElement iface;
private final ClassType recordClassType;
private final List<String> alternateMethods;
private static final String FAKE_METHOD_NAME = "__FAKE__";
private static final Set<String> javaBeanPrefixes = Set.of("get", "is");
private record Component(ExecutableElement element, Optional<String> alternateName){}
InternalRecordInterfaceProcessor(ProcessingEnvironment processingEnv, TypeElement iface, boolean addRecordBuilder, RecordBuilderMetaData metaData, Optional<String> packageNameOpt) {
this.processingEnv = processingEnv;
packageName = packageNameOpt.orElseGet(() -> ElementUtils.getPackageName(iface));
@@ -84,8 +79,6 @@ class InternalRecordInterfaceProcessor {
builder.addSuperinterface(builderClassType.typeName());
}
alternateMethods = buildAlternateMethods(recordComponents);
recordType = builder.build();
}
@@ -133,43 +126,22 @@ class InternalRecordInterfaceProcessor {
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(" {");
alternateMethods.forEach(method -> fixedRecord.append('\n').append(method));
fixedRecord.append('}');
return fixedRecord.toString();
return declaration + argumentList + " " + implementsSection + " {}";
}
private MethodSpec generateArgumentList()
{
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()));
recordComponents.forEach(element -> {
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(element.getReturnType()), element.getSimpleName().toString()).build();
builder.addTypeVariables(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());
}
private List<Component> getRecordComponents(TypeElement iface) {
List<Component> components = new ArrayList<>();
private List<ExecutableElement> getRecordComponents(TypeElement iface) {
List<ExecutableElement> components = new ArrayList<>();
try {
getRecordComponents(iface, components, new HashSet<>(), new HashSet<>());
if (components.isEmpty()) {
@@ -181,15 +153,15 @@ class InternalRecordInterfaceProcessor {
}
return components;
}
private static class IllegalInterface extends RuntimeException
{
public IllegalInterface(String message) {
super(message);
}
}
private void getRecordComponents(TypeElement iface, Collection<Component> components, Set<String> visitedSet, Set<String> usedNames) {
private void getRecordComponents(TypeElement iface, Collection<? super ExecutableElement> components, Set<String> visitedSet, Set<String> usedNames) {
if (!visitedSet.add(iface.getQualifiedName().toString())) {
return;
}
@@ -212,22 +184,10 @@ class InternalRecordInterfaceProcessor {
}
})
.filter(element -> usedNames.add(element.getSimpleName().toString()))
.map(element -> new Component(element, stripBeanPrefix(element.getSimpleName().toString())))
.collect(Collectors.toCollection(() -> components));
iface.getInterfaces().forEach(parentIface -> {
TypeElement parentIfaceElement = (TypeElement) processingEnv.getTypeUtils().asElement(parentIface);
getRecordComponents(parentIfaceElement, components, visitedSet, usedNames);
});
}
private Optional<String> stripBeanPrefix(String name)
{
return javaBeanPrefixes.stream()
.filter(prefix -> name.startsWith(prefix) && (name.length() > prefix.length()))
.findFirst()
.map(prefix -> {
var stripped = name.substring(prefix.length());
return Character.toLowerCase(stripped.charAt(0)) + stripped.substring(1);
});
}
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright 2019 Jordan Zimmerman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module io.soabase.record.builder.processor {
requires com.squareup.javapoet;
requires io.soabase.record.builder.core;
requires java.compiler;
exports io.soabase.recordbuilder.processor;
opens io.soabase.recordbuilder.processor;
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>io.soabase.record-builder</groupId>
<artifactId>record-builder</artifactId>
<version>1.18-java15</version>
<version>1.15.jdk14</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -43,10 +43,6 @@
<annotationProcessors>
<annotationProcessor>io.soabase.recordbuilder.processor.RecordBuilderProcessor</annotationProcessor>
</annotationProcessors>
<release>${jdk-version}</release>
<compilerArgs>
<arg>${enable-preview}</arg>
</compilerArgs>
</configuration>
</plugin>