How to exclude property from Lombok builder? - java

I have a class called as "XYZClientWrapper" , which have following structure:
#Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
}
What I want no build function generated for property XYZClient client
Does Lombok supports such use case?

Yes, you can place #Builder on a constructor or static (factory) method, containing just the fields you want.
Disclosure: I am a Lombok developer.

Alternatively, I found out that marking a field as final, static or static final instructs #Builder to ignore this field.
#Builder
public class MyClass {
private String myField;
private final String excludeThisField = "bar";
}
Lombok 1.16.10

Create the builder in code and add a private setter for your property.
#Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
public static class XYZClientWrapperBuilder {
private XYZClientWrapperBuilder client(XYZClient client) { return this; }
}
}

Here is my preferred solution. With that, you can create your field client at the end and have it depending on other fields that previously set by the builder.
XYZClientWrapper{
String name;
String domain;
XYZClient client;
#Builder
public XYZClientWrapper(String name, String domain) {
this.name = name;
this.domain = domain;
this.client = calculateClient();
}
}

For factory static method example
class Car {
private String name;
private String model;
private Engine engine; // we want to ignore setting this
#Builder
private static Car of(String name, String model){
Car car=new Car();
car.name = name;
car.model = model;
constructEngine(car); // some static private method to construct engine internally
return car;
}
private static void constructEngine(Car car) {
// car.engine = blabla...
// construct engine internally
}
}
then you can use as follows:
Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available
Notice the static method of will be called whenever build() is called, so doing something like Car.builder().name("Toyota") won't actually set the value "Toyota" into name unless build() is called and then assigning logic within the constructor static method of is executed.
Also, Notice that the of method is privately accessed so that build method is the only method visible to the callers

I found that I was able to implement a "shell" of the static Builder class, add the method I want to hide with a private access modifier, and it is no longer accessible in the builder. Likewise I can add custom methods to the builder as well.
package com.something;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;
#Data
#Entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class MyClass{
//The builder will generate a method for this property for us.
private String anotherProperty;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "localDateTime", column = #Column(name = "some_date_local_date_time")),
#AttributeOverride(name = "zoneId", column = #Column(name = "some__date_zone_id"))
})
#Getter(AccessLevel.PRIVATE)
#Setter(AccessLevel.PRIVATE)
private ZonedDateTimeEmbeddable someDateInternal;
public ZonedDateTime getSomeDate() {
return someDateInternal.toZonedDateTime();
}
public void setSomeDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
}
public static class MyClassBuilder {
//Prevent direct access to the internal private field by pre-creating builder method with private access.
private MyClassBuilder shipmentDateInternal(ZonedDateTimeEmbeddable zonedDateTimeEmbeddable) {
return this;
}
//Add a builder method because we don't have a field for this Type
public MyClassBuilder someDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
return this;
}
}
}

I found one more solution
You can wrap your field into initiated final wrapper or proxy.
The easiest way to wrap it into AtomicReference.
#Builder
public class Example {
private String field1;
private String field2;
private final AtomicReference<String> excluded = new AtomicReference<>(null);
}
You can interact with it inside by get and set methods but it won't be appeared in builder.
excluded.set("Some value");
excluded.get();

Adding a so called 'partial builder' to the class with Lombok #Builder can help. The trick is to add a inner partial builder class like this:
#Getter
#Builder
class Human {
private final String name;
private final String surname;
private final Gender gender;
private final String prefix; // Should be hidden, depends on gender
// Partial builder to manage dependent fields, and hidden fields
public static class HumanBuilder {
public HumanBuilder gender(final Gender gender) {
this.gender = gender;
if (Gender.MALE == gender) {
this.prefix = "Mr.";
} else if (Gender.FEMALE == gender) {
this.prefix = "Ms.";
} else {
this.prefix = "";
}
return this;
}
// This method hides the field from external set
private HumanBuilder prefix(final String prefix) {
return this;
}
}
}
PS: #Builder allows the generated builder class name to be changed. The example above assumed the default builder class name is used.

