Immutables

Generated code stars

Here's an illustration of the kind of code is generated by annotation processor.

Let's start with minimal example. In the first example builder and copy methods are suppressed, and there are no Guava or JSR-305 annotations libraries in the classpath. @Parameter annotation is used to specify constructor parameters so that constructor method is generated.

Minimal tuple

import org.immutables.value.Value;

@Value.Immutable(builder = false, copy = false)
interface Tuple {
  @Value.Parameter int getIndex();
  @Value.Parameter String getName();
}

The source code of the generated immutable implementation:

import java.util.Objects;

/**
 * Immutable implementation of {@link Tuple}.
 * <p>
 * Use the static factory method to create immutable instances:
 * {@code ImmutableTuple.of()}.
 */
@SuppressWarnings("all")
@Immutable
final class ImmutableTuple implements Tuple {
  private final int index;
  private final String name;

  private ImmutableTuple(int index, String name) {
    this.index = index;
    this.name = Objects.requireNonNull(name, "name");
  }

  /**
   * @return The value of the {@code index} attribute
   */
  @Override
  public int getIndex() {
    return index;
  }

  /**
   * @return The value of the {@code name} attribute
   */
  @Override
  public String getName() {
    return name;
  }

  /**
   * This instance is equal to all instances of {@code ImmutableTuple} that have equal attribute values.
   * @return {@code true} if {@code this} is equal to {@code another} instance
   */
  @Override
  public boolean equals(Object another) {
    if (this == another) return true;
    return another instanceof ImmutableTuple
        && equalTo((ImmutableTuple) another);
  }

  private boolean equalTo(ImmutableTuple another) {
    return index == another.index
        && name.equals(another.name);
  }

  /**
   * Computes a hash code from attributes: {@code index}, {@code name}.
   * @return hashCode value
   */
  @Override
  public int hashCode() {
    int h = 31;
    h = h * 17 + index;
    h = h * 17 + name.hashCode();
    return h;
  }

  /**
   * Prints the immutable value {@code Tuple...} with all non-generated
   * and non-auxiliary attribute values.
   * @return A string representation of the value
   */
  @Override
  public String toString() {
    return "Tuple{"
        + "index=" + index
        + ", name=" + name
        + "}";
  }

  /**
   * Construct a new immutable {@code Tuple} instance.
   * @param index The value for the {@code index} attribute
   * @param name The value for the {@code name} attribute
   * @return An immutable Tuple instance
   */
  public static ImmutableTuple of(int index, String name) {
    return new ImmutableTuple(index, name);
  }
}

By default, extra code is generated. These extras can be disabled. See the Style guide for examples of how this can be configured on package or using meta-annotations.

Typical value object

Here's a more typical example of a value object with a builder and copy methods. Google Guava is present in classpath.

import com.google.common.base.Optional;
import java.util.List;
import org.immutables.value.Value;

@Value.Immutable
interface ValueObject {
  long id();
  String name();
  List<Number> numbers();
  Optional<String> comment();
  long[] data();
}

You will probably agree that the code below would have been painful to write by hand:

import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.primitives.Longs;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Generated;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * Immutable implementation of {@link ValueObject}.
 * <p>
 * Use the builder to create immutable instances:
 * {@code ImmutableValueObject.builder()}.
 */
@SuppressWarnings("all")
@ParametersAreNonnullByDefault
@Generated({"Immutables.generator", "ValueObject"})
@Immutable
final class ImmutableValueObject implements ValueObject {
  private final long id;
  private final String name;
  private final ImmutableList<Number> numbers;
  private final Optional<String> comment;
  private final long[] data;

  private ImmutableValueObject(
      long id,
      String name,
      ImmutableList<Number> numbers,
      Optional<String> comment,
      long[] data) {
    this.id = id;
    this.name = name;
    this.numbers = numbers;
    this.comment = comment;
    this.data = data;
  }

  /**
   * @return The value of the {@code id} attribute
   */
  @Override
  public long id() {
    return id;
  }

  /**
   * @return The value of the {@code name} attribute
   */
  @Override
  public String name() {
    return name;
  }

  /**
   * @return The value of the {@code numbers} attribute
   */
  @Override
  public ImmutableList<Number> numbers() {
    return numbers;
  }

  /**
   * @return The value of the {@code comment} attribute
   */
  @Override
  public Optional<String> comment() {
    return comment;
  }

  /**
   * @return A cloned {@code data} array
   */
  @Override
  public long[] data() {
    return data.clone();
  }

  /**
   * Copy the current immutable object by setting a value for the {@link ValueObject#id() id} attribute.
   * A value equality check is used to prevent copying of the same value by returning {@code this}.
   * @param value A new value for id
   * @return A modified copy of the {@code this} object
   */
  public final ImmutableValueObject withId(long value) {
    if (this.id == value) return this;
    return new ImmutableValueObject(value, this.name, this.numbers, this.comment, this.data);
  }

  /**
   * Copy the current immutable object by setting a value for the {@link ValueObject#name() name} attribute.
   * An equals check used to prevent copying of the same value by returning {@code this}.
   * @param value A new value for name
   * @return A modified copy of the {@code this} object
   */
  public final ImmutableValueObject withName(String value) {
    if (this.name.equals(value)) return this;
    return new ImmutableValueObject(
        this.id,
        Preconditions.checkNotNull(value, "name"),
        this.numbers,
        this.comment,
        this.data);
  }

  /**
   * Copy the current immutable object with elements that replace the content of {@link ValueObject#numbers() numbers}.
   * @param elements The elements to set
   * @return A modified copy of {@code this} object
   */
  public final ImmutableValueObject withNumbers(Number... elements) {
    ImmutableList<Number> newValue = ImmutableList.copyOf(elements);
    return new ImmutableValueObject(this.id, this.name, newValue, this.comment, this.data);
  }

  /**
   * Copy the current immutable object with elements that replace the content of {@link ValueObject#numbers() numbers}.
   * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}.
   * @param elements An iterable of numbers elements to set
   * @return A modified copy of {@code this} object
   */
  public final ImmutableValueObject withNumbers(Iterable<? extends Number> elements) {
    if (this.numbers == elements) return this;
    ImmutableList<Number> newValue = ImmutableList.copyOf(elements);
    return new ImmutableValueObject(this.id, this.name, newValue, this.comment, this.data);
  }

  /**
   * Copy the current immutable object by setting a <i>present</i> value for the optional {@link ValueObject#comment() comment} attribute.
   * @param value The value for comment
   * @return A modified copy of {@code this} object
   */
  public final ImmutableValueObject withComment(String value) {
    Optional<String> newValue = Optional.of(value);
    return new ImmutableValueObject(this.id, this.name, this.numbers, newValue, this.data);
  }

  /**
   * Copy the current immutable object by setting an optional value for the {@link ValueObject#comment() comment} attribute.
   * A shallow reference equality check on the optional value is used to prevent copying of the same value by returning {@code this}.
   * @param optional A value for comment
   * @return A modified copy of {@code this} object
   */
  public final ImmutableValueObject withComment(Optional<String> optional) {
    Optional<String> value = Preconditions.checkNotNull(optional, "comment");
    if (this.comment == value) return this;
    return new ImmutableValueObject(this.id, this.name, this.numbers, value, this.data);
  }

  /**
   * Copy the current immutable object with elements that replace the content of {@link ValueObject#data() data}.
   * The array is cloned before being saved as attribute values.
   * @param elements The non-null elements for data
   * @return A modified copy of {@code this} object
   */
  public final ImmutableValueObject withData(long... elements) {
    long[] newValue = elements.clone();
    return new ImmutableValueObject(this.id, this.name, this.numbers, this.comment, newValue);
  }

  /**
   * This instance is equal to all instances of {@code ImmutableValueObject} that have equal attribute values.
   * @return {@code true} if {@code this} is equal to {@code another} instance
   */
  @Override
  public boolean equals(@Nullable Object another) {
    if (this == another) return true;
    return another instanceof ImmutableValueObject
        && equalTo((ImmutableValueObject) another);
  }

  private boolean equalTo(ImmutableValueObject another) {
    return id == another.id
        && name.equals(another.name)
        && numbers.equals(another.numbers)
        && comment.equals(another.comment)
        && Arrays.equals(data, another.data);
  }

  /**
   * Computes a hash code from attributes: {@code id}, {@code name}, {@code numbers}, {@code comment}, {@code data}.
   * @return hashCode value
   */
  @Override
  public int hashCode() {
    int h = 31;
    h = h * 17 + Longs.hashCode(id);
    h = h * 17 + name.hashCode();
    h = h * 17 + numbers.hashCode();
    h = h * 17 + comment.hashCode();
    h = h * 17 + Arrays.hashCode(data);
    return h;
  }

  /**
   * Prints the immutable value {@code ValueObject...} with all non-generated
   * and non-auxiliary attribute values.
   * @return A string representation of the value
   */
  @Override
  public String toString() {
    return MoreObjects.toStringHelper("ValueObject")
        .add("id", id)
        .add("name", name)
        .add("numbers", numbers)
        .add("comment", comment)
        .add("data", Arrays.toString(data))
        .toString();
  }

  /**
   * Creates an immutable copy of a {@link ValueObject} value.
   * Uses accessors to get values to initialize the new immutable instance.
   * If an instance is already immutable, it is returned as is.
   * @param instance The instance to copy
   * @return A copied immutable ValueObject instance
   */
  public static ImmutableValueObject copyOf(ValueObject instance) {
    if (instance instanceof ImmutableValueObject) {
      return (ImmutableValueObject) instance;
    }
    return ImmutableValueObject.builder()
        .from(instance)
        .build();
  }

