Change SuperBuilder method with Lombok - java

I have a class as follows:
#Data
#SuperBuilder
#NoArgsConstructor
#JsonInclude(Include.NON_NULL)
public class CustomObject extends Parent<CustomObject> {
...
#Accessors(
chain = true,
prefix = {"_"})
#Builder.Default
private Boolean _default = false;
}
This generates a builder with a default() method that is not usable due to it being a reserved word. Is there a way to change this behavior so the builder works?

Unfortunately, there is no nice way in this case. #Accessors affects getters, setters and builders, because this is what you need in the vast majority of the cases. You cannot switch it off only for builders.
This means the only way out here (apart from renaming the _default field) is to remove #Accessors and implement getters and setters manually:
#Data
#SuperBuilder
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
public class CustomObject {
#Builder.Default
#Getter(AccessLevel.NONE)
#Setter(AccessLevel.NONE)
private Boolean _default = false;
public Boolean getDefault() {
return _default;
}
public CustomObject setDefault(Boolean _default) {
this._default = _default;
return this;
}
}

Related

Deep copy with lombok toBuilder within a nested class - cleaner way?

#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public class Example {
public ExampleTwo exampleTwo;
#lombok.Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public static class ExampleTwo {
private SomeData someData;
private AnotherField anotherField;
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public class SomeData {
private String specialId;
private String firstName;
private String lastName;
}
So I retrieved an Example instance and made a deep copy to duplicate it. But I want to be able to set one of the fields of the copied object which is specialId from the SomeData nested class. My current working implementation is this:
SomeData someData = example.getExampleTwo().getSomeData().toBuilder().specialID("SPE_1").build();
Example.ExampleTwo exampleTwo = example.getExampleTwo().toBuilder().someData(someData).build();
Example duplicateExample = example.toBuilder().exampleTwo(exampleTwo).build();
Do you have thoughts on ways to make this cleaner without having to go through these additional steps? Would prefer it to be easier to read. I'm avoiding Serializable implementation and declaring it as a Cloneable interface since I've read to avoid those. Last resort would be to use a library.
#With might be helpful:
Example example = Example.builder().build();
Example.ExampleTwo exampleTwo = example.getExampleTwo();
SomeData someData = exampleTwo.getSomeData();
return example.withExampleTwo(
exampleTwo.withSomeData(
someData.withSpecialId("SPE_1")
)
);

MapStruct to not generate mappings for fields marked with specific annotation

