Skip to content
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

@Jacksonized: modify generated @(Super)Builders such that they will be used by Jackson's deserialization #2387

Merged
merged 3 commits into from
Mar 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/changelog.markdown
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
Lombok Changelog
----------------

### v.18.13 "Edgy Guinea Pig"
### v1.18.13 "Edgy Guinea Pig"
* BREAKING CHANGE: mapstruct users should not add a dependency to lombok-mapstruct-binding. This solves compiling modules with lombok (and mapstruct).
* FEATURE: Similar to `@Builder`, you can now configure a `@SuperBuilder`'s 'setter' prefixes via `@SuperBuilder(setterPrefix = "set")` for example. We still discourage doing this. [Pull Request #2357](https://github.com/rzwitserloot/lombok/pull/2357).
* FEATURE: If using `@Synchronized("lockVar")`, if `lockVar` is referring to a static field, the code lombok generates no longer causes a warning about accessing a static entity incorrectly. [Issue #678](https://github.com/rzwitserloot/lombok/issues/678)
* BUGFIX: Using `@SuperBuilder` on a class that has some fairly convoluted generics usage would fail with 'Wrong number of type arguments'. [Issue #2359](https://github.com/rzwitserloot/lombok/issues/2359) [Pull Request #2362](https://github.com/rzwitserloot/lombok/pull/2362)
* BUGFIX: Various lombok annotations on classes nested inside enums or interfaces would cause errors in eclipse. [Issue #2369](https://github.com/rzwitserloot/lombok/issues/2369)
* BUGFIX: Trying to add `@ExtensionMethod`s with exactly 2 arguments would fail in eclipse. [Issue #1441](https://github.com/rzwitserloot/lombok/issues/1441) [Pull Request #2376](https://github.com/rzwitserloot/lombok/pull/2376) thanks to __@Rawi01__.
* FEATURE: `@Jacksonized` on a `@Builder` or `@SuperBuilder` will configure [Jackson](https://github.com/FasterXML/jackson) to use this builder when deserializing. [Pull Request #2387](https://github.com/rzwitserloot/lombok/pull/2387). [@Jacksonized documentation](https://projectlombok.org/features/experimental/Jacksonized).

### v1.18.12 (February 1st, 2020)
* PLATFORM: Support for JDK13 (including `yield` in switch expressions, as well as delombok having a nicer style for arrow-style switch blocks, and text blocks).
Expand Down
9 changes: 9 additions & 0 deletions src/core/lombok/ConfigurationKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,15 @@ private ConfigurationKeys() {}
* If set, <em>any</em> usage of {@code @WithBy} results in a warning / error.
*/
public static final ConfigurationKey<FlagUsageType> WITHBY_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.withBy.flagUsage", "Emit a warning or error if @WithBy is used.") {};

// ----- Jacksonized -----

/**
* lombok configuration: {@code lombok.jacksonized.flagUsage} = {@code WARNING} | {@code ERROR}.
*
* If set, <em>any</em> usage of {@code @Jacksonized} results in a warning / error.
*/
public static final ConfigurationKey<FlagUsageType> JACKSONIZED_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.jacksonized.flagUsage", "Emit a warning or error if @Jacksonized is used.") {};

// ----- Configuration System -----

Expand Down
14 changes: 13 additions & 1 deletion src/core/lombok/core/handlers/HandlerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static int primeForNull() {
return 43;
}

public static final List<String> NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS;
public static final List<String> NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS;
static {
NONNULL_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
"androidx.annotation.NonNull",
Expand Down Expand Up @@ -315,6 +315,18 @@ public static int primeForNull() {
"com.fasterxml.jackson.annotation.JsonProperty",
"com.fasterxml.jackson.annotation.JsonSetter",
}));
JACKSON_COPY_TO_BUILDER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
janrieke marked this conversation as resolved.
Show resolved Hide resolved
"com.fasterxml.jackson.annotation.JsonFormat",
"com.fasterxml.jackson.annotation.JsonIgnoreProperties",
"com.fasterxml.jackson.annotation.JsonIgnoreType",
"com.fasterxml.jackson.annotation.JsonPropertyOrder",
"com.fasterxml.jackson.annotation.JsonRootName",
"com.fasterxml.jackson.annotation.JsonSubTypes",
"com.fasterxml.jackson.annotation.JsonTypeInfo",
"com.fasterxml.jackson.annotation.JsonTypeName",
"com.fasterxml.jackson.annotation.JsonView",
"com.fasterxml.jackson.databind.annotation.JsonNaming",
}));
}

