-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Default value #31
base: master
Are you sure you want to change the base?
Conversation
Hi @MehrunesSky, Thanks for submitting the PR! Before I review it in depth, can you share more about the use case here? In what situation would you want to have a default value only in the Builder, and not in the constructor of your class? Thanks, |
Hello, I have very large objects and jilt allows me to easily instantiate them with properties that are optional and others that are not. I don't know if it's clear, please don't hesitate if I need to clarify. |
Thanks for the explanation! I suspected this is related to some Lombok BS 😉. I was thinking of a slightly different design for this feature 🙂. Since having a default value implies the property is optional, I wouldn't introduce a new annotation just for this, but instead use the existing Another part I'm not super enthusiastic about is the initializer being inside a string. I'd much rather prefer if it was regular code. Given that, how about we change the implementation to instead take the Builder initializer from the field initializer, same way as Lombok's So, something like this: import lombok.AllArgsConstructor;
import org.jilt.Builder;
import org.jilt.Opt;
@AllArgsConstructor
@Builder
public final class MyClass {
@Opt
private String field = initializeMe();
// ...
} This would result in Alternatively, if you're worried about this change not being backwards compatible, I would also accept special-casing the treatment of Lombok's import lombok.AllArgsConstructor;
import lombok.Builder.Default;
import org.jilt.Builder;
@AllArgsConstructor
@Builder
public final class MyClass {
@Default
private String field = initializeMe();
// ...
} Note that we would have to check for the presence of Let me know what you think of these ideas! |
It's a great idea to use lombok's Builder.Default. Do you have any advice on how to implement it? Using a third-party dependency like ASM or Javassist? Or I'm thinking of initializing the Class and retrieving the value by reflection, but that seems a bit complex. I've never really used Java reflexion, so it's quite new for me to contribute to a code generation project. |
I would just copy the initializer code as-is, and make sure to clarify in the documentation that you have to use fully-qualified names if you use anything that requires an import (like your example with |
But how do you copy the initializer as-is without external dependencies? Is there a technique? Do you have an example? |
Like I said, you ignore external dependencies, and copy the code as-is. Which means you need to use fully-qualified names instead of imports. Exactly like you do right now to reference |
I don't quite understand. So if the class is like this : @AllArgsConstructor
@Builder
public final class MyClass {
@lombok.Builder.Default
private String field = initializeMe();
// ...
} The builder will look something like this: public final class MyClassBuilder {
private String field = initializeMe();
// ...
} But I can't see how to technically copy the initializeMe() to the builder. I don't think I can just grab the “initializeMe();” with the java api. |
So, in a test in your PR, you have: import org.jilt.Builder;
@Builder
public class DefaultValue {
@Builder.Default("java.util.Collections.emptySet()")
public Set<String> attrs;
// ...
} Notice it's If we want to be consistent with how Lombok's import lombok.AllArgsConstructor;
import lombok.Builder.Default;
import org.jilt.Builder;
@AllArgsConstructor
@Builder
public class DefaultValue {
@Default
public Set<String> attrs = java.util.Collections.emptySet();
// ...
} Similarly, with import lombok.AllArgsConstructor;
import lombok.Builder.Default;
import org.jilt.Builder;
@AllArgsConstructor
@Builder
public final class MyClass {
@Default
private String field = MyClass.initializeMe(); // just initializeMe() wouldn't work here
// ...
} And if import lombok.AllArgsConstructor;
import lombok.Builder.Default;
import org.jilt.Builder;
@AllArgsConstructor
@Builder
public final class MyClass {
@Default
private String field = com.mycompany.otherpackage.MyOtherClass.initializeMe();
// ...
} And we would explain all of these requirements in the documentation. Does this make sense? 🙂 |
Okay, I understand better. But do you have any idea how to implement your proposal? It seems very complicated to implement. |
Why? 🙂 Like I said before, just copy the initializer as-is from the class to the Builder, and you're done 🙂. |
Hello, I've just updated the code to take your suggestion in consideration. Is it pretty much what you had in mind? I haven't updated the doc yet, I'll update it when it's good on the code part. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the new revision @MehrunesSky! I have a few comments, most importantly, we need to make sure that, if a field is annotated with @lombok.Builder.Default
, it is treated as optional (same as if it was annotated with @Opt
).
src/main/java/org/jilt/Builder.java
Outdated
@@ -333,4 +333,5 @@ | |||
@Retention(RetentionPolicy.SOURCE) | |||
@interface Ignore { | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for this change I think 🙂.
@@ -100,6 +100,7 @@ dependencies { | |||
compileOnly 'com.squareup:javapoet:1.8.0' | |||
|
|||
testImplementation 'junit:junit:4.13.2' | |||
testImplementation 'org.projectlombok:lombok:1.18.36' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need Lombok also in the testAnnotationProcessor
configuration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fix :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need Lombok in both testImplementation
and testAnnotationProcessor
? Isn't testAnnotationProcessor
enough?
@@ -33,7 +36,11 @@ | |||
import java.util.List; | |||
|
|||
abstract class AbstractBuilderGenerator implements BuilderGenerator { | |||
|
|||
private static final CodeBlock CODE_BLOC_EMPTY = CodeBlock.of(""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this field. I think it will be used only once. I would remove it.
CodeBlock initializer = CODE_BLOC_EMPTY; | ||
if (attribute.getAnnotation(Opt.class) != null || getLombokBuilderDefaultAnnotation(attribute.getAnnotationMirrors())){ | ||
initializer = getCodeBlockInitializer(attribute); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of this code structure, given that getCodeBlockInitializer()
can also return an empty CodeBlock
.
So, let's change the code to be just:
CodeBlock initializer = CODE_BLOC_EMPTY; | |
if (attribute.getAnnotation(Opt.class) != null || getLombokBuilderDefaultAnnotation(attribute.getAnnotationMirrors())){ | |
initializer = getCodeBlockInitializer(attribute); | |
} | |
CodeBlock initializer = this.builderFieldInitializer(attribute); |
And at that point, the code is so simple, I would just inline the initializer
variable, and get rid of it completely.
(If it wasn't clear, I'm also not a huge fan of the getCodeBlockInitializer
name 😛)
public int attr4 = 1000; | ||
|
||
@lombok.Builder.Default | ||
public int attr5 = 1000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try referencing the field here, to check if that works? So something like
public int attr5 = 1000; | |
public int attr5 = attr1 + 1000; |
@Opt | ||
public int attr1 = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not implement this functionality for @Opt
. Let's make it work only for @lombok.Builder.Default
.
I would remove this entire class.
|
||
@Builder | ||
public class DefaultValueWithLombok { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
import java.util.Set; | ||
|
||
@Builder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is missing the functionality of marking a field annotated with @lombok.Builder.Default
as optional. We would catch it if we made the @Builder
here STAGED
.
So, let's change this to:
@Builder | |
@Builder(style = BuilderStyle.STAGED) |
and make sure the DefaultTest
test still passes.
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class DefaultTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this class LombokDefaultTest
, or something like that, instead.
Co-authored-by: Adam Ruka <[email protected]>
Hello,
I would like to add default values in JIlt without having to specify them in my constructor.
So I thought of an @Builder.Default annotation a bit like lombok, which takes the default value as a parameter.
This is just a suggestion. If you have any feedback or another idea for implementing this, please don't hesitate to contact me.