Lombok's `#Builder` annotation stubbornly stays package - private - java

I have the following #Builder - annotated class:
#Data
#Builder(access = AccessLevel.PUBLIC)
#Entity
public class LiteProduct
{
// Minimal information required by our application.
#Id
private Long id; // Unique
private String name;
private Long costInCents;
private String type;
// Model the product types as a Hash Set in case we end up with several
// and need fast retrieval.
public final static Set<String> PRODUCT_TYPES = new HashSet<>
(Arrays.asList("flower", "topical", "vaporizer", "edible", "pet"));
// Have to allow creation of products without args for Entities.
public LiteProduct()
{
}
public LiteProduct(#NonNull final Long id, #NonNull final String name,
#NonNull final String type, #NonNull final Long costInCents)
{
if(!PRODUCT_TYPES.contains(type))
{
throw new InvalidProductTypeException(type);
}
this.name = name;
this.id = id;
this.costInCents = costInCents;
}
Whenever I want to use the builder class that Lombok is purported to give me, despite the fact that the IDE seems to detect it just fine:
I get a compile-time error about its visibility:
I have looked at some workarounds such as this or this, and they all seem to imply that my problem ought to already be solved automatically and that Lombok by default produces public Builder classes. This does not seem to be implied from my output, and does not happen even after I put the parameter access=AccessLevel.PUBLIC in my #Builder annotation in LiteProduct. Any ideas? Is there something wrong with the fact that this class is also an #Entity? Something else I'm not detecting?
// Edit: I determined that when I move the class in the same package as the one I am calling the builder pattern from, it works just fine. This is not an #Entity issue, but a package visibility issue which based on what I'm reading should not be there.

The problem was that I was using the following line of code to create an instance of LiteProduct:
return new LiteProduct.builder().build();
instead of:
return LiteProduct.builder().build();
which is what the #Builder annotation allows you to do. Clearly builder() is like a factory method for Builders that already calls new for you.

Related

How to customize lombok's superbuilder?

I have an existing data model which was (unfortunately) written with bidirectional relationships. Currently, I'm trying to refactor it using Lombok. I've added the #SuperBuilder annotation, but the generated builder does not call my custom setter methods (the ones that ensure that the bidirectionality remain intact).
After running delombok and investigating the resulting code, it appears that a constructor is created on the class being built that takes an instance of the builder to use to set the values. Unfortunately, it simply assigns the field values directly. So I thought maybe I could just implement that constructor myself, and make the calls to the setters as required. Of course, this does not work. When I build I get an error because there are now apparently two implementations of that same method in my class (in other words SuperBuilder implemented it even though it was already implemented in the class).
Does anyone know how to override that constructor (or any other mechanism that would allow me to get the setters called when constructing my object using the SuperBuilder annotation)?
Edit: added code as requested
The entity class I'm trying to refactor to using lombok is:
#Entity
#Table(name = "APPLICATION_USER", uniqueConstraints = #UniqueConstraint(columnNames = { "PRINCIPAL_NAME", "APPLICATION", "SITE_ID" }))
#AttributeOverrides(#AttributeOverride(name = "id", column = #Column(name = "APP_USER_ID")))
#Filters({ #Filter(name = FilterQueryConstants.SITE_ID_FILTER_NAME, condition = FilterQueryConstants.SITE_ID_FILTER) })
#SuperBuilder
public class ApplicationUser extends User
{
private static final long serialVersionUID = -4160907033349418248L;
#Column(name = "APPLICATION", nullable=false)
private String application;
#ManyToMany(mappedBy = "applicationUsers", targetEntity = Group.class)
#Filters({ #Filter(name = FilterQueryConstants.GROUP_FILTER_NAME, condition = FilterQueryConstants.GROUP_FILTER),
#Filter(name = FilterQueryConstants.SITE_ID_FILTER_NAME, condition = FilterQueryConstants.SITE_ID_FILTER) })
#MappingTransform(operation = DTOSecurityOperation.ASSIGN_GROUP)
#Builder.Default
private Set groups = new HashSet ( );
// Other methods omitted for brevity
When I run the delombok, the resulting constructor looks like the following:
protected ApplicationUser(final ApplicationUserBuilder b) {
super(b);
this.application = b.application;
if (b.groups$set) this.groups = b.groups;
else this.groups = ApplicationUser.$default$groups();
}
So I thought I could just basically copy this code into my ApplicationUser class and modify it to call my setter method when it sets the value for groups (rather than just doing a direct assignment). I was thinking of something like this:
protected ApplicationUser(final ApplicationUserBuilder b) {
super(b);
this.application = b.application;
if (b.groups$set) this.setGroups(b.groups);
else this.setGroups(ApplicationUser.$default$groups());
}
Originally, when using 1.18.8, I was getting an error stating that this constructor already exists. Since updating to 1.18.22, I now get this:
error: cannot find symbol
if (b.groups$set) this.setGroups(b.groups);
^
symbol: variable groups
location: variable b of type ApplicationUserBuilder
Customizing #SuperBuilder only works in more recent lombok version; you should always use the most recent one, which is v1.18.22 at the time of the writing of this answer.
With that version, customizing a #SuperBuilder constructor is possible. However, you are using code as a basis for your constructor that has been delomboked with v1.18.8. That does not work any more with current lombok versions. lombok v1.18.10 introduced that the actual field value for #Default fields are stored in the builder in fields like fieldName$value, not simply fieldName.
Thus, your customized constructor has to look as follows:
protected ApplicationUser(final ApplicationUserBuilder<?, ?> b) {
super(b);
this.application = b.application;
if (b.groups$set) this.setGroups(b.groups$value);
else this.setGroups(ApplicationUser.$default$groups());
}

The Function implementation remains null if Lombok #Builder is used for initialization

I am using Lombok to initialize a class. That class also has some Functions defined. Those Functions remain null when called from the above initialized object.
VehicleTest Class:
public class VehicleTest {
public static void main(String...arg) {
Vehicle vehicle = Vehicle.builder()
.createdDateTime(DateUtil.getEpochTimeFromCurrentTimeZone())
.make("Toyota")
.year("2010")
.model("Fortunner")
.build();
System.out.println(vehicle.convertEpochToString.apply(DateUtil.getEpochTimeFromCurrentTimeZone()));
}
}
Vehicle Class:
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Vehicle {
Long createdDateTime;
String year;
String make;
String model;
public String getTime() {
return convertEpochToString.apply(createdDateTime);
}
public Function<Long,String> convertEpochToString = epochTime -> {
ZonedDateTime zonedDateTime = DateUtil.convertEpochToZonedDateTime(epochTime);
return DateUtil.formatZonedDateTime(zonedDateTime,"dd-MMM-yyyy");
};
}
As you can see in this debug mode, this convertEpochToString Function is null.
Note: This is not the actual way I am using in my project. This is just an example I made to depict my problem.
As mentioned in this link, if I use
#Builder.Default
on above Function, this seems to be working. But adding it on all 100+ Functions in each class will be a huge task for me. Is there any other alternative apart from the above one and static Function?
You can solve this case by adding modifier static to the function declaration.
But I'm not sure about the reasons for such behavior, I think Lombok code generation can make decisions if works with class and can't if works with instance of class and lazy function.
Solved this problem with
#Builder(toBuilder = true)
and modifying the initialization from
Vehicle vehicle = Vehicle.builder()
to
Vehicle vehicle = new Vehicle().toBuilder()
This seems to be quite easy for me.
Read it from here

exclude specific variables from lombok #AllArgsConstructor [duplicate]

If I specify #AllArgsConstructor using Lombok, it will generate a constructor for setting all the declared (not final, not static) fields.
Is it possible to omit some field and this leave generated constructor for all other fields?
No that is not possible. There is a feature request to create a #SomeArgsConstructor where you can specify a list of involved fields.
Full disclosure: I am one of the core Project Lombok developers.
Alternatively, you could use #RequiredArgsConstructor. This adds a constructor for all fields that are either #NonNull or final.
See the documentation
Just in case it helps, initialized final fields are excluded.
#AllArgsConstructor
class SomeClass {
final String s;
final int i;
final List<String> list = new ArrayList<>(); // excluded in constructor
}
var x = new SomeClass("hello", 1);
It makes sense especially for collections, or other mutable classes.
This solution can be used together with the other solution here, about using #RequiredArgsConstructor:
#RequiredArgsConstructor
class SomeClass2 {
final String s;
int i; // excluded because it's not final
final List<String> list = new ArrayList<>(); // excluded because it's initialized
}
var x = new SomeClass2("hello");
A good way to go around it in some cases would be to use the #Builder
This can be done using two annotations from lombok #RequiredArgsConstructor and #NonNull.
Please find the example as follows
package com.ss.model;
import lombok.*;
#Getter
#Setter
#RequiredArgsConstructor
#ToString
public class Employee {
private int id;
#NonNull
private String firstName;
#NonNull
private String lastName;
#NonNull
private int age;
#NonNull
private String address;
}
And then you can create an object as below
Employee employee = new Employee("FirstName", "LastName", 27, "Address");
Lombok is meant to take care of the boilerplate code for your POJOs. Customized constructors/setters/getters/toString/copy etc are not on the boilerplate side of code. For these cases, every Java IDE provide easy to use code generators to help you do things in no time.
In your case a
public MyClass(String firstName, String lastName) {....}
is much more readable and makes more sense than a hypothetic:
#AllArgsConstructor(exclude = "id", exclude = "phone")
Have fun!

Java - How to avoid creation of setter only for a particular class needs?

I am using Hibernate and currently using the setter to set the relation to parent in children at creation time (to avoid doing this manually for both sides). How I can avoid use of setter or avoid expose it to the rest of classes and get the same behaviour. Is it ok to use reflection? This is the code:
#Entity
#Table(name = "TEST_GROUP")
#Getter
public class TestGroupEntity extends AuditedEntity{
#ManyToOne
#JoinColumn(name = "owner", nullable = false)
protected UserEntity owner;
#Column(name = "description")
#Setter
protected String description;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
protected Set<TestEntity> tests = Sets.newHashSet();
public boolean addTest(TestEntity testEntity) {
return tests.add(testEntity);
}
public boolean removeTest(TestEntity testEntity) {
return tests.remove(testEntity);
}
public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
this.owner = owner;
owner.setTestGroupEntity(this); ! how to avoid creation of setter
this.tests = tests;
tests.stream().forEach(t -> t.setTestGroupEntity(this)); ! how to avoid creation of setter
}
}
This is the children class ( I would like to keep immutability on api level):
#MappedSuperclass
#AllArgsConstructor
public class TestEntity extends AuditedEntity {
#Column(name = "name", nullable = false)
protected String name;
#Column(name = "description")
protected String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "test_group", nullable = false)
protected TestGroupEntity testGroupEntity;
public void setTestGroupEntity(TestGroupEntity testGroupEntity) {
this.testGroupEntity = testGroupEntity;
}
}
Edit: I think commented sections of code was not visible. Sorry.
How I can avoid use of setter or avoid expose it to the rest of
classes and get the same behaviour. Is it ok to use reflection?
Of course you can for example reduce visibility of public setters to a visibility less wide than public in order that client classes of your entities cannot use them.
Which is in your case the real problem since accessing any data from inside the object is possible in anyway
From hibernate doc :
Attributes (whether fields or getters/setters) need not be declared
public. Hibernate can deal with attributes declared with public,
protected, package or private visibility. Again, if wanting to use
runtime proxy generation for lazy loading the visibility for the
getter/setter should be at least package visibility.
So, try to use private setter for desired field. It should address your problem.
Update After comment
You have several workarounds to address your problem :
using reflection (your basic idea).
Drawback : it brings a little complexity, not a full check at compile-time and at last, someone who sees your code could wonder why you used that...
It is the same thing for any concepts which relies on reflection such as AOP.
declaring these setters with package-private level and put the 3 classes in the same package. Drawback : the used package.
creating public init methods which raises an exception if it used more than once for a same object. In this way, you guarantee the coherence of the object if bad used. Drawback : method which should not be used by clients is still provided to clients.
Unfortunately, you have not a perfect solution since Java visibility mechanisms cannot provide a ready-to-use solution for what you are looking for.
Personally, I prefer reflection or init method solutions.
Personally, I noticed that in based-class languages as Java, a good developer has often the reflex to over- protect accessibility of objects/methods. In fact, in many cases, it is not needed because it will not break the application or data integrity.
Here an example with init method :
public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
this.owner = owner;
owner.constructInit(this);
this.tests = tests;
tests.stream().forEach(t -> t.constructInit(this));
}
public class UserEntity {
private TestGroupEntity testGroupEntity;
public void constructInit(TestGroupEntity testGroupEntity) {
if (this.testGroupEntity != null) {
throw new IllegalArgumentException("forbidden");
}
this.testGroupEntity=testGroupEntity;
}
}
Make a constructor in your parent class and call it from child.
Here is the parent constructor looks like
public AuditedEntity(UserEntity owner, Set<TestEntity> tests){
this.owner = owner;
this.tests = tests;
}
And change your child constructor like
public TestGroupEntity(UserEntity owner, Set<TestEntity> tests) {
super(owner,tests);
}