I have another approach using #Delegate and Inner Class, which supports "computed values" for the excluded fields.
First, we move the fields to be excluded into an Inner Class to avoid Lombok from including them in the Builder.
Then, we use #Delegate to expose Getters/Setters of the builder-excluded fields.
Example:
#Builder
#Getter #Setter #ToString
class Person {
private String name;
private int value;
/* ... More builder-included fields here */
#Getter #Setter #ToString
private class BuilderIgnored {
private String position; // Not included in the Builder, and remain `null` until p.setPosition(...)
private String nickname; // Lazy initialized as `name+value`, but we can use setter to set a new value
/* ... More ignored fields here! ... */
public String getNickname(){ // Computed value for `nickname`
if(nickname == null){
nickname = name+value;
}
return nickname;
}
/* ... More computed fields' getters here! ... */
}
#Delegate #Getter(AccessLevel.NONE) // Delegate Lombok Getters/Setters and custom Getters
private final BuilderIgnored ignored = new BuilderIgnored();
}
It will be transparent to outside of this Person class that position and nickname are actually inner class' fields.
Person p = Person.builder().name("Test").value(123).build();
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=null, nickname=Test123))
p.setNickname("Hello World");
p.setPosition("Manager");
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=Manager, nickname=Hello World))
Pros:
Do not force the excluded fields to be final
Support computed values for the excluded fields
Allow computed fields to refer to any fields set by the builder (In other words, allow the inner class to be non-static class)
Do not need to repeat the list of all fields (Eg. listing all fields except the excluded ones in a constructor)
Do not override Lombok library's #Builder (Eg. creating MyBuilder extends FooBuilder)
Cons:
The excluded fields are actually fields of Inner Class; however, using private identifier with proper Getters/Setters you can mimic as if they were real fields
Therefore, this approach limits you to access the excluded fields using Getters/Setters
The computed values are lazy initialized when Getters are invoked, not when .build().

One method I like and use is this.
Keep required parameters in constructor, and set optional through builder. Works if number of required is not very big.
class A {
private int required1;
private int required2;
private int optional1;
private int optional2;
public A(int required1, int required2) {
this.required1 = required1;
this.required2 = required2;
}
#Builder(toBuilder = true)
public A setOptionals(int optional1, int optional2) {
this.optional1 = optional1;
this.optional2 = optional2;
return this;
}
}
And then construct it with
A a = new A(1, 2).builder().optional1(3).optional2(4).build();
Nice thing with this approach is that optionals can also have default value.

One approach I have used before was to group instance fields into Configuration fields and Session fields. Configuration fields go as class instances and are visible to the Builder, while Session fields go into a nested private static class and are accessed via a concrete final instance field (which the Builder will ignore by default).
Something like this:
#Builder
class XYZClientWrapper{
private String name;
private String domain;
private static class Session {
XYZClient client;
}
private final Session session = new Session();
private void initSession() {
session.client = ...;
}
public void foo() {
System.out.println("name: " + name);
System.out.println("domain: " + domain;
System.out.println("client: " + session.client);
}
}

To exclude field from builder, try using #Builder.Default

Related

How to fix " Failed to instantiate 'className' using constructor NO_CONSTRUCTOR with arguments" in immutable class

I use MongoDBRepository in spring boot, and when I save some object in database everything is ok. but when I find object by id spring does not allow do that.
I try to change VehicleRoutingProblemSolution type to Object type, but VehicleRoutingProblemSolution have other object field PickupService and it without default constructor to. And yes, this class has immutable... I can't create default constructors, what can I do?
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "vrp_solutions")
public class VrpSolutionHolder {
// Specifies the solution id
#Id
#JsonProperty("id")
private String id;
// Specifies the solution id
#JsonProperty("solution")
private VehicleRoutingProblemSolution vehicleRoutingProblemSolution;
// Created at timestamp in millis
#JsonProperty("created_at")
private Long created_at = System.currentTimeMillis();
public VrpSolutionHolder(String id, VehicleRoutingProblemSolution vehicleRoutingProblemSolution) {
this.id = id;
this.vehicleRoutingProblemSolution = vehicleRoutingProblemSolution;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public VehicleRoutingProblemSolution getVehicleRoutingProblemSolution() {
return vehicleRoutingProblemSolution;
}
public void setVehicleRoutingProblemSolution(VehicleRoutingProblemSolution vehicleRoutingProblemSolution) {
this.vehicleRoutingProblemSolution = vehicleRoutingProblemSolution;
}
public Long getCreated_at() {
return created_at;
}
public void setCreated_at(Long created_at) {
this.created_at = created_at;
}
}
org.springframework.web.util.NestedServletException: Request
processing failed; nested exception is
org.springframework.data.mapping.model.MappingInstantiationException:
Failed to instantiate
com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution
using constructor NO_CONSTRUCTOR with arguments
I ran into the exact same problem. A persistent immutable class containing other class instances, throwing that aforementioned exception when retrieved by this repository method:
public interface ProjectCodeCacheRepository extends MongoRepository<CachedCode, String> {
public CachedCode findByCode(String code);
public List<CachedCode> findByClientId(UUID clientId);
}
...
List<CachedCode> cachedForClient = this.codeCacheRepo.`**findByClientId**`(clientId);
...
Following Erwin Smouts hints, this is nicely fixed by giving it a special constructor annotated org.springframework.data.annotation.PersistenceConstructor like so:
#Document(collection="cachedcodes")
public class CachedCode {
#PersistenceConstructor
public CachedCode(String code, UUID clientId, LocalDateTime expiration) {
this.code = code;
this.clientId = clientId;
this.expiration = expiration;
}
public CachedCode(String code, UUID clientId, long secondsExpiring) {
this.code = code;
this.clientId = clientId;
this.expiration = LocalDateTime.now().plusSeconds(secondsExpiring);
}
public UUID getClientId( ) {
return this.clientId;
}
public String getCode() {
return this.code;
}
public boolean hasExpired(LocalDateTime now) {
return (expiration.isBefore(now));
}
...
#Id
private final String code;
private final UUID clientId;
private final LocalDateTime expiration;
}
So, you should check if your VehicleRoutingProblemSolution has a) a constructor that matches the database fields (check in mongo client) and b) is annotated to be the one used by the driver (or whichever piece of Spring magic under the hood).
If your framework tool requires (visible) no-arg constructors (plus accompanying setters), and the class you have is required to stay as is, then you could roll your own, say, MutableVehicleRoutingProblemSolution where in the setters you could have :
this.vehicleRoutingProblemSolution = new VehicleRoutingProblemSolution(vehicleRoutingProblemSolution.getId(), newSolution);
Thus your MutableVehicleRoutingProblemSolution wraps around the existing VehicleRoutingProblemSolution.
Hacky smell to it, but it fits the requirements.
(Or you could try to find a tool that is able to use, not annotations on the contained fields, but annotations on constructor arguments.)
This is a problem where the corresponding class does not have a no-arg constructor like - I was facing an issue with java.io.File.
Solution:
In general - change the declaration to Object class and convert where we are using the class.
from
class MyClass{
File myfile;
}
to
class MyClass{
Object myFile;
}
For anyone using lombok, you need to remove the #Builder annotation on your class and use #Data instead, or follow the above solution to provide a specialized constructor
Oddly, I received this when I attempted to decorate a custom interface with ...
#Document(collection = "Person")
Example:
package test.barry.interfaces;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
#Document(collection = "Person")
public interface CustomRepository
{
void updatex(Query filterPredicate, UpdateDefinition updatePredicate);
}

Java: Lombok and unwrapping properties

I am working with JavaFx properties and Lombok
I started recently using Lombok, it makes my code much simpler and readable, but I have the issue with JavaFx properties, it doesn't unwrap them like I would generate them with IntelliJ I get a getter for the property itself and a getter for the value. Here is a simple example with explanation what I want to do.
public class LombokAndProperties {
public static void main(String[] args) {
Model model = new Model();
model.getStringProperty(); // returns the StringProperty instead of String
model.stringProperty(); // doesn't exist -> doesn't compile
// Expectation:
// model.getStringProperty() <- return the String that is stringProperty.get()
// model.stringProperty() <- return the StringProperty itself
}
#Getter
private static class Model{
private StringProperty stringProperty;
}
}
I know that I can use like: model.getStringProperty().get() to obtain the String value, but I would prefer the direct way if it exists.
Does any solution exist for this?
I've found a way:
public class LombokAndProperties {
public static void main(String[] args) {
Model model = new Model();
model.getStringProperty(); // <- return the String that is stringProperty.getStringProperty()
model.stringProperty(); // <- return the StringProperty itself
}
private static class Model{
private interface DelegateExample {
String getStringProperty();
}
#Accessors(fluent = true)
#Getter
#Delegate(types = DelegateExample.class)
private StringProperty stringProperty = new StringProperty();
}
private static class StringProperty {
String property = "p";
public String getStringProperty(){
return property;
}
}
}
With #Accessor annotation you can manipulate your getter name, while #Delegate gives you a delegation pattern. You can find more here and here. However notice two things: first, those annotations are marked as "experimental" by Lombok team. Second, to me this is a quite confusing API, so use it carefully.
If this solution is too complicated, I would suggest to adopt only #Accessor and creating your own delegate method:
public class LombokAndProperties {
public static void main(String[] args) {
Model model = new Model();
model.getStringProperty(); // <- return the String that is stringProperty.get()
model.stringProperty(); // <- return the StringProperty itself
}
private static class Model{
#Accessors(fluent = true)
#Getter
private StringProperty stringProperty;
public String getStringProperty(){
return stringProperty.get();
}
}
}

Eclipselink: Adding Where-Clause using customizer

In my current project, we will refractor the nls-support for database entities. In our previous version, we used the session language, but unfortualy this behaviour was not completely stable. So we will change the code, so that the language information is stored inside the query.
I would love to have a central instance to handle this language behaviour instead of changing each query, in every entity spread over the whole project.
E.g. I have this entity:
#NamedQueries({
#NamedQuery(name = NLSBackendEntity.findAll,
query = "select n from NLSBackendEntity n"),
#NamedQuery(name = NLSBackendEntity.getById,
query = "select n from NLSBackendEntity n where n.nlsBackendKey.key = :key") })
#Entity
#Table(name = "backend_key_al")
public class NLSBackendEntity implements Serializable
{
private static final long serialVersionUID = 1L;
public static final String findAll = "NLSBackend.findAll";
public static final String getById = "NLSBackend.getById";
#EmbeddedId
private NLSBackendKey nlsBackendKey;
/**
* The text in the language.
*/
#Lob
#Column(name = "TEXT")
private String text;
NLSBackendEntity()
{
// no arg constructor needed for JPA
}
public String getKey()
{
return nlsBackendKey.key;
}
public String getLanguage()
{
return nlsBackendKey.language;
}
public String getText()
{
return text;
}
#Embeddable
public static class NLSBackendKey implements Serializable
{
private static final long serialVersionUID = 1L;
/**
* the NLS-key.
*/
#Column(name = "KEY")
private String key;
/**
* The language of this entry.
*/
#Column(name = "LOCALE")
private String language;
}
}
One possibility would now be to add the n.nlsBackenKey.language = :locale to every NamedQuery and to change every call, where this NamedQuery is referenced.
A more favorable way would be, to have a Customizer to add the locale paramter. Atm I have this:
public class QueryLanguageCustomizer implements DescriptorCustomizer
{
#Override
public void customize(ClassDescriptor descriptor) throws Exception
{
ExpressionBuilder eb = new ExpressionBuilder(descriptor.getJavaClass());
Expression languageExp = eb.getField("LOCALE").equal(eb.getParameter("locale"));
descriptor.getQueryManager().setAdditionalJoinExpression(languageExp);
}
}
And I added this to the NLSBackendEntity: #Customizer(QueryLanguageCustomizer.class)
But now, I'm not able to set this parameter. Again, my favored way, would be to use a SessionEventAdapter:
public class LanguageSessionEventListener extends SessionEventAdapter {
/** Log for logging. */
private static final Log LOG = LogFactory
.getLog(LanguageSessionEventListener.class);
#Override
public void preExecuteQuery(SessionEvent event) {
LOG.debug("preExecuteQuery called for session= "
+ event.getSession().getName());
event.getQuery().getTranslationRow().put("LOCALE", getLocale());
super.preExecuteQuery(event);
}
private String getLocale() {
// uninteresting for this example
}
}
Unfortunatly no matter what I try, I'm unable to set this parameter. The getTransaltionRow() returns null, and every other possibility I tried failed.
Are there no possibilitys to set this parameter inside the preExecuteQuery block?
I'm using Eclipselink 2.5, any help is highly appreciated
If you don't mind using vendor-specific solution you could use EclipseLink #AdditionalCriteria annotation. You could use it as follows:
Create an abstract mapped class and derive all entities from it:
#MappedSuperclass
#AdditionalCriteria("this.language = :lang")
public class AbstractEntity {
private String language;
// getters + setters
}
Let your entities subclass it:
public class NLSBackendEntity extends AbstractEntity implements Serializable {
// ...
}
Set the value of language property on either the entity manager or entity manager factory:
entityManager.setProperty("language", "de");
before the queries are executed. EclipseLink should append language = ? to the where condition of your query binding the value you set on the entity manager.
You can use ThreadLocal<String> threadProps = new ThreadLocal<String>(); which you can set it on data or rest factory class and use it in your code.
The above solution will work if you not creating any new additional thread inside your functions.
Instead of parameter you can use threadProps.get();

Map this/self of source to specific field in target in Orika

Let's have source classes
#Data
public class Source {
private String name;
}
#Data
public class SourceParent {
private String parentName;
}
and target classes
#Data
public class Target {
private String name;
private TargetParent parent;
}
#Data
public class TargetParent {
private String parentName;
}
As you can see in Source I don't have the parent reference.
What I do is
Source s = findSource();
SourceParent sp = findParentForSource(s);
Target t = mapperFactory.map(s, Target.class);
mapperFactory.map(sp, t); //<--- Here is the problem
The problem is that I cannot map a SourceParent object to a specific field in Target.
I know how to do this using custom mappers or another "manual" ways. Is there a way to do it "Orika way"?
Something like:
mapperFactory.classMap(SourceParent.class, Target.class)
.fieldAtoB("?myslef?","parent")
.register();
Try with
mapperFactory.classMap(SourceParent.class, Target.class)
.fieldAtoB("","parent")
.register();

How to return an EnumMap if the keys are private

Here's a snippet of code I'm working on:
package testPack.model;
public class Person {
private static enum Field {
NAME, ALIASES, DATE_OF_BIRTH, DATE_OF_DEATH;
}
private EnumMap<Field, Optional<Instant>> lastUpdateTime;
private Name name;
private ArrayList<String> aliases;
private Optional<LocalDate> dateOfBirth;
private Optional<LocalDate> dateOfDeath;
public final EnumMap<Field, Optional<Instant>> getUpdateTimes() {
// Return a copy to avoid external changes to person
return new EnumMap<Field, Optional<Instant>>(lastUpdateTime);
}
}
The purpose of the Field enum is to keep track of the last time the fields of Person were updated. How can I make it so that another class can call getUpdateTimes and operate on the return without having access to Fields. To be specific, I want to be able to iterate over the values as well as get a specific value. Do I need to make Field public, and if so how can I use field without needing to import it (i.e. how do I avoid having to do import testpack.model.Person.Field)?
You are at least going to have to import the Person class. If you make the enum public, you should be able to access it from a class in a different package (and iterate through the keys as you say) as follows:
import testPack.model.Person;
...
Person person = new Person();
...
Map<Person.Field, Optional<Instant>> updateTimes = person.getUpdateTimes();
for (Person.Field field : updateTimes.keySet())
{
... // do something with the key
}
You can avoid the import, if that's really a requirement, by putting the two classes in the same package.
How about doing this?
Have the enum in Field.java
package testPack.model;
public enum Field {
NAME, ALIASES, DATE_OF_BIRTH, DATE_OF_DEATH;
}
Have a separate Person.java file
package testPack.model;
public class Person {
private static Field field;
private EnumMap<Field, Optional<Instant>> lastUpdateTime;
private Name name;
private ArrayList<String> aliases;
private Optional<LocalDate> dateOfBirth;
private Optional<LocalDate> dateOfDeath;
public final EnumMap<Field, Optional<Instant>> getUpdateTimes() {
// Return a copy to avoid external changes to person
return new EnumMap<Field, Optional<Instant>>(lastUpdateTime);
}
}
And, import the packages.class accordingly.
Also, it the makes the code loosely coupled

Categories