Can I use the Lombok #Builder passing the parent class as parameter? - java

I want to create a new Child instance passing a Parent and other additional parameters.
For example if I have:
public class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
}
public class Child extends Parent {
public String subValue;
}
With lombok, is there a builder that lets me create a Child instance passing the Parent and the missing value as parameters?
Would be easier if I could write something like:
Parent p = Parent.builder()
.param1("a")
.param2("b")
// many parameters
.paramN("b")
.build();
Child c = Child.builder(p).subValue("c").build();

Other answers don't truly make your client code simply reuse the parent instance you already have. But this is doable. You have two options:
The hard one is to write your custom annotation that does what you want. You can even make it generic so that it works for any classes the have parent/child hierarchy. Have a look at this example. If you feel brave you can raise a feature request on Lombok's github page.
Option two would be to write your custom builder for the child. See example here. In your custom builder in the init step you would be reading a passed in Parent instance, and setup the inherited fields only.

The regular #Builder is not sufficient here, because you are dealing with a class hierarchy. However, #SuperBuilder was made exactly for such a case.
#SuperBuilder generates complex code loaded with generics. That makes this solution difficult to understand without in-depth knowledge about the code #SuperBuilder generates. You should think about whether this is worth it.
Here's the solution (with Lombok >= 1.18.16):
#SuperBuilder(toBuilder = true)
public static class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
public abstract static class ParentBuilder<C extends Parent, B extends Parent.ParentBuilder<C, B>> {
protected B $fillValuesFromParent(Parent instance) {
$fillValuesFromInstanceIntoBuilder(instance, this);
return self();
}
}
}
#SuperBuilder(toBuilder = true)
public static class Child extends Parent {
public String subValue;
public static ChildBuilder<?, ?> toBuilder(Parent p) {
return new ChildBuilderImpl().$fillValuesFromParent(p);
}
}
The new toBuilder method on Child creates a new ChildBuilderImpl (which will create a Child instance when calling build()). To fill the values from the given Parent p, it calls the new $fillValuesFromParent method from ParentBuilder. This method further delegates the call to the method $fillValuesFromInstanceIntoBuilder, which is generated by Lombok and performs the actual copying of the field values to the new builder instance.
Also note the $ prefix on the methods. This basically says: I'm an implementation detail; don't use me unless you know what you are doing, I might break on the next Lombok version without further notice.

I would suggest you use #SuperBuilder
#SuperBuilder was introduced as experimental feature in lombok v1.18.2.
The #SuperBuilder annotation produces complex builder APIs for your
classes. In contrast to #Builder, #SuperBuilder also works with fields
from superclasses. However, it only works for types. Most importantly,
it requires that all superclasses also have the #SuperBuilder
annotation.
#Getter
#SuperBuilder
public class Parent {
public String name;
public String value;
}
#Getter
#SuperBuilder
public class Child extends Parent {
public String subValue;
}
Then all you need to do is
Child.builder().name("a").value("b").subValue("c").build();

Related

Lombok #SuperBuilder hide parent fields

I have the following classes
The first level of hierarchy:
abstract class Parent2 {
private P2_Param1 p2_param1;
private P2_Param2 p2_param2;
protected Parent2(P1_Param1 p1_param1) {
p2_param1 = p1_param1.something()
p2_param2 = p1_param1.somethingElse()
}
}
Second level:
abstract class Parent1 extends Parent2 {
private P1_Param1 p1_param1;
protected Parent1(P1_Param1 p1_param1) {
super(p1_param1);
this.p1_param1 = p1_param1;
}
}
And hundreds of classes that look like this:
class Child extends Parent1 {
private C_Param1 c_param1;
private C_Param2 c_param2;
private C_Param3 c_param3;
private C_Param4 c_param4;
// ... many more parameters here
public Child(P1_Param1 p1_param1) {
super(p1_param1);
}
}
These classes were used like this for a long time - child fields were used only to represent schema.
Now things changed and Child fields need to hold values.
I'd like to create as few changes in ChildX classes as possible and avoid implementing constructors that take all child parameters + p1_param1.
Ideally, it'd be great to use Lombok annotations or add some code in the parent classes.
I was thinking about instantiating the child class as it was before and using toBuilder = true to copy and fill values:
var child = new Child(p1_param1)
.toBuilder()
.c_param1(c_param1)
.build();
I tried to use the #SuperBuilder(toBuilder = true) annotation, but now I have an access to fill fields from parent classes (e.g. p2_param2) and I would like to avoid that.
Is my approach valid? Can I somehow make the parent fields not accessible via public child builder methods?
I found a solution to my question:
The answer is to use the Delombok option and then remove all methods from the generated (#SuperBuilder) parents' builders.

Lombok Inherithance: how to set default value for field in superclass without redeclaring it?

I am using Lombok and I need a way to automatically set the value for a superclass field in the subclass, without redeclaring it.
It is working fine on its own (#SuperBuiler and #Builder.Default) when using the builder, but it is giving troubles with Spring-Data MongoDB.
org.springframework.data.mapping.MappingException: Ambiguous field mapping detected! Both protected test.jackson.polymorphism.domain.enumeration.EvaluationType test.jackson.polymorphism.domain.models.EvaluationModel.type and private test.jackson.polymorphism.domain.enumeration.EvaluationType test.jackson.polymorphism.domain.models.QuantitativeEvaluationModel.type map to the same field name type! Disambiguate using #Field annotation!
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.assertUniqueness(BasicMongoPersistentEntity.java:368)
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.doWithPersistentProperty(BasicMongoPersistentEntity.java:354)
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.doWithPersistentProperty(BasicMongoPersistentEntity.java:348)
These are my classes:
public enum EvaluationType{
QUALITATIVE, QUANTITATIVE;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public abstract class EvaluationModel {
protected EvaluationType type;
protected Integer evaluation;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public class QualitativeEvaluationModel extends EvaluationModel {
/**
* How to set the default value for this field
* without redeclaring it?
*/
#Builder.Default
private EvaluationType type = EvaluationType.QUALITATIVE;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public class QuantitativeEvaluationModel extends EvaluationModel {
/**
* How to set the default value for this field
* without redeclaring it?
*/
#Builder.Default
private EvaluationType type = EvaluationType.QUANTITATIVE;
#Builder.Default
private String currencyCode = Currency.getInstance("EUR").getCurrencyCode();
private BigDecimal economicValue;
}
How do I make these subclass to have a default value (mayb final) for the type field, but without redeclaring the field?
This should work both with the contructor and with the builder.
A solution to fix this problem (not specific to Lombok) is to use an initializer block (https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) in the subclasses.
#Data
#SuperBuilder
public class QualitativeEvaluationModel extends EvaluationModel {
{
super.type = EvaluationType.QUALITATIVE;
}
}
From your description, it sounds a bit like that you don't want a settable field, but just a fixed value depending on the subclass instance. In that case, a manually implemented getter method without any field is the better choice.
However, if you really want the user to be able to set a value different from the default, this is your way:
#SuperBuilder
public class QualitativeEvaluationModel extends EvaluationModel {
public static abstract class QualitativeEvaluationModelBuilder<C extends QualitativeEvaluationModel, B extends QualitativeEvaluationModelBuilder<C, B>> extends EvaluationModel.EvaluationModelBuilder<C, B>
{
public QualitativeEvaluationModelBuilder() {
type(EvaluationType.QUALITATIVE);
}
}
}
It defines the builder class exactly like Lombok would create it (Lombok will add anything that you didn't write manually). As it's really filled with generics, I used delombok to get it right.
Next, you have to define a default value for your builder instance simply by calling the builder's setter method for type in the builder's constructor.
In this way, users that don't call the type() method on the builder instance will get the default value. If you want something else as type, just call the type() method on the builder instance.
Note that this only affects the builder. If you also want to instantiate via (no-args) constructor, you have to manually implement the constructor and set the default there.

Does lombok #Builder allow extends

I have 2 classes:
import lombok.Builder;
#Builder
public class B extends A {
}
and
import lombok.Builder;
#Builder
public class A {
}
on the #Builder on B I get the message:
The return type is incompatible with A.builder().
Is this a limitation of lombok? or something I'm doing wrong?
If I leave the #Builder off A, then the Builder on B doesn't seem to consider the fields in A in the constructors for B.
The latest lombok release 1.18.2 includes the new experimental #SuperBuilder. It supports inheritance and fields from superclasses (also abstract ones). The only requirement is that all superclasses must have the #SuperBuilder annotation. With it, the solution is as simple as this:
#SuperBuilder
public class B extends A {
private String b;
}
#SuperBuilder
public class A {
private String a;
}
B instance = B.builder().b("b").a("a").build();
It is only possible with a workaround (See #78)
From Reinhard.codes
We have been using #Builder on the class itself, but you can also put it on a class’s constructor or on a static method. In that case, Lombok will create a setter method on the builder class for every parameter of the constructor/method. That means you can create a custom constructor with parameters for all the fields of the class including its superclass.
#AllArgsConstructor
public class Parent {
private String a;
}
public class Child extends Parent {
private String b;
#Builder
private Child(String a, String b){
super(a);
this.b = b;
}
}
faced issue using lombok with java inheritance, resolved after using the below annotations on parent and child class:
#EqualsAndHashCode(callSuper = true)
#SuperBuilder
#Data
#AllArgsConstructor
#NoArgsConstructor
I cannot reproduce your exact problem anymore, but this may be because Lombok has evolved.
Part of your question, however, was that the builder for be does not include the fields for a. That remains true, as is also for #AllArgsConstructor. Inheritance is not Lombok’s strong suit.
Thad said, since you can write your constructor yourself and can put #Builder on the constructor, the following will generate a builder for B just as you wished:
#Builder
public class A {
String a;
}
public class B extends A {
#Builder
B(String a, String b) {
super(a);
this.b = b;
}
String b;
}
Without knowing the implementation details of lombok or trying it out i'd say no because the pattern won't allow it.
If you implement the builder pattern all of your methods (except of build() ) will always have the class which the builder exists for as return type.
That means class A's methods will only return A. So does B always return B.
If you now let B extend from A it will not override A's methods because it's return type does not match. Vice versa it cannot implement the builder methods in B because those methods already exist in A. They cannot coexist by OOP design.
You may be able to create a generic builder but that does not solve the problem. If you really need to extend from A you're problem may come from another design decision which the builder pattern cannot solve.
I'd assume that instead of extending the class you'd have default values in your builder which lombok should support. Those default values then reflect what class A may support by default. In a use-case where you'd rather have B doing stuff you'd then call the builder methods and override those default values.
Edit: Oh and maybe have a look here

Jackson Deserialize with Subclasses

Ok, I know there are a bunch of similar questions, but nothing seems to work.
I have the following structure set up for my entities.
public abstract class MyAbstractClass {
// bunch of properties, getters, and setters that subclasses share
public abstract String getType();
}
public class MySubclass1 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_1"; //always the same for each instance of MySubclass1
}
}
public class MySubclass2 extends MyAbstractClass {
// a few unique properties, getters, and setters
public String getType() {
return "Type_2"; //always the same for each instance of MySubclass2
}
}
In my controller, I try to map a request to the following method.
public #RequestBody MyAbstractClass saveObject(#RequestBody MyAbstractClass mac) {
// call model to save object
}
I would like to use 1 controller method versus separate ones for the 2 entities. But using the above results in the following.
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of path.to.my.entity.MyAbstractClass, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Makes sense.
TRY 1
#JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="implementingClass")
public abstract class MyAbstractClass
What I think it does - adds a metadata implementingClass property that will store the subclass class.
What the result is.
Unexpected token (END_OBJECT), expected FIELD_NAME: missing property 'implementingClass' that is to contain type id (for class path.to.my.entity.MyAbstractClass)
Tried with "class" instead of "implementingClass" for the property and got similar results.
TRY 2
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#Type(name="MySubclass1", value=MySubclass1.class),
#Type(name="MySubclass2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
What I think it does - uses the defined name to do some sort of wrapping thing.
What the result is.
Could not resolve type id 'myUuid' into a subtype of [simple type, class path.to.my.entity.MyAbstractClass]
Same results even when adding #JsonTypeName("MySubclass1") and #JsonTypeName("MySubclass2") to the 2 subclasses.
Other Tries
I tried a lot. Nothing works. Won't include everything here.
I feel like there should be a simple way to do this, but I just keep on configuring things incorrectly.
I feel like the getType could maybe be leveraged, but I don't want to add an actual property for type (it's just a helper method). Also I would like to do this with annotations versus other options.
Thank you.
I figured it out but I guess I'll answer in case anyone else has this problem.
I added a type property to my subclasses instead of just a helper method (one example included below).
public class MySubclass1 extends MyAbstractClass {
#Transient
private final String type = "TYPE_1";
public String getType() {
return type;
}
}
Then I did the following for my abstract superclass.
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#Type(name="TYPE_1", value=MySubclass1.class),
#Type(name="TYPE_2", value=MySubclass2.class)
})
public abstract class MyAbstractClass
When providing the JSON, I was sure to include the type. I won't include this because it's weird knockout insanity.
It's not great. But it worked.

JPA and generics

I'm wondering how an abstract class with generics would handle with JPA? I mean what kind of annotations do I need for the field?
Consider these:
#MappedSuperclass
public abstract class AbstractMyClass<T> {
// What about Strings and Integers? Do I need some kind of #LOB?
private T field;
public T getField() {
return field;
}
public void setField(T field) {
this.field = field;
}
}
And then these
#Entity
#Table(name = "String")
public class MyStringClass extends AbstractMyClass<String> {
}
#Entity
#Table(name = "Integer")
public class MyIntegerClass extends AbstractMyClass<Integer> {
}
JPA is perfectly able to handle your proposed, because the generic appears at the abstract class level and for your concrete classes it has exactly a single value per class. In fact, JPA will store your subclasses in one or more table, according to the #InheritanceStrategy you have chosen and uses different mechanism for that.
You can figure out yourself why your case is not a problem, reasoning about how an ORM could save the two classes on a DB:
You can store MyStringClass and MyIntegerClass in the same table, adding a Discriminator column so that the ORM, when it loads from the DB, know which constructor should be called.
You can store every subclass in more table.
What is not possible, on the other side, is to define a generic
#Entity
#Table(name = "MyGenericClass")
public class MyGenericClass<T> {
private T t;
public MyGenericClass(T t) {
this.t=t;
}
}
The reason for this is that, at compile time, the T is "erased" because of type erasure. It is used at compile time to verify signatures and correctness of types, but then it is turned into a java.lang.Object inside the JVM. If you follow until now, you should be able to understand the following:
In your case, every concrete subclass of AbstractMyClass has a type T which is defined for all instances of the class. While the T information is not retained into the AbstractMyClass, it is retained and unique inside the subclasses.
In the second case I posted, each possible concrete instance of MyGenericClass could have a possible different value for T, and because of type erasure this information is not retained.
*Note: the fact that the second case cannot be handled by JPA is absolutely reasonable and if you fall in that case you should ask yourself questions about your design. Generics are a great tool to design flexible classes which can handle other classes in a type-safe manner, but type-safe is a programming language concept which has nothing to do with persistance.
Extra : you could use javap to see what really is erasure. Take off annotations from MyGenericClass and compile it.
G:\>javac MyGenericClass.java
G:\>javap -p MyGenericClass
Compiled from "MyGenericClass.java"
public class MyGenericClass extends java.lang.Object{
private java.lang.Object t;
public MyGenericClass(java.lang.Object);
}
We can. if the T implements Serializable
#Entity
public class IgsSubject extends BasicObject implements Serializable{
private static final long serialVersionUID = -5387429446192609471L;
#MappedSuperclass
public class IgsBasicLog<T> extends BasicObject {
#ManyToOne
#JoinColumn(name = "ITEM_ID")
private T item;
#Entity
public class IgsLogA extends IgsBasicLog<IgsSubject> implements Serializable {
private static final long serialVersionUID = -8207430344929724212L;
}

Categories

Resources