I'm dealing with framework code that I cannot modify and which ends up throwing a NullPointerException during mapping because MapStruct thinks it should use a getter defined in a superclass.
Is there a way to tell MapStruct to ignore all getters marked with #JsonIgnore (a jackson library annotation) ?
Some more context
To provide a bit of code, here is part of the generated implementation by MapStruct:
if ( target.getChangedProperties() != null ) {
target.getChangedProperties().clear();
List<Property> list = src.getChangedProperties();
if ( list != null ) {
target.getChangedProperties().addAll( list );
}
}
The NPE is thrown from within target.getChangedProperties() because there are some uninitialized variables being accessed. However, in reality, I don't even want this getter to be part of MapStruct's implementation. (In fact, that getter isn't a getter for a specific variable, but more of a "utility getter", so I do wonder why MapStruct is trying to use it.)
My mapped class would look like:
#Entity
#Table(name = "myentity")
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyEntity extends TheFrameworkSuperClass {
#Id
private String id;
private String foo;
}
#MappedSuperclass
#JsonSerialize(include = JsonSerialize.Inclusion.NON_DEFAULT)
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class TheFrameworkSuperClass {
#Version
#JsonProperty(value = "Version")
private Long version;
#Transient
#JsonIgnore
protected UnitOfWorkChangeSet changes;
#Override
#JsonIgnore
public List<Property> getChangedProperties() {
// stuff happening before
this.changes.getObjectChangeSetForClone(this); // throws NPE
// stuff happening after
}
}
My MapStruct interface
I have no customization of the mapper's configs. And my interface is:
#Mapper(componentModel = "spring")
public interface MyMapper {
MyEntity boToBo(MyEntity destination);
void updateBo(MyEntity src, #MappingTarget MyEntity target);
}
I contemplated using #BeanMapping(ignoreByDefault = true) and then listing each field individually to make sure no extra getters are used, but that is far from being a satisfying solution due to the amount of times I'll have to do that.
Well, turns out even though the changedProperties field does not exist, since MapStruct picks up getChangedProperties() as a getter, you can nonetheless tell MapStruct to ignore that non-existing field...
#Mapper(componentModel = "spring")
public interface MyMapper {
#Mapping(target = "changedProperties", ignore = true)
MyEntity boToBo(MyEntity destination);
#Mapping(target = "changedProperties", ignore = true)
void updateBo(MyEntity src, #MappingTarget MyEntity target);
}

How to tell MapStruct "not" to use the Lombok Builder?

I have the following classes and mapper to map them.
How can I configure Mapstruct to "not" use the Lombok builder? (without removing the #Builder annotation)?
When using the latest version of Lombok and mapstruct, mapstruct will automatically use the Builder when the #Builder annotation is used. I can not find a way to disable that, as I need the Instance in an #AfterMapping method as the builder doesn't expose all required methods (#SuperBuilder is not allowed in this use case)
#Entity(name = "user_details")
#Data
#Builder
public class User extends AuditableEntityBase {
#Version
#NotNull
private Integer version;
#NotNull
private String name;
#NotNull
private String email;
#NotNull
private Address address; // Just another Class containing another class that is mapped as well.
}
#Value
#Builder
public class UserDto extends AuditableEntityBaseDto {
#NotNull
private Integer version;
#NotNull
private String name;
#NotNull
private String email;
#NotNull
private AddressDto address;
}
#Mapper(componentModel = "spring")
class UserRestMapper {
public abstract UserDto map(User obj);
}
#AfterMapping
public void decorate(User source, #MappingTarget AuditableEntityBase target) {
// Method is never called.
// Method is called in case the second argument is: "#MappingTarget UserDto.UserDtoBuilder target"
}
If you want to disable using builders you can do so by adding #Builder(disableBuilder = true) to your #Mapper.
e.g.
#Mapper(componentModel = "spring", builder = #Builder(disableBuilder = true))
class UserRestMapper {
public abstract UserDto map(User obj);
}
Note #Builder is from the org.mapstruct package
No idea about disabling it, but why not do it like this?
#Mapper(componentModel = "spring")
abstract class UserRestMapper {
public abstract UserDto map(User obj);
public UserDto decoratedMap(User obj) {
UserDto mapped = map(obj);
// your after mapping logic
return mapped;
}
}

Lombok #SuperBuilder doesn't take parameters

Assuming two simple classes:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class Party {
protected Long id;
protected String status;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Person extends Party {
private String name;
private Long sex;
}
The compilation fails on the following error. Upon reading Lombok | #SuperBuilder I have no idea what I could miss.
C:\Dev\companyproject\src\main\java\com\companyproject\entity\Person.java:12
java: type com.companyproject.entity.Party.PartyBuilder does not take parameters
The issue here is the incorrect #Builder annotation on the parent class. The documentation for #SuperBuilder mentions:
Most importantly, it requires that all superclasses also have the #SuperBuilder annotation.
So the correct parent class would be:
#Data
#SuperBuilder // <- annotation replaced here
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class Party {
protected Long id;
protected String status;
}
Addendum:
A key difference between both annotations is that #SuperBuilder also creates a constructor of the class expecting a corresponding builder argument.
For Party it would look like:
protected Party(PartyBuilder<?, ?> b) {
this.id = b.id;
this.status = b.status;
}
and for Person:
protected Person(PersonBuilder<?, ?> b) {
super(b);
this.name = b.name;
this.sex = b.sex;
}
As you can see, the child class constructor wants to pass its own builder to the parent class constructor and this will only be possible if there is a matching constructor there, and #Builder wouldn't generate it.
Also PersonBuilder<> extends PartyBuilder<>, that is why calling super with the child type builder works fine here.

JsonDeserialize set inherited properties also when objectmapper readvalue

JsonDeserialize not working when objectmapper readvalue for inherited properties.
Vehicle Class
#Getter
#Setter
#JsonDeserialize(builder = Vehicle.VehicleBuilder.class)
#Builder(builderClassName = "VehicleBuilder", toBuilder = true)
public class Vehicle{
private String name;
private String noOfTyres;
#JsonPOJOBuilder(withPrefix = "")
public static class VehicleBuilder{
}
}
Car class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
#Builder(builderClassName = "CarBuilder", toBuilder = true)
public class Car extends Vehicle {
private String carType;
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
I don't want to create #NoArgsConstructor ,#AllArgsConstructor in both classes.
My issue when Car car = om.readValue(jsonValue,Car.class);
When I parse Json to java object the parent class properties are not setting properly.
As of now I'm using #NoArgsConstructor ,#AllArgsConstructor for work around for the use case.
Is there any way to use it along with #JsonDeserialize and #JsonPOJOBuilder?
The problem with the code is that it assumes that builders in inherited classes will set the parent properties as well. Unfortunately, they don't do that out of the box. However, this is something that can be achieved with Lombok but requires some additional code, as described in this post.
A complete solution could look as follows.
Parent Class
#Getter
#Setter
#JsonDeserialize
#Builder(builderClassName = "VehicleBuilder", builderMethodName = "vehicleBuilder")
public class Vehicle {
private String name;
private String noOfTyres;
}
Child Class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
public class Car extends Vehicle {
private String carType;
#Builder
public Car(String name, String noOfTyres, String carType) {
super(name, noOfTyres);
this.carType = carType;
}
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
Notice that the builder on the extending class is achieved by supplying a constructor with the #Builder annotation. Also take notice that the extending class does not set annotation parameter toBuilder=true as that will require access to parent properties which are private. This can be achieved by setting parent class properties to protected.

Categories

Resources