RecordBuilder Documentation

repository·master·Indexed 19 days ago

https://github.com/randgalt/record-builder

An annotation processor for Java that enhances Java Records by generating companion builder classes and "wither" methods for copy-on-write functionality. It provides capabilities to generate records from interface templates, create builders for third-party classes via @RecordBuilder.Include, and implement Deconstructors to convert standard Java classes into records for use in pattern matching.

Tokens
5.6K
Snippets
20
Records
27
Agent score
76%

What's inside RecordBuilder

  1. How staged builders work

    master

    Staged builders enforce a specific order of construction. When using BuilderMode.STAGED or BuilderMode.STANDARD_AND_STAGED, each record component must be built in order and specified via individual staged builders.

    Staged Required Only Mode Using BuilderMode.STAGED_REQUIRED_ONLY or BuilderMode.STANDARD_AND_STAGED_REQUIRED_ONLY creates a variant that only stages required components. The following are added to the final stage instead of being staged:

    • Optional components (if addConcreteSettersForOptional is enabled).
    • Collections (if matching collection options are enabled).
    • Components with initializers (if skipStagingForInitializedComponents is enabled).
    @RecordBuilder.Options(builderMode = BuilderMode.STAGED)
    public record MyRecord(String name, int age) {}
  2. What are Deconstructors in RecordBuilder

    master

    In RecordBuilder, a Deconstructor is a mechanism used to decompose a standard Java class instance into a record (Data Access Object). This allows you to use standard classes in pattern matching (e.g., in switch statements) by first converting them into a generated record.

    RecordBuilder supports two ways to define a deconstructor:

    1. Deconstructor Method: A non-static, void method in a class or interface that uses Consumer types to expose state.
    2. Deconstructor Accessor: A class or interface where specific accessor methods or fields are marked to define the record's components.

    When a deconstructor is identified, RecordBuilder generates a record where the components match the deconstructor's parameters or accessor return types. The generated record includes a static from method (configurable) that accepts the original class instance and returns the new record.

  3. Generate a Java record from an Interface template

    master

    Use the @RecordInterface annotation on an interface to automatically generate a corresponding Java record. The generated record will implement the interface and, by default, also implement the generated With interface for wither support.

    Interface constraints:

    • Non-static methods must return a value and cannot have arguments or type parameters.
    • Methods with default implementations are used in generation unless annotated with @IgnoreDefaultMethod.
    • If the interface follows JavaBean conventions (e.g., getThing()), the "get" or "is" prefixes are stripped.

    Options:

    • To prevent a builder from being generated for the new record, use @RecordInterface(addRecordBuilder = false).
    @RecordInterface
    public interface NameAndAge {
        String name(); 
        int age();
    }
  4. Generate a companion builder for Java records

    master

    Use the @RecordBuilder annotation on a Java record to automatically generate a companion builder class. This builder allows for fluent construction of the record, creating copies with modified values, and using a static constructor/builder method.

    Example usage of the generated builder:

    • Build from components: NameAndAgeBuilder.builder().name("Alice").age(30).build()
    • Create a copy with a changed value: NameAndAgeBuilder.builder(existingRecord).age(31).build()
    • Static constructor: NameAndAgeBuilder.NameAndAge("Alice", 30)
    @RecordBuilder
    public record NameAndAge(String name, int age){}
  5. Define a deconstructor using a method

    master

    To define a deconstructor via a method, create a non-static void method in your class and annotate it with @RecordBuilder.Deconstructor. The method must accept one or more parameters of the following types:

    • Consumer<T>
    • IntConsumer
    • LongConsumer
    • DoubleConsumer

    Inside the method, you must call .accept() on each consumer, passing the internal state of the class.

    public class MyClass {
        private final int qty;
        private final String name;
        
        public MyClass(int qty, String name) {
            this.qty = qty;
            this.name = name;
        }
    
        @Deconstructor
        public void deconstructor(IntConsumer qty, Consumer<String> name) {
            qty.accept(this.qty);
            name.accept(this.name);
        }
    }
  6. Install RecordBuilder via Gradle

    master

    Add the following to your build.gradle file to include the annotation processor and the core library.

    dependencies {
        annotationProcessor 'io.soabase.record-builder:record-builder-processor:$version-goes-here'
        compileOnly 'io.soabase.record-builder:record-builder-core:$version-goes-here'
    }
  7. Generate builders and records via Includes

    master

    If you cannot annotate the source classes (e.g., they are in a third-party library), use the .Include variants of the annotations on a local class. This allows you to specify which classes or packages should trigger generation.

    • @RecordBuilder.Include: Generates a builder for the specified classes or all records in specified packages.
    • @RecordInterface.Include: Generates a record for the specified interfaces.

    The target package for the generated code is the same as the package containing the class where the @Include annotation is placed. You can use packagePattern to change this.

    import some.library.code.ImportedRecord;
    import some.library.code.ImportedInterface;
    
    @RecordBuilder.Include({
        ImportedRecord.class    // generates a record builder for ImportedRecord  
    })
    @RecordInterface.Include({
        ImportedInterface.class // generates a record interface for ImportedInterface 
    })
    public class Placeholder {
    }
  8. Configure RecordBuilder options in Gradle

    master

    To apply global RecordBuilder customizations in a Gradle project, add the -A arguments to the compilerArgs list within your compilation task.

    compilerArgs.addAll(['-AprefixEnclosingClassNames=false', '-AfileComment="something different"'])
  9. Define a deconstructor using accessors

    master

    You can define a deconstructor by annotating an entire class or interface with @RecordBuilder.Deconstructor. Then, mark specific accessor methods or fields with @RecordBuilder.DeconstructorAccessor.

    If annotating a field, there must be a matching public, non-static method named either the same as the field or prefixed with get or is.

    @Deconstructor
    public class MyClass {
        private final int qty;
        private final String name;
        
        private MyClass(int qty, String name) {
            this.qty = qty;
            this.name = name;
        }
    
        @DeconstructorAccessor
        public int getQty() {
            return qty;
        }
    
        @DeconstructorAccessor
        public String getName() {
            return name;
        }
    }
  10. Create a custom annotation template

    master

    You can define a reusable custom annotation using @RecordBuilder.Template. This allows you to bundle a specific set of @RecordBuilder.Options into a single annotation that you can apply to multiple records.

    To create a template, annotate your custom interface with @RecordBuilder.Template(options = @RecordBuilder.Options(...)) and ensure it has the appropriate metadata (@Retention(RetentionPolicy.SOURCE), @Target(ElementType.TYPE), and @Inherited).

    Additionally, the template mechanism supports creating @RecordInterface templates by setting the asRecordInterface attribute.

    @RecordBuilder.Template(options = @RecordBuilder.Options(
            fileComment = "MyCo license",
            withClassName = "Wither"
    ))
    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.TYPE)
    @Inherited
    public @interface MyCoRecordBuilder {
    }
  11. Configure RecordBuilder options in Maven

    master

    To apply global RecordBuilder customizations in a Maven project, add the -A arguments to the compilerArgs section of the maven-compiler-plugin configuration.

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin-version}</version>
        <configuration>
            <compilerArgs>
                <arg>-AprefixEnclosingClassNames=false</arg>
                <arg>-AfileComment="something different"</arg>
            </compilerArgs>
        </configuration>
    </plugin>