Giter VIP home page Giter VIP logo

auto-matter's Introduction

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

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

// Record style
@AutoMatter
record 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.26.2</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

  • AutoMatter provides implementations of getters, equals, hashCode, toString, etc for you, letting you quickly describe your data and move on to using it without spending time on error prone and repetitive scaffolding.

  • AutoMatter generates builders for your value types. No need to deal with long and error prone argument lists in factory methods and constructors.

  • AutoMatter allows the value type definitions to be as minimal as possible. No need to write your own factory methods, use abstract modifiers or add json annotations, etc.

  • The value and builder implementations are generated using standard Java annotation processing at build time. Thus all code is visible, navigable and debuggable using standard Java IDE's.

  • AutoMatter enforces non-nullity for fields by default, moving those pesky NullPointerExceptions closer to the source. @Nullable can be used to opt out of the null checks.

  • AutoMatter adds no runtime dependencies.

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 greater flexibility and more features, consider using Immutables or AutoValue.

For many simple use cases plain Records (JDK 17+) are likely sufficient.

Features

Records

AutoMatter will generate builders for Records when using JDK 17+. Record builders also have a builder factory method.

@AutoMatter
record Foobar(int a, String b) {}
  
var foobar = RecordFoobarBuilder.builder()
    .a(1)
    .b("2")
    .build();

This can be especially useful when working with complex records with multiple fields. The named setters of the builder can help avoid parameter ordering mistakes when fields have the same type.

@AutoMatter
record ComplexFoobar(String foo, String bar, String baz, String quux, String corge) {}

var foobar = ComplexFoobarBuilder.builder()
    .foo("foo")
    .bar("bar")
    .baz("baz")
    .quux("quux")
    .corge("corge")
    .build();

Jackson JSON Support

Note: Requires Jackson 2.4.0+.

<dependency>
  <groupId>io.norberg</groupId>
  <artifactId>auto-matter-jackson</artifactId>
  <version>0.26.2</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);

Note: When using Records, the auto-matter-jackson dependency and the AutoMatterModule is not needed as Jackson supports Records natively since version 2.12.3.

Gson Support

<dependency>
  <groupId>io.norberg</groupId>
  <artifactId>auto-matter-gson</artifactId>
  <version>0.26.2</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.

  • Using the FoobarBuilder.from(Foobar) method.
  • Adding a FoobarBuilder builder(); method to the type definition.
@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.

@AutoMatter
interface Foo {
    String foo();
}

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

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

@AutoMatter
interface Quux extends Baz {
    String quux();
}

// ...

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

The from method can create builders by copying values from both inherited (super) and inheriting (sub) value types. The super types must be annotated with @AutoMatter.

// Copy from subtype. Will have all fields populated
// as `Baz` extends `Foo`.
Foo fooFromBaz = FooBuilder.from(baz)
    .build(); 

// Copy from supertype. Will only have some fields
// populated, the remaining fields must be explicitly set.        
Baz bazFromFoo = BazBuilder.from(fooFromBaz)
    .bar(17)
    .baz(4711)
    .build();
        
// Copy from transitive supertype (parent of parent).
Quux quux = QuuxBuilder.from(fooFromBaz)
    .bar(17)
    .baz(4711)
    .quux("world")
    .build();

A builder can also be created by copying values from the builder of an inherited (super) type:

FooBuilder fooBuilder = new FooBuilder()
    .foo("hello");

// Copy from supertype builder. Will only have some fields
// populated, the remaining fields must be explicitly set.        
Baz bazFromFooBuilder = BazBuilder.from(fooBuilder)
    .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.

Redact a field's value in toString

There are cases that a field's value is too sensitive to reveal in toString, for example a password or token. To redact the actual value in toString, annotate a method with @AutoMatter.Redacted optionally with a customized value.

Given:

@AutoMatter
public interface RedactedFields {
  String userName();

  @AutoMatter.Redacted
  String password();

  @AutoMatter.Redacted(value = "....")
  String token();
}

The generated toString method will be like:

@Override
public String toString() {
  return "RedactedFields{" +
         "userName=" + userName +
         ", password=****" +
         ", token=...." +
         '}';
}

Verify complex type constraints

Sometimes it is desirable to only allow certain combinations of the values of the fields, or some fields should only be allowed to contain a subset of the values of the field type. This validation is something that would normally take place in the constructor or the builder for hand-written types.

As the constructor and builder is generated, we need some way of adding these checks. To verify these more complex type constraints, annotating a method with @AutoMatter.Check allows asserting on complex type constraints.

The method annotated with @AutoMatter.Check will be called from the constructor when all fields have been assigned and all null checks have been performed.

This method can be either a default method:

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

  @AutoMatter.Check
  default void check() {
    assert foo().length() < bar() : "bar needs to be greater than length of foo";
  }
}

Or a static method:

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

  @AutoMatter.Check
  static void check(Foobar v) {
    if (v.foo().length() >= v.bar()) {
      throw new IllegalArgumentException("bar needs to be greater than length of foo");
    }
  }
}

These kinds of checks is sometimes called invariant checks for mutable objects. But as these objects are immutable, it is sufficient to have it as a precondition check.

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.

#17

Known workarounds:

  • Clean when building. E.g. mvn clean test
  • Use maven-compiler-plugin 2.x (e.g. 2.5.1)
  • Disable the maven-compiler-plugin useIncrementalCompilation configuration option

auto-matter's People

Contributors

andreas-karlsson avatar bndbsh avatar danielnorberg avatar dependabot[bot] avatar djui avatar honnix avatar lndbrg avatar mbruggmann avatar mzeo avatar onemanbucket avatar pabu avatar petedmarsh avatar renovate-bot avatar renovate[bot] avatar rouzwawi avatar rowland-current avatar sisidra avatar theindifferent avatar tlcs avatar togi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

auto-matter's Issues

generic type miss match when field is Optional<List<T>>

As we talked about in the Slack channel...

When I wanted to make a generic type interface with automatter and have a field type that's nested with Optional and List, the generated code is incorrect in handling the types.

The issue I had was the following interface definition
@AutoMatter
public interface ResponseAndEvents<T> {
Response response();
Optional<List<T>> eventsMaybe()
}

@Nullable in Record types is not respected

Hi, I think I've found a case in which the generated builder is not as it should.

If I have a record like such

public Record(int a, @Nullable String b) {}

A builder which asserts non-nulls on 'b' will be created. I cannot use Optional<String> instead because Optional is not Serializable and I need my record to be so.

@JsonCreator annotation overwritten by the Jackson AutoMatterModule

Found this the other day when I tried to parse a 2-element JSON list into a Guava range using a custom @JsonCreator Jackson annotation on a AutoMatter interface. The cause seems to be that the module always treats the constructor of the generated value type as the creator method.

Testcase:

public class JsonCreatorExample {

  @AutoMatter
  public interface WithJsonCreator {
    int val();

    @JsonCreator static WithJsonCreator of(final int val) {
      return new WithJsonCreatorBuilder().val(val+1).build();
    }
  }

  public static void main(final String... args) throws IOException {
    final WithJsonCreator c = new ObjectMapper()
        .registerModule(new AutoMatterModule())
        .readValue("{ \"val\": 3 }", WithJsonCreator.class);
    System.out.println(c.val());
  }
}

Possible to add a `getFields` instance method?

Any thoughts on introducing a getFields method to automatter objects? Would love to be able to iterate through an Automatter Object's fields. I have a usecase to check that at least one field is populated in the object and right now, I'm forced to manually check every field to get that.

Does not support inheritance of generic Collection

Hi,
Let's say I have the following classes :

@AutoMatter
public interface Foo<T> {
    List<T> foos();
}

and

@AutoMatter
public interface Bar extends Foo<Integer> {
}

The generated BarBuilder is manipulating List<? extends T> instead of List<? extends Integer>, resulting in building error.

I suppose this is not intended ?
BR

Add feature guide

Hi,

