We are using Immutables with MapStruct and ran into a problem while converting an entity to dto.
#Value.Immutable
public interface ProjectDto {
String getId();
String getName();
//ProjectStatisticsDto getStatistics();
}
#Value.Immutable
public interface ProjectStatisticsDto {
Long getCount();
}
#Immutable
public interface Project extends Serializable {
#JsonProperty("_id")
String getId();
String getName();
//ProjectStatistics getStatistics();
}
#Immutable
public interface ProjectStatistics extends Serializable {
Long getCount();
}
The mapper class
#Mapper
public interface ProjectMapper {
ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class);
ImmProjectDto toDto(ImmProject project); // This works only when the inner model of project statistics is commented.
//ProjectDto toDto(Project project); THIS DOES NOT WORK (Error 1)
// ImmProjectDto toDto(ImmProject project); After I uncomment the inner class of project statistics then even this does not work (Error 2)
In the cases of error, the issue is exactly the same
Error 1: No implementation was created for ProjectMapper due to having a problem in the erroneous element com.xyz.ProjectDto.
Error 2: No implementation was created for ProjectMapper due to having a problem in the erroneous element com.xyz.ProjectStatisticsDto.
I checked the tests for mapstruct with immutables and there is nothing different I see https://github.com/mapstruct/mapstruct/blob/master/integrationtest/src/test/resources/immutablesBuilderTest/mapper/src/main/java/org/mapstruct/itest/immutables/Person.java.
I tried removing serialization statements but no luck. I added some verbose statements which say
Note: MapStruct: Immutables found on classpath
Note: MapStruct: Using accessor naming strategy: org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy
Note: MapStruct: Using builder provider: org.mapstruct.ap.spi.ImmutablesBuilderProvider
Note: MapStruct: Using enum naming strategy: org.mapstruct.ap.spi.DefaultEnumMappingStrategy
And this looks absolutely correct
Looking at the title of the question "Inner immutable class with mapStruct" I guess that your immutable classes are inside another class.
This is a known problem for MapStruct (see mapstruct/mapstruct#2198) that already has a PR for it and it will be fixed in the next non patch release.
In the meantime you will have to make your Immutable classes top level classes.
Related
I'm using lombok in my project and I have an interface :
public interface MyInterface{
Object getA()
}
And a class
#Getter
public class MyClass implements MyInterface{
private Object a;
private Object b
}
And i've checked the generated class and the method generated in the class is not #Override
I'm wondering how to add this annotation ? And what are the consequences of a missing #Override ?
It's maybe an another question but this code is analyzed by sonarqube and sonar say that private field a is never used.
I've already seen the subject about sonarqube + lombok = false positives
But In my case b doesn't create a false positive. So I don't think this is directly related
Do you see a solution to avoid this problems without reimplement getA() ?
You can use the onMethod attribute of the annotation to add any annotation you want to the generated method.
#Getter
public class MyClass implements MyInterface{
#Getter(onMethod = #__(#Override))
private Object a;
private Object b
}
As a matter of style in this case, I would probably move the class-level #Getter to the other field. If there were c, d, e which should all use normal (no annotation) #Getter logic, I would leave it as above.
public class MyClass implements MyInterface{
#Getter(onMethod = #__(#Override))
private Object a;
#Getter
private Object b
}
You may also want to enable Lombok's generated annotations. It will prevent some tools (coverage, static analysis, etc) from bothering to check Lombok's methods. Not sure if it will help in this case, but I pretty much have it enabled in all of my projects.
lombok.addLombokGeneratedAnnotation = true
You can add #Override to a particular field by using #Getter's onMethod.
e.g.
#Getter(onMethod = #__(#Override))
private Object a;
#Override has no affect on the code itself. It simply informs the compiler that the method is overriding a method from its superclass. The compiler will fail the build if the method does not override a method from a superclass.
A more general way to prevent false positives detections of missing #Override by Error Prone (or similar tools) is to configure Lombok to annotate its generated code with #lombok.Generated. This can be done - globally - by adding the following entry to Lombok's lombok.config:
lombok.addLombokGeneratedAnnotation = true
Note that this is not a universal solution - the relevant tool needs to respect #lombol.Generated (or better, any #*.Generated) annotation.
Tools like Jacoco and Error Prone (very recently) already support it.
I have the following classes:
#SuperBuilder(toBuilder = true)
public abstract class Parent {
//...
}
#SuperBuilder(toBuilder = true)
public class Child extends Parent {
//...
}
#SuperBuilder(toBuilder = true)
public class Child2 extends Parent {
//...
}
Why am I unable to call toBuilder() on an instance of the abstract class (Parent) as shown in the following code?
public copy(Parent parent) {
parent.toBuilder().build();
}
In fact, as Hossein Nasr already explained, Lombok cannot know whether there are subclasses that do not have toBuilder=true.
Lombok could require all direct subclasses of an abstract class to also use toBuilder by adding an abstract toBuilder() method on the abstract class. However, there may be use-cases where subclasses should not have a toBuilder (although I haven't seen any good examples of those). Furthermore, indirect subclasses may still lack the toBuilder feature. Therefore, lombok does not enforce toBuilder on subclasses of abstract classes.
Good news is that you can easily work around it in your case (only direct subclasses) by adding the abstract toBuilder() method to your abstract class Parent manually:
#SuperBuilder(toBuilder = true)
public abstract class Parent {
public abstract ParentBuilder<?, ?> toBuilder();
}
With this change, your copy method compiles and works as expected.
If you also have indirect subclasses, you have to find other means to ensure they also have #SuperBuilder(toBuilder = true). If those would not have this annotation, you may experience strange behavior (e.g. calling copy() will instantiate a different class).
Disclaimer: I implemented the #SuperBuilder feature.
It's probably because Lombok can not guarantees that every child class of Parent is also marked as #SuperBuilder(toBuilder=true) and if so, Lombok can not call the toBuilder of that instance;
I have added Lombok's JAR file in STS (eclipse).
I am using Lombok to create object using builder(). But, I am facing issue in inheritance.
If I am using Lombok's builder pattern to create objects it's working in workspace & in executable JAR file.
But, If I am using Lombok's builder pattern to create objects which inherit another object, then it's not working.
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class BaseEmp {
private int a;
private int b;
}
#Data
#NoArgsConstructor
#Builder
class Emp extends BaseEmp implements Serializable {
private static final long serialVersionUID = 1L;
#Builder
public Emp(int a, int b) {
super(a, b);
}
}
Emp emp = Emp.builder.a(ipA).b(ipB).build();
In this one when I am printing object, a and b values are null in JAR and working in STS.
But, when I converted to normal object creation in workspace and JAR, in both places it is working.
Means, upon compile, Lombok processor somehow misses inheritance class field.
If you extend another class, you should really think about using #SuperBuilder. Although it is still experimental, the Lombok maintainers made clear that this is mainly because it is a very young, extremely complex feature that will not receive support/bugfixes as fast as the core features. It is unlikely that #SuperBuilder will be redesigned or dropped in the future.
However, if you want to stick with #Builder, you must not have #Builder annotations on both the class and the constructor. Just put it on the constructor and it should work.
Furthermore, your superclass should also not have #Builder, otherwise you'll get a name clash on the builder() method. (You can work around that by renaming it using the parameter builderMethodName.)
I've read other questions regarding lombok's builder and inheritance but none of the solutions have worked. Using Lombok version 1.18.4 and Java 11.
I'm trying to inherit the parent builder while also satisfying an interface, using only immutable fields. This is my class structure:
The Code
public interface FooInterface {
String getFoo();
}
The getFoo logic is very common across all implementations, so I decided to make an Abstract helper to avoid copy-pasting the same code everywhere.
#Data
#SuperBuilder
public abstract class AbstractFoo implements FooInterface {
#Builder.Default
private final String foo = "foo";
}
And the actual Foo implementation:
#Data
#SuperBuilder
public class FooTest extends AbstractFoo {
private final String bar;
}
'Win Condition'
I would like Lombok to
Recognize fields required by the parent class.
Include those fields in the generated Builders of child classes.
In code:
final FooInterface fooTest = FooTest.builder.foo("string").bar("string").build();
assertThat("string").equals(fooTest.getFoo());
assertThat("string").equals(fooTest.getBar());
Attempted Solutions
The problem is, IntelliJ highlights the #Data annotation with this error:
Lombok needs a default constructor in the base class.
If I remove #Data from FooTest I get this error:
There is no default constructor available in base class.
So I removed the #SuperBuilder from AbstractFoo and added a manually-created constructor with all the arguments. The error persists. I've tried other things and annotation combinations, but none have worked.
I also tried -in vain- to set all AbstractFoo fields to protected final, and declare Foo implementations final themselves, which would be consistent with my business rules.
#SuperBuilder isn't supported by current version of IntelliJ IDEA plugin yet.
There's an open issue on project's Github tracker - https://github.com/mplushnikov/lombok-intellij-plugin/issues/513
Although it's targeted for 0.25 release which has been released just a few days ago -
https://github.com/mplushnikov/lombok-intellij-plugin/releases/tag/releasebuild_0.25
Issue still seems to be open and not yet implemented.
I'd suggest to just try version 0.25 and wait for the next release if it won't work.
Let's say I have the following two classes:
package example.model;
public class Model {
public static class Inner {}
public Other prop;
}
and
package example.model;
public class Other {
public static class Inner {}
public String prop;
}
and I create a JAXB context with JAXBContext.newInstance(example.model.Model.class).
With the default JAXB implementation from Java 6 this works without any annotations, and a generated model does not mention "inner". with EclipseLink I get a "Name collision. Two classes have the XML type with uri and name inner."
I know that making at least one of the inner classes #XmlTransient gets rid of the problem. What I would like to know is how this difference relates to the JAXB standard,
and, I guess, also if there is any other way to make MOXy ignore these classes like the default JAXB implementation does.
This appears to be a bug in EclipseLink JAXB (MOXy). We are currently working on a fix for the EclipseLink 2.3.3 and 2.4.0 streams. You can track our progress using the following link:
https://bugs.eclipse.org/374429
Once the fix is available you will be able to download a nightly build from the following link:
http://www.eclipse.org/eclipselink/downloads/nightly.php
Workaround
As you mention you can mark the static inner class with #XmlTransient.
package example.model;
public class Model {
#XmlTransient
public static class Inner {}
public Other prop;
}