Skip to content

#1135 added test and implementation for overloading#1136

Closed
quantumlexa wants to merge 1 commit intomapstruct:masterfrom
quantumlexa:1135
Closed

#1135 added test and implementation for overloading#1136
quantumlexa wants to merge 1 commit intomapstruct:masterfrom
quantumlexa:1135

Conversation

@quantumlexa
Copy link
Copy Markdown

@quantumlexa quantumlexa commented Mar 12, 2017

Mapstruct does support overloading of target setters(see: Issue892Test test) but choosing preferred type logic is based on getter type of target - not source, which looks a bit strange.
Here is straightforward implementation of this

@mapstruct-robot
Copy link
Copy Markdown
Collaborator

(Message from the pull-request-builder): Admins, please verify this patch for it to build in the pull-request-builder.

@gunnarmorling
Copy link
Copy Markdown
Member

ok to test

Copy link
Copy Markdown
Member

@filiphr filiphr left a comment

Choose a reason for hiding this comment

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

@quantumlexa a really good addition to the framework. Thanks a lot for doing the PR. In general it is good.

I have some comments inline.

Maybe it will be good to add some tests for which MapStruct will generate a forged method (see NestedBeanMappingsTest). You can add fields to those places as well if you want to.

Map<String, Accessor> accessors = method.getResultType().getPropertyWriteAccessors( cms );
Map<String, Accessor> srcReadAccessors = Collections.emptyMap();
if (sourceMethod instanceof SourceMethod) {
srcReadAccessors = ( (SourceMethod) sourceMethod ).getGetSourceReadAccessors();
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.

I don't think that it is entirely correct to use this only for SourceMethod(s). We also have the ForgedMethod(s) that are used for nested target mappings, automatic name based methods creation etc. Maybe you can add a method to the new MappingMethodUtils (you'll have to rebase on top of the latest master).

On the other side, you won't be able to use memoization. You can add the getGetSourceReadAccessors() or even name it getSourceReadAccessors to the Method interface and do memoization where needed (SourceMethod and ForgedMethod, the others can just return empty map`).

return presenceCheckers;
}

public Map<String, Accessor> getPropertyWriteAccessors( CollectionMappingStrategyPrism cmStrategy ) {
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.

Can you copy the previous Javadoc here and add the sourceAccessor(s) parameter to the new method signature?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done


private Type determinePreferredType(Accessor readAccessor) {
private TypeMirror extractTypeMirror( Accessor accessor ) {
return accessor.getExecutable().getParameters().get( 0 ).asType();
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 is wrong it can lead to NPE. The accessor does not always have an executable. MapStruct can also map public field and that is why we have introduced the Accessor. I think that this can be done by only using the MapStruct Type and not the TypeMirror. Maybe you can also use Type in the isBetter check as well.

*/
public static List<Accessor> getAllEnclosedAccessors(Elements elementUtils, TypeElement element) {
public static List<Accessor> getAllEnclosedAccessors( Elements elementUtils, TypeElement element ) {
if (element == null) {
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.

Did you have problems with an NPE here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes


@WithClasses({
SourceTargetMapper.class,
SourceTargetMapperPropertyBean.class,
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.

What is the difference between the SourceTargetMapper and the SourceTargetMapperPropertyBean is it only the @Mapping annotation? Btw. when the source and target have the same name you don't need it. If you want to test that it works for different name you can just add new fields :).

Source.class,
Target.class
})
public void testShouldGenerateCorrectMapperImplementationJsr() {
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.

Why do you need this test method? Also the @WithClasses on the method is not needed, because those classes are already added on the class level. We usually add @WithClasses to methods when we want to test errors within the mappers.

public void testShouldGenerateCorrectMapperImplementation() {
Source source = new Source( new Date() );
Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source );
Assert.assertTrue( target.getUpdatedOn() > 0 );
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.

Can you use assertj Assertions here? I would also add the implementation class as a comparison fixture to the GeneratedSource rule. You can have a look at the ArrayMappingTest and the ScienceMapperImpl.java fixture for how it works.

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.

As this PR adresses only a behavioral change and doesn't structurally change the generated code, I don't think we need that fixture here - we should try keep the usage of those to a minimum, as we'll always need to keep maintaining them in the future... 🙂

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.

@agudian is right for the fixture. Just to make sure that we don't miss this in the future, maybe the setUpdateOn(long updateOn) in the Target should throw an exception. That method should not be called.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

removed body of test at all. It should compile ant that's enough.

@quantumlexa quantumlexa force-pushed the 1135 branch 2 times, most recently from a29f5ba to b099446 Compare March 31, 2017 02:49
Copy link
Copy Markdown
Member

@filiphr filiphr left a comment

Choose a reason for hiding this comment

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

There are still some issues with the PR:

  • I think that by mistake you have reverted the changes you had (the commits that moved the getGetSourceReadAccessors to the Method interface and it's renaming
  • This solution only works if the target and source properties have the same name. If they are different, but they are matched with @Mapping then it doesn't work

return getMappingTargetParameter() != null;
}

public Map<String, Accessor> getGetSourceReadAccessors() {
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.

It looks like your last version was reverted to before you moved the getSourceReadAccessors to the Method interface.

* @return an unmodifiable map of all write accessors indexed by property name
*/
public Map<String, Accessor> getPropertyWriteAccessors( CollectionMappingStrategyPrism cmStrategy ) {
public Map<String, Accessor> getPropertyWriteAccessors( CollectionMappingStrategyPrism cmStrategy,
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 will only work when the source and target properties are the same.

If you modify the name in either Source or Target and accordingly use @Mapping then it'll fail.

}

private TypeMirror extractTypeMirror( Accessor accessor ) {
return accessor.getExecutable().getParameters().get( 0 ).asType();
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.

In theory you should not be able to get up to here if your Accessor is field. However, I think that a null check needs to be done on the accessor.getExecutable().

@filiphr
Copy link
Copy Markdown
Member

filiphr commented Mar 31, 2017

@agudian, I had another look at the PR. There are some issues which are addressed in my second review. However, I don't think that there is an issue with the selectors:

  • If none of the source accessors can be matched to the target then the type of the getter is used
  • If a source accessor is used, then a direct mapping will always be done, because the type of the source is either the same as the target, or it is assignable to the target.

@quantumlexa Can you perhaps add more properties and make sure that all the branches in Type#isBetter are tested?

@quantumlexa
Copy link
Copy Markdown
Author

due to some errors with git branch - recreated it in: #1136

@filiphr
Copy link
Copy Markdown
Member

filiphr commented Apr 2, 2017

PR moved to #1167

@filiphr filiphr closed this Apr 2, 2017
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.

5 participants