I would like to contribute minor improvements to this library. Do you have any plan for the futur for this project ? Do you have any guide for contributing to the project ?

For example I just added @Nullable to builder setters parameters if the value is defined as @Nullable in the interface : vincentbrison@2bfa2b9

I think it would be a nice feature since the developer would directly know if a field can be null or no when building it. Can I send you a PR for it ?

Inheritance does not compile when overriding properties with `default` methods

    @AutoMatter
    interface SuperType {

        @JsonProperty
        String field();

        @JsonProperty
        String overrideMe();
    }

    @AutoMatter
    interface SubType extends SuperType {

        @Override
        @JsonProperty
        default String overrideMe() {
               return "overridden hardcoded value";
        }
    }

which generates the following

  final class SubTypeBuilder {
      
      private String field;
  
      private SubTypeBuilder(SuperType v) {
           this.overrideMe= v.overrideMe();
       }
       
       ....
  }

where overrideMe is not a class variable because it was overridden by a default method thus causing a compile error

tried fixing this locally and had success by checking whether the method is default here: https://github.com/danielnorberg/auto-matter/blob/master/processor/src/main/java/io/norberg/automatter/processor/AutoMatterProcessor.java#L411-L415

and skipping if it was, happy to open a PR if it helps

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • chore(deps): update dependency com.github.spotbugs:spotbugs to v4.8.6
  • chore(deps): update dependency com.github.spotbugs:spotbugs-maven-plugin to v4.8.6.2
  • chore(deps): update dependency maven to v3.9.8
  • chore(deps): update dependency org.apache.maven.plugins:maven-source-plugin to v3.3.1
  • chore(deps): update dependency org.jacoco:jacoco-maven-plugin to v0.8.12
  • chore(deps): update dependency com.google.errorprone:error_prone_core to v2.30.0
  • chore(deps): update dependency com.spotify.fmt:fmt-maven-plugin to v2.23
  • chore(deps): update dependency maven-wrapper to v3.3.2
  • chore(deps): update dependency org.apache.maven.plugins:maven-compiler-plugin to v3.13.0
  • chore(deps): update dependency org.apache.maven.plugins:maven-dependency-plugin to v3.7.1
  • chore(deps): update dependency org.apache.maven.plugins:maven-enforcer-plugin to v3.5.0
  • chore(deps): update dependency org.apache.maven.plugins:maven-gpg-plugin to v3.2.5
  • chore(deps): update dependency org.apache.maven.plugins:maven-jar-plugin to v3.4.2
  • chore(deps): update dependency org.apache.maven.plugins:maven-javadoc-plugin to v3.8.0
  • chore(deps): update dependency org.sonatype.plugins:nexus-staging-maven-plugin to v1.7.0
  • fix(deps): update dependency com.github.javaparser:javaparser-core to v3.26.1
  • fix(deps): update dependency com.google.code.gson:gson to v2.11.0
  • fix(deps): update dependency com.google.errorprone:error_prone_annotations to v2.30.0
  • fix(deps): update dependency com.google.truth:truth to v1.4.4
  • fix(deps): update dependency org.checkerframework:checker-qual to v3.46.0
  • fix(deps): update javahamcrest monorepo to v3 (major) (org.hamcrest:hamcrest-core, org.hamcrest:hamcrest-library, org.hamcrest:hamcrest)
  • ๐Ÿ” Create all rate-limited PRs at once ๐Ÿ”

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

circleci
.circleci/config.yml
  • cimg/openjdk 17.0.9
  • cimg/openjdk 17.0.9
  • cimg/openjdk 17.0.9
maven
annotation/pom.xml
bom/pom.xml
dependencies/pom.xml
  • junit:junit 4.13.2
  • com.github.javaparser:javaparser-core 3.25.6
  • com.squareup:javapoet 1.13.0
  • com.google.code.gson:gson 2.10.1
  • com.google.code.findbugs:jsr305 3.0.2
  • com.google.guava:guava 31.1-jre
  • com.google.truth:truth 1.1.5
  • com.google.testing.compile:compile-testing 0.19
  • com.google.auto:auto-common 1.2.2
  • com.google.errorprone:error_prone_annotations 2.23.0
  • org.checkerframework:checker-qual 3.40.0
  • org.hamcrest:hamcrest 2.2
  • org.hamcrest:hamcrest-library 2.2
  • org.hamcrest:hamcrest-core 2.2
