Skip to content

#1479 Add support for Builders with multiple build methods#1498

Merged
filiphr merged 1 commit into
mapstruct:masterfrom
filiphr:1479-handle-multiple-build-methods
Jul 12, 2018
Merged

#1479 Add support for Builders with multiple build methods#1498
filiphr merged 1 commit into
mapstruct:masterfrom
filiphr:1479-handle-multiple-build-methods

Conversation

@filiphr

@filiphr filiphr commented May 20, 2018

Copy link
Copy Markdown
Member
  • Add new @builder annotation for defining a build method
  • When there are multiple build methods look for a method named build and if found use it
  • If @builder is defined than look for a build method with the defined method
  • When a type has multiple builder creation methods throw an exception and don't use the builder

Defaulting to a method named build will make sure that a correct method is selected for:

  • FreeBuilder - it has two methods: build and buildPartial
  • Protobuf - it has three methods: getDefaultInstanceForType, build and buildPartial

Fixes #1479
Edit: changed text to match the final commit

@filiphr

filiphr commented May 20, 2018

Copy link
Copy Markdown
Member Author

@chris922 You also had a look at the issue, if you have time you can also review this PR. Thanks 😄

@chris922

Copy link
Copy Markdown
Member

@filiphr I just had a quick look:

  1. In the reference documentation you are mentioning the new exceptions. As a user of mapstruct this would be a bit confusing for me, because - as far as I got it - those exceptions are used internally and are only exposed to the user as warnings/errors during processing and nothing I can catch. So is it really helpful to mention these exception names? Maybe for a custom SPI? (haven't yet checked the builder SPI)
  2. This will prevent implementing an own @AfterMapping that is calling the correct builder method in case one of the new exceptions would be thrown, isn't it? I still think as a user writing a custom SPI is rather complex. You may use different builder patterns in case you are using different libraries and the SPI must support everything together. So using @AfterMapping is for me still a good and easy solution to select the correct build method.

@filiphr

filiphr commented May 21, 2018

Copy link
Copy Markdown
Member Author

Thanks a lot @chris922.

  1. I mentioned the exceptions because they are in the public SPI package and they can be thrown by a custom SPI implementations. However, you are right, maybe I can remove them from the documentation and just mention what happens when that happens (no reference to exceptions).

  2. You might be right. I think that we need to move that to a separate issue. What we can do is to allow setting the methods to null and just passing a type. In such case we can even use an @ObjectFactory to create a builder instance and we probably need something like @ObjectCreator or whatever that can be invoked to produce a target instance. In any case I think that we need to do that in a new issue and target it for Beta2.

@sjaakd sjaakd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure whether this is the way to go.. Can't we provide a buildMethodName in the @Mapper and/or @MappingConfig? and have a decent default e.g. build? MapStruct should alleviate the user from too complicated stuff like SPI's ..

* The builder type has a parameterless public method (build method) that returns the type being build
In our example `PersonBuilder` has a method returning `Person`.
* In case there are multiple build methods, MapStruct will look for a method called `build` if such methods exists
than this would be used, otherwise a `TooManyBuildMethodsException` would be thrown.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

During compilation? Or is this part of the SPI? Normally we don't refer to exceptions in MapStruct. We just refer to the warnings / messages that occur during the compilation.. Perhaps its time to write a separate SPI guide..

GENERAL_NOT_ALL_FORGED_CREATED( "Internal Error in creation of Forged Methods, it was expected all Forged Methods to finished with creation, but %s did not" ),
GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible parameterless constructor." ),

BUILDER_TOO_MANY_BUILDER_CREATION_METHODS( "Too many builder creation methods for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Too many == More than one.. What is 'too many'? 1, 2, 5?

GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible parameterless constructor." ),

BUILDER_TOO_MANY_BUILDER_CREATION_METHODS( "Too many builder creation methods for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),
BUILDER_TOO_MANY_BUILD_METHODS( "Too many build methods in \"%s\" for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same as above?

*
* @throws TypeHierarchyErroneousException if the type that needs to be visited is not ready yet, this signals the
* MapStruct processor to postpone the generation of the mappers to the next round
* @throws TooManyBuildMethodsException if the Builder type has more than one method that can build the type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MoreThanOneBuildMethodException..

@chris922

Copy link
Copy Markdown
Member

MapStruct should alleviate the user from too complicated stuff like SPI's ..

I totally agree with this point. SPI should only be required for really complex stuff and not for basic stuff like deciding which build method should be called.

Having a new parameter for @Mapper/@MapperConfig sounds good to me.
Maybe this value should also be available in @BeanMapping to be able to decide this per mapping method and not just per mapping class?

@filiphr

filiphr commented May 22, 2018

Copy link
Copy Markdown
Member Author

@chris922 and @sjaakd I hear you 😄. I will try to find a better approach for this.

I agree that we need to make it easier for the users. The only issue is that the Type is created really early on and it's properties are required really early on, maybe even before we even touch the methods. Therefore it is a bit difficult too handle the more than one builder creation exception. Without an SPI there is no way for us to know the type for the builder and thus no way to know how to get the properties. I am open to ideas 😄

As for the build method, I would propose that we introduce some new annotation like @BuilderFinisher or whatever and we can then filter in the methods that we have and if we find one we invoke that method instead.

For example instead of doing return builder.build() we would do return builderFinisher(builder)/

Btw, I am travelling until Sunday so won't be able to work on this

@filiphr

filiphr commented Jun 2, 2018

Copy link
Copy Markdown
Member Author

@sjaakd and @chris922 I've thought a bit about this one. I will add a new annotation so people can hook in custom build method.

However, I am still struggling when there is more than one builder creation method. We need the type of the builder really early on in the compilation and we can use a method to get it as it would be too late. What is your suggestion about this? Is it OK to leave this for a custom SPI?

@sjaakd

sjaakd commented Jun 3, 2018

Copy link
Copy Markdown
Contributor

What is your suggestion about this? Is it OK to leave this for a custom SPI?

Is there a way to automatically detect what kind of builder pattern is present given the builder technology used? I think we did this recently as well, right?

@filiphr

filiphr commented Jun 3, 2018

Copy link
Copy Markdown
Member Author

Is there a way to automatically detect what kind of builder pattern is present given the builder technology used? I think we did this recently as well, right?

By builder technology do you mean Immutables, FreeBuilder etc, or something else?

The current mechanism works in the following way:

  • Find all public static methods on the type being build that have no parameters
  • For each of those methods get the return type and in that type look for a public method without parameters that has a return type same as the type being build

What we can actually work with is when there is more that one builder creation method to not use builders. However, if there is a single builder creation method and multiple build methods than we would assume the user has defined a BuilderFinisher or BuildFactory and we would invoke that to finish the building. The BuildFactory can always be used, in the same way the @ObjectFactory is used.

@sjaakd

sjaakd commented Jun 3, 2018

Copy link
Copy Markdown
Contributor

By builder technology do you mean Immutables, FreeBuilder etc, or something else?

or even protobuf (believe it uses a builder pattern as well, right?

What we can actually work with is when there is more that one builder creation method to not use builders. However, if there is a single builder creation method and multiple build methods than we would assume the user has defined a BuilderFinisher or BuildFactory and we would invoke that to finish the building. The BuildFactory can always be used, in the same way the @objectfactory is used.

So.. If i understand you correctly, in case of conflicts (multiple qualifying methods), you leave it to the user to point out the right "factory" for creation, and the right "finisher" so the user could delegate to the generated code, right?

Could we also use the qualifier approach? Because we normally use that to resolve conflicts somehow..?

@filiphr

filiphr commented Jun 3, 2018

Copy link
Copy Markdown
Member Author

So.. If i understand you correctly, in case of conflicts (multiple qualifying methods), you leave it to the user to point out the right "factory" for creation, and the right "finisher" so the user could delegate to the generated code, right?

Factory for creation would be to implement an SPI as we need the builder really early, even before we work with the methods. The finisher can be done with an annotation.

Could we also use the qualifier approach? Because we normally use that to resolve conflicts somehow..?

Not really, most of the time the builder code is generated, so there is no way for users to put the qualifiers. At least I don't see it, If you have some idea please do share it.

Example for the more than one builder creation method:

public Person {

    public static PersonBuilder builder() {}

    public static SecondPersonBuilder secondBuilder() {}
}

In the case of the builder creator for Person without an SPI we cannot figure out what is the type of the builder that we need, PersonBuilder or SecondPersonBuilder. If such case occurs the user will need to implement an SPI.

For the more than one build methods:

public class Vehicle {

    public static VehicleBuilder builder(){}

    public static class VehicleBuilder {

        public Vehicle create() {}
   
        public Vehicle otherCreate() {}
    }
}

In this case we can find the type of the builder (VehicleBuilder). However, the user will need to define something like:

@Mapper
public interface MyMapper {

    @BuilderFinisher
    default Vehicle build(VehicleBuilder builder) {
        return builder.otherCreate();
    }
}

With @BuilderFinisher the users would be able to define their own way of invoking the builder.

How does this sound?

@sjaakd

sjaakd commented Jun 3, 2018 via email

Copy link
Copy Markdown
Contributor

@sjaakd

sjaakd commented Jun 3, 2018

Copy link
Copy Markdown
Contributor

@filiphr .. I understand your approach.

However it would still take a considerable amount of work to do this for each method. If you only get 3 or 4 properties to map in a bean, you now need to implement possibly a creater and a finisher method. Do you know if the builders predominantly use a consistent naming convention that we could use? E.g. if its always createXYZ this could be considered a pattern. You could also do this on the @MappingConfig, @Mapping and @BeanMapping as two properties. Like builderCreatetor = "^createSecond.*$", builderFinisher = "^buildSecond.*$".

@filiphr filiphr force-pushed the 1479-handle-multiple-build-methods branch from 34031f6 to c15266e Compare June 9, 2018 11:58
@filiphr

filiphr commented Jun 9, 2018

Copy link
Copy Markdown
Member Author

@sjaakd, @chris922 I've changed the approach a bit. I took ideas from you, @sjaakd . There is now @BuilderMapping in which you can set the pattern for the finisher method.

I still need to update the Javadoc and the reference documentation, but wanted to get your opinion first. I went with a standalone annotation as that opens the path to meta annotations for 2.0 (perhaps).

WDYT?

@sjaakd sjaakd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@filiphr. Your approach looks fine.

I do have doubts on the new BuilderMapping annotation (see comments), it comes down to:

  1. it doesn't make sense if the user applies it to for instance MapMapping and IterableMapping (if you want to do it right, you need to check if that's the case and raise an error).
  2. We abandon the hierarchy (no MapperConfig level), right?

}
else {
BuilderMappingPrism builderMapping = builderMappingPrism( method, ctx );
String buildMethodPattern = "build";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

constant?.. E.g. DEFAULT_BUILD_METHOD_NAME?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Makes sense

ElementType.TYPE,
ElementType.METHOD
})
public @interface BuilderMapping {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the new meta annotation could be confusing for the user. If we do this, we should probably do it for all in 2.0.

Consider this: Would we 'normally' have made this a property of BeanMapping (we use a hierarchy
from @MapperConfig to @Mapper) ? In other words: does it make sense to use this mapping also on MapMapping, ValueMapping or StreamMapping methods?

Can you apply this BuilderMapping on MappingConfig level as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You are right. It might not make sense to MapMapping. However, it will make sense during the computation of the intermediate methods for the keys / values mappings. In my opinion we need to change the MapperConfig and allow use of meta annotations in 2.0. The mapper config might even not be needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In my opinion we need to change the MapperConfig and allow use of meta annotations in 2.0. The mapper config might even not be needed.

Ok.. I can live with that.. But.. shall we take it in 2.0 then? Now it looks like we mix two strategies.. So in 2.0 do a major overhaul of the way we set configuration/ handle annotations. Have MetaMapping definitions of property mapping as well (in stead of having the need to base class).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes we do that in 2.0. I will apply the changes here.

}
for ( ExecutableElement buildMethod : buildMethods ) {
String methodName = buildMethod.getSimpleName().toString();
if ( methodName.matches( buildMethodPattern ) ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we also check that the method has no args, has a return type?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes we could. However, currently we are making sure that this can't happen in the DefaultBuilderProvider. We can even make that as part of the contract of the BuilderInfo.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok

})
public @interface BuilderMapping {

String builderFinisher() default "build";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I thought the problem was 2 fold: builderFinisher and builderCreator.. where's the latter one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The builderCreator one is a more complex one. We need the type of the builder at the time of the creation of the Type itself. As we need the properties really early on, when we are constructing the Target and Source references. That's why that is not hear, and I am having hard time pulling that here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok.. but then leave it open as a new issue / improvement.. From a user perspective, its strange, agreed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll create an issue for that. yes it might be a bit strange from a user perspective.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Issue created #1547

if ( builderInfo == null ) {
return null;
}
ExecutableElement buildMethod = builderInfo.getBuildMethod();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this check obsolete?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Probably this check belongs to the BuilderFinisherMethodResolver now. As we can check the method return type there. Although this can't happen with the default builder provider as we have this check there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok

@filiphr

filiphr commented Jun 10, 2018

Copy link
Copy Markdown
Member Author

@sjaakd it might actually make sense to apply it on MapMapping or IterableMapping. The reason is that we are constructing intermediary methods in there, so if they need builders we can use that from there (although I think it is not passed appropriately now).

  1. Yes my idea was to go with meta annotations for the future. In any case I can change this and apply the BuilderMapping as a field in the Mapper, MapperConfig and BeanMapping in order to be consistent with the current paradigm.

How should I proceed with this one?

@sjaakd

sjaakd commented Jun 10, 2018

Copy link
Copy Markdown
Contributor

How should I proceed with this one?

I would prefer to have a consists meta annotation interface in 2.0 and (a consistent one now) 😄

//If we reach this method this should never happen
return null;
}
else if ( buildMethods.size() == 1 ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If a @BuilderMapping was defined with an explicit name for the builderFinisher, but only a different method was found, the wrong method will be used. The user would not notice this.

In case he entered a builderFinisher I would expect a warning or even better an error in case the only method found has a different name. The issue is most likely how we should know that he explicitly entered a name, because you defined build as a default name in the annotation. So we will always have a builderFinisher as soon as the @BuilderMapping annotation (or whatever it will be at the end) will be used.

The only way I see to solve this issue is to use an empty string as a default annotation attribute value and in this case use build as a constant value here in the code (the DEFAULT_BUILD_METHOD_NAME).
And switch the if-else ordering.. if a builderFinisher was defined try to use a method with exactly this name, if not possible -> error. If nothing was defined fallback to the way you are doing it right now (size == 1, then methodName equals build).

GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible parameterless constructor." ),

BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD( "More than one builder creation method for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),
BUILDER_MORE_THAN_ONE_BUILD_METHOD_DEFAULT_PATTERN( "More than one build method in \"%s\" for \"%s\". Found methods: \"%s\". Using default \"build\" method pattern. Consider to add @BuilderMapping in order to select the correct build method.", Diagnostic.Kind.ERROR ),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This "Using default .. method pattern" sounds confusing for me. Just after reading this message I thought this is an INFO that he found more than one possible build method, but uses at the end the one with the name build. Maybe something like:

More than one build method in \"%s\" for \"%s\". Found methods: \"%s\", but no method is named \"build\". Consider to add @BuilderMapping in order to select the correct build method.


BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD( "More than one builder creation method for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),
BUILDER_MORE_THAN_ONE_BUILD_METHOD_DEFAULT_PATTERN( "More than one build method in \"%s\" for \"%s\". Found methods: \"%s\". Using default \"build\" method pattern. Consider to add @BuilderMapping in order to select the correct build method.", Diagnostic.Kind.ERROR ),
BUILDER_MORE_THAN_ONE_BUILD_METHOD_DEFINED_PATTERN( "More than one build method in \"%s\" for \"%s\". Found methods: \"%s\". Using defined \"%s\" method pattern.", Diagnostic.Kind.ERROR ),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above, maybe:

More than one build method in \"%s\" for \"%s\". Found methods: \"%s\", but no method is named \"%s\" as defined in @BuilderMapping#builderFinisher.

@filiphr filiphr force-pushed the 1479-handle-multiple-build-methods branch from c15266e to d46e7b8 Compare July 8, 2018 21:19
@filiphr

filiphr commented Jul 8, 2018

Copy link
Copy Markdown
Member Author

I've finally found the time to go over the changes in this PR 😄.

Thank you @chris922 and @sjaakd for the reviews.

I've applied both of your comments.

  • We now have the @BuilderMapping as part of the @BeanMapping, @Mapper and/or @MapperConfig
  • I've adapted the error messages a bit, they should be more descriptive now.

If you have time let me know what you think.

@filiphr filiphr force-pushed the 1479-handle-multiple-build-methods branch from d46e7b8 to 1800a00 Compare July 8, 2018 22:17
@sjaakd

sjaakd commented Jul 9, 2018

Copy link
Copy Markdown
Contributor

Hey Filip.. Looks more consistent.

I'm still a bit struggling whether we want to have annotations within annotations to solve this problem. I know you are considering some kind of meta annotation mechanism for 2.0.

However, It would be more usable (and step-up to 2.0) if you could define an annotation as a user (a bit like we do with qualifiers), grouping specific @BuilderMapping properties for reuse and mark that annotation with @BuilderMapping

So:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@BuilderMapping(builderFinisher = "create")
public @interface BuilderMappingForFrameworkX { }
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@BuilderMapping(builderFinisher = "wrongCreate")
public @interface BuilderMappingForFrameworkY { }

You should then also be able to use the @BuilderMappingForFrameworkX and @BuilderMappingForFrameworkY directly :

@Mapper(builder = @BuilderMappingForFrameworkX)
public interface BuilderMappingDefinedMapper {

    BuilderMappingDefinedMapper INSTANCE = Mappers.getMapper( BuilderMappingDefinedMapper.class );

    Process map(Source source);

    @BeanMapping(builder = @BuilderMappingForFrameworkY)
    Process wrongMap(Source source);
}

If this is possible, we could:

  1. Extend this mechanism to other places (e.g. reusable pieces of mappings instead of the @InheritConfiguration / @InheritInverseConfiguration
  2. Define our own extra's... e.g. @BuilderMappingForProtoBuf, @BuilderMappingForLombok

Note: Having only one property in @BuilderMapping makes it a bit hard to see where you are heading for.

Shall we discuss this in our Tuesday meeting?

EDIT:

  1. if possible, in 2.0, we could have a generic handling of the stuff above. Possibly resolving configuration annotations on the same level also we do @Mapper
  2. next to the options above it should be possible to define the mapping in line as well (your approach). So we could see the above as an extension.
  3. if we are going for meta annotations, providing this approach does work, we should do this for all configuration stuff, in order to keep the api consistent. This means that 2.0 will not be backward compatible with 1.x
  4. I don't want to make the new way of configuring blocking for 1.3: going for a non-annotation property would also be fine for me.

EDIT II:

  • you should probably squash and adapt the commit name. You don't throw any exceptions anymore 😄

import java.lang.annotation.Target;

/**
* Annotation that can be used to provide a custom finisher for the builders in case of multiple build methods.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's not repeat that it's an annotation, that's clear from its definition:

Configuration of builders, e.g. the name of the final build method.

*/
@Retention(RetentionPolicy.CLASS)
@Target({})
public @interface BuilderMapping {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should it be marked as @Incubating or are we sure that it won't be altered incompatibly down the road?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We have @Experimental I think we can use that instead

@filiphr

filiphr commented Jul 10, 2018

Copy link
Copy Markdown
Member Author

Rename @BuilderMapping should be renamed to @Builder and builderFinisher into buildMethod

* Add new @builder annotation for defining a build method
* When there are multiple build methods look for a method named `build` and if found use it
* If @builder is defined than look for a build method with the defined method
* When a type has multiple builder creation methods throw an exception and don't use the builder

Defaulting to a method named `build` will make sure that a correct method is selected for:
* FreeBuilder - it has two methods: `build` and `buildPartial`
* Protobuf - it has three methods: `getDefaultInstanceForType`, `build` and `buildPartial`
@filiphr filiphr force-pushed the 1479-handle-multiple-build-methods branch from 1800a00 to 2c7e102 Compare July 12, 2018 20:48
@filiphr filiphr changed the title #1479 Throw an exception when there are multiple builder creation and build methods #1479 Add support for Builders with multiple build methods Jul 12, 2018
@filiphr

filiphr commented Jul 12, 2018

Copy link
Copy Markdown
Member Author

I've squashed all commits, did the rename, update the commit message to:

  • Add new @builder annotation for defining a build method
  • When there are multiple build methods look for a method named build and if found use it
  • If @builder is defined than look for a build method with the defined method
  • When a type has multiple builder creation methods throw an exception and don't use the builder

Defaulting to a method named build will make sure that a correct method is selected for:

  • FreeBuilder - it has two methods: build and buildPartial
  • Protobuf - it has three methods: getDefaultInstanceForType, build and buildPartial

I also updated the documentation. Once the build is done, I'll do the merge

@filiphr filiphr merged commit ef270ca into mapstruct:master Jul 12, 2018
@filiphr filiphr deleted the 1479-handle-multiple-build-methods branch July 12, 2018 21:16

@chris922 chris922 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@filiphr I just went quickly through the javadocs, just smaller improvements - hopefully I was faster then you noticed that the build was successful 😄

... I wasn't, damn! 😋 - anyway nothing really important

/**
* The name of the build method that needs to be invoked on the builder to create the type being build
*
* @return the method that needs to tbe invoked on the builder

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

to be :)

* NOTE: In case no builder is defined here, in {@link BeanMapping} or {@link MapperConfig} and there is a single
* build method, then that method would be used.
* <p>
* If the builder is defined and there is a single method that does not match the name of the finisher than

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the name of the build method as we don't use finisher anymore?

* build method, then that method would be used.
* <p>
* If the builder is defined and there is a single method that does not match the name of the finisher than
* a compile error will occurs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

occur.

* @return the builder information
*
* @since 1.3
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here

So for example `Person` has a public static method that returns `PersonBuilder`.
* The builder type has a parameterless public method (build method) that returns the type being build
In our example `PersonBuilder` has a method returning `Person`.
* In case there are multiple build methods, MapStruct will look for a method called `build` if such methods exists

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

method

@filiphr

filiphr commented Jul 12, 2018

Copy link
Copy Markdown
Member Author

Thanks for the final checks @chris922. I'll commit those directly on master 😄

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.

4 participants