  /**
   * Creates a builder for {@link ImmutableValueObject ImmutableValueObject}.
   * @return A new ImmutableValueObject builder
   */
  public static ImmutableValueObject.Builder builder() {
    return new ImmutableValueObject.Builder();
  }

  /**
   * Builds instances of type {@link ImmutableValueObject ImmutableValueObject}.
   * Initialize attributes and then invoke the {@link #build()} method to create an
   * immutable instance.
   * <p><em>{@code Builder} is not thread-safe and generally should not be stored in a field or collection,
   * but instead used immediately to create instances.</em>
   */
  @NotThreadSafe
  static final class Builder {
    private static final long INIT_BIT_ID = 0x1L;
    private static final long INIT_BIT_NAME = 0x2L;
    private static final long INIT_BIT_DATA = 0x4L;
    private long initBits = 0x7;

    private long id;
    private @Nullable String name;
    private ImmutableList.Builder<Number> numbersBuilder = ImmutableList.builder();
    private Optional<String> comment = Optional.absent();
    private @Nullable long[] data;

    private Builder() {
    }

    /**
     * Fill a builder with attribute values from the provided {@code ValueObject} instance.
     * Regular attribute values will be replaced with those from the given instance.
     * Absent optional values will not replace present values.
     * Collection elements and entries will be added, not replaced.
     * @param instance The instance from which to copy values
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder from(ValueObject instance) {
      Preconditions.checkNotNull(instance, "instance");
      id(instance.id());
      name(instance.name());
      addAllNumbers(instance.numbers());
      Optional<String> commentOptional = instance.comment();
      if (commentOptional.isPresent()) {
        comment(commentOptional);
      }
      data(instance.data());
      return this;
    }

    /**
     * Initializes the value for the {@link ValueObject#id() id} attribute.
     * @param id The value for id
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder id(long id) {
      this.id = id;
      initBits &= ~INIT_BIT_ID;
      return this;
    }

    /**
     * Initializes the value for the {@link ValueObject#name() name} attribute.
     * @param name The value for name
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder name(String name) {
      this.name = Preconditions.checkNotNull(name, "name");
      initBits &= ~INIT_BIT_NAME;
      return this;
    }

    /**
     * Adds one element to {@link ValueObject#numbers() numbers} list.
     * @param element A numbers element
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder addNumbers(Number element) {
      numbersBuilder.add(element);
      return this;
    }

    /**
     * Adds elements to {@link ValueObject#numbers() numbers} list.
     * @param elements An array of numbers elements
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder addNumbers(Number... elements) {
      numbersBuilder.add(elements);
      return this;
    }

    /**
     * Sets or replaces all elements for {@link ValueObject#numbers() numbers} list.
     * @param elements An iterable of numbers elements
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder numbers(Iterable<? extends Number> elements) {
      numbersBuilder = ImmutableList.builder();
      return addAllNumbers(elements);
    }

    /**
     * Adds elements to {@link ValueObject#numbers() numbers} list.
     * @param elements An iterable of numbers elements
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder addAllNumbers(Iterable<? extends Number> elements) {
      numbersBuilder.addAll(elements);
      return this;
    }

    /**
     * Initializes the optional value {@link ValueObject#comment() comment} to comment.
     * @param comment The value for comment
     * @return {@code this} builder for chained invocation
     */
    public final Builder comment(String comment) {
      this.comment = Optional.of(comment);
      return this;
    }

    /**
     * Initializes the optional value {@link ValueObject#comment() comment} to comment.
     * @param comment The value for comment
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder comment(Optional<String> comment) {
      this.comment = Preconditions.checkNotNull(comment, "comment");
      return this;
    }

    /**
     * Initializes the value for the {@link ValueObject#data() data} attribute.
     * @param elements The elements for data
     * @return {@code this} builder for use in a chained invocation
     */
    public final Builder data(long... elements) {
      this.data = elements.clone();
      initBits &= ~INIT_BIT_DATA;
      return this;
    }

    /**
     * Builds a new {@link ImmutableValueObject ImmutableValueObject}.
     * @return An immutable instance of ValueObject
     * @throws java.lang.IllegalStateException if any required attributes are missing
     */
    public ImmutableValueObject build() throws IllegalStateException {
      if (initBits != 0) {
        throw new IllegalStateException(formatRequiredAttributesMessage());
      }
      return new ImmutableValueObject(id, name, numbersBuilder.build(), comment, data);
    }

    private String formatRequiredAttributesMessage() {
      List<String> attributes = Lists.newArrayList();
      if ((initBits & INIT_BIT_ID) != 0) attributes.add("id");
      if ((initBits & INIT_BIT_NAME) != 0) attributes.add("name");
      if ((initBits & INIT_BIT_DATA) != 0) attributes.add("data");
      return "Cannot build ValueObject, some of required attributes are not set " + attributes;
    }
  }
}