example/pom.xml
  • org.apache.maven.plugins:maven-compiler-plugin 3.11.0
  • com.google.errorprone:error_prone_core 2.23.0
  • org.apache.maven.plugins:maven-compiler-plugin 3.11.0
  • com.google.errorprone:error_prone_core 2.23.0
  • org.apache.maven.plugins:maven-javadoc-plugin 3.6.2
  • com.github.spotbugs:spotbugs-maven-plugin 4.8.1.0
  • com.github.spotbugs:spotbugs 4.8.1
  • com.fasterxml.jackson:jackson-bom 2.15.3
  • com.google.code.gson:gson 2.10.1
  • com.google.guava:guava 31.1-jre
  • com.google.code.findbugs:jsr305 3.0.2
gson/pom.xml
jackson/pom.xml
pom.xml
  • com.fasterxml.jackson:jackson-bom 2.15.3
  • com.google.auto.service:auto-service 1.1.1
  • com.google.auto.service:auto-service-annotations 1.1.1
  • org.apache.maven.plugins:maven-dependency-plugin 3.6.1
  • org.apache.maven.plugins:maven-jar-plugin 3.3.0
  • org.apache.maven.plugins:maven-surefire-plugin 2.22.2
  • com.spotify.fmt:fmt-maven-plugin 2.21.1
  • org.apache.maven.plugins:maven-compiler-plugin 3.11.0
  • org.sonatype.plugins:nexus-staging-maven-plugin 1.6.13
  • org.apache.maven.plugins:maven-release-plugin 2.5.3
  • org.apache.maven.scm:maven-scm-provider-gitexe 1.13.0
  • org.apache.maven.scm:maven-scm-api 1.13.0
  • org.apache.maven.plugins:maven-enforcer-plugin 3.4.1
  • org.apache.maven.plugins:maven-source-plugin 3.3.0
  • org.apache.maven.plugins:maven-javadoc-plugin 3.6.2
  • org.apache.maven.plugins:maven-gpg-plugin 3.1.0
  • org.jacoco:jacoco-maven-plugin 0.8.11
  • com.google.auto.service:auto-service 1.1.1
processor/pom.xml
record-test/pom.xml
maven-wrapper
.mvn/wrapper/maven-wrapper.properties
  • maven 3.9.5
  • maven-wrapper 3.2.0

  • Check this box to trigger a request for Renovate to run again on this repository

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: Cannot find preset's package (github>whitesource/merge-confidence:beta)

Does not compile twice with maven-compiler-plugin 3.3

I noticed this in one of my own projects where I was using the latest maven-compiler-plugin 3.3, but you can reproduce it in auto-matter as well.

$ git diff
diff --git a/pom.xml b/pom.xml
index eee9221..b31cb35 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,7 +57,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>2.5.1</version>
+        <version>3.3</version>
         <configuration>
           <source>1.7</source>
           <target>1.7</target>
$ mvn compile
...
[INFO] BUILD SUCCESS

First time works.

$ java -cp example/target/classes io.norberg.automatter.example.SimpleExample 
bar: 17
foo: hello world
foobar: Foobar{foo=hello world, bar=17}
modified: Foobar{foo=hello world, bar=18}

Compiling a second time breaks

$ mvn compile
...
[INFO] --- maven-compiler-plugin:3.3:compile (default-compile) @ auto-matter-example ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 12 source files to /var/tmp/auto-matter/example/target/classes
An exception has occurred in the compiler (1.8.0_40). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport)  after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report.  Thank you.
java.lang.IllegalStateException: endPosTable already set
    at com.sun.tools.javac.util.DiagnosticSource.setEndPosTable(DiagnosticSource.java:136)
    at com.sun.tools.javac.util.Log.setEndPosTable(Log.java:350)
