Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java optional codegen #1858

Closed
wants to merge 43 commits into from
Closed

Java optional codegen #1858

wants to merge 43 commits into from

Conversation

aslakhellesoy
Copy link
Contributor

@aslakhellesoy aslakhellesoy commented Jan 5, 2022

Summary

Use custom code generation for Java

Details

Switch from using jsonschema2pojo-maven-plugin to using a custom code generator.

Motivation and Context

In order to have better type safety for non-required fields, we want the generated Java code to use Optional in getters. See cucumber/ci-environment#51 for context.

Types of changes

  • Bug fix (non-breaking change which fixes an issue).
  • New feature (non-breaking change which adds functionality).
  • Breaking change (fix or feature that would cause existing functionality to not work as expected).

Checklist:

  • The change has been ported to Java.
  • The change has been ported to Ruby.
  • The change has been ported to JavaScript.
  • The change has been ported to Go.
  • The change has been ported to .NET.
  • I've added tests for my code.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have updated the CHANGELOG accordingly.

@jamietanna
Copy link
Contributor

Is this required? I see useOptionalForGetters is an option for the Maven/Gradle plugin so it should mean we can avoid hand-writing stuff?

@aslakhellesoy
Copy link
Contributor Author

Oh nice! I didn't see it in the on the website and I also found joelittlejohn/jsonschema2pojo#1057 which led me to believe it wasn't supported. But here it is!

I'll take it for a spin.

@aslakhellesoy
Copy link
Contributor Author

aslakhellesoy commented Jan 6, 2022

The custom code generator I implemented would do requireNonNull checks in getters, setters and the argument constructor (not the no-arg constructor though). This will fail earlier in case we forget to set some required fields.

I could add a #validate() method as well which would do the same, and we could call this in the code that puts the messages on the event bus.

Worthwhile restoring the custom code generator for those purposes? I think so!

@aslakhellesoy aslakhellesoy marked this pull request as ready for review January 6, 2022 14:25
@aslakhellesoy
Copy link
Contributor Author

JSON Schema has a oneOf keyword.

I think it would be a lot of work to support this in our custom code generators, so I've opted for a simpler solution in 5daf24a.

WDYT?

@aslakhellesoy
Copy link
Contributor Author

Consider overloading Envelope.from(PropertyName propertyName) with different parameter types instead of naming the method differently for each property. Envelope.fromPropertyName(new PropertyName(....)) is very verbose.

Done! Been doing too much TypeScript lately...

@aslakhellesoy
Copy link
Contributor Author

aslakhellesoy commented Jan 12, 2022

I think it would be cleaner to wrap List constructor arguments in unmodifiable lists, to make the objects truly immutable. Agree?

@aslakhellesoy
Copy link
Contributor Author

I made them unmodifiable :-)

@mpkorstanje
Copy link
Contributor

I made them unmodifiable :-)

Great. It's not the same as immutable though. Nice little gotcha from way back then.

@aslakhellesoy
Copy link
Contributor Author

Good to merge @mpkorstanje ?

<%- end -%>
<%- end -%>

private void validate() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to remove this.

Copy link
Contributor Author

@aslakhellesoy aslakhellesoy Jan 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why @mpkorstanje? We're calling validate() in the constructors and the static from methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the constructors already have the requireNonNull for each required argument then what is there left to validate?

@mpkorstanje
Copy link
Contributor

Good to merge @mpkorstanje ?

I'll have to take a look at the generated code. But hard to say right now just from the git diff.

@mpkorstanje
Copy link
Contributor

Ah! Thanks that helps a lot!

Consider:

  1. making all fields final. This will allow the compiler to enforce that all fields are set in a constructor.
  2. Use the pattern this.field = requireNonNull(field); instead of a separate validate method.
  3. And I think that since java 17 the message can be omitted from requireNonNull as java can see which argument was the problem. Not sure about that.

The combination of the first two make it easy to read the code and see what are the required and optional fields

@aslakhellesoy
Copy link
Contributor Author

If we make all fields final we cannot use the current implementation of from. Instead we would have to implement from like this:

public static Envelope from(Source source) {
    return new Envelope(
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            Objects.requireNonNull(source, "Envelope.source cannot be null"),
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
    );
}

That's fine with me - is this what you're suggesting? With this we can also remove the private empty constructors, as well as the validate() method since the validation no longer needs to be called in from - it can be inlined in the constructor.

@mpkorstanje
Copy link
Contributor

Instead we would have to implement from like this:

No major objects. Though what was the reason we wanted to use from and not a 1-argument constructor?

@aslakhellesoy
Copy link
Contributor Author

Instead we would have to implement from like this:

No major objects. Though what was the reason we wanted to use from and not a 1-argument constructor?

So we don't have to create Envelope objects like this:

return new Envelope(
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            Objects.requireNonNull(source, "Envelope.source cannot be null"),
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
    )

@aslakhellesoy
Copy link
Contributor Author

Oh I see what you mean - might as well be a 1-arg constructor. I'll change to that in #1879

@aslakhellesoy
Copy link
Contributor Author

Closing in favour of #1879

@mattwynne mattwynne deleted the java-optional-codegen branch April 1, 2022 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants