PR for issues #131, #2438, #366#2512
Conversation
mapstruct#131, mapstruct#2438, mapstruct#366: Added support for SubClassMapping annotations. This will allow for hierarchy mappings.
|
Wow, thanks a lot for this PR @Zegveld. I haven't gotten around in reviewing this (picking the low hanging fruits first). However, we would look into this to make the 1.5.0.Beta2 release |
filiphr
left a comment
There was a problem hiding this comment.
Just finished with the review on this one. Really nice work @Zegveld.
I've left some comments inline and I have some more general comments here.
I see that you are using the _bugs folder. We use this usually for actual bugs, not new features. I would suggest instead to use subclassmapping as top package and put your test there. You can then use different sub packages to show different use cases. In addition to that I am looking at the tests for #131 and #366. Correct me if I am wrong, but the only difference is the fact that in #131 Vehicle is not abstract, right?
Let's try to merge these things together and use the same source types and different target types. Perhaps you can also rename the source to VehicleEntity or somehow else, in order to avoid using full qualified names in the tests to make it more readable.
Can we also add some fixture tests, to see how the generated code looks like? As you know we strive to generate code which looks human readable 😄 .
Can we also add some erroneous tests to test the non happy paths for the new messages.
I am really excited in getting this in the upcoming 1.5.
Something that could be good, but maybe we need to do it in a separate PR is to validate that the SubClassMapping make sense. For example if the first sub class mapping does not hide the second one.
e.g.
interface Vehicle { }
interface Car extends Vehicle { }
class SUV implements Car { }
class Sedan implements Car { }
And then if you defined sub class mapping like:
@SubClassMapping(source = Vehicle.class, target = ...)
@SubClassMapping(source = Car.class, target = ...)
Those subclass mappings will generate code which will never be executed.
e.g.
if (source instanceof Vehicle) {
// ...
} else if (source instanceof Car) {
// ...
}
After having written all of this, I think it makes sense to tackle it in a different PR. Let's do it like that, once we have this merged.
| for ( SourceMethod method : methods ) { | ||
| for ( SubClassMappingOptions subClassMapping : method.getOptions().getSubClassMappings() ) { | ||
| Type type = typeFactory.getType( subClassMapping.getTargetClass() ); | ||
| extraImports.add( type ); | ||
| type = typeFactory.getType( subClassMapping.getSourceClass() ); | ||
| extraImports.add( type ); | ||
| } | ||
| } |
There was a problem hiding this comment.
I do not think that this is needed. The SubClassMapping already implements the getImportTypes, so you only need to call that for each subClassMapping in the BeanMappingMethod#getImportTypes
| public List<SubClassMapping> constructSubClassMappings(List<Parameter> parameters) { | ||
| subClasses.forEach( subClass -> subClass.updateWithParameters( parameters ) ); | ||
| return subClasses; | ||
| } |
There was a problem hiding this comment.
We strive for the models to be immutable if possible. This code is invoked from the BeanMappingMethod.ftl. Can we do this in a different place and only get the subClassMappings here?
| ENUMMAPPING_ILLEGAL_TRANSFORMATION( "Illegal transformation for '%s' EnumTransformationStrategy. Error: '%s'." ), | ||
|
|
||
| SUBCLASSMAPPING_ILLEGAL_SUBCLASS("Class '%s' is not a subclass of '%s'."), | ||
| SUBCLASSMAPPING_METHOD_SIGNATURE_NOT_SUPPORTED("SubClassMapping annotation can not be used on Suppliers and Runnables."), |
There was a problem hiding this comment.
I do not think that this error message is clear. Having void return type means that the user is using update mappings, most likely we do not support that for now, so I would change the message to something like that.
By the way, you can do update mappings with a return type e.g. Target update(@MappingTarget Target target, Source source)
There was a problem hiding this comment.
So something like: @SubClassMapping do not support update mappings.?
Open for suggestions to make this message better, because the one above doesn't feel quite right either.
Edit: Probably still needs more work but something for the second review round.
|
By the way @Zegveld in #131 (comment) you are saying that you are not happy with the implementation route you took? Can you explain why? I personally thought that it is quite neat 😄 . |
Well that was before I made a lot more changes where in I made it quite neat ;) |
|
Only done the bits so far where I've added a thumbs up. |
No problem @Zegveld, take as much time as you need. Please ping me once the PR is fully ready for the next iteration of review. |
…f on the run each call.
|
@filiphr , Think I've done all the adjustments. So that part is ready for a next iteration of review. Hopefully I haven't missed anything. |
|
Just read the first review again and noticed the bit about: Also added a warning for |
|
I've added fixture tests. Made these separate so that the main variants are covered and not impacted by changes within the general tests. |
sjaakd
left a comment
There was a problem hiding this comment.
First of all: what a tremendous feat of work. Thanks!. When discussing this with @filiphr I was amazed by the effort done on this.
I did not check the complete implementation. I recon that @filiphr did that already. So I focussed on the API extensions and the implications there.
Now: we do like to go more into meta-annotations in 2.0. So, isolating functionality (sourceClass and targetClass) into a new annotation is a good idea. I've one consideration here though.
MapStruct recognises a few major classes of mappings: mapping bean-to-bean, mapping iterable-to-iterable, map-to-map and value-to-value. BeanMapping obviously the most important one. Also, each comes with its own annotation for doing configuration: @BeanMapping, @IterableMapping, @MapMapping, @ValueMapping.
There is check functionality that guarantees that a MapMapping is not accidentally applied to @BeanMapping. In fact, we do have a small problem with the just released map-to-bean-mapping, which under some conditions gets mixed up with bean mapping.
Having said that: would it not be better to make @SubclassMapping a property of @BeanMapping? Although more verbose, it would keep functionality together and make sure @SubclassMapping is not accidentally applied to another form of mapping.
In 2.0 we could give those mappings a more " independent life", perhaps making even use of the fact that they are part of a @BeanMapping. But lets do that step-by-step.
Next, I was thinking how this functionality would interplay with the new MapToBean functionality. Do we have an idea 'bout that?
anyway @Zegveld : what a nice piece of work on an issue which has a long history. Really looking forward to work with you on more issues in the future.
|
Thanks for the review @sjaakd. However, I have to say that I do not quite agree with moving Adding it to the
@Zegveld you mean the refactoring you did, right? I found it really great, already said that in #2512 (comment) 😉 . @Zegveld I've been a bit busy, so I haven't gotten around in finishing the review with your last changes. I haven't forgotten about this one yet. |
I didn't think about this initially, but it would be possible to do submappings for iterables or maps as well. This would be useful if the mapping is reused multiple times, but the implementation collection varies. And you could even support implementations of the Collection framework that you do not know exist. (e.g. project specific ones)
I think that could turn out to be an adjusted variant of the which could result in something like:
Likewise.
@filiphr, no I did a refactor which changes several Set's into specifically LinkedHashSet's. Was talking about that one.
No problem I've got plenty of waiting time. Currently implementing just a lot of instanceof checks manually when I'm using it instead of generating them. :) |
You have a point here. Did not consider that. I think @filiphr and I were discussing the change of the default implementation (don't remember if it was
That would be really a niche feature. I was discussing mis-use of annotations with @filiphr just earlier and he pointed out that we don't have (presumably) checks for Finally, applying it to the new map-to-bean would indeed qualify as a follow up PR. So, as far as I'm concerned: go ahead. |
|
I checked out the PR locally today and did some changes in it. I've pushed the changes to https://github.com/filiphr/mapstruct/tree/feature/PR-131 (I couldn't push it to your branch for some reason, most likely you have not allowed maintainers to push changes to it). Can you please have a look at them and let me know whether you agree with them @Zegveld? I tried to do them atomically in order to show my thought process for some of them, everything will be merged into one commit in the end.
I saw that one, and I didn't really like it. I do not like having collection implementation types as fields of classes or parameters. I reverted some of that change in a separate PR. Some other more notable changes I did are:
Some final things to address before merging:
I think that this would be the last change I request, together with my changes the PR would be ready for merging and doing a beta release shortly after that. It would be a great addition to the 1.5 release 😄 Side note: Since your email address is private, can you please reach out to me somehow? You could use Twitter or my email address (it is in my commits). I'd like to discuss something with you. |
* getImportType() is already implemented in the parent * getThrownTypes() is needed, since it is possible that an already existing method is used and that one throws an exception
|
I've looked through the changes and they are indeed an improvement as to what I did. Concerning the IllegalStateException: side note: I've sent you an e-mail. |
That's not what I had in mind. All of that is runtime behaviour and not compile time behaviour. From a runtime point of view the runtime the What I had in mind is that we do not do some of the changes you did in the
Once we have support for sealed types we can avoid that part if all of them have been covered. In order to see what I mean you can remove the
Thanks for reaching out to me. |
|
I did take that route under consideration, but the use case that I had which caused me to write this enhancement was one where I never have an implementation for general usage. In this use case the classes are generated from two xsd's and I need to transfer information between the two models. The best solution I can think of usage side without having the exception would be to manually create an implementation/extension of the base class which then would throw an IllegalStateException in it's constructor because it should not be created. That one would then be the default implementation that mapstruct falls back to. This does not feel that good as a solution for this problem. What if we add another annotation |
That's what I head in mind when I was saying about the We have ideas for the future (2.0) to have a different approach for our APIs and have a single |
…rt from the default error handling behavior.
|
@filiphr , I've introduced the SubclassExhaustiveStrategy and did some more renaming/refactoring. Also updated the documentation to have it mentioned. |
filiphr
left a comment
There was a problem hiding this comment.
It looks great. One final comment in the javadoc of the SubclassExhaustiveStrategy and we are done 😄
|
And done one of our oldest feature request has been implemented. Thanks a lot for the huge effort you put in this @Zegveld. |
#131 #2438 #366 : Added support for SubClassMapping annotations. This will allow for hierarchy mappings.
if a parent mapping method is annotated with @SubClassMapping it will now generate an instanceof check inside the parent mapping and generate the subclass mappings if they are not manually defined. See issues #131 #2438 and #366 for more information.