...

https://gist.github.com/jooon/386352b9f922d39dda78

Is this an actual java bug or a problem with maven-compiler-plugin or auto-matter?

Deserialising field named "private"

Hello,

I was wondering whether anyone would have any ideas on how to declare and gson deserialise an entity with a field named "private"?

Thanks!

Wrong hashcode method for classes with a field name "result"

Let's say I want a model matching the following Json:

{
  "result": "OK"
}

I would create an AutoMatter class like this:

@AutoMatter
public interface RestResult {
    String result();
}

The current version of AutoMatter would generate the following hashCode() function:

@Override
public int hashCode() {
  int result = 1;
  long temp;
  result = 31 * result + (result != null ? result.hashCode() : 0);
  return result;
}

This would not compile because there is a conflict between the ivar result and the field with the same name.
The proper code should be :

@Override
public int hashCode() {
  int result = 1;
  long temp;
  result = 31 * result + (this.result != null ? this.result.hashCode() : 0);
  return result;
}

So the library would need to add this. when referencing fields to avoid conflicts with method's variables.

@JsonProperty broken for Jackson 2.7.x

...
Running io.norberg.automatter.jackson.AutoMatterModuleTest
Tests run: 7, Failures: 0, Errors: 5, Skipped: 0, Time elapsed: 0.277 sec <<< FAILURE!
testPublicInner(io.norberg.automatter.jackson.AutoMatterModuleTest)  Time elapsed: 0.208 sec  <<< ERROR!
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "foobar" (class io.norberg.automatter.jackson.PublicBarBuilder$Value), not marked as ignorable (4 known properties: "a", "b", "aCamelCaseField", "isReallyFoobar"])
 at [Source: {"a":17,"b":"foobar","aCamelCaseField":true,"foobar":false}; line: 1, column: 60] (through reference chain: io.norberg.automatter.jackson.Value["foobar"])
    at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:62)
    at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:855)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1083)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1389)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperties(BeanDeserializerBase.java:1343)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:455)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1123)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:298)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:133)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3788)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2779)
    at io.norberg.automatter.jackson.AutoMatterModuleTest.testPublicInner(AutoMatterModuleTest.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

...

Support copying data between inherited types

Currently value-type inheritance works great, but there's no good way to copy data between an instance of a value type to one that inherits from it.

For example:

@AutoMatter
public interface FooValue {
	String foo();
}

@AutoMatter
public interface BarValue extends FooValue {
	String bar();
}

// If we have...
FooValue foo = new FooValueBuilder()
	.foo("Hello ")
	.build();

// It would be great to be able to do this:
BarValue bar = BarValueBuilder.from(foo)
	.bar("world!")
	.build();

// Or maybe this, to support inheriting from more than one interface:
BarValue bar2 = new BarValueBuilder()
	.include(foo) // better names could be ".import()" or ".copy()"
	.bar("world!")
	.build();

This could probably be simplified if you only include support for ancestor interfaces which are also annotated with @AutoMatter.

The above example is a bit contrived, of course, but you could see how this would get to be a huge pain if the base interface had a lot of fields.

Bean Validation not usable

As methods without the get prefix are not considered properties per the JavaBean spec, Bean Validation constraint annotations (e.g. @NotNull, @NotEmpty, etc) are not by vanilla Bean Validation frameworks.

I.e., unless fields are explicitly named getFoo and getBar etc, niceties like Dropwizard Validation does not work.

NPE on from() when nullable collection unset (since 0.22)

I've noticed that in all the versions since 0.22 there was a change behaviour which causes a NullPointerExcection.

Step to reproduce:

  • use Nullable annotation on a field which is a collection
  • create a builder with that collection left unset
  • try to copy that builder using from() method

Expected behaviour: copy of the builder is created (with nullable collection set to null)
Actual behaviour: NPE is thrown due to nullable collection being unset

Earlier version - 0.21 - worked as expected.

Example

Builder:

    @AutoMatter
    public interface Foobar {
        @Nullable
        List<String> fruits();
    }

Usage:

        // regular builder usage with nullable works fine
        var builder = new FoobarBuilder().build();
        assert builder.fruits() == null;

        // copying builder works as expected if nullable field is set on the source builder
        var builderWithNullableSet = new FoobarBuilder().fruits("banana");
        var builderCopy = FoobarBuilder.from(builderWithNullableSet);
        assert builderCopy.fruits().equals(List.of("banana"));

        // copying builder throws NPE if nullable collection not set on the source builder
        var builderWithNullableUnset = new FoobarBuilder();
        // throws NPE:  Cannot invoke "java.util.Collection.toArray()" because "c" is null
        FoobarBuilder.from(builderWithNullableUnset);

Suspected cause

I believe that this behaviour was caused by this change (Fix Spotbugs complaints):
#184

As it removed extra null checks - e.g. the generated code in builders changed from:

this.foos = (v.foos() == null) ? null : new ArrayList<T>(v.foos());

to

this.foos = new ArrayList<T>(v.foos());

It may as well be that I missed some context and I am not right about it, but it looks to me like a bug.

Excessive depency on h2 from modelshape

It seems that auto-matter pulls in the h2 database through modeshape-common. Modeshape is only used to get the singularize method out of an Inflector.

It would be nice to avoid pulling a full database and modeshape itself for that. As an alternative, maybe there is a lighter-weight inflector from another library or alternatively this Inflector could be vendored in?

Thanks!

Record methods are not ignored

Documentation says it's possible to add behavior to a value type and give an example using an interface definition.

I tried to do the same using a record definition without success. Let's use this dumb example:

@AutoMatter
public record IntString(String asString) {

    int asInt() {
        return Integer.parseInt( asString() );
    }
}

AutoMatter generates following code:

  private IntStringBuilder(IntString v) {
    this.asInt = v.asInt();
    this.asString = v.asString();
  }

  private IntStringBuilder(IntStringBuilder v) {
    this.asInt = v.asInt();
    this.asString = v.asString();
  }

  public IntString build() {
    if (asString == null) {
      throw new NullPointerException("asString");
    }
    return new IntString(asInt, asString);
  }

I think that any methods which is not a record component must be ignored by the builder.

Exception thrown when deserializing Optional fields

Note: Running project can be found here: https://github.com/andersonvom/auto-matter-bug

In the following setup

<!-- pom.xml -->
<dependency>
  <groupId>io.norberg</groupId>
  <artifactId>auto-matter</artifactId>
  <version>0.15.3</version>
</dependency>
<dependency>
  <groupId>io.norberg</groupId>
  <artifactId>auto-matter-jackson</artifactId>
  <version>0.15.3</version>
</dependency>
// Foobar.java
@AutoMatter
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
interface Foobar {
  @Nullable String foo();
  Optional<String> bar();
}

The optional attribute Optional<String> bar() gets serialized as "bar":{"empty":false,"present":true}} and throw the following exception when it is deserialized:

ObjectMapper objectMapper = new ObjectMapper().registerModule(new AutoMatterModule());

Foobar foobar = new FoobarBuilder().foo("foo").bar("bar").build();
String json = objectMapper.writeValueAsString(foobar);
System.out.println("serialized: " + json);

// throws com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
//     Unrecognized field "empty" (class java.util.Optional),
//     not marked as ignorable (0 known properties: ])
Foobar parsed = objectMapper.readValue(json, Foobar.class);
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "empty" (class java.util.Optional), not marked as ignorable (0 known properties: ])
 at [Source: {"foo":"foo","bar":{"empty":false,"present":true}}; line: 1, column: 34] (through reference chain: com.mycompany.app.FoobarBuilder$Value["bar"]->java.util.Optional["empty"])

	at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:62)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:834)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1093)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1489)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1467)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:282)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:504)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:511)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:400)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1191)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:314)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3814)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2858)
	at com.mycompany.app.AppTest.testApp(AppTest.java:29)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:44)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:74)
	at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)
	at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
	at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Am I doing something wrong or is this an actual bug?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.