-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Adding cond mapping #2148
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
Adding cond mapping #2148
Conversation
* mapstruct#2139 reproducer * mapstruct#2139 solution * mapstruct#2139 license
…hod (mapstruct#2141) * mapstruct#2135 improved messages for not able to select qualified method
|
I'm a bit surprised by the JDK 13 fail - I don't get these locally. Has anybody seen a similar error? |
|
Thanks for the PR @Desislav-Petrov, we are a bit busy with the 1.4 release currently. We will try to find some time to do a proper review on this PR. Don't worry about the JDK 13 failure, for some reason there are issues like that sporadically on GitHub actions. |
|
Hi, Did you manage to take a look? Thanks, |
filiphr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I finally found the time to do this review.
I left some comments, but those are minor.
I have something more general to say. Thank you for your time working on this PR, it is always nice to get such contributions. However, you'll need to iterate on it again. Currently MapStruct will use what you write in condition without any checks whether the method exists, the source type matches, the method returns boolean etc. There is no discovery of condition methods, which means that it is not possible to define conditions in a util class and use it via Mapper#uses (which is what I assume most people will use). The reason why we do those things is because we want to provide the experience to our users. Do not compile the code if there are certain errors that we can detect and also allow them to use @Condition in case they define it without them needing to write anything in condition.
I hope that you will understand our position on this and the reason why we can't merge this PR in this state.
| * | ||
| * @return The name of the condition method to be evaluated when trting to do the given target mapping | ||
| */ | ||
| String condition() default ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example that you are using from the defaultExpression is not something valid for condition.
I would suggest the following:
Example:
@Mapping(
target = "name",
condition = "isNotBlank"
)
generates:
if ( isNotBlank( source.getName() ) ) {
target.setName( source.getName() );
}
And the condition method should be defined as:
boolean isNotBlank(String value);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that this makes it really flexible:
target="name", condition="isNotBlack" always assumes that the condition will be based on the source's corresponding field but this might not be the case.
It would be much more flexible if the condition method is of the form:
boolean someName(SourceObject type)
Let me know what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can follow both ideas. I like having the flexibility @Desislav-Petrov proposes with getting the whole source object to do some checks on this one.
But keeping it strict to the value to which the check is assigned as @filiphr proposes makes it easier to reuse the condition methods. Sticking to the isNotBlank example: This could be used for all String source values, getting the whole source type will make it more complicated to reuse it for other source values.
One idea to support both: Default to @filiphr proposals and if there is an (not annotated) argument for the method this will be the source value.
And also support if an argument was annotated with @MappingSource the source object will be used (do we already have an annotation like this? tbh I dont remember, just can find @MappingTarget that maybe could also be supported?!)
|
|
||
| private static final Pattern JAVA_EXPRESSION = Pattern.compile( "^java\\((.*)\\)$" ); | ||
|
|
||
| public String getCondition() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please put this getter next to the other getters in this class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep
|
Hi Filip, Thanks for the review I did expect I'd have to iterate over. My intention was to put something as a starting point as it's my first commit to the project, get your feedback and move on from there. Will try to address your comments shortly. Thanks |
|
@Desislav-Petrov are you still interested and have time to work and finish this feature? Please let us know if that is not the case in order for us to take over and drive it home. |
|
Sorry for the delay - I'm still interested, will try to put updated pr this week. |
� Conflicts: � processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java � processor/src/main/java/org/mapstruct/ap/internal/util/Message.java � processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java
| } | ||
|
|
||
| //check if there's a condition and if the method exists | ||
| if ( mapping.getCondition() != null ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried to do this check in a few different places but this one seems to be the most appropriate one. Could somebody please confirm? If so, I'll add a check for the conditional method param type.
|
Hi @filiphr , I've done another pass and would appreciate feedback on a few things:
It will take me a few iterations to get there, but I'm sure with a bit of help we will have this feature closed shortly. Thanks |
|
Hey @Desislav-Petrov, What I had in mind is to do something similar like we've done with the following:
All those go through The examples above should be able to answer your question 1 and question 3. OK I read #2148 (comment) and I understand your point. You would like to pass the entire object to determine if something is there or not. To be honest I didn't really think about that possible use case. I was mostly thinking in the direction of checking if a String is not empty / blank, checking that a number is different than something, etc. Basically making this not aware about the source of the location. At this moment I am not sure whether this makes sense or not. I would advise tackling the first part and then we can iterate again and think about this one. |
|
Hey @Desislav-Petrov, we are planning our 1.5 release and we would like to include this feature as well. Do you think that you will have time to drive this to the end or do you want some of us to take it over and finish it? |
|
Hey @filiphr, sorry about the delay, been quite tied up at work. I'm still willing to get this done so I should have another PR the coming week. |
|
Hi @filiphr, I've been playing around and managed to get the MethodSelectors working for the conditions that you declare as a part of the interface/abstract class itself. However, it gets more complicated when you consider adding conditions from external static utilities. First of all, what would you do with the syntax.. would it be condition = fully.qualified.ClassName.method ? Then, when it comes to the resolution, now you are working with classes that aren't define anywhere in the mapper (not even imported). How would you go about verifying if this exists and has correct target/source params - I'm not sure the examples for lifecycle/object factory cover this case? We can potentially introduce something like @condition, then pull all methods that have this annotation and do the method selection on them? |
We currently have similar constraints for other types of methods for MapStruct. Therefore, I would say that ideally you will need to create your own utilities and annotate them. Keep in mind that the method selectors use all classes that are available via
I would try to entirely base the Perhaps for the future we can add However, for this PR I would like to focus on treating |
|
Hi, I've done another pass now, main points:
If you're happy with that I can do another small polishing pass in terms of any additional exception handling, namings etc. Regards, |
| * | ||
| * @return The name of the condition method to be evaluated when trting to do the given target mapping | ||
| */ | ||
| String condition() default ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can follow both ideas. I like having the flexibility @Desislav-Petrov proposes with getting the whole source object to do some checks on this one.
But keeping it strict to the value to which the check is assigned as @filiphr proposes makes it easier to reuse the condition methods. Sticking to the isNotBlank example: This could be used for all String source values, getting the whole source type will make it more complicated to reuse it for other source values.
One idea to support both: Default to @filiphr proposals and if there is an (not annotated) argument for the method this will be the source value.
And also support if an argument was annotated with @MappingSource the source object will be used (do we already have an annotation like this? tbh I dont remember, just can find @MappingTarget that maybe could also be supported?!)
Co-authored-by: Christian Bandowski <[email protected]>
filiphr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your changes @Desislav-Petrov.
I think that I found the the reason why you need @Ignore. Have a look at my comment.
In any case, I still stand by my statement that we should only check based on the mapping type and treat this like a presence check. I don't like the fact that you need to pass the entire object. I would prefer something like isNotBlank and the likes.
We can iterate on it and add support for passing the source object as well. However, the biggest functionality for me currently is to make this work like an external presence check without knowing the source type.
Not that I don't see the benefit of the examples in this PR, but in my opinion these are not covering the biggest number of use cases that I see for this type of feature.
| VALUEMAPPING_ANY_REMAINING_FOR_NON_ENUM( "Source = \"<ANY_REMAINING>\" can only be used on targets of type enum and not for %s." ), | ||
| VALUEMAPPING_ANY_REMAINING_OR_UNMAPPED_MISSING( "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" is advisable for mapping of type String to an enum type.", Diagnostic.Kind.WARNING ), | ||
| VALUEMAPPING_NON_EXISTING_CONSTANT_FROM_SPI( "Constant %s doesn't exist in enum type %s. Constant was returned from EnumMappingStrategy: %s"), | ||
| VALUEMAPPING_NON_EXISTING_CONSTANT_FROM_SPI( "Constant %s doesn't exist in enum type %s. Constant was returned from EnumNamingStrategy: %s"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this change needed? This is the reason for the test which is failing and you disabled it with @Ignore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry - my bad, think it went wrong when I was using IntelliJ renaming refactor, sorted out now.
So based on your previous comment, shall I take that you aren't willing to include this PR?
I see the reasoning for the presence checks approach but I think I see it as complimentary to what I've tried to achieve here, which is based on a real usecase I had for mapstruct.
This PR will not go to waste. I will get it locally and try it out. Maybe I can do some polishing and make sure that both use cases are covered. |
|
Sure, thanks. What I suggest we can do is to try to get it to an MVP state for the 1.5 release. After 1.5 is out, I'm happy to continue enhancing the 'conditional mapping' feature if we have a good idea what we want the end state to look like. |
|
Closing this since the reworked PR #2403 has been integreated. Thanks a lot for your contribution @Desislav-Petrov. Please give the SNAPSHOT a go and let us know whether it works for you or not. |
|
@filiphr I've seen the polished PR, and yes, makes sense to me and covers the usecase. Thanks for the continuous guidance during the work on this issue, I'm a lot more familiar with the codebase now so hope to be back soon with more PRs :) |
Related to: #2051