Best practice and implementation of a builder pattern when using JPA

I have a class that is suitable for a builder pattern, there are many params and I'd rather not use a ton of telescopic constructors.
My problem is that this class is a JPA entity and that is very new to me.
Having private final data members is throwing an error as I they are not initialized in the constructor and as far as I'm aware, JPA requires an empty protected constructor.
Can anyone help please? An example would be fantastic, I've included a basic example of the code below but it's very generic. I've omitted many of the accessors and data members to save space/time.
#Entity//(name= "TABLE_NAME") //name of the entity / table name
public class Bean implements Serializable {
private static final long serialVersionUID = 1L;
#Id //primary key
#GeneratedValue
Long id;
private final DateTime date;
private final String title;
private final String intro;
//used by jpa
protected Bean(){}
private Bean(Bean Builder beanBuilder){
this.date = beanBuilder;
this.title = beanBuilder;
this.intro = beanBuilder;
}
public DateTime getDate() {
return date;
}
public String getTitle() {
return title;
}
public static class BeanBuilder Builder{
private final DateTime date;
private final String title;
//private optional
public BeanBuilder(DateTime date, String title) {
this.date = date;
this.title = title;
}
public BeanBuilder intro(String intro){
this.intro = intro;
return this;
}
public BeanBuilder solution(String solution){
this.intro = solution;
return this;
}
public Bean buildBean(){
return new Bean(this);
}
}
}
Member fields marked as final must have a value assigned during construction and this value is final (i.e. cannot change). As a consequence, all declared constructors must assign a value to all final fields.
This explain your compiler error.
From the JLS:
A blank final instance variable must be definitely assigned at the end of every constructor of the class in which it is declared, or a compile-time error occurs (§8.8, §16.9).
Not sure why you want to do that. Maybe it is better to define the member variable as
#Column(name = "id", nullable = false, updatable = false)
for example
The JPA 2.1 specification, section "2.1 The Entity Class", says:
No methods or persistent instance variables of the entity class may be
final.
..meaning that there's no way for you to build a truly immutable JPA entity. But, I don't really see how that can be such a big issue. Just don't let the entity class expose public setters?
I'm not sure what you meant for that, but having immutable objects is not a great idea when working in Hibernate (not to say you cannot do it, or you shouldn't).
Think about it, because Hibernate/JPA defines "states" for objects they are meant to be mutable; otherwise you would have a static database, or something like insert-once-and-never-modify database.
The immutable concept is a very known (nowadays) concept borrowed mainly from Functional Programming that doesn't really apply in the same way to OOP. And if you are working with Hibernate you shouldn't have immutable objects...at least till today's date.
UPDATE
If you want to have what they call read-only entities, you can use the #Immutable annotation from Hibernate itself. Pay close attention to collections as entity members.
Entities are meant to be mutable when it comes to strict Java immutability. For example, lazily loaded associations will change the object state once the association is accessed.
If you need to use entity data in a real immutable fashion (for multi-threaded purposes for example), then consider using DTOs (because entities are not meant to be accessed cuncurrently either).

Categories

Resources