Auto

repository·main·Indexed 27 days ago

https://github.com/google/auto

A suite of Java annotation processors and code generators designed to automate repetitive boilerplate code. It includes AutoFactory for JSR-330 compatible factories, AutoService for java.util.ServiceLoader metadata, AutoValue for immutable value types (including a Serializable extension), AutoBuilder for generalized builders, and a Common library providing utility classes for writing annotation processors.

Tokens
21.5K
Snippets
55
Records
79
Agent score
94%

What's inside Auto

  1. Overview of AutoValue

    main

    AutoValue is a library for generating immutable value classes for Java 8+. It automates the implementation of equals, hashCode, and toString methods, which are typically boilerplate-heavy and error-prone when written manually. This reduces noise in your codebase and minimizes bugs related to value equality.

    Note for Kotlin and modern Java users:

    • If using Kotlin, data classes are generally preferred over AutoValue.
    • If using a version of Java that supports records, records are usually more appropriate.
    • You can still use [AutoBuilder] to generate builders for Kotlin data classes or Java records.
  2. Overview of Auto source code generators

    main
    Auto is a collection of Java source code generators designed to automate mechanical, repetitive, and error-prone coding tasks. It helps developers reduce boilerplate and prevent subtle bugs by generating code that would otherwise be written manually.
  3. Overview of Auto Common Utility classes

    main

    The auto-common library provides several utility classes to simplify working within an annotation processing environment:

    • MoreTypes: Provides utilities and Equivalence wrappers for TypeMirror and related subtypes.
    • MoreElements: Provides utilities for Element and related subtypes.
    • SuperficialValidation: A simple scanner used to ensure an Element is valid and free from distortion caused by upstream compilation errors.
    • Visibility: Utilities for managing and checking Element visibility levels (e.g., public, protected).
    • BasicAnnotationProcessor / Step: Base types for implementing annotation processors. They support validating processors, deferring invalid elements for later processing, and breaking processor actions into discrete Steps (which can handle different annotations).
  4. Explore Auto subprojects

    main

    Auto is composed of several specialized subprojects. Depending on your needs, you can use one or more of the following:

    • AutoFactory: Generates JSR-330-compatible factories.
    • AutoService: Automatically generates provider-configuration files for the Java ServiceLoader mechanism.
    • AutoValue: Provides immutable value-type code generation for Java 8+.
    • Common: Provides helper utilities specifically for writing annotation processors.
  5. Use AutoService to generate ServiceLoader metadata

    main

    AutoService is a configuration/metadata generator for java.util.ServiceLoader-style service providers. By annotating a class with @AutoService, the library automatically generates the required META-INF/services/ metadata files. This prevents typos and errors during refactoring when registering implementations of well-known types.

    package foo.bar;
    
    import com.google.auto.service.AutoService;
    import javax.annotation.processing.Processor;
    
    @AutoService(Processor.class)
    final class MyProcessor implements Processor {
      // …
    }
  6. Configure AutoValue with Maven

    main

    To use AutoValue with Maven, you need two dependencies: auto-value-annotations for your compile-time classpath and auto-value as an annotation processor.

    If you are building a library, it is recommended to set the scope of auto-value-annotations to provided so it is not a transitive dependency for your users.

    <!-- Dependency for annotations -->
    <dependencies>
      <dependency>
        <groupId>com.google.auto.value</groupId>
        <artifactId>auto-value-annotations</artifactId>
        <version>${auto-value.version}</version>
        <scope>provided</scope>
      </dependency>
    </dependencies>
    
    <!-- Configuration for the annotation processor -->
    <build>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <annotationProcessorPaths>
              <path>
                <groupId>com.google.auto.value</groupId>
                <artifactId>auto-value</artifactId>
                <version>${auto-value.version}</version>
              </path>
            </annotationProcessorPaths>
          </configuration>
        </plugin>
      </plugins>
    </build>
  7. Avoid mutable property types in AutoValue classes

    main

    To prevent exposing internal state, avoid using mutable types (including arrays) for your properties, especially if accessor methods are public. The generated accessors do not copy the field value when returning it.

    While properties should be immutable, your static factory methods can still accept mutable types as input parameters by copying them into immutable versions during construction.

    @AutoValue
    public abstract class ListExample {
      abstract ImmutableList<String> names();
    
      public static ListExample create(List<String> mutableNames) {
        return new AutoValue_ListExample(ImmutableList.copyOf(mutableNames));
      }
    }
  8. Use nullable properties

    main

    By default, the generated constructor rejects null values. To allow nulls, apply any annotation named @Nullable to the accessor method.

    Best Practice: Also annotate the corresponding parameter in your factory create method for better documentation, though AutoValue only requires the annotation on the accessor to remove null checks and generate null-friendly equals, hashCode, and toString implementations.

    @AutoValue
    public abstract class Foo {
      public static Foo create(@Nullable Bar bar) {
        return new AutoValue_Foo(bar);
      }
    
      @Nullable abstract Bar bar();
    }
  9. Validate property values during `build()`

    main

    To perform validation, split the build process into two methods: an internal, abstract autoBuild() method (which AutoValue implements) and a public, concrete build() method. The concrete build() method should call autoBuild() and then perform validation checks on the resulting instance.

    @AutoValue
    public abstract class Animal {
      public abstract String name();
      public abstract int numberOfLegs();
    
      public static Builder builder() {
        return new AutoValue_Animal.Builder();
      }
    
      @AutoValue.Builder
      public abstract static class Builder {
        public abstract Builder setName(String value);
        public abstract Builder setNumberOfLegs(int value);
    
        abstract Animal autoBuild();  // AutoValue implements this
    
        public final Animal build() {
          Animal animal = autoBuild();
          Preconditions.checkState(animal.numberOfLegs() >= 0, "Negative legs");
          return animal;
        }
      }
    }
  10. Use AutoBuilder with Kotlin data classes

    main

    AutoBuilder can be used to construct Kotlin data classes from Java code. When using Kotlin, ensure you have a dependency on org.jetbrains.kotlin:kotlin-metadata-jvm (or org.jetbrains.kotlinx:kotlinx-metadata-jvm) so AutoBuilder can understand Kotlin class metadata.

    If using kapt, you can define the builder interface directly inside the Kotlin data class.

    class KotlinData(val level: Int, val name: String?, val id: Long = -1L) {
      @AutoBuilder
      interface Builder {
        fun setLevel(x: Int): Builder
        fun setName(x: String?): Builder
        fun setId(x: Long): Builder
        fun build(): KotlinData
      }
    
      fun toBuilder(): Builder = AutoBuilder_KotlinData_Builder(this)
    
      companion object {
        @JvmStatic fun builder(): Builder = AutoBuilder_KotlinData_Builder()
      }
    }
  11. Use a custom implementation of equals, hashCode, or toString

    main

    You can provide your own implementation of equals, hashCode, or toString. AutoValue will detect the hand-written method and skip generating its own (this is called "underriding").

    Best Practices:

    • Mark underriding methods as final to clarify they are not overridden by AutoValue.
    • Ensure you follow standard hash code rules (equal objects must have equal hash codes).
    • Use EqualsTester from guava-testlib to verify your custom implementation.
  12. Use collection-valued properties in AutoValue builders

    main

    When using immutable collections (like Guava's ImmutableSet, ImmutableList, etc.) as property types, AutoValue allows your builder to be more flexible than the property type itself.

    Flexible Setters

    You can define builder methods that accept any type compatible with the collection's copyOf method (e.g., Set, Collection, Iterable, or Array). This prevents callers from having to manually construct the immutable collection type before calling the setter.

    Accumulating Values with propertyBuilder()

    To avoid passing all elements at once, you can define a method named {propertyName}Builder() (e.g., countriesBuilder()) that returns the collection's builder type.

    Note: Using propertyBuilder() directly breaks the method chain. To maintain a fluent API, you can implement a public add{PropertyName}(T value) method that internally uses the collection builder.

    @AutoValue
    public abstract class Animal {
      public abstract String name();
      public abstract int numberOfLegs();
      public abstract ImmutableSet<String> countries();
    
      public static Builder builder() {
        return new AutoValue_Animal.Builder();
      }
    
      @AutoValue.Builder
      public abstract static class Builder {
        public abstract Builder setName(String value);
        public abstract Builder setNumberOfLegs(int value);
        
        // Option 1: Flexible setter
        public abstract Builder setCountries(Set<String> value);
        public abstract Builder setCountries(String... value);
    
        // Option 2: Accumulation via internal builder (breaks chain)
        abstract ImmutableSet.Builder<String> countriesBuilder();
    
        // Option 3: Accumulation via helper (maintains chain)
        public final Builder addCountry(String value) {
          countriesBuilder().add(value);
          return this;
        }
    
        public abstract Animal build();
      }
    }