/** Checks if the given name is a valid identifier.
Expand Down
35 changes: 25 additions & 10 deletions src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1957,12 +1957,16 @@ public static Annotation[] addGenerated(EclipseNode node, ASTNode source, Annota
result = addAnnotation(source, result, JAVAX_ANNOTATION_GENERATED, new StringLiteral(LOMBOK, 0, 0, 0));
}
if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) {
result = addAnnotation(source, result, LOMBOK_GENERATED, null);
result = addAnnotation(source, result, LOMBOK_GENERATED);
}
return result;
}

static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn, ASTNode arg) {
static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn) {
return addAnnotation(source, originalAnnotationArray, annotationTypeFqn, (ASTNode[]) null);
}

static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn, ASTNode... args) {
char[] simpleName = annotationTypeFqn[annotationTypeFqn.length - 1];

if (originalAnnotationArray != null) for (Annotation ann : originalAnnotationArray) {
Expand All @@ -1984,20 +1988,23 @@ static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotatio
QualifiedTypeReference qualifiedType = new QualifiedTypeReference(annotationTypeFqn, poss);
setGeneratedBy(qualifiedType, source);
Annotation ann;
if (arg instanceof Expression) {
if (args != null && args.length == 1 && args[0] instanceof Expression) {
SingleMemberAnnotation sma = new SingleMemberAnnotation(qualifiedType, pS);
sma.declarationSourceEnd = pE;
arg.sourceStart = pS;
arg.sourceEnd = pE;
sma.memberValue = (Expression) arg;
args[0].sourceStart = pS;
args[0].sourceEnd = pE;
sma.memberValue = (Expression) args[0];
setGeneratedBy(sma.memberValue, source);
ann = sma;
} else if (arg instanceof MemberValuePair) {
} else if (args != null && args.length >= 1 && arrayHasOnlyElementsOfType(args, MemberValuePair.class)) {
NormalAnnotation na = new NormalAnnotation(qualifiedType, pS);
na.declarationSourceEnd = pE;
arg.sourceStart = pS;
arg.sourceEnd = pE;
na.memberValuePairs = new MemberValuePair[] {(MemberValuePair) arg};
na.memberValuePairs = new MemberValuePair[args.length];
for (int i = 0; i < args.length; i++) {
args[i].sourceStart = pS;
args[i].sourceEnd = pE;
na.memberValuePairs[i] = (MemberValuePair) args[i];
}
setGeneratedBy(na.memberValuePairs[0], source);
setGeneratedBy(na.memberValuePairs[0].value, source);
na.memberValuePairs[0].value.sourceStart = pS;
Expand All @@ -2016,6 +2023,14 @@ static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotatio
return newAnnotationArray;
}

private static boolean arrayHasOnlyElementsOfType(Object[] array, Class<?> clazz) {
for (Object element : array) {
if (!clazz.isInstance(element))
return false;
}
return true;
}

/**
* Generates a new statement that checks if the given local variable is null, and if so, throws a specified exception with the
* variable name as message.
Expand Down
56 changes: 31 additions & 25 deletions src/core/lombok/eclipse/handlers/HandleBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -376,32 +376,9 @@ private static final char[] prefixWith(char[] prefix, char[] name) {
thrownExceptions = md.thrownExceptions;
nameOfStaticBuilderMethod = md.selector;
if (replaceNameInBuilderClassName) {
char[] token;
if (md.returnType instanceof QualifiedTypeReference) {
char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens;
token = tokens[tokens.length - 1];
} else if (md.returnType instanceof SingleTypeReference) {
token = ((SingleTypeReference) md.returnType).token;
if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) {
for (TypeParameter tp : typeParams) {
if (Arrays.equals(tp.name, token)) {
annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
return;
}
}
}
} else {
annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem.");
char[] token = returnTypeToBuilderClassName(annotationNode, md, typeParams);
if (token == null)
return;
}

if (Character.isLowerCase(token[0])) {
char[] newToken = new char[token.length];
System.arraycopy(token, 1, newToken, 1, token.length - 1);
newToken[0] = Character.toTitleCase(token[0]);
token = newToken;
}

builderClassName = builderClassName.replace("*", new String(token));
}
} else {
Expand Down Expand Up @@ -550,6 +527,35 @@ private static final char[] prefixWith(char[] prefix, char[] name) {
}
}
}

static char[] returnTypeToBuilderClassName(EclipseNode annotationNode, MethodDeclaration md, TypeParameter[] typeParams) {
char[] token;
if (md.returnType instanceof QualifiedTypeReference) {
char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens;
token = tokens[tokens.length - 1];
} else if (md.returnType instanceof SingleTypeReference) {
token = ((SingleTypeReference) md.returnType).token;
if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) {
for (TypeParameter tp : typeParams) {
if (Arrays.equals(tp.name, token)) {
annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
return null;
}
}
}
} else {
annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem.");
return null;
}

if (Character.isLowerCase(token[0])) {
char[] newToken = new char[token.length];
System.arraycopy(token, 1, newToken, 1, token.length - 1);
newToken[0] = Character.toTitleCase(token[0]);
token = newToken;
}
return token;
}

private static final char[] BUILDER_TEMP_VAR = {'b', 'u', 'i', 'l', 'd', 'e', 'r'};
private MethodDeclaration generateToBuilderMethod(CheckerFrameworkVersion cfv, boolean isStatic, String methodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, List<BuilderFieldData> builderFields, boolean fluent, ASTNode source, AccessLevel access, String prefix) {
Expand Down