AutoMatter

Maven Central Build Status

A small library for materializing value classes and builders from value types defined as minimal interfaces. Inspired by AutoValue.

Usage

In Foobar.java

@AutoMatter
public interface Foobar {
  String foo();
  int bar();
}

In Application.java

Foobar foobar = new FoobarBuilder()
    .foo("hello world")
    .bar(17)
    .build();

out.println("foo: " + foobar.foo());
out.println("bar: " + foobar.bar());
out.println("foobar: " + foobar);

In pom.xml

<dependency>
  <groupId>io.norberg</groupId>
  <artifactId>auto-matter</artifactId>
  <version>0.15.3</version>
  <scope>provided</scope>
</dependency>

Note: Use <scope>provided</scope> to avoid pulling in the runtime dependencies of the annotation processor itself. The generated code does not have any runtime dependencies on auto-matter.

Why

Why Not

AutoMatter is designed to work well for pure data value type use cases by generating as much as possible of the scaffolding needed in a straightforward manner. As such, it might not be flexible enough for all use cases. For example, it is not possible to add your own methods to the generated builders. For maximum flexibility, although at a higher cost, consider AutoValue.

Features

Jackson JSON Support

Note: Requires Jackson 2.4.0+

<dependency>
  <groupId>io.norberg</groupId>
  <artifactId>auto-matter-jackson</artifactId>
  <version>0.15.3</version>
</dependency>
ObjectMapper mapper = new ObjectMapper()
    .registerModule(new AutoMatterModule());

Foobar foobar = new FoobarBuilder()
    .bar(17)
    .foo("hello world")
    .build();

String json = mapper.writeValueAsString(foobar);

Foobar parsed = mapper.readValue(json, Foobar.class);

Gson Support

<dependency>
  <groupId>io.norberg</groupId>
  <artifactId>auto-matter-gson</artifactId>
  <version>0.15.3</version>
</dependency>
Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(new AutoMatterTypeAdapterFactory())
    .create();

Foobar foobar = new FoobarBuilder()
    .foo("hello world")
    .bar(17)
    .build();

String json = gson.toJson(foobar);

Foobar parsed = gson.fromJson(json, Foobar.class);

Copying

A value can be copied into a new builder in two ways.

@AutoMatter
interface Foobar {
    String foo();
    int bar();

    // Note: This method is an optional convenience.
    FoobarBuilder builder();
}

// ...

Foobar original = ... ;

// Using a static method on the builder
Foobar copy1 = FoobarBuilder.from(original);
    .foo("this is a copy")
    .build();

// Using a FoobarBuilder builder() method optionally defined on the value type
Foobar copy2 = original.builder()
    .foo("this is another copy")
    .build();

@Nullable

AutoMatter will omit null checks for fields annotated with @Nullable.

@AutoMatter
interface Foobar {
    @Nullable String foo();
    int bar();
}

// ...

Foobar foobar = new FoobarBuilder()
    .foo(null)
    .bar(17)
    .build();

assert foobar.foo() == null;

The @Nullable annotation can be e.g. javax.annotation.Nullable from jsr305. A @Nullable annotation from any other package will also work.

Collections

AutoMatter emits convenient setters and adders for List, Set and Map fields.

@AutoMatter
interface Foobar {
    List<String> oxen();
    List<String> cows();
    List<Integer> foo();

    Map<String, Integer> ages();
}

// ...

Foobar foobar = new FoobarBuilder()
    .oxen("mooie", "looie")
    .addOx("moo!")
    .addOx("mooo!!")
    .addCow("moooo!!!")
    .foo(17, 18)
    .ages("junior", 1,
          "spotty", 3)
    .putAge("cassie", 5)
    .putAge("henrietta", 7)
    .build();

assert foobar.oxen().equals(Arrays.asList("mooie", "looie", "moo!", "mooo!!"));
assert foobar.cows().equals(Arrays.asList("moooo!!!"));
assert foobar.foo().equals(Arrays.asList(17, 18));
assert foobar.ages().equals(ImmutableMap.of("junior", 1,
                                            "spotty", 3,
                                            "cassie", 5,
                                            "henrietta", 7));

Optional

AutoMatter also supports Guava and JDK8+ Optional fields, which can be a safer alternative to @Nullable.

@AutoMatter
interface Foobar {
    Optional<String> foo();
    Optional<String> bar();
    Optional<String> baz();
}

// ...

Foobar foobar = new FoobarBuilder()
    .foo("hello")
    .bar(null)
    .build();

assert foobar.foo().get().equals("hello");
assert !foobar.bar().isPresent();
assert !foobar.baz().isPresent();

static & default methods (JDK 8+)

AutoMatter ignores static and default methods, which can be useful for adding behavior to a value type. Note that static and default methods in interfaces require JDK 8+.

@AutoMatter
interface Baz {

    String baz();

    static String quux() {
        return "world";
    }

    default String bazquux() {
        return baz() + " " + quux();
    }
}

// ...

Baz baz = new BazBuilder()
        .baz("hello")
        .build();

assert baz.baz().equals("hello");
assert Baz.quux().equals("world");
assert baz.bazquux().equals("hello world");

Generics

AutoMatter value types can be generic.

@AutoMatter
interface Foobar<T> {
    T foo();
    List<T> moreFoos();
    Map<String, T> mappedFoos();
    Optional<T> maybeFoo();
}

// ...

Foobar<String> foobar = new FoobarBuilder<String>()
    .foo("hello")
    .moreFoos("foo", "bar")
    .putMappedFoo("the", "baz")
    .maybeFoo("quux")
    .build();

Inheritance

AutoMatter value types can inherit fields from interfaces.

interface Foo {
    String foo();
}

interface Bar<T> {
    T bar();
}

@AutoMatter
interface Baz extends Foo, Bar<Integer> {
    int baz();
}

// ...

Baz baz = new BazBuilder()
    .foo("hello")
    .bar(17)
    .baz(4711)
    .build();

Customizing toString

The AutoMatter-generated toString method can be overriden by annotating a method with @AutoMatter.ToString.

This method can be either a default method:

@AutoMatter
interface Foobar {
  String foo();
  int bar();

  @AutoMatter.ToString
  default String overrideToString() {
    return foo() + " " + bar();
  }
}

Or a static method:

@AutoMatter
interface Foobar {
  String foo();
  int bar();

  @AutoMatter.ToString
  static String toString(Foobar v) {
    return v.foo() + " " + v.bar();
  }
}

Note that in the case of a default method, the method name cannot be toString as default methods are not allowed to override methods from java.lang.Object.

Known Issues

There's an issue with maven-compiler-plugin 3.x and annotation processors that causes recompilation without a preceding mvn clean to fail.

https://github.com/danielnorberg/auto-matter/issues/17

Known workarounds: