I want Lombok to take care of my builder API, while also having a constructor to start with.
I started out with a constructor on the #Data class combined with #Builder(toBuilder = true), but that left me with forcing invalid or dummy values on final fields as well as a less expressive fluent API. I finally solved my situation using a static method, but I'm hoping Lombok has a better solution for my use case.
API using toBuilder
fooHandler.accept(new TweakedFoo(Foo.class, Mode.QUICK).toBuilder()
.mappingOutcomeFor(FooOutcome.class)
.mappingOutcome(toEvent(BarOutcome.class))
.build()));
API using static method
fooHandler.accept(tweakFoo(Foo.class, Mode.QUICK)
.mappingOutcomeFor(FooOutcome.class)
.mappingOutcome(toEvent(BarOutcome.class))
.build()));
See how the second setup flows better?
Respective Lombok setup (simplified)
#Data
#Builder(toBuilder = true)
public class TweakedFoo {
private final Class<Foo> from;
private final Mode mode;
private final Class<?> to;
public TweakedFoo(Class<Foo> from, Mode mode) {
this.from = from;
this.mode = mode;
this.to = null; // eww
}
}
And:
#Data
#Builder(builderMethodName = "useTweakedFooDotTweakedFooInsteadPlease")
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TweakedFoo {
private final Class<Foo> from;
private final Mode mode;
private final Class<?> to;
public static TweakedFooBuilder tweakFoo(Class<Foo> from, Mode mode) {
return TweakedFoo.useTweakedFooDotTweakedFooInsteadPlease()
.from(from)
.mode(mode);
}
}
The actual parameters don't make much sense here, but this setup illustrates my real-world use case.
Not only is the second approach more concise, it needs no dummy constructor field initialization and also it hides the constructor so you can't get an instance other than through the builder. However, the second approach requires me to obscure the builder starting method Lombok generates in favor of my own static method.
Is there a better way with Lombok?
You can customize your builder() method by simply implementing it yourself:
#Data
#Builder
public class TweakedFoo {
// ...
public static TweakedFooBuilder builder(Class<Foo> from, Mode mode) {
return new TweakedFooBuilder()
.from(from)
.mode(mode);
}
// ...
}
Lombok will not generate another builder() method in this case, because it recognizes the existing method with the same name. If you want the method to be named differently, e.g. tweakFoo, use #Builder(builderMethodName="tweakFoo").
Related
I am trying to implement a simple java event-handler lambda for AWS. It receives sqs events and should make appropriate updates to the dynamoDB table.
One of the attributes in this table is a status field that has 4 defined states; therefore I wanted to use an enum class in java and map it to this attribute.
Under AWS SDK v1 I could use the #DynamoDBTypeConvertedEnum annotation. But it does not exist anymore in v2. Instead, there is the #DynamoDbConvertedBy() which receives a converter class reference. There is also an EnumAttributeConverter class which should work nicely with it.
But for some reason, it does not work. The following is a snip from my current code:
#Data
#DynamoDbBean
#NoArgsConstructor
public class Task{
#Getter(onMethod_ = {#DynamoDbPartitionKey})
String id;
...
#Getter(onMethod_ = {#DynamoDbConvertedBy(EnumAttributeConverter.class)})
ExportTaskStatus status;
}
The enum looks as follows:
#RequiredArgsConstructor
public enum TaskStatus {
#JsonProperty("running") PROCESSING(1),
#JsonProperty("succeeded") COMPLETED(2),
#JsonProperty("cancelled") CANCELED(3),
#JsonProperty("failed") FAILED(4);
private final int order;
}
With this, I get the following exception when launching the application:
Class 'class software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter' appears to have no default constructor thus cannot be used with the BeanTableSchema
For anyone else coming here, it looks do me like just dropping the annotation from the enum altogether works just fine, i.e. the SDK applies the provided attribute converters implicitly. This is also mentioned in this Github issue. My own class looks like this (Brand is an enum here), and the enum is converted without any issues when fetching items.
#Value
#Builder(toBuilder = true)
#DynamoDbImmutable(builder = User.UserBuilder.class)
public class User {
#Getter(onMethod = #__({#DynamoDbPartitionKey}))
String id;
Brand brand;
...
}
How can I use Java Enums with Amazon DynamoDB and AWS SDK v2?
Although the documentation doesn't state it, the DynamoDbConvertedBy annotation requires any AttriuteConverter you supply to contain a parameterles default constructor
Unfortunately for you and me, whoever wrote many of the built-in AttributeConverter classes decided to use static create() methods to instantiate them instead of a constructor (maybe they're singletons under the covers? I don't know). This means anyone who wants to use these helpful constructor-less classes like InstantAsStringAttributeConverter and EnumAttributeConverter needs to wrap them in custom wrapper classes that simple parrot the converters we instantiated using create. For a non-generic typed class like InstantAsStringAttributeConverter, this is easy. Just create an wrapper class that parrots the instance you new up with create() and refer to that instead:
public class InstantAsStringAttributeConverterWithConstructor implements AttributeConverter<Instant> {
private final static InstantAsStringAttributeConverter CONVERTER = InstantAsStringAttributeConverter.create();
#Override
public AttributeValue transformFrom(Instant instant) {
return CONVERTER.transformFrom(instant);
}
#Override
public Instant transformTo(AttributeValue attributeValue) {
return CONVERTER.transformTo(attributeValue);
}
#Override
public EnhancedType<Instant> type() {
return CONVERTER.type();
}
#Override
public AttributeValueType attributeValueType() {
return CONVERTER.attributeValueType();
}
}
Then you update your annotation to point to that class intead of the actual underlying library class.
But wait, EnumAttributeConverter is a generic typed class, which means you need to go one step further. First, you need to create a version of the converter that wraps the official version but relies on a constructor taking in the type instead of static instantiation:
import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnumAttributeConverter;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
public class EnumAttributeConverterWithConstructor<T extends Enum<T>> implements AttributeConverter<T> {
private final EnumAttributeConverter<T> converter;
public CustomEnumAttributeConverter(final Class<T> enumClass) {
this.converter = EnumAttributeConverter.create(enumClass);
}
#Override
public AttributeValue transformFrom(T t) {
return this.converter.transformFrom(t);
}
#Override
public T transformTo(AttributeValue attributeValue) {
return this.converter.transformTo(attributeValue);
}
#Override
public EnhancedType<T> type() {
return this.converter.type();
}
#Override
public AttributeValueType attributeValueType() {
return this.converter.attributeValueType();
}
}
But that only gets us half-way there-- now we need to generate a version for each enum type we want to convert that subclasses our custom class:
public class ExportTaskStatusAttributeConverter extends EnumAttributeConverterWithConstructor<ExportTaskStatus> {
public ExportTaskStatusAttributeConverter() {
super(ExportTaskStatus.class);
}
}
#DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)
public ExportTaskStatus getStatus() { return this.status; }
Or the Lombok-y way:
#Getter(onMethod_ = {#DynamoDbConvertedBy(ExportTaskStatusAttributeConverter.class)})
ExportTaskStatus status;
It's a pain. It's a pain that could be solved with a little bit of tweaking and a tiny bit of reflection in the AWS SDK, but it's where we're at right now.
I am thinking that your annotations might actually be the problem here. I would remove all annotations that mention a constructor, and instead, write out your own constructor(s). For both Task and TaskStatus.
The dynamodb-enhanced SDK does this out of the box.
When you declare a #DynamoDbBean the DefaultAttributeConverterProvider provides a long list of possible ways to convert attributes between java types, including an EnumAttributeConverter which is used if type.rawClass().isEnum() is true. So you don't need to worry about it.
If you ever wanted to extend the number of converters, you would need to add the converterProviders annotation parameter, and declare the default one (or omit it), as well as any other providers you want.
Example:
#DynamoDbBean(converterProviders = { DefaultAttributeConverterProvider.class, MyCustomAttributeConverterProvider.class });
Solution based on watkinsmatthewp Answer:
public class TaskStatusConverter implements AttributeConverter<TaskStatus> {
#Delegate
private final EnumAttributeConverter<TaskStatus> converter;
public TaskStatusConverter() {
converter = EnumAttributeConverter.create(TaskStatus.class);
}
}
Task status attribute looks like this:
#Getter(onMethod_ = {#DynamoDbConvertedBy(TaskStatusConverter.class)})
TaskStatus status;
my goal is to avoid problem 'Private field is never assigned' without using #SupressWarnings or creating a defined constructor.
I am aware using annotation will lead to technical debt for the long run. However, I can't not justify the Java verbosity (although I love it at times when debugging a bug), this code is easier to read.
Method that I do not wish to use:
SupressWarnings("unused") written above the class statement.
Creating a defined constructor which is not necessary since MyBatis can modify the object attribute regardless there is a setters or not for example when you use #SelectKey.
Creating a setter which will never be used.
This is the sample code for the model I am going to standardize for MyBatis.
model/NameModel.java
package com.example.mssqlserver.model;
import com.fasterxml.jackson.annotation.JsonInclude;
#SuppressWarnings("unused") // MyBatis does not need a defined constructor nor a setters.
#JsonInclude(JsonInclude.Include.NON_NULL) // filter: only non_null, alternative: spring.jackson.default-property-inclusion=NON_NULL in application.properties
public class NameModel {
private Integer id;
private String name;
private String newid;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public String getNewid() {
return newid;
}
public boolean requestIsValid() {
return !this.name.isEmpty();
}
}
the first is like this
public class MainActivity extends AppCompatActivity {
private PDFView pdfView;
public MainActivity(PDFView pdfView) {
this.pdfView = pdfView; }
and i edit like this
public class MainActivity extends AppCompatActivity {
private final PDFView pdfView;
public MainActivity(PDFView pdfView) {
this.pdfView = pdfView;
}
and the problem solved. thanks
javac will not complain on this.
javac -cp *.jar com/example/mssqlserver/model/NameModel.java
So it is likely be the IDE being used.
It may have a configuration Errors/Warnings option for this specific case.
Unfortunately couldn't find such an option in my Eclipse IDE - though I remember there was one - so cannot point the exact option.
A related question: Why no "field is never assigned" warning with #Mock
I am using Lombok library in my project and I am not able to use a class annotated with #Builder in outer packages.
Is there a way to make the builder public?
MyClass instance = new MyClass.MyClassBuilder().build();
The error is:
'MyClassBuilder()' is not public in
'com.foo.MyClass.MyClassBuilder'. Cannot be accessed
from outside package
#Builder already produces public methods, it's just the constructor that's package-private. The reason is that they intend for you to use the static builder() method, which is public, instead of using the constructor directly:
Foo foo = Foo.builder()
.property("hello, world")
.build();
If you really, really, really want the constructor to be public (there seems to be some suggestion that other reflection-based libraries might require it), then Lombok will never override anything that you've already declared explicitly, so you can declare a skeleton like this with a public constructor and Lombok will fill in the rest, without changing the constructor to package-private or anything.
#Builder
public class Foo
{
private final String property;
public static class FooBuilder
{
public FooBuilder() { }
// Lombok will fill in the fields and methods
}
}
This general strategy of allowing partial implementations to override default behaviour applies to most (maybe all) other Lombok annotations too. If your class is annotated with #ToString but you already declared a toString method, it will leave yours alone.
Just to show you everything that gets generated, I wrote the following class:
#Builder
public class Foo
{
private final String property;
}
I then ran it through delombok to see everything that was generated. As you can see, everything is public:
public class Foo
{
private final String property;
#java.beans.ConstructorProperties({"property"})
Foo(final String property) {
this.property = property;
}
public static FooBuilder builder() {
return new FooBuilder();
}
public static class FooBuilder
{
private String property;
FooBuilder() { }
public FooBuilder property(final String property) {
this.property = property;
return this;
}
public Foo build() {
return new Foo(property);
}
public String toString() {
return "Foo.FooBuilder(property=" + this.property + ")";
}
}
}
The problem is you are using #Builder in the wrong way.
When Builder Pattern is used, you only need to use the static method to invoke it and then build, for example:
MyClass instance = MyClass.builder().build(); .
Please do not new the MyClassBuilder again, it breaks the encapsulation the pattern has since you are directly using the inner MyClassBuilder class. This constructor is been hided from outside, that's why you get the not accessible error. Instead it provides you the static method builder().
I have found this neat workaround:
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
#Builder
public class Customer {
private String id;
private String name;
public static MessageBuilder builder() {return new CustomerBuilder();}
}
The problem with this builder annotation is that, if you delombok you'll see, the generated constructor for the builder has no access indicator (public, private, protected) therefore is only visible within the same package.
This would work if the extended classes were in the same package.
I'm having the same problem and I think that lombok does not support this, for now.
I was able to find the feature request in here https://github.com/rzwitserloot/lombok/issues/1489
My suggestion is to hard implement builder pattern in this class.
as mentioned you can use the builder, now instead of user property builder() will return the instance create so you can treat as normal builder ( no need to use property)
instance = MyClass.MyClassBuilder().property1(value1).property1(value2).build();
I have a lot of value objects in my project.
I'm using project lombok to eliminate some boilerplate, so my value objects look like the following one:
#Value
#Accessors(fluent = true)
public class ValueObject {
private final String firstProp;
private final int secondProp;
}
Not bad, almost no boilerplate.
And now, I'm using the all-args constructor quite often in my tests. It looks quite messy, so I thought I will introduce Builder Pattern variant instead:
public class ValueObjectBuilder {
private static final int DEFAULT_VALUE_FOR_SECOND_PROP = 666;
private String firstProp = "default value for first prop;
private int secondProp = DEFAULT_VALUE_FOR_SECOND_PROP;
private ValueObjectBuilder() {}
public static ValueObjectBuilder newValueObject() {
return new ValueObjectBuilder();
}
public ValueObjectBuilder withFirstProp(String firstProp) {
this.firstProp = firstProp
return this;
}
public ValueObjectBuilder withFirstProp(int secondProp) {
this.secondProp = secondProp;
return this;
}
public ValueObject build() {
return new ValueObject(
firstProp, secondProp
);
}
}
and the code looks quite nice now:
ValueObjectBuilder
.newValueObject()
.withFirstProp("prop")
.withSecondProp(15)
.build();
Now, the problem is - as I mentioned, I have to write a lot of similar classes... I'm already tired with copy-paste'ing them.
What I'm looking for, is a black-magic-smart-tool, which will somehow generate this code for me.
I know, there is a #Builder annotation in Lombok, but it doesn't meet my requirements. Here's why:
1) I'm unable to provide default values in lombok's Builder. Well, actually, it is possible - by implementing builder class template myself like
#Builder
public class Foo {
private String prop;
public static class FooBuilder() {
private String prop = "def value";
...
}
}
which generates some boilerplate too.
2) I can't find any way to put prefix on each field accessor in lombok's builder. Maybe #Wither could help here? But I don't know, how to use it properly.
3) The most important reason: I'm not creating a "natural" builder. As far as I understand, lombok is designed to create Builder for a given, annotated class - I don't know if there is a way to return any other object from within build() method.
So, to sum up:
Do you know any tool which could possibly help me? Or maybe all those things I mentioned are in fact possible to achieve using Lombok?
EDIT
Ok, so I probably found a solution to this particular case. With lombok we can use:
#Setter
#Accessors(chain = true, fluent = true)
#NoArgsConstructor(staticName = "newValueObject")
public class ValueObjectBuilder {
private String firstProp = "default";
private int secondProp = 666;
public ValueObject build() {
return new ValueObject(firstProp, secondProp);
}
}
Cheers,
Slawek
I know this is old but if anyone else runs into this, I found an alternative solution for providing default values for a builder.
Override the builder method and provide the default values before returning a builder. So in the above case:
#Builder
public class Foo {
private String prop;
public static FooBuilder builder() {
return new FooBuilder().prop("def value");
}
}
It's not an ideal solution but beats having to override the whole builder itself or have a custom constructor (which is painful IMHO if there are a lot of variables. It would still be nice to have something along the lines of a #With or #Default annotation to handle this.
Try Bob-the-builder for eclipse. Hmm.. I guess that works best if you happen to be using eclipse! If you are not using eclipse, there are a few related projects mentioned at the bottom of the page linked here that may be useful.
Suppose I have a DTO class:
public class SomeImmutableDto {
private final String someField;
private final String someOtherField;
public SomeImmutableDto(String someField, String someOtherField) {
// field setting here
}
// getters here
}
This is a nice immutable DTO. But what if I have 20 fields? It leads to the proliferation of a lot of unreadable constructors and unmaintainable code.
There is a solution for this problem however, the Builder pattern:
public class SomeImmutableDto {
private final String someField;
private final String someOtherField;
private SomeImmutableDto(Builder builder) {
// field setting here
}
public static class Builder {
private String someField;
private String someOtherField;
// setters here
public SomeImmutableDto build() {
// building code here
}
}
// getters here
}
Now I can do something like this:
SomeImmutableDto dto = new SomeImmutableDto.Builder()
.setSomeField(/* ... */)
*setSomeOtherField(/* ... */)
.build();
Now I have an immutable dto which does not have an abundance of ugly constructors.
My problem is that I need a dto which has public setters AND immutable because there are some legacy code in the project which cannot be refactored at the moment and it requires the presence of public setters in order to initialize dto objects.
Is there some pattern which is usable here or this won't work? I'm thinking about the Proxy pattern but I'm not sure it can be applied in a way it is not looking like an ugly hack.
I think if you need to be legacy-code-compliant, the best way is to use a non modifiable wrapper just like in Collections.unmodifiableList method it is done.
It is "hack", but I think it is forced by legacy code and it is "not so bad" :)