Advanced Record Utils is an annotation-processor based source code generator that creates a companion *Utils
class (e.g., PersonUtils
for a Person
record) that contains:
-
A "Builder" for the record
-
A "With" interface
-
A "Merger" utility and interface
-
An "XML" utility and interface for serialisation to XML
-
A "Diff" interface and result class
-
An "All" interface that bundles all the other interfaces together
It works recursively through a structure to create a "tree" of records, can import all records/interfaces in a package, or import specific records/interfaces from libraries or previously compiled modules.
Feel free to jump to the quick start if you are in a hurry, or use the navigation options on the left to find a specific documentation item.
1. Reading this document
The following are some details about how this document is written and intended to be read.
1.1. Callouts/Admonitions
Throughout this documentation, you will find several types of admonition blocks used to highlight information. Understanding their meaning will help you get the most out of this guide.
This is a tip. A tip can offer a helpful suggestion, a shortcut, or a best practice that can improve your experience or efficiency when using the library. |
This is a note. A note contains supplementary information that might be relevant but isn’t critical to the main point. Think of it as an "FYI" or a "by the way." |
This is something important. This highlights information that is crucial for the correct functioning of a feature. |
This is something to be cautious of. These generally advise you against a course of action, to inform you of potential challenges, or to act carefully. |
This is a warning. Ignoring a warning is very likely to result in significant problems. Warnings are used to call out things that will break your code, or segments of code that are deliberately bad examples. |
1.2. Collapsible segments
Generated code - and sometimes other items - is contained in collapsible segments like the following:
Knock, knock
Who’s there?
1.3. Code blocks
Throughout this document, you’ll see code blocks that look like this:
public record Person(String name, int age, List<String> favouriteColours) {}
Code blocks in this document do not include import statements.
2. Get started
We recommend the approach of adding the annotations as a dependency and the processor to your Maven Compiler Plugin configuration. This is so that dependencies that the processor has don’t end up in your project, and because the default in Java 23+ is to not automatically scan dependencies for annotation processors (which could be a security risk!)
2.1. Quick Start
Quick Start - Maven
The latest version of Advanced Record Utils is
Merge the following with your existing Maven pom.xml
:
<project>
<properties>
<aru.version>0.6.5</aru.version>
</properties>
<dependencies>
<dependency>
<groupId>io.github.cbarlin</groupId>
<artifactId>advanced-record-utils-annotations</artifactId>
<version>${aru.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.github.cbarlin</groupId>
<artifactId>advanced-record-utils-processor</artifactId>
<version>${aru.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Annotate a record like so:
@AdvancedRecordUtils
public record Person (
String name,
int age,
List<String> favouriteColours
) implements PersonUtils.All {}
Start using the generated builder/wither:
Person personA = PersonUtils.builder()
.name("Rufus")
.age(30)
.addFavouriteColours("blue")
.addFavouriteColours("purple")
.build();
Quick Start - Maven (plus Reproducible Builds and Enforcer)
This is an opinionated setup for reproducible builds and using the enforcer to help maintain that. For a better understanding of reproducible builds, please check out the Reproducible Builds website and the Maven Documentation. For a better understanding of the enforcer for maven, please check out the Enforcer Plugin documentation. |
The intent of this opinionated setup is to:
-
Ensure that outside of running a recent version of Maven, the version of Maven you are using is largely irrelevant
-
Ensure that versions of dependencies are exact versions, and therefore won’t suddenly change if your dependency changes (most Java projects already do this)
Merge the following with your existing Maven pom.xml
:
<project>
<properties>
<!-- Timestamp for output - can be any time really -->
<project.build.outputTimestamp>2025-09-08T10:01:11Z</project.build.outputTimestamp>
<!-- Dependency versions -->
<aru.version>0.6.5</aru.version>
<!-- Plugin versions -->
<maven-artifact-plugin.version>3.6.0</maven-artifact-plugin.version>
<maven-clean-plugin.version>3.5.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<maven-dependency-plugin.version>3.8.1</maven-dependency-plugin.version>
<maven-deploy-plugin.version>3.1.4</maven-deploy-plugin.version>
<maven-enforcer-plugin.version>3.6.1</maven-enforcer-plugin.version>
<maven-install-plugin.version>3.1.4</maven-install-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<maven-javadoc-plugin.version>3.11.3</maven-javadoc-plugin.version>
<maven-project-info-reports-plugin.version>3.9.0</maven-project-info-reports-plugin.version>
<maven-release-plugin.version>3.1.1</maven-release-plugin.version>
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
<maven-site-plugin.version>3.21.0</maven-site-plugin.version>
<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>io.github.cbarlin</groupId>
<artifactId>advanced-record-utils-annotations</artifactId>
<version>${aru.version}</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-artifact-plugin</artifactId>
<version>${maven-artifact-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven-install-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>${maven-site-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${maven-project-info-reports-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${maven-enforcer-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>${maven-release-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.github.cbarlin</groupId>
<artifactId>advanced-record-utils-processor</artifactId>
<version>${aru.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>ban-dynamic-versions</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<banDynamicVersions />
<dependencyConvergence />
<reactorModuleConvergence />
<banDuplicatePomDependencyVersions />
<requirePluginVersions />
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
You can then run the following to ensure your build is reproducible:
mvn clean install && mvn clean verify artifact:compare
2.2. Configuring your build tooling
2.2.1. Maven
-
Add the annotations as a dependency:
pom.xml<dependency> <groupId>io.github.cbarlin</groupId> <artifactId>advanced-record-utils-annotations</artifactId> <version>${aru.version}</version> </dependency>
-
Add the annotation processor to your annotation processor paths:
If you are using some optional integrations with e.g. avaje-jsonb, then ensure that our processor comes first pom.xml<path> <groupId>io.github.cbarlin</groupId> <artifactId>advanced-record-utils-processor</artifactId> <version>${aru.version}</version> </path>
Where does this block go?
If you don’t have a
build
section of your pom, you can use the below.pom.xml<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>io.github.cbarlin</groupId> <artifactId>advanced-record-utils-processor</artifactId> <version>${aru.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
2.2.2. Gradle
Gradle 5.2 added better support for annotation processors, so ensure you are using that version or newer.
-
Add the declarations for the dependency and the annotation processor like so:
Gradle filedependencies { implementation("io.github.cbarlin:advanced-record-utils-annotations:${aru.version}") annotationProcessor("io.github.cbarlin:advanced-record-utils-processor:${aru.version}") testAnnotationProcessor("io.github.cbarlin:advanced-record-utils-processor:${aru.version}") }
2.3. Java modules
If you are using Java’s module system, you will need to add the below to your module-info.java
file:
module org.example {
requires io.github.cbarlin.aru.annotations;
// No explicit 'requires org.jspecify' needed; it's re-exported transitively by the annotations module
}
2.4. Optional dependencies
This library has optional integration with several dependencies. Some of these integrations, such as XML serialisations, are enabled based on the options you chose and are mandatory for those options. Where an option would require you to have a dependency, this will be documented.
Other integrations are based purely on detecting the dependency in your classpath and using them where it makes sense to do so without you being required to do anything. These integrations are:
-
Eclipse Collections - Collections from Eclipse Collections are detected if referenced and handled appropriately (e.g. creating
ImmutableList
if requested). -
Apache Commons Lang 3 - Commons Lang 3, if detected, will be used for
Validate
andStringUtils
classes -
MapStruct - Integration has been made on our end so that MapStruct will use our builders.
2.5. Reproducible Builds
This project is reproducible (and independently verified - thanks hboutemy!). In addition, our GitHub builds ensure that the generated code is also reproducible.
Assuming you have done everything else you need to do for reproducible builds (see the JVM guide) then use of Advanced Record Utils will not stop that (barring one edge case - see "Importing records/interfaces" below).
If your build isn’t reproducible and it should be, please create a new issue on our GitHub. |
If you use MapStruct and want reproducible builds, you’ll need to set mapstruct.suppressGeneratorTimestamp=true and possibly also mapstruct.suppressGeneratorVersionInfoComment=true . See their documentation for details on how to set those settings.
|
2.6. Generated code
Code is usually generated in the target/generated-sources/annotations/
. We aim to generate readable code.
The top-level *Utils
class contains the builder
static method to create a new builder. Depending on your settings, other static methods may be available. The *Utils
class also contains other classes and interfaces, depending on your options.
Classes that begin with an _ (an underscore) are intended to be "internal". While these are still readable, you aren’t intended to directly use them. They are still public though, as other *Utils classes may use them. Static methods that you may wish to use should have a setting to expose them on the top-level *Utils class
|
The top-level *Utils
class also has an @AdvancedRecordUtilsGenerated
annotation on it. This contains metadata about the generated code and serves two purposes:
-
It’s used by the processor if the generated code is used in a library to re-create the "tree" of records (if applicable), as well as containing details about the settings used, generated classes, and version of the processor.
-
If there’s an issue involving generated code, it can be used to confirm things like the version and settings that were "detected" (see cascading and using a package to apply settings for details).
Generated methods and classes should have:
-
relevant Javadoc
-
a
@Generated
annotation that contains information that’s useful for debugging and "hiding" from code coverage tools.
We consider it a bug if a method, interface, or class doesn’t have Javadoc or if it is missing its a @Generated annotation.
|
3. Detailed Usage
3.1. Record usage
3.1.1. Basic usage
Annotate a record like so:
@AdvancedRecordUtils
public record Person(String name, int age, List<String> favouriteColours) {}
When you build the class, the processor will take that and generate readable source code for you, which will then be compiled.
Full generated source code for default settings
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
/**
* An auto-generated utility class to work with {@link Person} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = Person.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils,
internalUtils = {
@AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = PersonUtils.All.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = PersonUtils.Builder.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = PersonUtils.With.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = PersonUtils._MatchingInterface.class)
},
references = {
},
usedTypeConverters = {
}
)
public final class PersonUtils implements GeneratedUtil {
/**
* Create a blank builder of {@link Person}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return Builder.builder();
}
/**
* Creates a new builder of {@link Person} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder builder(final Person original) {
return Builder.builder(original);
}
/**
* A class used for building {@link Person} objects
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
comments = "Related class claim: builder"
)
public static final class Builder {
@Nullable
private int age;
@NonNull
private ArrayList<String> favouriteColours = new ArrayList<String>();
@Nullable
private String name;
/**
* Create a blank builder of {@link Person}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return new Builder();
}
/**
* Creates a new builder of {@link Person} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder builder(final Person original) {
Objects.requireNonNull(original, "Cannot copy a null instance");
// "Copying an existing instance"
return Builder.builder()
.name(original.name())
.age(original.age())
.favouriteColours(original.favouriteColours());
}
/**
* Add a singular {@link String} to the collection for the field {@code favouriteColours}
* <p>
* Supplying a null value will set the current value to null
*
* @param favouriteColours A singular instance to be added to the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
comments = "Related component claim: builderAdd"
)
public Builder addFavouriteColours(@Nullable final String favouriteColours) {
this.favouriteColours.add(favouriteColours);
return this;
}
/**
* Adds all elements of the provided collection to {@code favouriteColours}
*
* @param favouriteColours A collection to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addFavouriteColours(@NonNull final Collection<String> favouriteColours) {
if (Objects.nonNull(favouriteColours)) {
this.favouriteColours.addAll(favouriteColours);
}
return this;
}
/**
* Adds all elements of the provided iterable to {@code favouriteColours}
*
* @param favouriteColours An iterable to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addFavouriteColours(@NonNull final Iterable<String> favouriteColours) {
if (Objects.nonNull(favouriteColours)) {
for (final String __addable : favouriteColours) {
this.addFavouriteColours(__addable);
}
}
return this;
}
/**
* Adds all elements of the provided iterator to {@code favouriteColours}
*
* @param favouriteColours An iterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addFavouriteColours(@NonNull final Iterator<String> favouriteColours) {
if (Objects.nonNull(favouriteColours)) {
while(favouriteColours.hasNext()) {
this.addFavouriteColours(favouriteColours.next());
}
}
return this;
}
/**
* Adds all elements of the provided spliterator to {@code favouriteColours}
*
* @param favouriteColours A spliterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addFavouriteColours(@NonNull final Spliterator<String> favouriteColours) {
if (Objects.nonNull(favouriteColours)) {
favouriteColours.forEachRemaining(this::addFavouriteColours);
}
return this;
}
/**
* Returns the current value of {@code age}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public int age() {
return this.age;
}
/**
* Updates the value of {@code age}
* <p>
* Supplying a null value will set the current value to null
*
* @param age The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder age(@Nullable final int age) {
this.age = age;
return this;
}
/**
* Creates a new instance of {@link Person} from the fields set on this builder
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
comments = "Related class claim: builderBuild"
)
public Person build() {
// "Creating new instance"
return new Person(
this.name(),
this.age(),
this.favouriteColours()
);
}
/**
* Returns the current value of {@code favouriteColours}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
comments = "Related component claim: builderGetter"
)
public List<String> favouriteColours() {
final List<String> ___immutable = this.favouriteColours.stream()
.filter(Objects::nonNull)
.toList();
return ___immutable;
}
/**
* Updates the value of {@code favouriteColours}
* <p>
* Supplying a null value will set the current value to null/empty
*
* @param favouriteColours The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder favouriteColours(@Nullable final List<String> favouriteColours) {
this.favouriteColours.clear();
if (Objects.nonNull(favouriteColours)) {
this.favouriteColours.addAll(favouriteColours);
}
return this;
}
/**
* Returns the current value of {@code name}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public String name() {
return this.name;
}
/**
* Updates the value of {@code name}
* <p>
* Supplying a null value will set the current value to null
*
* @param name The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder name(@Nullable final String name) {
this.name = name;
return this;
}
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
comments = "Related class claim: allIface"
)
public interface All extends With {
}
/**
* An interface that provides the ability to create new instances of a record with modifications
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
comments = "Related class claim: wither"
)
interface With extends _MatchingInterface {
/**
* Creates a builder with the current fields
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
comments = "Related class claim: witherToBuilder"
)
default Builder with() {
return Builder.builder()
.name(this.name())
.age(this.age())
.favouriteColours(this.favouriteColours());
}
/**
* Allows creation of a copy of this instance with some tweaks via a builder
*
* @param subBuilder A function to modify a new copy of the object
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
comments = "Related class claim: witherFluentBuilder"
)
default Person with(@NonNull final Consumer<Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final Builder ___builder = this.with();
subBuilder.accept(___builder);
return ___builder.build();
}
/**
* Return a new instance with a different {@code favouriteColours} field
*
* @param favouriteColours Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
comments = "Related component claim: witherWithAdd"
)
default Person withAddFavouriteColours(final List<String> favouriteColours) {
return this.with()
.addFavouriteColours(favouriteColours)
.build();
}
/**
* Return a new instance with a different {@code age} field
*
* @param age Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default Person withAge(final int age) {
return this.with()
.age(age)
.build();
}
/**
* Return a new instance with a different {@code favouriteColours} field
*
* @param favouriteColours Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default Person withFavouriteColours(final List<String> favouriteColours) {
return this.with()
.favouriteColours(favouriteColours)
.build();
}
/**
* Return a new instance with a different {@code name} field
*
* @param name Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default Person withName(final String name) {
return this.with()
.name(name)
.build();
}
}
@NullUnmarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
comments = "Related component claim: internalMatchingIface"
)
interface _MatchingInterface {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
int age();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
List<String> favouriteColours();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
String name();
}
}
And you’ll have access to a builder that you can use!
Person personA = PersonUtils.builder()
.name("Rufus")
.age(30)
.addFavouriteColours("blue")
.addFavouriteColours("purple")
.build();
Person personB = PersonUtils.builder()
.name("Aerith")
.favouriteColours(List.of("red"))
.build();
You can also "copy" an existing instance:
Person personC = PersonUtils.builder(personA)
.name("Barret")
.build();
3.1.2. Target Constructor
In a record with a non-canonical constructor that is a subset of the canonical one, the processor will generate the "Builder", "With", and "Merger" against that constructor instead of the canonical one.
A “subset constructor” is a non-canonical constructor whose parameter list is a proper subset of the canonical constructor’s parameters. The parameters have the same name and type, but some are omitted. |
Take the following example:
@AdvancedRecordUtils
public record SomeRecord (
String a,
String b,
String c,
String ab
) {
public SomeRecord (String a, String b, String c) {
this(a, b, c, "Mist");
}
}
You are then not able to do:
With this example, the below code block will not compile. |
SomeRecordUtils.builder()
.ab("Inaba"); // Compile error - the method does not exist!
If you have multiple subset constructors to choose from, you can annotate the one you want the processor to use:
@AdvancedRecordUtils
public record SomeRecord (
String a,
String b,
String c,
String ab
) {
@AdvancedRecordUtils.TargetConstructor
public SomeRecord (String a, String b, String c) {
this(a, b, c, "I Mist");
}
public SomeRecord (String a, String b) {
this(a, b, "I tried to catch fog yesterday.", "I Mist");
}
}
If you have multiple subset constructors and do not annotate one, the processor will use the canonical one. You must be explicit. |
3.1.3. Wither
The With and All interfaces are generated by default. See further down for details on these settings.
|
To use with
methods (methods that create a new copy of the record with a field changed), you can implement the *Utils.All
interface that’s generated for you.
@AdvancedRecordUtils
public record Person(String name, int age, List<String> favouriteColours) implements PersonUtils.All { }
Implementing the All interface is preferable to implementing the With interface directly because if you enable more utilities they’ll be immediately available. Using the All interface also generates sealed interfaces.
|
And now you can use with
style methods!
// personA and personB defined earlier
Person aButDifferentAge = personA.withAge(42); // Obtains a copy that has the `age` set to 42
PersonUtils.Builder backToBuilder = personB.with(); // Returns a builder instance that is pre-populated with the current values
Person aViaBuilder = personA.with(builder -> builder.name("Tifa")); // Fluent use of the builder, allowing for multiple changes to be made easily
The with
methods generated broadly align with the same methods generated on the Builder
. For example, if the builder has add
methods enabled on collections (like addFavouriteColours
) then the wither will have an withAdd
method as well (withAddFavouriteColours
), which creates a copy with the element having been added to that field.
Person withMoreColour = personA.withAddFavouriteColours("Cerulean");
3.1.4. Merger
The merger allows you to "merge" two instances of a record together. The merge process involves two instances of a record (personA
and personB
for example) and creates a new instance where the value of each field is determined like so:
“Preferred instance” means the left-hand operand of the merge — for this.merge(other) , this wins when both values are non-null and non-mergeable.
|
You can use Optional (or OptionalInt , OptionalLong , or OptionalDouble ) instead of nullable fields - the merger will treat an empty optional the same as a null value
|
To enable the generation of a Mergeable
interface, enable the merger
setting. To use, implement the All
(or Mergeable
) interface like so:
@AdvancedRecordUtils(merger = true)
public record Person(String name, Integer age, List<String> favouriteColours) implements PersonUtils.All {}
Merging primitives will always take the "preferred" instance as there is no null , so this example has swapped to a boxed value. An OptionalInt would also work for this example.
|
The generated code does not account for the possibility of cyclic references. |
Generated Utils subclasses
public final class PersonUtils implements GeneratedUtil {
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
public static final class _MergerUtils {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
private _MergerUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Merge two instances of {@link Person} together
*
* @param preferred The preferred element
* @param other The non-preferred element
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.MergeMethod"},
comments = "Related class claim: mergeStaticMergeMethod"
)
public static Person merge(@Nullable final Person preferred, @Nullable final Person other) {
if (Objects.isNull(other)) {
// "Short-circuit of merge - other is null"
return preferred;
} else if (Objects.isNull(preferred)) {
// "Short-circuit of merge - preferred is null"
return other;
}
// "Merging two instances together"
return Builder.builder()
.name(_MergerUtils.mergeString(preferred.name(), other.name()))
.age(_MergerUtils.mergeInteger(preferred.age(), other.age()))
.favouriteColours(_MergerUtils.mergeListString(preferred.favouriteColours(), other.favouriteColours()))
.build();
}
/**
* Merger for fields of class {@link Integer}
*
* @param elA The preferred input
* @param elB The non-preferred input
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.Fallback"},
comments = "Related component claim: mergerAddFieldMergerMethod"
)
private static final Integer mergeInteger(@Nullable final Integer elA, @Nullable final Integer elB) {
if (Objects.isNull(elA)) {
return elB;
}
return elA;
}
/**
* Merger for fields of class {@link List<String>}
*
* @param elA The preferred input
* @param elB The non-preferred input
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.CollectionMerge"},
comments = "Related component claim: mergerAddFieldMergerMethod"
)
private static final List<String> mergeListString(@Nullable final List<String> elA, @Nullable final List<String> elB) {
if (Objects.isNull(elA) || elA.isEmpty()) {
return elB;
} else if (Objects.isNull(elB) || elB.isEmpty()) {
return elA;
}
final ArrayList<String> combined = new ArrayList();
combined.addAll(elA);
combined.addAll(elB);
return combined;
}
/**
* Merger for fields of class {@link String}
*
* @param elA The preferred input
* @param elB The non-preferred input
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.CharSequenceField"},
comments = "Related component claim: mergerAddFieldMergerMethod"
)
private static final String mergeString(@Nullable final String elA, @Nullable final String elB) {
return (Objects.nonNull(elA) && Objects.nonNull(elA.toString()) && (!elA.toString().isBlank())) ? elA : elB;
}
}
/**
* Interface for a record that can be merged with itself.
* <p>
* Intended merge process is that, for each field:
* <ol>
* <li>If both of the two instances have a null value, then the result is null</li>
* <li>If one of the two instances has a null value, then take the non-null value</li>
* <li>If both are non-null, and the field is itself can be merged, then merge the values using the other merger</li>
* <li>If both are non-null, and the field is a collection, then union the collections</li>
* <li>Otherwise, keep the value in this instance (instead of the one in the other instance)</li>
* </ol>
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
interface Mergeable extends _MatchingInterface {
/**
* Merge the current instance into the other instance, if it is present
* @return The result of the merge
*
* @param other The element to merge into this one, if it is present
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeOptionalMethod"},
comments = "Related class claim: mergeInterfaceMergeOptionalMethod"
)
default Person merge(@NonNull final Optional<Person> other) {
Objects.requireNonNull(other, "You cannot supply a null Optional parameter");
return other.map(oth -> this.merge(oth)).orElse(this.merge((Person) null));
}
/**
* Merge the current instance into the other instance.
* @return The result of the merge
*
* @param other The element to merge into this one
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeMethod"},
comments = "Related class claim: mergeInterfaceMergeMethod"
)
default Person merge(@Nullable final Person other) {
final var optOther = Optional.ofNullable(other);
return Builder.builder()
.name(_MergerUtils.mergeString(this.name(), optOther.map(Person::name).orElse(null)))
.age(_MergerUtils.mergeInteger(this.age(), optOther.map(Person::age).orElse(null)))
.favouriteColours(_MergerUtils.mergeListString(this.favouriteColours(), optOther.map(Person::favouriteColours).orElse(null)))
.build();
}
}
}
And then you can "merge" instances of records together:
Person missingName = PersonUtils.builder().age(23).favouriteColours(List.of("red", "violet")).build();
Person missingAge = PersonUtils.builder().name("Zack").addFavouriteColour("blue").build();
Person allTogether = missingName.merge(missingAge);
// Which would be the same as doing:
Person isTheSameAs = PersonUtils.builder().age(23).favouriteColours(List.of("red", "violet", "blue")).name("Zack").build();
3.1.5. Differ
The "differ" allows you to compute the difference between two instances of a record. The processor creates a new class that contains details about the difference between the two instances field by field, including:
To enable the generation of a Diffable
interface, enable the diffable
setting. To use, implement the All
(or Diffable
) interface like so:
@AdvancedRecordUtils(diffable = true)
public record Person(String name, int age, List<String> favouriteColours) implements PersonUtils.All {}
Generated Utils subclasses
public final class PersonUtils implements GeneratedUtil {
/**
* The result of a diff between two instances of {@link Person}
* <p>
* Results are pre-computed and stored in memory
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
comments = "Related class claim: differResult"
)
public static final class DiffOfPerson {
/**
* Has any field changed in this diff?
*/
private final boolean __overallChanged;
/**
* Has the field named $S changed?
*/
private final boolean age;
/**
* Diff of the field named $S
*/
private final ListString favouriteColours;
/**
* Has the field named $S changed?
*/
private final boolean name;
/**
* The original field named $S
*/
@Nullable
private final int originalAge;
/**
* The original field named $S
*/
@Nullable
private final List<String> originalFavouriteColours;
/**
* The original field named $S
*/
@Nullable
private final String originalName;
/**
* The (potentially) updated field named $S
*/
@Nullable
private final int updatedAge;
/**
* The (potentially) updated field named $S
*/
@Nullable
private final List<String> updatedFavouriteColours;
/**
* The (potentially) updated field named $S
*/
@Nullable
private final String updatedName;
/**
* Creates a new diff between two instances of {@link Diffable}
* <p>
* Results are pre-computed and stored in memory
*
* @param original The originating element of the diff
* @param updated The (potentially) changed element of the diff
*/
public DiffOfPerson(final Diffable original, final Diffable updated) {
Objects.requireNonNull(original, "The originating element cannot be null");
Objects.requireNonNull(updated, "The (potentially) changed element cannot be null");
// "Computing the diff of " + "name" + " by calling Objects.equals"
this.name = _DifferUtils.hasStringChanged(original.name(), updated.name());
this.originalName = original.name();
this.updatedName = updated.name();
// "Computing the diff of " + "age" + " by calling Objects.equals"
this.age = _DifferUtils.hasintChanged(original.age(), updated.age());
this.originalAge = original.age();
this.updatedAge = updated.age();
// "Computing the diff of " + "favouriteColours" + " by using their generated diff utils"
this.favouriteColours = _DifferUtils.hasListStringChanged(original.favouriteColours(), updated.favouriteColours());
this.originalFavouriteColours = original.favouriteColours();
this.updatedFavouriteColours = updated.favouriteColours();
this.__overallChanged = this.hasNameChanged() || this.hasAgeChanged() || this.hasFavouriteColoursChanged() || false;
}
/**
* Creates a new diff between two instances of {@link Person}
* <p>
* Results are pre-computed and stored in memory
*
* @param original The originating element of the diff
* @param updated The (potentially) changed element of the diff
*/
DiffOfPerson(final Person original, final Person updated) {
Objects.requireNonNull(original, "The originating element cannot be null");
Objects.requireNonNull(updated, "The (potentially) changed element cannot be null");
// "Computing the diff of " + "name" + " by calling Objects.equals"
this.name = _DifferUtils.hasStringChanged(original.name(), updated.name());
this.originalName = original.name();
this.updatedName = updated.name();
// "Computing the diff of " + "age" + " by calling Objects.equals"
this.age = _DifferUtils.hasintChanged(original.age(), updated.age());
this.originalAge = original.age();
this.updatedAge = updated.age();
// "Computing the diff of " + "favouriteColours" + " by using their generated diff utils"
this.favouriteColours = _DifferUtils.hasListStringChanged(original.favouriteColours(), updated.favouriteColours());
this.originalFavouriteColours = original.favouriteColours();
this.updatedFavouriteColours = updated.favouriteColours();
this.__overallChanged = this.hasNameChanged() || this.hasAgeChanged() || this.hasFavouriteColoursChanged() || false;
}
/**
* Obtains the diff of the "favouriteColours" field
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerCollectionDiffResultCreator"},
comments = "Related component claim: differComputation"
)
public final ListString diffFavouriteColours() {
return this.favouriteColours;
}
/**
* Has the "age" field changed between the two versions?
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerStandardFieldChanged"},
comments = "Related component claim: differComputation"
)
public final boolean hasAgeChanged() {
return this.age;
}
/**
* Has any field changed in this diff?
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerStandardHasChangedCheck"},
comments = "Related class claim: differGlobalHasChanged"
)
public final boolean hasChanged() {
return this.__overallChanged;
}
/**
* Has the "favouriteColours" field changed between the two versions?
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerCollectionDiffResultCreator"},
comments = "Related component claim: differComputation"
)
public final boolean hasFavouriteColoursChanged() {
// If there are no added elements and no removed elements, nothing has changed
return !(this.favouriteColours.addedElements().isEmpty() && this.favouriteColours.removedElements().isEmpty());
}
/**
* Has the "name" field changed between the two versions?
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.EagerStandardFieldChanged"},
comments = "Related component claim: differComputation"
)
public final boolean hasNameChanged() {
return this.name;
}
/**
* Return the original value for "age"
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
comments = "Related component claim: differValueHolding"
)
public final int originalAge() {
return this.originalAge;
}
/**
* Return the original value for "favouriteColours"
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
comments = "Related component claim: differValueHolding"
)
public final List<String> originalFavouriteColours() {
return this.originalFavouriteColours;
}
/**
* Return the original value for "name"
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
comments = "Related component claim: differValueHolding"
)
public final String originalName() {
return this.originalName;
}
/**
* Return the non-original value for "age"
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
comments = "Related component claim: differValueHolding"
)
public final int updatedAge() {
return this.updatedAge;
}
/**
* Return the non-original value for "favouriteColours"
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
comments = "Related component claim: differValueHolding"
)
public final List<String> updatedFavouriteColours() {
return this.updatedFavouriteColours;
}
/**
* Return the non-original value for "name"
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.results.ValueHolder"},
comments = "Related component claim: differValueHolding"
)
public final String updatedName() {
return this.updatedName;
}
/**
* A record containing the difference between two collections
*
* @param addedElements The elements added to the collection
* @param elementsInCommon The elements in common between the two instances
* @param removedElements The elements removed from the collection
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.CollectionDiffCreation"},
comments = "Related component claim: differUtilsComputation"
)
public record ListString(
List<String> addedElements,
List<String> elementsInCommon,
List<String> removedElements
) {
}
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
comments = "Related class claim: differUtils"
)
public static final class _DifferUtils {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
comments = "Related class claim: differUtils"
)
private _DifferUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.CollectionDiffCreation"},
comments = "Related component claim: differUtilsComputation"
)
public static final DiffOfPerson.ListString hasListStringChanged(@Nullable final List<String> original,
@Nullable final List<String> updated) {
// Create frequency maps to count occurrences
final Map<String, Long> originalFreq = Objects.requireNonNullElse(original, List.<String>of()).stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
final Map<String, Long> updatedFreq = Objects.requireNonNullElse(updated, List.<String>of()).stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
final List<String> added = new ArrayList<>();
final List<String> removed = new ArrayList<>();
final List<String> common = new ArrayList<>();
// Obtain unique elements
final Set<String> allUniqueElements = new HashSet<>(originalFreq.keySet());
allUniqueElements.addAll(updatedFreq.keySet());
for (final String element : allUniqueElements) {
final long originalCount = originalFreq.getOrDefault(element, 0L);
final long updatedCount = updatedFreq.getOrDefault(element, 0L);
final long commonCount = Math.min(originalCount, updatedCount);
for (long i = 0; i < commonCount; i++) {
common.add(element);
}
if (originalCount > updatedCount) {
final long removedCount = originalCount - updatedCount;
for (long i = 0; i < removedCount; i++) {
removed.add(element);
}
}
if (updatedCount > originalCount) {
final long addedCount = updatedCount - originalCount;
for (long i = 0; i < addedCount; i++) {
added.add(element);
}
}
}
return new DiffOfPerson.ListString(List.copyOf(added), List.copyOf(common), List.copyOf(removed));
}
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.BasicHasChangedCheck"},
comments = "Related component claim: differUtilsComputation"
)
public static final boolean hasStringChanged(@Nullable final String original, @Nullable final String updated) {
return !Objects.equals(original, updated);
}
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.utils.BasicHasChangedCheck"},
comments = "Related component claim: differUtilsComputation"
)
public static final boolean hasintChanged(@Nullable final int original, @Nullable final int updated) {
return !Objects.equals(original, updated);
}
}
/**
* Interface for a record that can compute differences against another instance of the same type
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
comments = "Related class claim: differInterface"
)
interface Diffable extends _MatchingInterface {
/**
* Generate the diff between this instance ("original") and the provided instance ("updated")
* <p>
* Diff is computed as soon as this method is called
* @return The result of the diff
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.diff.DiffFactory"},
comments = "Related class claim: differInterface"
)
default DiffOfPerson diff(@NonNull final Diffable updated) {
return new DiffOfPerson(this, updated);
}
}
}
And then you can compute the difference between two instances:
Person personA = PersonUtils.builder()
.name("Aerith")
.addFavouriteColours("red")
.addFavouriteColours("pink")
.build();
Person personB = PersonUtils.builder()
.name("Tifa")
.favouriteColours(List.of("purple", "teal"))
.build();
PersonUtils.DiffOfPerson diff = personA.diff(personB);
if (diff.hasAgeChanged()) {
// Do something
}
if (diff.diffFavouriteColours().addedElements().isEmpty()) {
// Do something
}
3.1.6. XML
The XML utility allows you to serialise a record to XML. At the moment, only serialisation is supported.
Using this utility requires you to have jakarta.xml.bind-api on the classpath as you will need to use the annotations from that library to control the output. It also requires the JDK provided XML StAX utilities (no dependency), which are part of the java.xml module.
|
To enable the generation of an XML
interface, enable the xmlable
setting and mark your record fields with the required annotations. To use, implement the All
(or XML
) interface like so:
@AdvancedRecordUtils(xmlable = true)
public record Person(
@XmlElement(name = "Name")
String name,
@XmlAttribute(name = "Age")
int age,
@XmlTransient
List<String> favouriteColours
) implements PersonUtils.All {
}
By default, you must annotate each component. If a required annotation is missing, the processor fails compilation, and explains what to add. To exclude a component, use @XmlTransient (or Jackson’s or Avaje Jsonb’s ignore annotation).
|
One of the settings (inferXmlElementName) gives you the option to have the XmlElement annotation "inferred" for you so you don’t have to annotate those fields.
|
The processor will automatically handle Optional
(including OptionalInt
, OptionalDouble
, and OptionalLong
) as well as Collection
items (like List
and Set
), and will treat empty values the same as null
values.
The processor also handles interfaces, assuming it knows the implementing records.
Generated Utils subclasses
public final class PersonUtils implements GeneratedUtil {
@NullUnmarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.XmlStaticClassGenerator"},
comments = "Related class claim: xmlStaticClass"
)
public static final class _XmlUtils {
private static final Optional<String> __DEFAULT_NAMESPACE_URI = Optional.empty();
private static final String __DEFAULT_TAG_NAME = "Person";
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.XmlStaticClassGenerator"},
comments = "Related class claim: xmlStaticClass"
)
private _XmlUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param el The item to write to XML
* @param output The output to write to
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.ToXmlMethodNoTag"},
comments = "Related class claim: xmlStaticClassToXmlNoTag"
)
public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output) throws XMLStreamException {
_XmlUtils.writeToXml(el, output, null, null, null);
}
/**
* Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param el The item to write to XML
* @param output The output to write to
* @param requestedTagName The tag name requested for this element. If null, will use the default tag name
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.ToXmlMethodNoNamespace"},
comments = "Related class claim: xmlStaticClassToXmlNoNS"
)
public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output, final String requestedTagName) throws
XMLStreamException {
_XmlUtils.writeToXml(el, output, requestedTagName, null, null);
}
/**
* Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param el The item to write to XML
* @param output The output to write to
* @param requestedTagName The tag name requested for this element. If null, will use the default tag name
* @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.ToXmlMethodNoDefNamespace"},
comments = "Related class claim: xmlStaticClassToXmlNoDefNS"
)
public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output, final String requestedTagName,
final String requestedNamespace) throws XMLStreamException {
_XmlUtils.writeToXml(el, output, requestedTagName, requestedNamespace, null);
}
/**
* Write out the provided instance to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param el The item to write to XML
* @param output The output to write to
* @param requestedTagName The tag name requested for this element. If null, will use the default tag name
* @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
* @param currentDefaultNamespace The current default namespace
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.WriteStaticToXml"},
comments = "Related class claim: xmlStaticClassToXml"
)
public static void writeToXml(@NonNull final Person el, @NonNull final XMLStreamWriter output, final String requestedTagName,
final String requestedNamespace, final String currentDefaultNamespace) throws XMLStreamException {
Objects.requireNonNull(el, "Cannot supply null element to be written to XML");
Objects.requireNonNull(output, "Cannot supply null output for XML content");
final String tag = _XmlUtils.createTag(requestedTagName);
final Optional<String> namespace = _XmlUtils.createNamespace(requestedNamespace, currentDefaultNamespace);
final Optional<String> defNs = _XmlUtils.__DEFAULT_NAMESPACE_URI.filter(ignored -> Objects.nonNull(requestedNamespace) && (!requestedNamespace.isBlank()));
if (defNs.isPresent()) {
output.setDefaultNamespace(defNs.get());
}
final @Nullable String namespaceToPassDown = defNs.orElse(currentDefaultNamespace);
if (namespace.isPresent()) {
output.writeStartElement(namespace.get(), tag);
} else {
output.writeStartElement(tag);
}
// The write order is as follows:
// 1. Attributes in the propOrder if present
// 2. Remaining attributes in declaration order
// 3. Element or Elements in the propOrder if present
// 4. Element or Elements in declaration order
_XmlUtils.age(output, el.age(), namespaceToPassDown);
_XmlUtils.name(output, el.name(), namespaceToPassDown);
output.writeEndElement();
}
/**
* Add the {@code age} field to the XML output
*
* @param output The output to write to
* @param val The item to write
* @param currentDefaultNamespace The current default namespace
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.attribute.WritePrimitiveInt"},
comments = "Related component claim: xmlWriteField"
)
private static final void age(@NonNull final XMLStreamWriter output, @Nullable final int val, @Nullable final String currentDefaultNamespace)
throws XMLStreamException {
output.writeAttribute("Age", String.valueOf(val));
}
/**
* Determine the final namespace of the current XmlType
*
* @param requestedNamespace The requested namespace when called
* @param currentDefaultNamespace The currently available namespace
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.WriteStaticToXml"},
comments = "Related class claim: xmlStaticClassToXml"
)
private static final Optional<String> createNamespace(@Nullable final String requestedNamespace,
@Nullable final String currentDefaultNamespace) {
return Optional.ofNullable(requestedNamespace)
.filter(Objects::nonNull)
.filter(Predicate.not(String::isBlank))
.filter(x -> !AdvancedRecordUtilsGenerated.XML_DEFAULT_STRING.equals(x))
.or(() -> __DEFAULT_NAMESPACE_URI)
.or(
() -> Optional.ofNullable(currentDefaultNamespace)
.filter(Objects::nonNull)
.filter(Predicate.not(String::isBlank))
.filter(x -> !AdvancedRecordUtilsGenerated.XML_DEFAULT_STRING.equals(x))
);
}
/**
* Determine the tag to write out for the current XML Element
*
* @param incomingTag The incoming tag that was requested
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.WriteStaticToXml"},
comments = "Related class claim: xmlStaticClassToXml"
)
private static final String createTag(final String incomingTag) {
return Optional.ofNullable(incomingTag)
.filter(Objects::nonNull)
.filter(Predicate.not(String::isBlank))
.filter(x -> !AdvancedRecordUtilsGenerated.XML_DEFAULT_STRING.equals(x))
.orElse(__DEFAULT_TAG_NAME);
}
/**
* Add the {@code name} field to the XML output
*
* @param output The output to write to
* @param val The item to write
* @param currentDefaultNamespace The current default namespace
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.utils.elements.noncollections.WriteCharSequence"},
comments = "Related component claim: xmlWriteField"
)
private static final void name(@NonNull final XMLStreamWriter output, @Nullable final CharSequence val,
@Nullable final String currentDefaultNamespace) throws XMLStreamException {
if (Objects.nonNull(val) && Objects.nonNull(val.toString()) && (!val.toString().isBlank()) ) {
output.writeStartElement("Name");
output.writeCharacters(val.toString());
output.writeEndElement();
}
}
}
/**
* Provides the ability for a class to convert itself into XML
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.XmlInterfaceGenerator"},
comments = "Related class claim: xmlInterface"
)
interface XML extends _MatchingInterface {
/**
* Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param output The output to write to
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.ToXmlMethodNoTag"},
comments = "Related class claim: xmlInterfaceToXmlNoTag"
)
default void writeSelfTo(@NonNull final XMLStreamWriter output) throws XMLStreamException {
this.writeSelfTo(output, null, null, null);
}
/**
* Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param output The output to write to
* @param requestedTagName The tag name requested for this element. If null, will use the default tag name
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.ToXmlMethodNoNamespace"},
comments = "Related class claim: xmlInterfaceToXmlNoNs"
)
default void writeSelfTo(@NonNull final XMLStreamWriter output, @Nullable final String requestedTagName) throws XMLStreamException {
this.writeSelfTo(output, requestedTagName, null, null);
}
/**
* Write out the class to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param output The output to write to
* @param requestedTagName The tag name requested for this element. If null, will use the default tag name
* @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.ToXmlMethodNoDefNamespace"},
comments = "Related class claim: xmlInterfaceToXmlNoDefNs"
)
default void writeSelfTo(@NonNull final XMLStreamWriter output, @Nullable final String requestedTagName,
@Nullable final String requestedNamespace) throws XMLStreamException {
this.writeSelfTo(output, requestedTagName, requestedNamespace, null);
}
/**
* Write out the current instance to the requested {@link XMLStreamWriter}, referring to itself with the reqested tag name
* <p>
* This method will close any tags it opens. It is not expected that it will start or end the document.
*
* @param output The output to write to
* @param requestedTagName The tag name requested for this element. If null, will use the default tag name
* @param requestedNamespace The namespace requested for this element. If null, will use the default namespace (NOT the one on the element)
* @param currentDefaultNamespace The current default namespace
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.xml.iface.WriteIfaceToXml"},
comments = "Related class claim: xmlInterfaceToXml"
)
default void writeSelfTo(@NonNull final XMLStreamWriter output, @Nullable final String requestedTagName,
@Nullable final String requestedNamespace, final String currentDefaultNamespace) throws XMLStreamException {
Objects.requireNonNull(output, "Cannot supply null output for XML content");
final String tag = _XmlUtils.createTag(requestedTagName);
final Optional<String> namespace = _XmlUtils.createNamespace(requestedNamespace, currentDefaultNamespace);
final Optional<String> defNs = _XmlUtils.__DEFAULT_NAMESPACE_URI.filter(ignored -> Objects.isNull(requestedNamespace) || (requestedNamespace.isBlank()));
if (defNs.isPresent()) {
output.setDefaultNamespace(defNs.get());
}
final @Nullable String namespaceToPassDown = defNs.orElse(currentDefaultNamespace);
if (namespace.isPresent()) {
output.writeStartElement(namespace.get(), tag);
} else {
output.writeStartElement(tag);
}
// The write order is as follows:
// 1. Attributes in the propOrder if present
// 2. Remaining attributes in declaration order
// 3. Element or Elements in the propOrder if present
// 4. Element or Elements in declaration order
_XmlUtils.age(output, this.age(), namespaceToPassDown);
_XmlUtils.name(output, this.name(), namespaceToPassDown);
output.writeEndElement();
}
}
}
And then you can convert an instance into XML:
// You will need to handle the XMLStreamException that can be thrown in this example
final XMLStreamWriter streamWriter = createXmlWriter();
streamWriter.writeStartDocument();
// personA from earlier example
personA.writeSelfTo(streamWriter);
streamWriter.writeEndDocument();
There are multiple writeSelfTo
methods generated:
The defaults are based on available annotations (such as XmlType
) if they are present, before finally defaulting to the name of the record (in this case, Person
).
The order in which components are written is:
We support the following annotations:
Component annotations
@XmlElement
Indicates that a component of a record should be turned into an XML Element.
There is a setting that allows the processor to pretend that un-annotated components in records are annotated with @XmlElement . See inferXmlElementName for details.
|
public record Person(
@XmlElement
String name
) {}
public interface PersonIface {
@XmlElement
String name();
}
public record Person(
String name
) implements PersonIface {}
We support the following fields:
-
name
- the name of the written element -
namespace
- the namespace of the written element -
defaultValue
- the default value to be written into the element if none is defined -
required
- indicates that the field is required, and throw an exception if it’s not defined
See the Javadoc for XmlElement for more details
Generated code varies depending on the kind of the record component, if it is required or not, and if there is a default specified. Generally speaking, the processor converts the component value into a sensible string value and outputs that. The methods are named after the component. |
@XmlAttribute
Indicates that a component of a record should be turned into an XML Attribute.
public record Person(
@XmlAttribute
String name
) {}
public interface PersonIface {
@XmlAttribute
String name();
}
public record Person(
String name
) implements PersonIface {}
We support the following options:
-
name
- the name of the written attribute -
namespace
- the namespace of the written attribute -
required
- indicates that the field is required, and throw an exception if it’s not defined
See the Javadoc for XmlAttribute for more details
Generated code varies depending on the kind of the record component, and if it is required or not. Generally speaking, the processor converts the component value into a sensible string value and outputs that. The methods are named after the component. |
@XmlTransient
Indicates that a component of a record should be skipped when converting to XML.
You can also use Jackson or Avaje Jsonb annotations ignore annotations - the processor will "infer" that the component is annotated with @XmlTransient
|
public record Person(
@XmlTransient
String name
) {}
public interface PersonIface {
@XmlTransient
String name();
}
public record Person(
String name
) implements PersonIface {}
See the Javadoc for XmlTransient for more details
@XmlElements
Indicates that a component of a record that points at an interface should be turned into different elements based on the implementing type.
public record MyRecord(
@XmlElements({
@XmlElement(type = ImplementationOne.class, name = "ImplementationA"),
@XmlElement(type = ImplementationTwo.class),
})
SomeInterface someInterface
) {}
public interface AnIface {
@XmlElements({
@XmlElement(type = ImplementationOne.class, name = "ImplementationA"),
@XmlElement(type = ImplementationTwo.class),
})
SomeInterface someInterface();
}
public record MyRecord(
SomeInterface someInterface
) implements AnIface {}
We support the following fields:
-
name
- the name of the written element -
namespace
- the namespace of the written element -
type
- the target type of the interface
See the Javadoc for XmlElements for more details
@XmlElements is inferred if there are other ways in which the processor can find implementing records. When doing this, the processor uses the calculated defaults for the name and namespace of the element.
|
@XmlElementWrapper
Indicates that a collection component of a record that is annotated with either @XmlElement
or @XmlElements
(or if they are inferred) should be wrapped in another element when converting to XML.
public record Address(
@XmlElementWrapper(name = "Components")
@XmlElement(name = "Component")
List<String> component
) {}
public interface AddressIface {
@XmlElementWrapper(name = "Components")
@XmlElement(name = "Component")
List<String> component();
}
public record Address(
List<String> component
) implements AddressIface {}
We support the following options:
-
name
- the name of the written wrapper -
namespace
- the namespace of the written wrapper
See the Javadoc for XmlElementWrapper for more details
Interface annotations
@XmlSeeAlso
Lists the implementing records of an interface
@XmlSeeAlso({ImplementationOne.class, ImplementationTwo.class})
public interface MyInterface {}
See the Javadoc for XmlSeeAlso for more details
Record annotations
@XmlType
Defines options for the serialisation of a record.
@XmlType
public record Person(
String name
) {}
We support the following fields:
-
name
- the default name for the record when being serialised -
namespace
- the default namespace for the record when being serialised -
propOrder
- the names of components of the record, in the order in which they should be written
The propOrder must match the name of the component not the name of the element when serialised, as per the documentation of the annotation. In addition, all attributes will be written before all elements (although this will control the order within those categories)
|
See the Javadoc for XmlType for more details
@XmlRootElement
Defines options for the serialisation of the root of a tree of records
@XmlRootElement
public record Person(
String name
) {}
We support the following fields:
-
name
- the default name for the record when being serialised -
namespace
- the default namespace for the record when being serialised and all child elements.
See the Javadoc for XmlRootElement for more details
The settings @XmlType take priority over the @XmlRootElement . However, the @XmlRootElement determines where to set the information from @XmlSchema (if applicable)
|
@XmlAccessorOrder
Defines the order in which the components are written.
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public record Person(
String name,
int age
) {}
package-info.java
) to apply to all records in the package.@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
package org.example;
If applied to both a record and a package, the setting on the record will apply. |
There are only two possible values:
When ordering alphabetically, we sort on the name of the component not the name of the element when serialised, as per the documentation of the annotation. In addition, because we use the XMLStreamWriter , all attributes are written before all elements (although this will control the order within those categories).
|
The processor currently treats UNDEFINED as the order in which the components are defined in the record. However, strictly speaking, the specification doesn’t mandate this and therefore the order may be changed in a future major version.
|
See the Javadoc for XmlAccessorOrder for more details.
Package annotations
@XmlSchema
Defines information about the schemas for all records in the package
@XmlSchema
package org.example;
We support the following fields:
-
namespace
- sets the default namespace for the records in the package -
xmlns
- sets the namespace URI prefixes for the package
The settings from the @XmlSchema annotation are applied when processing records with @XmlRootElement annotations
|
See the Javadoc for XmlSchema for more details
@XmlAccessorOrder
Defines the order in which the components are written.
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public record Person(
String name,
int age
) {}
package-info.java
) to apply to all records in the package.@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
package org.example;
If applied to both a record and a package, the setting on the record will apply. |
There are only two possible values:
When ordering alphabetically, we sort on the name of the component not the name of the element when serialised, as per the documentation of the annotation. In addition, because we use the XMLStreamWriter , all attributes are written before all elements (although this will control the order within those categories).
|
The processor currently treats UNDEFINED as the order in which the components are defined in the record. However, strictly speaking, the specification doesn’t mandate this and therefore the order may be changed in a future major version.
|
See the Javadoc for XmlAccessorOrder for more details.
3.2. Type Converter
By annotating a public static
method with @TypeConverter
, then any time that the return type of that method is a field of a record, the generated builder will also include a method that invokes the TypeConverter
annotated method. This applies globally across all builders.
Because @TypeConverter applies globally, using the same permutation of return type and parameters more than once will cause compilation to fail. This includes @TypeConverter methods discovered via imports (see below for details on imports).
|
A good example of this is for enums. Given the following enum using the common pattern of having a label
that we would like to be able to use:
import io.github.cbarlin.aru.annotations.TypeConverter;
public enum AnEnumInDep {
MONDAY("Monday"),
TUESDAY("Tuesday");
private final String label;
AnEnumInDep(final String label) {
this.label = label;
}
public String label() {
return label;
}
@TypeConverter
public static AnEnumInDep fromLabel(final String label) {
if ("Monday".equals(label)) {
return MONDAY;
}
return TUESDAY;
}
}
Then if we have a record that uses that enum:
@AdvancedRecordUtils
public record MyRecord(
AnEnumInDep andImAnEnum,
AnEnumInDep theEnumAgain
) {}
Then you can implicitly use the fromLabel
method on the builder of MyRecord
like so:
final MyRecord myB = MyRecordUtils.builder()
// This internally calls the method annotated with TypeConverter
.andImAnEnum("Monday")
// You can still pass in the item directly
.theEnumAgain(AnEnumInDep.TUESDAY)
.build();
Generated Builder Methods
/**
* Updates the value of {@code andImAnEnum}
* <p>
* Supplying a null value will set the current value to null
*
* @param andImAnEnum The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder andImAnEnum(@Nullable final AnEnumInDep andImAnEnum) {
this.andImAnEnum = andImAnEnum;
return this;
}
/**
* Updates the value of {@code andImAnEnum}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.UsingTypeConverter"},
comments = "Related component claim: builderUseTypeConverter"
)
public Builder andImAnEnum(final String label) {
return this.andImAnEnum(AnEnumInDep.fromLabel(label));
}
This is not restricted to enums - you can translate any parameters to (almost) any return type and it’ll be picked up.
By default, the processor rejects methods that use extremely common types (e.g. String ) that may "flood" generated builders. If you wish to do this anyway, you can add a permitReturnTypeWhichMayResultInTooManyMethods = true setting on the @TypeConverter
|
3.3. Cascading usage
In the event that you have records that reference other records that are in the same package or sub-package, the processor will automatically "cascade" down the "tree" and work on the referenced record without needing to annotate it. This can be extremely useful if you have a large tree of records, such as one that represents an XML schema.
This is the default behaviour, so as an example take the following records:
public record MyRecordA(
String someStringField,
int someIntField
) {
}
@AdvancedRecordUtils(merger = true)
public record MyRecordB(
MyRecordA otherItem,
MyRecordA woo,
MyRecordB recursionFtw // The processor deals with self-referencing types
) {
}
Then both a MyRecordBUtils
and a MyRecordAUtils
are created, despite the MyRecordA
not having an annotation!
Generated MyRecordAUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
/**
* An auto-generated utility class to work with {@link MyRecordA} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = MyRecordA.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
merger = true
),
internalUtils = {
@AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordAUtils.All.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordAUtils.Builder.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Mergeable", implementation = MyRecordAUtils.Mergeable.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordAUtils.With.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordAUtils._MatchingInterface.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MergerUtils", implementation = MyRecordAUtils._MergerUtils.class)
},
references = {
},
usedTypeConverters = {
}
)
public final class MyRecordAUtils implements GeneratedUtil {
/**
* Create a blank builder of {@link MyRecordA}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return Builder.builder();
}
/**
* Creates a new builder of {@link MyRecordA} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder builder(final MyRecordA original) {
return Builder.builder(original);
}
/**
* A class used for building {@link MyRecordA} objects
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
comments = "Related class claim: builder"
)
public static final class Builder {
@Nullable
private int someIntField;
@Nullable
private String someStringField;
/**
* Create a blank builder of {@link MyRecordA}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return new Builder();
}
/**
* Creates a new builder of {@link MyRecordA} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder builder(final MyRecordA original) {
Objects.requireNonNull(original, "Cannot copy a null instance");
// "Copying an existing instance"
return Builder.builder()
.someStringField(original.someStringField())
.someIntField(original.someIntField());
}
/**
* Creates a new instance of {@link MyRecordA} from the fields set on this builder
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
comments = "Related class claim: builderBuild"
)
public MyRecordA build() {
// "Creating new instance"
return new MyRecordA(
this.someStringField(),
this.someIntField()
);
}
/**
* Returns the current value of {@code someIntField}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public int someIntField() {
return this.someIntField;
}
/**
* Updates the value of {@code someIntField}
* <p>
* Supplying a null value will set the current value to null
*
* @param someIntField The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder someIntField(@Nullable final int someIntField) {
this.someIntField = someIntField;
return this;
}
/**
* Returns the current value of {@code someStringField}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public String someStringField() {
return this.someStringField;
}
/**
* Updates the value of {@code someStringField}
* <p>
* Supplying a null value will set the current value to null
*
* @param someStringField The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder someStringField(@Nullable final String someStringField) {
this.someStringField = someStringField;
return this;
}
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
comments = "Related class claim: allIface"
)
public interface All extends Mergeable, With {
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
public static final class _MergerUtils {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
private _MergerUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Merge two instances of {@link MyRecordA} together
*
* @param preferred The preferred element
* @param other The non-preferred element
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.MergeMethod"},
comments = "Related class claim: mergeStaticMergeMethod"
)
public static MyRecordA merge(@Nullable final MyRecordA preferred, @Nullable final MyRecordA other) {
if (Objects.isNull(other)) {
// "Short-circuit of merge - other is null"
return preferred;
} else if (Objects.isNull(preferred)) {
// "Short-circuit of merge - preferred is null"
return other;
}
// "Merging two instances together"
return Builder.builder()
.someStringField(_MergerUtils.mergeString(preferred.someStringField(), other.someStringField()))
.someIntField(_MergerUtils.mergeint(preferred.someIntField(), other.someIntField()))
.build();
}
/**
* Merger for fields of class {@link String}
*
* @param elA The preferred input
* @param elB The non-preferred input
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.CharSequenceField"},
comments = "Related component claim: mergerAddFieldMergerMethod"
)
private static final String mergeString(@Nullable final String elA, @Nullable final String elB) {
return (Objects.nonNull(elA) && Objects.nonNull(elA.toString()) && (!elA.toString().isBlank())) ? elA : elB;
}
/**
* Merger for a primitive field
*
* @param elA The preferred input
* @param elB The non-preferred input
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.Primitive"},
comments = "Related component claim: mergerAddFieldMergerMethod"
)
private static final int mergeint(final int elA, final int elB) {
// For primitives, we simply return the preferred value
return elA;
}
}
/**
* Interface for a record that can be merged with itself.
* <p>
* Intended merge process is that, for each field:
* <ol>
* <li>If both of the two instances have a null value, then the result is null</li>
* <li>If one of the two instances has a null value, then take the non-null value</li>
* <li>If both are non-null, and the field is itself can be merged, then merge the values using the other merger</li>
* <li>If both are non-null, and the field is a collection, then union the collections</li>
* <li>Otherwise, keep the value in this instance (instead of the one in the other instance)</li>
* </ol>
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
interface Mergeable extends _MatchingInterface {
/**
* Merge the current instance into the other instance, if it is present
* @return The result of the merge
*
* @param other The element to merge into this one, if it is present
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeOptionalMethod"},
comments = "Related class claim: mergeInterfaceMergeOptionalMethod"
)
default MyRecordA merge(@NonNull final Optional<MyRecordA> other) {
Objects.requireNonNull(other, "You cannot supply a null Optional parameter");
return other.map(oth -> this.merge(oth)).orElse(this.merge((MyRecordA) null));
}
/**
* Merge the current instance into the other instance.
* @return The result of the merge
*
* @param other The element to merge into this one
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeMethod"},
comments = "Related class claim: mergeInterfaceMergeMethod"
)
default MyRecordA merge(@Nullable final MyRecordA other) {
final var optOther = Optional.ofNullable(other);
return Builder.builder()
.someStringField(_MergerUtils.mergeString(this.someStringField(), optOther.map(MyRecordA::someStringField).orElse(null)))
.someIntField(_MergerUtils.mergeint(this.someIntField(), optOther.map(MyRecordA::someIntField).orElse(null)))
.build();
}
}
/**
* An interface that provides the ability to create new instances of a record with modifications
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
comments = "Related class claim: wither"
)
interface With extends _MatchingInterface {
/**
* Creates a builder with the current fields
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
comments = "Related class claim: witherToBuilder"
)
default Builder with() {
return Builder.builder()
.someStringField(this.someStringField())
.someIntField(this.someIntField());
}
/**
* Allows creation of a copy of this instance with some tweaks via a builder
*
* @param subBuilder A function to modify a new copy of the object
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
comments = "Related class claim: witherFluentBuilder"
)
default MyRecordA with(@NonNull final Consumer<Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final Builder ___builder = this.with();
subBuilder.accept(___builder);
return ___builder.build();
}
/**
* Return a new instance with a different {@code someIntField} field
*
* @param someIntField Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordA withSomeIntField(final int someIntField) {
return this.with()
.someIntField(someIntField)
.build();
}
/**
* Return a new instance with a different {@code someStringField} field
*
* @param someStringField Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordA withSomeStringField(final String someStringField) {
return this.with()
.someStringField(someStringField)
.build();
}
}
@NullUnmarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
comments = "Related component claim: internalMatchingIface"
)
interface _MatchingInterface {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
int someIntField();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
String someStringField();
}
}
Generated MyRecordBUtils
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
/**
* An auto-generated utility class to work with {@link MyRecordB} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = MyRecordB.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
merger = true
),
internalUtils = {
@AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordBUtils.All.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordBUtils.Builder.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Mergeable", implementation = MyRecordBUtils.Mergeable.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordBUtils.With.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordBUtils._MatchingInterface.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MergerUtils", implementation = MyRecordBUtils._MergerUtils.class)
},
references = {
MyRecordAUtils.class
},
usedTypeConverters = {
}
)
public final class MyRecordBUtils implements GeneratedUtil {
/**
* Create a blank builder of {@link MyRecordB}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return Builder.builder();
}
/**
* Creates a new builder of {@link MyRecordB} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder builder(final MyRecordB original) {
return Builder.builder(original);
}
/**
* A class used for building {@link MyRecordB} objects
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
comments = "Related class claim: builder"
)
public static final class Builder {
@Nullable
private MyRecordA otherItem;
@Nullable
private MyRecordB recursionFtw;
@Nullable
private MyRecordA woo;
/**
* Create a blank builder of {@link MyRecordB}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return new Builder();
}
/**
* Creates a new builder of {@link MyRecordB} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder builder(final MyRecordB original) {
Objects.requireNonNull(original, "Cannot copy a null instance");
// "Copying an existing instance"
return Builder.builder()
.otherItem(original.otherItem())
.woo(original.woo())
.recursionFtw(original.recursionFtw());
}
/**
* Creates a new instance of {@link MyRecordB} from the fields set on this builder
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
comments = "Related class claim: builderBuild"
)
public MyRecordB build() {
// "Creating new instance"
return new MyRecordB(
this.otherItem(),
this.woo(),
this.recursionFtw()
);
}
/**
* Returns the current value of {@code otherItem}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public MyRecordA otherItem() {
return this.otherItem;
}
/**
* Updates the value of {@code otherItem}
* <p>
* Supplying a null value will set the current value to null
*
* @param otherItem The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder otherItem(@Nullable final MyRecordA otherItem) {
this.otherItem = otherItem;
return this;
}
/**
* Uses a supplied builder to replace the value at {@code otherItem}
*
* @param subBuilder Builder that can be used to replace {@code otherItem}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromRecord"},
comments = "Related component claim: builderFluentSetter"
)
public Builder otherItem(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordAUtils.Builder builder = (Objects.isNull(this.otherItem())) ? MyRecordAUtils.Builder.builder() : MyRecordAUtils.Builder.builder(this.otherItem());
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.otherItem(builder.build());
}
/**
* Returns the current value of {@code recursionFtw}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public MyRecordB recursionFtw() {
return this.recursionFtw;
}
/**
* Updates the value of {@code recursionFtw}
* <p>
* Supplying a null value will set the current value to null
*
* @param recursionFtw The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder recursionFtw(@Nullable final MyRecordB recursionFtw) {
this.recursionFtw = recursionFtw;
return this;
}
/**
* Uses a supplied builder to replace the value at {@code recursionFtw}
*
* @param subBuilder Builder that can be used to replace {@code recursionFtw}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromRecord"},
comments = "Related component claim: builderFluentSetter"
)
public Builder recursionFtw(@NonNull final Consumer<Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final Builder builder = (Objects.isNull(this.recursionFtw())) ? Builder.builder() : Builder.builder(this.recursionFtw());
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.recursionFtw(builder.build());
}
/**
* Returns the current value of {@code woo}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public MyRecordA woo() {
return this.woo;
}
/**
* Updates the value of {@code woo}
* <p>
* Supplying a null value will set the current value to null
*
* @param woo The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder woo(@Nullable final MyRecordA woo) {
this.woo = woo;
return this;
}
/**
* Uses a supplied builder to replace the value at {@code woo}
*
* @param subBuilder Builder that can be used to replace {@code woo}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromRecord"},
comments = "Related component claim: builderFluentSetter"
)
public Builder woo(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordAUtils.Builder builder = (Objects.isNull(this.woo())) ? MyRecordAUtils.Builder.builder() : MyRecordAUtils.Builder.builder(this.woo());
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.woo(builder.build());
}
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
comments = "Related class claim: allIface"
)
public interface All extends Mergeable, With {
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
public static final class _MergerUtils {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
private _MergerUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Merge two instances of {@link MyRecordB} together
*
* @param preferred The preferred element
* @param other The non-preferred element
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.MergeMethod"},
comments = "Related class claim: mergeStaticMergeMethod"
)
public static MyRecordB merge(@Nullable final MyRecordB preferred, @Nullable final MyRecordB other) {
if (Objects.isNull(other)) {
// "Short-circuit of merge - other is null"
return preferred;
} else if (Objects.isNull(preferred)) {
// "Short-circuit of merge - preferred is null"
return other;
}
// "Merging two instances together"
return Builder.builder()
.otherItem(_MergerUtils.mergeMyRecordA(preferred.otherItem(), other.otherItem()))
.woo(_MergerUtils.mergeMyRecordA(preferred.woo(), other.woo()))
.recursionFtw(_MergerUtils.mergeMyRecordB(preferred.recursionFtw(), other.recursionFtw()))
.build();
}
/**
* Merger for fields of class {@link MyRecordA}
*
* @param elA The preferred input
* @param elB The non-preferred input
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.OtherProcessed"},
comments = "Related component claim: mergerAddFieldMergerMethod"
)
private static final MyRecordA mergeMyRecordA(@Nullable final MyRecordA elA, @Nullable final MyRecordA elB) {
return MyRecordAUtils._MergerUtils.merge(elA, elB);
}
/**
* Merger for fields of class {@link MyRecordB}
*
* @param elA The preferred input
* @param elB The non-preferred input
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.utils.OtherProcessed"},
comments = "Related component claim: mergerAddFieldMergerMethod"
)
private static final MyRecordB mergeMyRecordB(@Nullable final MyRecordB elA, @Nullable final MyRecordB elB) {
return _MergerUtils.merge(elA, elB);
}
}
/**
* Interface for a record that can be merged with itself.
* <p>
* Intended merge process is that, for each field:
* <ol>
* <li>If both of the two instances have a null value, then the result is null</li>
* <li>If one of the two instances has a null value, then take the non-null value</li>
* <li>If both are non-null, and the field is itself can be merged, then merge the values using the other merger</li>
* <li>If both are non-null, and the field is a collection, then union the collections</li>
* <li>Otherwise, keep the value in this instance (instead of the one in the other instance)</li>
* </ol>
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.MergerFactory"},
comments = "Related class claim: mergerStaticClass"
)
interface Mergeable extends _MatchingInterface {
/**
* Merge the current instance into the other instance, if it is present
* @return The result of the merge
*
* @param other The element to merge into this one, if it is present
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeOptionalMethod"},
comments = "Related class claim: mergeInterfaceMergeOptionalMethod"
)
default MyRecordB merge(@NonNull final Optional<MyRecordB> other) {
Objects.requireNonNull(other, "You cannot supply a null Optional parameter");
return other.map(oth -> this.merge(oth)).orElse(this.merge((MyRecordB) null));
}
/**
* Merge the current instance into the other instance.
* @return The result of the merge
*
* @param other The element to merge into this one
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.merger.iface.MergeMethod"},
comments = "Related class claim: mergeInterfaceMergeMethod"
)
default MyRecordB merge(@Nullable final MyRecordB other) {
final var optOther = Optional.ofNullable(other);
return Builder.builder()
.otherItem(_MergerUtils.mergeMyRecordA(this.otherItem(), optOther.map(MyRecordB::otherItem).orElse(null)))
.woo(_MergerUtils.mergeMyRecordA(this.woo(), optOther.map(MyRecordB::woo).orElse(null)))
.recursionFtw(_MergerUtils.mergeMyRecordB(this.recursionFtw(), optOther.map(MyRecordB::recursionFtw).orElse(null)))
.build();
}
}
/**
* An interface that provides the ability to create new instances of a record with modifications
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
comments = "Related class claim: wither"
)
interface With extends _MatchingInterface {
/**
* Creates a builder with the current fields
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
comments = "Related class claim: witherToBuilder"
)
default Builder with() {
return Builder.builder()
.otherItem(this.otherItem())
.woo(this.woo())
.recursionFtw(this.recursionFtw());
}
/**
* Allows creation of a copy of this instance with some tweaks via a builder
*
* @param subBuilder A function to modify a new copy of the object
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
comments = "Related class claim: witherFluentBuilder"
)
default MyRecordB with(@NonNull final Consumer<Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final Builder ___builder = this.with();
subBuilder.accept(___builder);
return ___builder.build();
}
/**
* Return a new instance with a different {@code otherItem} field
*
* @param otherItem Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordB withOtherItem(final MyRecordA otherItem) {
return this.with()
.otherItem(otherItem)
.build();
}
/**
* Return a new instance with a different {@code otherItem} field, obtaining the value by invoking the builder
*
* @param subBuilder Builder that can be used to replace {@code otherItem}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithFluentMethod"},
comments = "Related component claim: witherWithFluent"
)
default MyRecordB withOtherItem(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
return this.with()
.otherItem(subBuilder)
.build();
}
/**
* Return a new instance with a different {@code recursionFtw} field
*
* @param recursionFtw Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordB withRecursionFtw(final MyRecordB recursionFtw) {
return this.with()
.recursionFtw(recursionFtw)
.build();
}
/**
* Return a new instance with a different {@code recursionFtw} field, obtaining the value by invoking the builder
*
* @param subBuilder Builder that can be used to replace {@code recursionFtw}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithFluentMethod"},
comments = "Related component claim: witherWithFluent"
)
default MyRecordB withRecursionFtw(@NonNull final Consumer<Builder> subBuilder) {
return this.with()
.recursionFtw(subBuilder)
.build();
}
/**
* Return a new instance with a different {@code woo} field
*
* @param woo Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordB withWoo(final MyRecordA woo) {
return this.with()
.woo(woo)
.build();
}
/**
* Return a new instance with a different {@code woo} field, obtaining the value by invoking the builder
*
* @param subBuilder Builder that can be used to replace {@code woo}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithFluentMethod"},
comments = "Related component claim: witherWithFluent"
)
default MyRecordB withWoo(@NonNull final Consumer<MyRecordAUtils.Builder> subBuilder) {
return this.with()
.woo(subBuilder)
.build();
}
}
@NullUnmarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
comments = "Related component claim: internalMatchingIface"
)
interface _MatchingInterface {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
MyRecordA otherItem();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
MyRecordB recursionFtw();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
MyRecordA woo();
}
}
And you can use them both:
final MyRecordA myA = MyRecordAUtils.builder().someStringField("blah").someIntField(42).build();
final MyRecordB firstRound = MyRecordBUtils.builder()
.woo(myA)
// A fluent use of a sub-builder is available by default
.otherItem((MyRecordAUtils.Builder otherBuilder) -> otherBuilder.someIntField(42))
.build();
final MyRecordB secondRound = MyRecordBUtils.builder(firstRound)
// You aren't required to define the type either
.otherItem(otherBuilder -> otherBuilder.someIntField(420))
.build();
Since MyRecordB
had enabled merger
then MyRecordA
will also have a merger, so MyRecordA
instances can be merged too!
Deep or partially adopted hierarchies can introduce edge cases. If only some records in a chain implement the generated interfaces (e.g., via per-record adoption, cross-package imports, or mixed compilation units), certain deep behaviours (like deep diffs or mergers) may stop at non-participating boundaries. Prefer annotating the root(s) so the cascade can generate utilities across the entire in-package tree. |
3.4. Package usage
It is also possible to annotate a package. This has two possible uses:
-
setting some defaults for all records in that package
-
(optionally) automatically applying to all items in that package
Packages will not have their own *Utils classes generated
|
3.4.1. Using a package to set default settings
By placing an annotation on a package element and adding some settings, those settings are applied to all annotated records in that package.
Say we have the following:
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(merger = true)
package org.example;
package org.example;
@AdvancedRecordUtils
public record Person(String name, int age, List<String> favouriteColours) {}
package org.example;
@AdvancedRecordUtils
public record Address(String country, String state) {}
Then that would be the equivalent of doing the following and not having a package-info.java
:
package org.example;
@AdvancedRecordUtils(merger = true)
public record Person(String name, int age, List<String> favouriteColours) {}
package org.example;
@AdvancedRecordUtils(merger = true)
public record Address(String country, String state) {}
If an annotation inside a package has configuration items, then the processor will merge the settings, giving priority to items that are on the record. As an example:
@AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(
buildMethodName = "make",
setTimeNowMethodPrefix = "update"
)
)
package org.example;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
package org.example;
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
builtCollectionType = BuiltCollectionType.JAVA_IMMUTABLE,
setTimeNowMethodPrefix = "ensure"
)
)
public record Person(String name, int age, List<String> favouriteColours) {}
Then that would be the same as doing the following and not having a package-info.java
:
package org.example;
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
buildMethodName = "make",
builtCollectionType = BuiltCollectionType.JAVA_IMMUTABLE,
// This was set on both the package and record - record took priority
setTimeNowMethodPrefix = "ensure"
)
)
public record Person(String name, int age, List<String> favouriteColours) {}
3.4.2. Automatically applying to all items in a package
You can also request that all records within a given package have *Utils
classes generated for them. These records will use the settings specified on the package, similar to the above case of packages specifying default settings. To use, set the applyToAllInPackage to true. You can still add annotations on the individual records including settings for that record, and that record will respect its own settings.
Say we have the following:
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(merger = true, applyToAllInPackage = true)
package org.example;
package org.example;
public record Person(String name, int age, List<String> favouriteColours) {}
package org.example;
public record Address(String country, String state) {}
Then that would be the equivalent of doing the following and not having a package-info.java
:
package org.example;
@AdvancedRecordUtils(merger = true)
public record Person(String name, int age, List<String> favouriteColours) {}
package org.example;
@AdvancedRecordUtils(merger = true)
public record Address(String country, String state) {}
3.5. Meta-annotations
You can also use meta-annotations to define a group of settings to apply to a number of disparate records/interfaces. Like the normal annotation, these settings cascade (see above for details), and like the normal annotation the settings it defines get "merged" with others. The order of preference for the merge is:
-
Settings on the record itself
-
Settings inherited via meta-annotations
-
Settings inherited from the package
-
Settings inherited from the meta-annotation’s package
Meta-annotations only apply within one compilation unit (e.g. Maven module). They do not work across compilation boundaries. |
With the following example:
@AdvancedRecordUtils(
merger = true,
applyToAllInPackage = true,
builderOptions = @AdvancedRecordUtils.BuilderOptions(
buildMethodName = "make"
)
)
package org.example;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(
diffable = true,
builderOptions = @AdvancedRecordUtils.BuilderOptions(
buildMethodName = "be"
)
)
public @interface GenerateDiff {}
package org.example;
public record Person(String name, int age, List<String> favouriteColours) {}
package org.example;
@GenerateDiff
@AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(
buildMethodName = "make"
)
)
public record Address(String country, String state) {}
package org.example;
@GenerateDiff
public record Video(String name, int uploadYear) {}
Then that is the equivalent of doing the following without the package-info.java
or the GenerateDiff
annotation:
@AdvancedRecordUtils(
merger = true,
builderOptions = @AdvancedRecordUtils.BuilderOptions(
buildMethodName = "make"
)
)
public record Person(String name, int age, List<String> favouriteColours) {}
@AdvancedRecordUtils(
merger = true,
diffable = true,
builderOptions = @AdvancedRecordUtils.BuilderOptions(
buildMethodName = "make"
)
)
public record Address(String country, String state) {}
@AdvancedRecordUtils(
merger = true,
diffable = true,
builderOptions = @AdvancedRecordUtils.BuilderOptions(
buildMethodName = "be"
)
)
public record Video(String name, int uploadYear) {}
There is an @AdvancedRecordUtilsFull which acts similarly to this (except that it is known to the processor ahead-of-time), and has most optional settings turned on.
|
3.6. Interfaces
Advanced Record Utils can handle the use of interfaces in a tree of records that use/reference each other.
Take the following structure:
This is to give you a baseline for the following content - doing exactly this will not work as you expect! |
public interface SomeInterface {
}
public record MyRecordFOptionA(
String someStrField
) implements SomeInterface {
}
public record MyRecordFOptionB(
int someIntField
) implements SomeInterface {
}
@AdvancedRecordUtils
public record MyRecordG(
SomeInterface anInterface
) {}
As described above, the processor is not able to work with the SomeInterface
interface, and so will not generate *Utils
classes for the two implementing records, nor will there be builder methods (such as fluent setters) for them. This also applies to any other utilities generated - no mergers or differs - and so those tools will fall back to default methods of working.
There are several approaches that can be used to resolve this.
3.6.1. Seal the interface
If you change the definition of the interface so that it is sealed, like so:
public sealed interface SomeInterface
permits MyRecordFOptionA, MyRecordFOptionB {
}
public record MyRecordFOptionA(
String someStrField
) implements SomeInterface {
}
public record MyRecordFOptionB(
int someIntField
) implements SomeInterface {
}
@AdvancedRecordUtils
public record MyRecordG(
SomeInterface anInterface
) {}
Then the processor will be able to read the permits
clause and the cascading ability will build *Utils
classes for:
-
SomeInterface
-
MyRecordFOptionA
-
MyRecordFOptionB
Generated *Utils
classes for the interface
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import org.jspecify.annotations.NullMarked;
/**
* The Utils class for an interface. Serves mostly to point to concrete implementationsAn auto-generated utility class to work with {@link SomeInterface} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@NullMarked
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = SomeInterface.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
attemptToFindExistingUtils = true
),
internalUtils = {
},
references = {
MyRecordFOptionAUtils.class,
MyRecordFOptionBUtils.class
},
usedTypeConverters = {
}
)
public final class SomeInterfaceUtils implements GeneratedUtil {
/**
* Obtain the builder for {@link MyRecordFOptionA}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
comments = "Related class claim: builderEmpty"
)
public static final MyRecordFOptionAUtils.Builder builderAsMyRecordFOptionA() {
return MyRecordFOptionAUtils.Builder.builder();
}
/**
* Obtain the builder for {@link MyRecordFOptionB}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
comments = "Related class claim: builderEmpty"
)
public static final MyRecordFOptionBUtils.Builder builderAsMyRecordFOptionB() {
return MyRecordFOptionBUtils.Builder.builder();
}
/**
* Obtain the builder for {@link MyRecordFOptionA}
*
* @param existing The instance to copy
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
comments = "Related class claim: builderCopy"
)
public static final MyRecordFOptionAUtils.Builder fromAsMyRecordFOptionA(final MyRecordFOptionA existing) {
return MyRecordFOptionAUtils.Builder.from(existing);
}
/**
* Obtain the builder for {@link MyRecordFOptionB}
*
* @param existing The instance to copy
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
comments = "Related class claim: builderCopy"
)
public static final MyRecordFOptionBUtils.Builder fromAsMyRecordFOptionB(final MyRecordFOptionB existing) {
return MyRecordFOptionBUtils.Builder.from(existing);
}
}
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
/**
* An auto-generated utility class to work with {@link MyRecordG} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = MyRecordG.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
attemptToFindExistingUtils = true
),
internalUtils = {
@AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordGUtils.All.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordGUtils.Builder.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordGUtils.With.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordGUtils._MatchingInterface.class)
},
references = {
MyRecordFOptionAUtils.class,
MyRecordFOptionBUtils.class,
SomeInterfaceUtils.class
},
usedTypeConverters = {
}
)
public final class MyRecordGUtils implements GeneratedUtil {
/**
* Create a blank builder of {@link MyRecordG}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return Builder.builder();
}
/**
* Creates a new builder of {@link MyRecordG} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder from(final MyRecordG original) {
return Builder.from(original);
}
/**
* A class used for building {@link MyRecordG} objects
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
comments = "Related class claim: builder"
)
public static final class Builder {
@Nullable
private SomeInterface anInterface;
@NonNull
private ArrayList<SomeInterface> listOfInterface = new ArrayList<SomeInterface>();
/**
* Create a blank builder of {@link MyRecordG}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return new Builder();
}
/**
* Creates a new builder of {@link MyRecordG} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder from(final MyRecordG original) {
Objects.requireNonNull(original, "Cannot copy a null instance");
// "Copying an existing instance"
return Builder.builder()
.anInterface(original.anInterface())
.listOfInterface(original.listOfInterface());
}
/**
* Add a singular {@link SomeInterface} to the collection for the field {@code listOfInterface}
* <p>
* Supplying a null value will set the current value to null
*
* @param listOfInterface A singular instance to be added to the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
comments = "Related component claim: builderAdd"
)
public Builder addListOfInterface(@Nullable final SomeInterface listOfInterface) {
this.listOfInterface.add(listOfInterface);
return this;
}
/**
* Adds all elements of the provided collection to {@code listOfInterface}
*
* @param listOfInterface A collection to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Collection<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
this.listOfInterface.addAll(listOfInterface);
}
return this;
}
/**
* Adds all elements of the provided iterable to {@code listOfInterface}
*
* @param listOfInterface An iterable to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Iterable<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
for (final SomeInterface __addable : listOfInterface) {
this.addListOfInterface(__addable);
}
}
return this;
}
/**
* Adds all elements of the provided iterator to {@code listOfInterface}
*
* @param listOfInterface An iterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Iterator<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
while(listOfInterface.hasNext()) {
this.addListOfInterface(listOfInterface.next());
}
}
return this;
}
/**
* Adds all elements of the provided spliterator to {@code listOfInterface}
*
* @param listOfInterface A spliterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Spliterator<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
listOfInterface.forEachRemaining(this::addListOfInterface);
}
return this;
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and add to the value of {@link addListOfInterface}
*
* @param subBuilder Builder used to invoke {@code addListOfInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder addListOfInterfaceToMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionAUtils.Builder builder = MyRecordFOptionAUtils.Builder.builder();
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.addListOfInterface(builder.build());
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and add to the value of {@link addListOfInterface}
*
* @param subBuilder Builder used to invoke {@code addListOfInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder addListOfInterfaceToMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionBUtils.Builder builder = MyRecordFOptionBUtils.Builder.builder();
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.addListOfInterface(builder.build());
}
/**
* Returns the current value of {@code anInterface}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public SomeInterface anInterface() {
return this.anInterface;
}
/**
* Updates the value of {@code anInterface}
* <p>
* Supplying a null value will set the current value to null
*
* @param anInterface The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder anInterface(@Nullable final SomeInterface anInterface) {
this.anInterface = anInterface;
return this;
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and replace the value of {@link anInterface}
*
* @param subBuilder Builder that can be used to replace {@code anInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder anInterfaceAsMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionAUtils.Builder builder;
if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionA oth) {
builder = MyRecordFOptionAUtils.Builder.from(oth);
} else {
builder = MyRecordFOptionAUtils.Builder.builder();
}
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.anInterface(builder.build());
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and replace the value of {@link anInterface}
*
* @param subBuilder Builder that can be used to replace {@code anInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder anInterfaceAsMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionBUtils.Builder builder;
if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionB oth) {
builder = MyRecordFOptionBUtils.Builder.from(oth);
} else {
builder = MyRecordFOptionBUtils.Builder.builder();
}
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.anInterface(builder.build());
}
/**
* Creates a new instance of {@link MyRecordG} from the fields set on this builder
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
comments = "Related class claim: builderBuild"
)
public MyRecordG build() {
// "Creating new instance"
return new MyRecordG(
this.anInterface(),
this.listOfInterface()
);
}
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
comments = "Related component claim: builderGetter"
)
public List<SomeInterface> listOfInterface() {
return this.listOfInterface;
}
/**
* Updates the value of {@code listOfInterface}
* <p>
* Supplying a null value will set the current value to null/empty
*
* @param listOfInterface The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder listOfInterface(@Nullable final List<SomeInterface> listOfInterface) {
this.listOfInterface.clear();
if (Objects.nonNull(listOfInterface)) {
this.listOfInterface.addAll(listOfInterface);
}
return this;
}
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
comments = "Related class claim: allIface"
)
public interface All extends With {
}
/**
* An interface that provides the ability to create new instances of a record with modifications
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
comments = "Related class claim: wither"
)
interface With extends _MatchingInterface {
/**
* Creates a builder with the current fields
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
comments = "Related class claim: witherToBuilder"
)
default Builder with() {
return Builder.builder()
.anInterface(this.anInterface())
.listOfInterface(this.listOfInterface());
}
/**
* Allows creation of a copy of this instance with some tweaks via a builder
*
* @param subBuilder A function to modify a new copy of the object
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
comments = "Related class claim: witherFluentBuilder"
)
default MyRecordG with(@NonNull final Consumer<Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final Builder ___builder = this.with();
subBuilder.accept(___builder);
return ___builder.build();
}
/**
* Return a new instance with a different {@code listOfInterface} field
*
* @param listOfInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
comments = "Related component claim: witherWithAdd"
)
default MyRecordG withAddListOfInterface(final List<SomeInterface> listOfInterface) {
return this.with()
.addListOfInterface(listOfInterface)
.build();
}
/**
* Return a new instance with a different {@code anInterface} field
*
* @param anInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordG withAnInterface(final SomeInterface anInterface) {
return this.with()
.anInterface(anInterface)
.build();
}
/**
* Return a new instance with a different {@code listOfInterface} field
*
* @param listOfInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordG withListOfInterface(final List<SomeInterface> listOfInterface) {
return this.with()
.listOfInterface(listOfInterface)
.build();
}
}
@NullUnmarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
comments = "Related component claim: internalMatchingIface"
)
interface _MatchingInterface {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
SomeInterface anInterface();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
List<SomeInterface> listOfInterface();
}
}
3.6.2. Annotate the records
If you annotate the records that implement the interface as well as MyRecordG
like so:
public interface SomeInterface {
}
@AdvancedRecordUtils
public record MyRecordFOptionA(
String someStrField
) implements SomeInterface {
}
@AdvancedRecordUtils
public record MyRecordFOptionB(
int someIntField
) implements SomeInterface {
}
@AdvancedRecordUtils
public record MyRecordG(
SomeInterface anInterface
) {}
Then when the processor reads the interface defined on both of the records it is able to incorporate it into its internal workflows. This means it will build *Utils
classes for:
-
SomeInterface
-
MyRecordFOptionA
-
MyRecordFOptionB
The below collapsible is the same as the one under "Seal the interface" |
Generated *Utils
classes for the interface
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import org.jspecify.annotations.NullMarked;
/**
* The Utils class for an interface. Serves mostly to point to concrete implementationsAn auto-generated utility class to work with {@link SomeInterface} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@NullMarked
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = SomeInterface.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
attemptToFindExistingUtils = true
),
internalUtils = {
},
references = {
MyRecordFOptionAUtils.class,
MyRecordFOptionBUtils.class
},
usedTypeConverters = {
}
)
public final class SomeInterfaceUtils implements GeneratedUtil {
/**
* Obtain the builder for {@link MyRecordFOptionA}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
comments = "Related class claim: builderEmpty"
)
public static final MyRecordFOptionAUtils.Builder builderAsMyRecordFOptionA() {
return MyRecordFOptionAUtils.Builder.builder();
}
/**
* Obtain the builder for {@link MyRecordFOptionB}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
comments = "Related class claim: builderEmpty"
)
public static final MyRecordFOptionBUtils.Builder builderAsMyRecordFOptionB() {
return MyRecordFOptionBUtils.Builder.builder();
}
/**
* Obtain the builder for {@link MyRecordFOptionA}
*
* @param existing The instance to copy
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
comments = "Related class claim: builderCopy"
)
public static final MyRecordFOptionAUtils.Builder fromAsMyRecordFOptionA(final MyRecordFOptionA existing) {
return MyRecordFOptionAUtils.Builder.from(existing);
}
/**
* Obtain the builder for {@link MyRecordFOptionB}
*
* @param existing The instance to copy
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
comments = "Related class claim: builderCopy"
)
public static final MyRecordFOptionBUtils.Builder fromAsMyRecordFOptionB(final MyRecordFOptionB existing) {
return MyRecordFOptionBUtils.Builder.from(existing);
}
}
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
/**
* An auto-generated utility class to work with {@link MyRecordG} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = MyRecordG.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
attemptToFindExistingUtils = true
),
internalUtils = {
@AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordGUtils.All.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordGUtils.Builder.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordGUtils.With.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordGUtils._MatchingInterface.class)
},
references = {
MyRecordFOptionAUtils.class,
MyRecordFOptionBUtils.class,
SomeInterfaceUtils.class
},
usedTypeConverters = {
}
)
public final class MyRecordGUtils implements GeneratedUtil {
/**
* Create a blank builder of {@link MyRecordG}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return Builder.builder();
}
/**
* Creates a new builder of {@link MyRecordG} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder from(final MyRecordG original) {
return Builder.from(original);
}
/**
* A class used for building {@link MyRecordG} objects
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
comments = "Related class claim: builder"
)
public static final class Builder {
@Nullable
private SomeInterface anInterface;
@NonNull
private ArrayList<SomeInterface> listOfInterface = new ArrayList<SomeInterface>();
/**
* Create a blank builder of {@link MyRecordG}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return new Builder();
}
/**
* Creates a new builder of {@link MyRecordG} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder from(final MyRecordG original) {
Objects.requireNonNull(original, "Cannot copy a null instance");
// "Copying an existing instance"
return Builder.builder()
.anInterface(original.anInterface())
.listOfInterface(original.listOfInterface());
}
/**
* Add a singular {@link SomeInterface} to the collection for the field {@code listOfInterface}
* <p>
* Supplying a null value will set the current value to null
*
* @param listOfInterface A singular instance to be added to the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
comments = "Related component claim: builderAdd"
)
public Builder addListOfInterface(@Nullable final SomeInterface listOfInterface) {
this.listOfInterface.add(listOfInterface);
return this;
}
/**
* Adds all elements of the provided collection to {@code listOfInterface}
*
* @param listOfInterface A collection to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Collection<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
this.listOfInterface.addAll(listOfInterface);
}
return this;
}
/**
* Adds all elements of the provided iterable to {@code listOfInterface}
*
* @param listOfInterface An iterable to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Iterable<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
for (final SomeInterface __addable : listOfInterface) {
this.addListOfInterface(__addable);
}
}
return this;
}
/**
* Adds all elements of the provided iterator to {@code listOfInterface}
*
* @param listOfInterface An iterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Iterator<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
while(listOfInterface.hasNext()) {
this.addListOfInterface(listOfInterface.next());
}
}
return this;
}
/**
* Adds all elements of the provided spliterator to {@code listOfInterface}
*
* @param listOfInterface A spliterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Spliterator<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
listOfInterface.forEachRemaining(this::addListOfInterface);
}
return this;
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and add to the value of {@link addListOfInterface}
*
* @param subBuilder Builder used to invoke {@code addListOfInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder addListOfInterfaceToMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionAUtils.Builder builder = MyRecordFOptionAUtils.Builder.builder();
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.addListOfInterface(builder.build());
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and add to the value of {@link addListOfInterface}
*
* @param subBuilder Builder used to invoke {@code addListOfInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder addListOfInterfaceToMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionBUtils.Builder builder = MyRecordFOptionBUtils.Builder.builder();
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.addListOfInterface(builder.build());
}
/**
* Returns the current value of {@code anInterface}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public SomeInterface anInterface() {
return this.anInterface;
}
/**
* Updates the value of {@code anInterface}
* <p>
* Supplying a null value will set the current value to null
*
* @param anInterface The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder anInterface(@Nullable final SomeInterface anInterface) {
this.anInterface = anInterface;
return this;
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and replace the value of {@link anInterface}
*
* @param subBuilder Builder that can be used to replace {@code anInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder anInterfaceAsMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionAUtils.Builder builder;
if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionA oth) {
builder = MyRecordFOptionAUtils.Builder.from(oth);
} else {
builder = MyRecordFOptionAUtils.Builder.builder();
}
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.anInterface(builder.build());
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and replace the value of {@link anInterface}
*
* @param subBuilder Builder that can be used to replace {@code anInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder anInterfaceAsMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionBUtils.Builder builder;
if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionB oth) {
builder = MyRecordFOptionBUtils.Builder.from(oth);
} else {
builder = MyRecordFOptionBUtils.Builder.builder();
}
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.anInterface(builder.build());
}
/**
* Creates a new instance of {@link MyRecordG} from the fields set on this builder
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
comments = "Related class claim: builderBuild"
)
public MyRecordG build() {
// "Creating new instance"
return new MyRecordG(
this.anInterface(),
this.listOfInterface()
);
}
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
comments = "Related component claim: builderGetter"
)
public List<SomeInterface> listOfInterface() {
return this.listOfInterface;
}
/**
* Updates the value of {@code listOfInterface}
* <p>
* Supplying a null value will set the current value to null/empty
*
* @param listOfInterface The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder listOfInterface(@Nullable final List<SomeInterface> listOfInterface) {
this.listOfInterface.clear();
if (Objects.nonNull(listOfInterface)) {
this.listOfInterface.addAll(listOfInterface);
}
return this;
}
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
comments = "Related class claim: allIface"
)
public interface All extends With {
}
/**
* An interface that provides the ability to create new instances of a record with modifications
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
comments = "Related class claim: wither"
)
interface With extends _MatchingInterface {
/**
* Creates a builder with the current fields
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
comments = "Related class claim: witherToBuilder"
)
default Builder with() {
return Builder.builder()
.anInterface(this.anInterface())
.listOfInterface(this.listOfInterface());
}
/**
* Allows creation of a copy of this instance with some tweaks via a builder
*
* @param subBuilder A function to modify a new copy of the object
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
comments = "Related class claim: witherFluentBuilder"
)
default MyRecordG with(@NonNull final Consumer<Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final Builder ___builder = this.with();
subBuilder.accept(___builder);
return ___builder.build();
}
/**
* Return a new instance with a different {@code listOfInterface} field
*
* @param listOfInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
comments = "Related component claim: witherWithAdd"
)
default MyRecordG withAddListOfInterface(final List<SomeInterface> listOfInterface) {
return this.with()
.addListOfInterface(listOfInterface)
.build();
}
/**
* Return a new instance with a different {@code anInterface} field
*
* @param anInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordG withAnInterface(final SomeInterface anInterface) {
return this.with()
.anInterface(anInterface)
.build();
}
/**
* Return a new instance with a different {@code listOfInterface} field
*
* @param listOfInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordG withListOfInterface(final List<SomeInterface> listOfInterface) {
return this.with()
.listOfInterface(listOfInterface)
.build();
}
}
@NullUnmarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
comments = "Related component claim: internalMatchingIface"
)
interface _MatchingInterface {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
SomeInterface anInterface();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
List<SomeInterface> listOfInterface();
}
}
If you only annotate one of the records, then a *Utils class won’t be generated for the omitted record, and both the interface’s and MyRecordG generated *Utils classes will only be able to reference the one record
|
3.6.3. Annotations from serialisation libraries
If you are using various libraries, you can use their annotations to inform the processor as to implementations of an interface.
Jakarta XML Bindings
If you are using Jakarta’s XML Binding annotations, you can use either:
-
@XmlSeeAlso
on the interface, listing the implementing records -
@XmlElements
on a component, listing the implementing records
Jackson
If you are using Jackson, you can use an @JsonSubTypes
on the interface, listing the implementing records.
As of 22nd August 2025 the latest version is 0.6.1, and this version of the processor doesn’t process the annotation on record components. |
Avaje Jsonb
If you are using Avaje’s Jsonb, you can use an @Json.SubTypes
on the interface, listing the implementing records.
3.6.4. Use package-wide application
If you annotate the package that contains the structure like so:
@io.github.cbarlin.aru.annotations.AdvancedRecordUtils(applyToAllInPackage = true)
package org.example;
public interface SomeInterface {
}
public record MyRecordFOptionA(
String someStrField
) implements SomeInterface {
}
public record MyRecordFOptionB(
int someIntField
) implements SomeInterface {
}
public record MyRecordG(
SomeInterface anInterface
) {}
Then when the processor reads the interface defined on both of the records it is able to incorporate it into its internal workflows. This means it will build *Utils
classes for:
-
SomeInterface
-
MyRecordFOptionA
-
MyRecordFOptionB
The below collapsible is the same as the one under "Seal the interface" |
Generated *Utils
classes for the interface
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import org.jspecify.annotations.NullMarked;
/**
* The Utils class for an interface. Serves mostly to point to concrete implementationsAn auto-generated utility class to work with {@link SomeInterface} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@NullMarked
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = SomeInterface.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
attemptToFindExistingUtils = true
),
internalUtils = {
},
references = {
MyRecordFOptionAUtils.class,
MyRecordFOptionBUtils.class
},
usedTypeConverters = {
}
)
public final class SomeInterfaceUtils implements GeneratedUtil {
/**
* Obtain the builder for {@link MyRecordFOptionA}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
comments = "Related class claim: builderEmpty"
)
public static final MyRecordFOptionAUtils.Builder builderAsMyRecordFOptionA() {
return MyRecordFOptionAUtils.Builder.builder();
}
/**
* Obtain the builder for {@link MyRecordFOptionB}
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderEmpty"},
comments = "Related class claim: builderEmpty"
)
public static final MyRecordFOptionBUtils.Builder builderAsMyRecordFOptionB() {
return MyRecordFOptionBUtils.Builder.builder();
}
/**
* Obtain the builder for {@link MyRecordFOptionA}
*
* @param existing The instance to copy
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
comments = "Related class claim: builderCopy"
)
public static final MyRecordFOptionAUtils.Builder fromAsMyRecordFOptionA(final MyRecordFOptionA existing) {
return MyRecordFOptionAUtils.Builder.from(existing);
}
/**
* Obtain the builder for {@link MyRecordFOptionB}
*
* @param existing The instance to copy
*/
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.IfaceAddBuilderCopy"},
comments = "Related class claim: builderCopy"
)
public static final MyRecordFOptionBUtils.Builder fromAsMyRecordFOptionB(final MyRecordFOptionB existing) {
return MyRecordFOptionBUtils.Builder.from(existing);
}
}
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtilsGenerated;
import io.github.cbarlin.aru.annotations.Generated;
import io.github.cbarlin.aru.annotations.GeneratedUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
/**
* An auto-generated utility class to work with {@link MyRecordG} objects
* <p>
* This includes a builder, as well as other generated utilities based on the values provided to the {@link AdvancedRecordUtils} annotation
* <p>
* For more details, see the GitHub page for cbarlin/advanced-record-utils
*/
@Generated("io.github.cbarlin.aru.core.AdvRecUtilsProcessor")
@AdvancedRecordUtilsGenerated(
generatedFor = MyRecordG.class,
version = @AdvancedRecordUtilsGenerated.Version(
major = 0,
minor = 6,
patch = 0
),
settings = @AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(copyCreationName = "from", builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO),
attemptToFindExistingUtils = true
),
internalUtils = {
@AdvancedRecordUtilsGenerated.InternalUtil(type = "All", implementation = MyRecordGUtils.All.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "Builder", implementation = MyRecordGUtils.Builder.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "With", implementation = MyRecordGUtils.With.class),
@AdvancedRecordUtilsGenerated.InternalUtil(type = "_MatchingInterface", implementation = MyRecordGUtils._MatchingInterface.class)
},
references = {
MyRecordFOptionAUtils.class,
MyRecordFOptionBUtils.class,
SomeInterfaceUtils.class
},
usedTypeConverters = {
}
)
public final class MyRecordGUtils implements GeneratedUtil {
/**
* Create a blank builder of {@link MyRecordG}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return Builder.builder();
}
/**
* Creates a new builder of {@link MyRecordG} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder from(final MyRecordG original) {
return Builder.from(original);
}
/**
* A class used for building {@link MyRecordG} objects
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.BuilderClassCreatorVisitor"},
comments = "Related class claim: builder"
)
public static final class Builder {
@Nullable
private SomeInterface anInterface;
@NonNull
private ArrayList<SomeInterface> listOfInterface = new ArrayList<SomeInterface>();
/**
* Create a blank builder of {@link MyRecordG}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddEmptyConstruction"},
comments = "Related class claim: builderEmpty"
)
public static final Builder builder() {
return new Builder();
}
/**
* Creates a new builder of {@link MyRecordG} by copying an existing instance
*
* @param original The existing instance to copy
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddCopyConstruction"},
comments = "Related class claim: builderCopy"
)
public static final Builder from(final MyRecordG original) {
Objects.requireNonNull(original, "Cannot copy a null instance");
// "Copying an existing instance"
return Builder.builder()
.anInterface(original.anInterface())
.listOfInterface(original.listOfInterface());
}
/**
* Add a singular {@link SomeInterface} to the collection for the field {@code listOfInterface}
* <p>
* Supplying a null value will set the current value to null
*
* @param listOfInterface A singular instance to be added to the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAdder"},
comments = "Related component claim: builderAdd"
)
public Builder addListOfInterface(@Nullable final SomeInterface listOfInterface) {
this.listOfInterface.add(listOfInterface);
return this;
}
/**
* Adds all elements of the provided collection to {@code listOfInterface}
*
* @param listOfInterface A collection to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Collection<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
this.listOfInterface.addAll(listOfInterface);
}
return this;
}
/**
* Adds all elements of the provided iterable to {@code listOfInterface}
*
* @param listOfInterface An iterable to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Iterable<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
for (final SomeInterface __addable : listOfInterface) {
this.addListOfInterface(__addable);
}
}
return this;
}
/**
* Adds all elements of the provided iterator to {@code listOfInterface}
*
* @param listOfInterface An iterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Iterator<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
while(listOfInterface.hasNext()) {
this.addListOfInterface(listOfInterface.next());
}
}
return this;
}
/**
* Adds all elements of the provided spliterator to {@code listOfInterface}
*
* @param listOfInterface A spliterator to be merged into the collection
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddAddAll"},
comments = "Related component claim: builderAddAllIterable"
)
public Builder addListOfInterface(@NonNull final Spliterator<SomeInterface> listOfInterface) {
if (Objects.nonNull(listOfInterface)) {
listOfInterface.forEachRemaining(this::addListOfInterface);
}
return this;
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and add to the value of {@link addListOfInterface}
*
* @param subBuilder Builder used to invoke {@code addListOfInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder addListOfInterfaceToMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionAUtils.Builder builder = MyRecordFOptionAUtils.Builder.builder();
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.addListOfInterface(builder.build());
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and add to the value of {@link addListOfInterface}
*
* @param subBuilder Builder used to invoke {@code addListOfInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddFluentAdderFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder addListOfInterfaceToMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionBUtils.Builder builder = MyRecordFOptionBUtils.Builder.builder();
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.addListOfInterface(builder.build());
}
/**
* Returns the current value of {@code anInterface}
*/
@Nullable
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddGetter"},
comments = "Related component claim: builderGetter"
)
public SomeInterface anInterface() {
return this.anInterface;
}
/**
* Updates the value of {@code anInterface}
* <p>
* Supplying a null value will set the current value to null
*
* @param anInterface The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder anInterface(@Nullable final SomeInterface anInterface) {
this.anInterface = anInterface;
return this;
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionA} and replace the value of {@link anInterface}
*
* @param subBuilder Builder that can be used to replace {@code anInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder anInterfaceAsMyRecordFOptionA(@NonNull final Consumer<MyRecordFOptionAUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionAUtils.Builder builder;
if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionA oth) {
builder = MyRecordFOptionAUtils.Builder.from(oth);
} else {
builder = MyRecordFOptionAUtils.Builder.builder();
}
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.anInterface(builder.build());
}
/**
* Uses a supplied builder to build an instance of {@link MyRecordFOptionB} and replace the value of {@link anInterface}
*
* @param subBuilder Builder that can be used to replace {@code anInterface}
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.AddFluentSetterFromInterface"},
comments = "Related component claim: builderFluentSetter"
)
public Builder anInterfaceAsMyRecordFOptionB(@NonNull final Consumer<MyRecordFOptionBUtils.Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final MyRecordFOptionBUtils.Builder builder;
if (Objects.nonNull(this.anInterface()) && this.anInterface() instanceof MyRecordFOptionB oth) {
builder = MyRecordFOptionBUtils.Builder.from(oth);
} else {
builder = MyRecordFOptionBUtils.Builder.builder();
}
// "Passing over to provided consumer"
subBuilder.accept(builder);
return this.anInterface(builder.build());
}
/**
* Creates a new instance of {@link MyRecordG} from the fields set on this builder
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.core.impl.visitors.builder.AddPlainBuild"},
comments = "Related class claim: builderBuild"
)
public MyRecordG build() {
// "Creating new instance"
return new MyRecordG(
this.anInterface(),
this.listOfInterface()
);
}
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddGetter"},
comments = "Related component claim: builderGetter"
)
public List<SomeInterface> listOfInterface() {
return this.listOfInterface;
}
/**
* Updates the value of {@code listOfInterface}
* <p>
* Supplying a null value will set the current value to null/empty
*
* @param listOfInterface The replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.builder.collection.AddSetter"},
comments = "Related component claim: builderPlainSetter"
)
public Builder listOfInterface(@Nullable final List<SomeInterface> listOfInterface) {
this.listOfInterface.clear();
if (Objects.nonNull(listOfInterface)) {
this.listOfInterface.addAll(listOfInterface);
}
return this;
}
}
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.AllInterfaceGenerator"},
comments = "Related class claim: allIface"
)
public interface All extends With {
}
/**
* An interface that provides the ability to create new instances of a record with modifications
*/
@NullMarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WitherPrismInterfaceFactory"},
comments = "Related class claim: wither"
)
interface With extends _MatchingInterface {
/**
* Creates a builder with the current fields
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BackToBuilder"},
comments = "Related class claim: witherToBuilder"
)
default Builder with() {
return Builder.builder()
.anInterface(this.anInterface())
.listOfInterface(this.listOfInterface());
}
/**
* Allows creation of a copy of this instance with some tweaks via a builder
*
* @param subBuilder A function to modify a new copy of the object
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.BuilderFluent"},
comments = "Related class claim: witherFluentBuilder"
)
default MyRecordG with(@NonNull final Consumer<Builder> subBuilder) {
Objects.requireNonNull(subBuilder, "Cannot supply a null function argument");
final Builder ___builder = this.with();
subBuilder.accept(___builder);
return ___builder.build();
}
/**
* Return a new instance with a different {@code listOfInterface} field
*
* @param listOfInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithAdd"},
comments = "Related component claim: witherWithAdd"
)
default MyRecordG withAddListOfInterface(final List<SomeInterface> listOfInterface) {
return this.with()
.addListOfInterface(listOfInterface)
.build();
}
/**
* Return a new instance with a different {@code anInterface} field
*
* @param anInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordG withAnInterface(final SomeInterface anInterface) {
return this.with()
.anInterface(anInterface)
.build();
}
/**
* Return a new instance with a different {@code listOfInterface} field
*
* @param listOfInterface Replacement value
*/
@NonNull
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.wither.WithMethodOnField"},
comments = "Related component claim: witherWith"
)
default MyRecordG withListOfInterface(final List<SomeInterface> listOfInterface) {
return this.with()
.listOfInterface(listOfInterface)
.build();
}
}
@NullUnmarked
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceFactory"},
comments = "Related component claim: internalMatchingIface"
)
interface _MatchingInterface {
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
SomeInterface anInterface();
@Generated(
value = {"io.github.cbarlin.aru.core.AdvRecUtilsProcessor", "io.github.cbarlin.aru.impl.misc.MatchingInterfaceGenerator"},
comments = "Related component claim: internalMatchingIface"
)
List<SomeInterface> listOfInterface();
}
}
3.7. Type Alias
TODO: Write this section
3.8. Importing items
You can also import records and generate *Utils
classes for them, or make use of their existing *Utils
classes if they exist.
3.8.1. Importing records/interfaces to create new *Utils
classes
You can import records and interfaces in any place that you can place an @AdvancedRecordUtils
annotation by using the importTargets
setting. This can be used to create *Utils
classes for records in a library, or in a different package to the one the record is defined in.
This process will not cascade record/interface references. If you wish to import a tree of records, you will have to include all elements of the tree in your import. |
An example that’s on a package-info.java
(but you can use it on a record or an interface if you wish):
@AdvancedRecordUtils(
importTargets = {
ExternalDependencyRecordA.class,
ExternalDependencyRecordB.class,
ExternalDependencyInterface.class
}
)
package org.example;
import io.github.cbarlin.aru.annotations.AdvancedRecordUtils;
This will generate *Utils
classes for each of the requested items, using the settings of the item doing the importing, and places them in the same package as the place they were imported.
Importing the same record or interface twice in two different packages or with two different settings will result in unreproducible and unreliable builds as which location/settings are used isn’t guaranteed. |
3.8.2. Importing existing utils classes
Importing existing *Utils
classes allows the following:
-
Use of a
TypeConverter
defined in that library and used by a*Utils
class -
Fluent methods when using the associated record in your records
When importing trees of records, you only need to import the "root" of each tree. This is because the *Utils classes include a metadata annotation that the processor can read back in to recreate the tree. That includes *Utils classes that it imported too!
|
A *Utils class can be imported multiple times in multiple ways without any negative effects. It can also only be imported in one place and used everywhere - their context, like @TypeConverter , is global.
|
There are a few ways to do this:
-
You can place the
@AdvancedRecordUtils.ImportLibraryUtils
annotation on any of:-
module-info.java
-
Any
package-info.java
-
Any record, interface, or even normal class!
When defined, you list in an array the classes of the
*Utils
implementations to be imported.An example:
module-info.javaimport io.github.cbarlin.aru.annotations.AdvancedRecordUtils; @AdvancedRecordUtils.ImportLibraryUtils({TopLevelRecordUtils.class}) module org.example { requires io.github.cbarlin.aru.annotations; }
-
-
You can use the aforementioned
importTargets
and target the*Utils
class instead of the record/interface. -
On a record that references the record/interface that has a
*Utils
class already generated, you can add theattemptToFindExistingUtils = true
top level setting like so:MyCurrentRecord.java@AdvancedRecordUtils( attemptToFindExistingUtils = true ) public record MyCurrentRecord( MyRecordFromLibrary myRecordFromLibrary ) {}
Note that this method is:
-
Slow; and
-
Will only detect the
*Utils
that is named using either:-
The current settings; or
-
The default settings
-
It’s probably better to use either of the above methods instead
-
4. Configuration options
This section outlines every configuration toggle the processor understands, with defaults, trade‑offs, and cross‑references to usage examples.
4.1. General output options
These are applied on the annotation directly, such as:
@AdvancedRecordUtils(wither = false)
wither
Enables or disables the generation of the With
interface. With-methods provide an immutable way to create a new record instance with a single field changed.
- Type
-
boolean
- Default
-
true
@AdvancedRecordUtils(wither = false)
public record Person(String name, int age) {}
For more information on the Wither, see the earlier section. For more information on the options for the Wither, see the later section.
merger
Enables or disables the generation of the Mergeable
interface (and internal _MergerUtils
). This allows you to merge two instances of a record.
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(merger = true)
public record Person(String name, int age) {}
For more information on the Merger, see the earlier section. For more information on the options for the Merger, see the later section.
xmlable
Enables or disables the generation of the XML
interface (and internal _XmlUtils
). This allows you to convert an instance of your record to XML.
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(xmlable = true)
public record Person(String name, int age) {}
For more information on the XML generation, see the earlier section including the section on supported annotations. For more information on the options for the XML, see the later section.
diffable
Enables or disables the generation of the Diffable
interface and diff result classes (and internal _DiffUtils
). This allows you to compute and inspect differences between two instances of a record.
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(diffable = true)
public record Person(String name, int age) {}
For more information on the Differ, see the earlier section. For more information on the options for the Differ, see the later section.
createAllInterface
Enables or disables the generation of the All
interface. This interface extends all the generated interfaces, so you only need to import the one thing. If you implement it, the processor will seal the interfaces it generates.
- Type
-
boolean
- Default
-
true
All
interface@AdvancedRecordUtils(createAllInterface = false)
public record Person(String name, int age) {}
logGeneration
Defines the type of logging performed by generated code, if any.
- Type
- Default
-
LoggingGeneration.NONE
@AdvancedRecordUtils(logGeneration = LoggingGeneration.SLF4J_GENERATED_UTIL_INTERFACE)
public record Person(String name, int age) {}
Valid values are:
-
NONE
(Default) - no logs are generated (instead, comments are written into the generated code) -
SLF4J_GENERATED_UTIL_INTERFACE
- Use the SLF4J library to write logs as if they came from theGeneratedUtil
interface -
SLF4J_UTILS_CLASS
- Use the SLF4J library to write logs as if they all came from the top-level*Utils
class -
SLF4J_PER_SUBCLASS
- Use the SLF4J library to write logs on a per-subclass basis
Using the SLF4J_GENERATED_UTIL_INTERFACE option can make it easier to control the logging of all generated code by setting the level of the io.github.cbarlin.aru.annotations package.
|
When enabled, the generated logs include a number of attributes in order to make them easily filterable. These are:
-
advancedRecordUtilsProcessor - a constant, "io.github.cbarlin.aru.core.AdvRecUtilsProcessor"
-
originalType - this is the FQN of the record class that we are generating code for
-
intendedType - this is the type that the builder will build for you. It may be the same as "originalType". See
useInterface
option -
advancedRecordUtilsVisitor - an internal reference for AdvancedRecordUtils maintainers. It’s the FQN of the class that generated the method (and thus, the log line)
-
claimedOperationName - an internal reference. It’s the "claim" that was obtained to generate the method
-
claimedOperationType - for the aforementioned claim, was it made at a class or component level
addJsonbImportAnnotation
Enables or disables the generation of an Avaje @Json.Import
annotation on the *Utils
classes.
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(addJsonbImportAnnotation = true)
public record Person(String name, int age) {}
Particularly useful on large trees of records, as the cascading effect means you do not have to annotate each record. The processor also automatically handles interfaces.
When generating for interfaces, the annotation uses Avaje’s default convention for type determination. |
4.2. Scope control options
These are applied on the annotation directly, such as:
@AdvancedRecordUtils(recursiveExcluded = {RecordToMakeExcluded.class})
recursiveExcluded
When cascading, do not include the listed classes.
- Type
-
Class<?>[]
- Default
-
{}
public record Address(String state) {}
@AdvancedRecordUtils(recursiveExcluded = {Address.class})
public record Person(String name, int age, Address address) {}
This does not apply globally, only to the current element being analysed and everything that cascades from it. If there are two annotations that from the originating element can form a path to a record, and you put the exclusion on one of them, then that record will still be detected from the other annotation. |
importTargets
Allows the importing of elements into the current generation scope.
- Type
-
Class<?>[]
- Default
-
{}
@AdvancedRecordUtils(
importTargets = {
ExternalDependencyRecordA.class,
ExternalDependencyRecordB.class,
ExternalDependencyInterface.class
}
)
package org.example;
See the earlier section on importing, particularly the section on Importing records/interfaces to create new *Utils
classes.
applyToAllInPackage
Allows package-wide *Utils
generation.
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(
applyToAllInPackage = true
)
package org.example;
See the earlier section on Automatically applying to all items in a package for more details.
attemptToFindExistingUtils
Request that when the processor is evaluating a record as part of its cascading evaluation, that it first attempts to find an existing *Utils
class.
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(attemptToFindExistingUtils = true)
package org.example;
See the earlier section on importing, particularly the section on Importing existing utils classes.
useInterface
Request that when generating builders, withers, etc that the return type is instead an interface that the record implements, rather than the record’s type itself.
- Type
-
Class<?>
- Default
-
DEFAULT.class
(internal sentinel value for "disabled")
public interface MyInterface {
}
@AdvancedRecordUtils(useInterface = MyInterface.class)
public record MyInterfaceImpl() implements MyInterface {}
4.3. Top-level naming options
These control the name of the top‑level *Utils
class. Apply them via the typeNameOptions = @TypeNameOptions()
sub‑option, like so:
@AdvancedRecordUtils(
typeNameOptions = @TypeNameOptions(
utilsImplementationPrefix = "UtilFor",
utilsImplementationSuffix = ""
)
)
public record Person(String name, int age) {}
There are only two options:
utilsImplementationSuffix
A suffix to be applied to the record/interface name for the top-level class
- Type
-
String
- Default
-
"Utils"
utilsImplementationPrefix
A prefix to be applied to the record/interface name for the top-level class
- Type
-
String
- Default
-
""
(empty string)
For the above settings, instead of generating a PersonUtils
class, this will generate a UtilForPerson
class.
4.4. Builder options
These control the methods available on, and the behaviour of, the generated Builder
subclass. These are applied to the builderOptions = @BuilderOptions()
sub setting like so:
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
// Options go here
)
)
public record Person(String name, int age) {}
The generated With and Mergeable interfaces delegate all usage to the Builder , so all settings here impact those. In addition, the With interface creates some methods based on the methods being generated for the Builder .
|
4.4.1. Naming options
builderName
Set the name of the Builder
subclass.
- Type
-
String
- Default
-
"Builder"
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
builderName = "Factory"
)
)
public record Person(String name, int age) {}
In the above example, the generated "Builder" would be found at PersonUtils.Factory
:
PersonUtils.Factory personFactory = PersonUtils.builder();
The default looks like so:
PersonUtils.Builder personBuilder = PersonUtils.builder();
emptyCreationName
Set the name of the no-arg static method that creates an empty instance of the "Builder" class.
- Type
-
String
- Default
-
"builder"
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
emptyCreationName = "factory"
)
)
public record Person(String name, int age) {}
In the above example, the generated "Builder" can be created by using the method named factory
PersonUtils.Builder personBuilder = PersonUtils.factory();
The default looks like so:
PersonUtils.Builder personBuilder = PersonUtils.builder();
copyCreationName
Set the name of the static method that creates an instance of the "Builder" class by copying from an existing instance.
- Type
-
String
- Default
-
"builder"
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
copyCreationName = "from"
)
)
public record Person(String name, int age) {}
In the above example, the generated "Builder" can copy an existing instance using the from
method like so:
Person personA = // create an instance
PersonUtils.Builder personBuilder = PersonUtils.from(personA);
The default looks like so:
Person personA = // create an instance
PersonUtils.Builder personBuilder = PersonUtils.builder(personA);
buildMethodName
Set the name of the method that the "Builder" has to create a new instance based on its internal state
- Type
-
String
- Default
-
"build"
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
buildMethodName = "make"
)
)
public record Person(String name, int age) {}
In the above example, the generated "Builder" creates a new instance using a method named make
like so:
Person personA = PersonUtils.builder()
.make();
The default looks like so:
Person personA = PersonUtils.builder()
.build();
adderMethodPrefix
/ adderMethodSuffix
Set the prefix and suffix that are joined with the name of a collection component for “add”-er methods on the “Builder”.
- Type
-
String
- Default
-
"add"
(prefix),""
(suffix)
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
adderMethodPrefix = "",
adderMethodSuffix = "Append"
)
)
public record Person(Set<PhoneNumber> phoneNumbers) {}
In the above example, the generated "Builder" contains a method called phoneNumbersAppend
that will add a new PhoneNumber
to the builder
PersonUtils.builder()
.phoneNumbersAppend(new PhoneNumber());
The default looks like so:
PersonUtils.builder()
.addPhoneNumbers(new PhoneNumber());
setTimeNowMethodPrefix
/ setTimeNowMethodSuffix
Set the prefix and suffix that are joined with the name of a time component for setting its value to the current time.
- Type
-
String
- Default
-
"set"
(prefix),"ToNow"
(suffix)
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
setTimeNowMethodPrefix = "update",
setTimeNowMethodSuffix = "ToPresent"
)
)
public record Person(OffsetDateTime dateOfBirth) {}
In the above example, the generated “Builder” contains a method called updateDateOfBirthToPresent
that will set the dateOfBirth
value to the present time:
PersonUtils.builder()
.updateDateOfBirthToPresent();
The default looks like so:
PersonUtils.builder()
.setDateOfBirthToNow();
multiTypeSetterBridge
Set the bridge between the name of a component and the name of an implementing class on the setter, if the component targets an interface and we know the implementing records.
- Type
-
String
- Default
-
"As"
public sealed interface MyInterface permits ImplA, ImplB {}
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
multiTypeSetterBridge = "OfKind"
)
)
public record Person(MyInterface metadata) {}
In the above example, the generated “Builder” contains methods called metadataOfKindImplA
and metadataOfKindImplB
that only allow passing instances of that particular record:
PersonUtils.builder()
.metadataOfKindImplA(new ImplA());
The default looks like so:
PersonUtils.builder()
.metadataAsImplA(new ImplA());
This is particularly useful for using fluent nested builders.
PersonUtils.builder()
.metadataAsImplA(implABuilder -> implABuilder.someField("a"));
multiTypeAdderBridge
Set the bridge between the name of a component and the name of an implementing class on the adder, if the component targets a collection of an interface and we know the implementing records.
- Type
-
String
- Default
-
"To"
public sealed interface MyInterface permits ImplA, ImplB {}
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
multiTypeAdderBridge = "In"
)
)
public record Person(Set<MyInterface> metadata) {}
In the above example, the generated “Builder” contains methods called addImplAInMetadata
and addImplBInMetadata
that only allow passing instances of that particular record:
PersonUtils.builder()
.addImplAInMetadata(new ImplA());
The default looks like so:
PersonUtils.builder()
.addImplAToMetadata(new ImplA());
This is particularly useful for using fluent nested builders.
PersonUtils.builder()
.addImplAToMetadata(implABuilder -> implABuilder.someField("a"));
4.4.2. Generation options
fluent
Determine if fluent versions of methods should be built. These are methods that take a lambda with a single argument, which is the referenced type’s builder.
- Type
-
boolean
- Default
-
"true"
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
fluent = false
)
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}
public record Address(String country) {}
public record PhoneNumber(String number) {}
With the default setting of true
, you can set the "address" field like so:
PersonUtils.builder()
.address(addressBuilder -> addressBuilder.country("Australia"));
You can also add to the "phoneNumbers" field (assuming you haven’t disabled that functionality) like so:
PersonUtils.builder()
.addPhoneNumbers(builder -> builder.number("04 4444 4444"));
The processor also handles self-referencing types easily. Take the following definition:
@AdvancedRecordUtils(merger = true)
public record MyRecordB(
String aField,
MyRecordB recursionFtw // The processor deals with self-referencing types
) {
}
You can then do:
MyRecordBUtils.builder()
.recursionFtw(
b1 -> b1.aField("Nesting 1")
.recursionFtw(
b2 -> b2.aField("Nesting 2")
.recursionFtw(
b3 -> b3.aField("Nesting 3")
)
)
);
createAdderMethods
Should "add"-er type methods be created on the Builder?
- Type
-
boolean
- Default
-
"true"
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
createAdderMethods = false
)
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}
public record Address(String country) {}
public record PhoneNumber(String number) {}
With the default setting of true
, you can add to the "phoneNumbers" field like so:
PersonUtils.builder()
.addPhoneNumbers(new PhoneNumber("04 4444 4444"));
nullReplacesNotNull
Should the builder allow setting a "null" value if there’s already a non-null value?
- Type
-
boolean
- Default
-
"true"
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
nullReplacesNotNull = false
)
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}
public record Address(String country) {}
public record PhoneNumber(String number) {}
If you set this to false
, the below will pass:
PersonUtils.Builder builder = PersonUtils.builder()
.address(new Address("Australia"))
.address(null);
// It's not null on the builder object
Objects.requireNonNull(builder.address());
// And it's not null when built
Objects.requireNonNull(builder.build().address());
builtCollectionType
If the Builder points to a generic collection type (such as List
or Set
), what should the builder construct when building?
If you define a concrete type the Builder will attempt to respect that type (e.g. specifying a LinkedList ).
|
- Type
- Default
-
BuiltCollectionType.JAVA_IMMUTABLE
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
builtCollectionType = AdvancedRecordUtils.BuiltCollectionType.AUTO
)
)
public record Person(Address address, List<PhoneNumber> phoneNumbers, Set<String> names) {}
public record Address(String country) {}
public record PhoneNumber(String number) {}
It has the following values:
-
AUTO
- the collection will be whatever type is passed into the builder, unless you attempt to call an "add"-er method, in which case a sane default will be chosen (ArrayList
,HashSet
, etc) -
JAVA_IMMUTABLE
- the resulting collection will always be immutable
buildNullCollectionToEmpty
If the Builder is going to result in a null
value for a collection, should we instead return an empty collection?
- Type
-
boolean
- Default
-
true
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
buildNullCollectionToEmpty = false
)
)
public record Person(Address address, List<PhoneNumber> phoneNumbers) {}
public record Address(String country) {}
public record PhoneNumber(String number) {}
Instead of modifying the "getter" for the builder, this instead modifies the default value of the internal field when the builder is constructed as well as the "setter" to have different behaviour if you pass in null .
|
setTimeNowMethods
Should the builder contain methods that automatically set OffsetDateTime
, ZonedDateTime
, and LocalDateTime
to the current time?
- Type
-
boolean
- Default
-
true
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
setTimeNowMethods = false
)
)
public record Person(
OffsetDateTime offsetDateTime,
ZonedDateTime zonedDateTime,
LocalDateTime localDateTime
) {
}
setToNullMethods
Should the builder generate methods to explicitly reset a field to a “null-like” state?
The result of the method is:
-
empty for
Optional
components -
empty for collection components when
buildNullCollectionToEmpty
istrue
-
null
for all other component types.- Type
-
boolean
- Default
-
false
When nullReplacesNotNull = false , these setXToNull methods will not be generated at all.
|
@AdvancedRecordUtils(
builderOptions = @AdvancedRecordUtils.BuilderOptions(
setToNullMethods = true
)
)
public record Person(
OffsetDateTime offsetDateTime,
List<String> tags,
String name
) { }
concreteSettersForOptional
If an optional type is used, should there be a setter that takes the concrete type?
- Type
-
boolean
- Default
-
true
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
concreteSettersForOptional = false
)
)
public record Person(
OptionalInt age,
Optional<String> surname
) {
}
If set to true
(default), you are able to do the following:
PersonUtils.builder()
.age(42)
.surname((String) null);
validatedBuilder
Should there be versions of the build methods that validate the built item?
- Type
- Default
-
ValidationApi.NONE
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
validatedBuilder = ValidationApi.AVAJE
)
)
public record Person(
@NonNull @NotBlank @Size(max = 50)
String name
) {
}
There are three possible values, with the default NONE
not generating any additional methods.
Use of any value other than NONE will need the necessary dependencies.
|
The value of JAKARTA_PLAIN
generates methods that allow you to use Jakarta’s validation API directly in order to validate the built object like so:
Validator validator = obtainValidator(); // You will need a validator
PersonUtils.builder()
.build(
validator,
(Set<Set<ConstraintViolation<Person>>> violations) -> {
// This method is only called if there's at least one violation
}
);
// Alternatively, you can do:
PersonUtils.builder()
.build(
validator,
(Set<Set<ConstraintViolation<Person>>> violations, Person personThatWasBuilt) -> {
// This method is always called
}
);
The value of AVAJE
generates methods that allows you to use the Avaje Validator. You can use it like so:
Validator validator = obtainValidator(); // You will need a validator
PersonUtils.Builder builder = PersonUtils.builder();
try {
// These use your validator:
builder.build(validator);
builder.build(validator, GroupA.class, GroupB.class);
// These use the default validator
builder.buildAndValidate();
builder.buildAndValidate(GroupA.class, GroupB.class);
// And you can still use the original builder:
builder.build();
} catch (ConstraintViolationException e) {
// Do something with the violations
}
mapStructValidatesWithAvaje
When using both the Avaje validator (see validation settings) and MapStruct, should we request that MapStruct calls the method that uses the default validator?
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(
builderOptions = @BuilderOptions(
validatedBuilder = ValidationApi.AVAJE,
mapStructValidatesWithAvaje = true
)
)
@Valid
public record Person(
@NonNull @NotBlank @Size(max = 50)
String name
) {
}
When set to true
and the "buildAndValidate" method has been created then the MapStruct integration will call that method. If false, or if there’s no "buildAndValidate" method, then it will use the default "build" method.
4.5. Wither options
These control the naming of the generated With
interface, and its methods. Apply them via the witherOptions = @WitherOptions()
sub‑setting:
@AdvancedRecordUtils(
witherOptions = @WitherOptions(
// Options go here
)
)
public record Person(String name, int age) {}
witherName
Sets the name of the With
interface.
- Type
-
String
- Default
-
"With"
@AdvancedRecordUtils(
witherOptions = @WitherOptions(
witherName = "Having"
)
)
public record Person(String name, int age)
implements PersonUtils.Having {}
convertToBuilder
Sets the name of the method that converts an instance of the record back into the builder.
- Type
-
String
- Default
-
"with"
@AdvancedRecordUtils(
witherOptions = @WitherOptions(
convertToBuilder = "toBuilder"
)
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, the generated method is callable like so
PersonUtils.Builder personFactory = person.toBuilder();
The default looks like so:
PersonUtils.Builder personBuilder = person.with();
withMethodPrefix
/ withMethodSuffix
Set the prefix and suffix for the with
methods.
These are combined with the name of the method on the `Builder , which is based on the component name. This is because the `With internally calls the Builder to perform the requested change.
|
Setting both of these to blank strings will result in conflicts with the accessor methods on the record. |
- Type
-
String
- Default
-
"with"
(prefix),""
(suffix)
@AdvancedRecordUtils(
witherOptions = @WitherOptions(
withMethodPrefix = "",
withMethodSuffix = "ButDifferent"
)
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, the generated method is callable like so
Person personC = personA.nameButDifferent("Cloud");
The default looks like so:
Person personC = personA.withName("Cissnei");
4.6. Merger options
These control the names and behaviour for generating the Mergeable
interface. These are applied to the mergerOptions = @MergerOptions()
sub-setting like so:
@AdvancedRecordUtils(
mergerOptions = @MergerOptions(
// Options go here
),
merger = true
)
public record Person(String name, int age) {}
mergerName
Sets the name of the Mergeable
interface.
- Type
-
String
- Default
-
"Mergeable"
@AdvancedRecordUtils(
mergerOptions = @MergerOptions(
mergerName = "Combinable"
),
merger = true
)
public record Person(String name, int age)
implements PersonUtils.Combinable {}
mergerMethodName
Sets the name of the method that combines two instances of the record together.
- Type
-
String
- Default
-
"merge"
@AdvancedRecordUtils(
mergerOptions = @MergerOptions(
mergerMethodName = "combine"
),
merger = true
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, the generated method is callable like so:
Person personC = personA.combine(personB);
The default looks like so:
Person personC = personA.merge(personB);
staticMethodsAddedToUtils
Should a static version of the merge
method be placed on the top-level *Utils
class?
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(
mergerOptions = @MergerOptions(
staticMethodsAddedToUtils = true
),
merger = true
)
public record Person(String name, int age) {}
You can then use it like so:
Person personC = PersonUtils.merge(personA, personB);
4.7. Diffable Options
These control the names and behaviour for generating the Diffable
interface and related classes. These are applied to the diffOptions = @DiffOptions()
sub-setting like so:
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
// Options go here
),
diffable = true
)
public record Person(String name, int age) {}
differName
Sets the name of the Diffable
interface.
- Type
-
String
- Default
-
"Diffable"
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
differName = "DeltaGeneratable"
),
diffable = true
)
public record Person(String name, int age)
implements PersonUtils.DeltaGeneratable {}
differMethodName
Sets the name of the method that computes the diff.
- Type
-
String
- Default
-
"diff"
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
differMethodName = "delta"
),
diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, the generated method is callable like so:
DiffOfPerson diff = personA.delta(personA);
The default looks like so:
DiffOfPerson diff = personA.diff(personA);
diffResultPrefix
/ diffResultSuffix
Sets the prefix and suffix applied to the record that contains the results of the diff computation.
- Type
-
String
- Default
-
"DiffOf"
(prefix),""
(suffix)
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
diffResultPrefix = "",
diffResultSuffix = "Changes"
),
diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, the result of the diff is stored in an PersonChanges
class like so:
PersonChanges diff = personA.diff(personA);
The default looks like so:
DiffOfPerson diff = personA.diff(personA);
originatingElementName
/ comparedToElementName
Sets the name of the first and second elements being compared.
- Type
-
String
- Default
-
"original"
(originating),"updated"
(comparedTo)
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
originatingElementName = "parent",
comparedToElementName = "child"
),
diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, obtaining the input items or their fields works like so:
DiffOfPerson diff = personA.diff(personA);
String parentName = diff.parentName();
String childName = diff.childName();
The default looks like so:
DiffOfPerson diff = personA.diff(personA);
String originalName = diff.originalName();
String updatedName = diff.updatedName();
changedCheckPrefix
/ changedCheckSuffix
Sets the prefix and suffix applied to the component name that reports if that component has changed.
- Type
-
String
- Default
-
"has"
(prefix),"Changed"
(suffix)
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
changedCheckPrefix = "is",
changedCheckSuffix = "Different"
),
diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, to see if a field has changed you can do this:
DiffOfPerson diff = personA.diff(personA);
if (diff.isAgeDifferent()) {
// Do something
}
The default looks like so:
DiffOfPerson diff = personA.diff(personA);
if (diff.hasAgeChanged()) {
// Do something
}
changedAnyMethodName
Sets the name of the method that reports if any component is different between the two instances.
- Type
-
String
- Default
-
"hasChanged"
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
changedAnyMethodName = "isDifferent"
),
diffable = true
)
public record Person(String name, int age)
implements PersonUtils.All {}
In the above example, the generated method is callable like so
DiffOfPerson diff = personA.diff(personA);
if (diff.isDifferent()) {
// Do something
}
The default looks like so:
DiffOfPerson diff = personA.diff(personA);
if (diff.hasChanged()) {
// Do something
}
staticMethodsAddedToUtils
Should a static version of the diff
method be placed on the top-level *Utils
class?
- Type
-
boolean
- Default
-
false
@AdvancedRecordUtils(
diffOptions = @DiffOptions(
staticMethodsAddedToUtils = true
),
diffable = true
)
public record Person(String name, int age) {}
You can then use it like so:
DiffOfPerson diff = PersonUtils.diff(personA, personB);
4.8. XML Options
These control the names and behaviour for generating the XML
interface and related classes. These are applied to the xmlOptions = @XmlOptions()
sub-setting like so:
@AdvancedRecordUtils(
xmlOptions = @XmlOptions(
// Options go here
),
xmlable = true
)
public record Person(
@XmlElement(name = "Name")
String name,
@XmlElement(name = "Age")
int age
) {}
xmlName
Sets the name of the XML
interface.
- Type
-
String
- Default
-
"XML"
@AdvancedRecordUtils(
xmlOptions = @XmlOptions(
xmlName = "SerialXml"
),
xmlable = true
)
public record Person(
@XmlElement(name = "Name")
String name,
@XmlElement(name = "Age")
int age
)
implements PersonUtils.SerialXml {}
continueAddingToXmlMethodName
Sets the name of the method on the interface that writes the object to an XMLStreamWriter
.
- Type
-
String
- Default
-
"writeSelfTo"
@AdvancedRecordUtils(
xmlOptions = @XmlOptions(
continueAddingToXmlMethodName = "out"
),
xmlable = true
)
public record Person(
@XmlElement(name = "Name")
String name,
@XmlElement(name = "Age")
int age
)
implements PersonUtils.All {}
In the above example, the generated method is callable like so:
// You will need to handle the XMLStreamException that can be thrown in this example
personA.out(xmlStreamWriter);
The default looks like so:
// You will need to handle the XMLStreamException that can be thrown in this example
personA.writeSelfTo(xmlStreamWriter);
inferXmlElementName
Determines if @XmlElement
annotations should be inferred and, if so, how the inference is done.
- Type
- Default
-
NameGeneration.NONE
@AdvancedRecordUtils(
xmlOptions = @XmlOptions(
inferXmlElementName = .AdvancedRecordUtils.NameGeneration.UPPER_FIRST_LETTER
),
xmlable = true
)
public record Person(
String name,
int age
)
implements PersonUtils.All {}
The three values are:
-
NONE
- do not infer an@XmlElement
annotation -
MATCH
- if there are no other@Xml*
annotations, infer an@XmlElement
with aname
value that matches the component name -
UPPER_FIRST_LETTER
- if there are no other@Xml*
annotations, infer an@XmlElement
with aname
value that matches the component name with the first letter capitalised.
The default behaviour of NONE requires you to specify an annotation. If you do not specify one, the processor will fail compilation and tell you to specify one. If you wish to exclude a component, use @XmlTransient .
|
The above example is the equivalent of doing:
@AdvancedRecordUtils(
xmlable = true
)
public record Person(
@XmlElement(name = "Name")
String name,
@XmlElement(name = "Age")
int age
)
implements PersonUtils.All {}
The final value of MATCH
would be the equivalent of doing:
@AdvancedRecordUtils(
xmlable = true
)
public record Person(
@XmlElement(name = "name")
String name,
@XmlElement(name = "age")
int age
)
implements PersonUtils.All {}
Both of these settings "skip" anything that is annotated already with an @Xml*
annotation (@XmlTransient
, @XmlAttribute
, or @XmlElements
).