diff options
26 files changed, 1194 insertions, 104 deletions
diff --git a/src/core/lombok/Getter.java b/src/core/lombok/Getter.java index 7e545ae0..97ce315c 100644 --- a/src/core/lombok/Getter.java +++ b/src/core/lombok/Getter.java @@ -65,6 +65,8 @@ public @interface Getter { */ AnyAnnotation[] onMethod() default {}; + boolean lazy() default false; + /** * Placeholder annotation to enable the placement of annotations on the getter method. * @deprecated Don't use this annotation, since we might remove it. diff --git a/src/core/lombok/eclipse/Eclipse.java b/src/core/lombok/eclipse/Eclipse.java index 4101370c..5dcd9de8 100644 --- a/src/core/lombok/eclipse/Eclipse.java +++ b/src/core/lombok/eclipse/Eclipse.java @@ -352,16 +352,24 @@ public class Eclipse { if (base != null) { if (dims > 0) { - return new ArrayTypeReference(base, dims, pos(pos)); + TypeReference result = new ArrayTypeReference(base, dims, pos(pos)); + Eclipse.setGeneratedBy(result, pos); + return result; } - return new SingleTypeReference(base, pos(pos)); + TypeReference result = new SingleTypeReference(base, pos(pos)); + Eclipse.setGeneratedBy(result, pos); + return result; } if (binding.isAnonymousType()) { ReferenceBinding ref = (ReferenceBinding)binding; ReferenceBinding[] supers = ref.superInterfaces(); if (supers == null || supers.length == 0) supers = new ReferenceBinding[] {ref.superclass()}; - if (supers[0] == null) return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(pos, 3)); + if (supers[0] == null) { + TypeReference result = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(pos, 3)); + Eclipse.setGeneratedBy(result, pos); + return result; + } return makeType(supers[0], pos, false); } @@ -371,9 +379,12 @@ public class Eclipse { if (binding.isUnboundWildcard()) { if (!allowCompound) { - return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(pos, 3)); + TypeReference result = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(pos, 3)); + Eclipse.setGeneratedBy(result, pos); + return result; } else { Wildcard out = new Wildcard(Wildcard.UNBOUND); + Eclipse.setGeneratedBy(out, pos); out.sourceStart = pos.sourceStart; out.sourceEnd = pos.sourceEnd; return out; @@ -387,6 +398,7 @@ public class Eclipse { return makeType(wildcard.bound, pos, false); } else { Wildcard out = new Wildcard(Wildcard.EXTENDS); + Eclipse.setGeneratedBy(out, pos); out.bound = makeType(wildcard.bound, pos, false); out.sourceStart = pos.sourceStart; out.sourceEnd = pos.sourceEnd; @@ -394,12 +406,15 @@ public class Eclipse { } } else if (allowCompound && wildcard.boundKind == Wildcard.SUPER) { Wildcard out = new Wildcard(Wildcard.SUPER); + Eclipse.setGeneratedBy(out, pos); out.bound = makeType(wildcard.bound, pos, false); out.sourceStart = pos.sourceStart; out.sourceEnd = pos.sourceEnd; return out; } else { - return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(pos, 3)); + TypeReference result = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(pos, 3)); + Eclipse.setGeneratedBy(result, pos); + return result; } } @@ -433,23 +448,34 @@ public class Eclipse { if (parts.length > 1) { TypeReference[][] typeArguments = new TypeReference[parts.length][]; typeArguments[typeArguments.length - 1] = params; - return new ParameterizedQualifiedTypeReference(parts, typeArguments, dims, poss(pos, parts.length)); + TypeReference result = new ParameterizedQualifiedTypeReference(parts, typeArguments, dims, poss(pos, parts.length)); + Eclipse.setGeneratedBy(result, pos); + return result; } - return new ParameterizedSingleTypeReference(parts[0], params, dims, pos(pos)); + TypeReference result = new ParameterizedSingleTypeReference(parts[0], params, dims, pos(pos)); + Eclipse.setGeneratedBy(result, pos); + return result; } if (dims > 0) { if (parts.length > 1) { - return new ArrayQualifiedTypeReference(parts, dims, poss(pos, parts.length)); + TypeReference result = new ArrayQualifiedTypeReference(parts, dims, poss(pos, parts.length)); + Eclipse.setGeneratedBy(result, pos); + return result; } - return new ArrayTypeReference(parts[0], dims, pos(pos)); + TypeReference result = new ArrayTypeReference(parts[0], dims, pos(pos)); + Eclipse.setGeneratedBy(result, pos); + return result; } if (parts.length > 1) { - return new QualifiedTypeReference(parts, poss(pos, parts.length)); + TypeReference result = new QualifiedTypeReference(parts, poss(pos, parts.length)); + Eclipse.setGeneratedBy(result, pos); + return result; } - return new SingleTypeReference(parts[0], pos(pos)); - + TypeReference result = new SingleTypeReference(parts[0], pos(pos)); + Eclipse.setGeneratedBy(result, pos); + return result; } private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 921d955e..4097362b 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -175,8 +175,29 @@ public class EclipseHandlerUtil { return null; } - static TypeReference getFieldType(EclipseNode field, boolean useFieldsDirectly) { - GetterMethod getter = useFieldsDirectly ? null : findGetter(field); + enum FieldAccess { + GETTER, PREFER_FIELD, ALWAYS_FIELD; + } + + static boolean lookForGetter(EclipseNode field, FieldAccess fieldAccess) { + if (fieldAccess == FieldAccess.GETTER) return true; + if (fieldAccess == FieldAccess.ALWAYS_FIELD) return false; + + // If @Getter(lazy = true) is used, then using it is mandatory. + for (EclipseNode child : field.down()) { + if (child.getKind() != Kind.ANNOTATION) continue; + if (Eclipse.annotationTypeMatches(Getter.class, child)) { + AnnotationValues<Getter> ann = Eclipse.createAnnotation(Getter.class, child); + if (ann.getInstance().lazy()) return true; + } + } + return false; + } + + static TypeReference getFieldType(EclipseNode field, FieldAccess fieldAccess) { + boolean lookForGetter = lookForGetter(field, fieldAccess); + + GetterMethod getter = lookForGetter ? findGetter(field) : null; if (getter == null) { return ((FieldDeclaration)field.get()).type; } @@ -184,11 +205,13 @@ public class EclipseHandlerUtil { return getter.type; } - static Expression createFieldAccessor(EclipseNode field, boolean useFieldsDirectly, ASTNode source) { + static Expression createFieldAccessor(EclipseNode field, FieldAccess fieldAccess, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - GetterMethod getter = useFieldsDirectly ? null : findGetter(field); + boolean lookForGetter = lookForGetter(field, fieldAccess); + + GetterMethod getter = lookForGetter ? findGetter(field) : null; if (getter == null) { FieldDeclaration fieldDecl = (FieldDeclaration)field.get(); @@ -219,11 +242,13 @@ public class EclipseHandlerUtil { return call; } - static Expression createFieldAccessor(EclipseNode field, boolean useFieldsDirectly, ASTNode source, char[] receiver) { + static Expression createFieldAccessor(EclipseNode field, FieldAccess fieldAccess, ASTNode source, char[] receiver) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - GetterMethod getter = useFieldsDirectly ? null : findGetter(field); + boolean lookForGetter = lookForGetter(field, fieldAccess); + + GetterMethod getter = lookForGetter ? findGetter(field) : null; if (getter == null) { NameReference ref; diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 2b830241..895c076e 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -113,7 +113,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA } } - generateMethods(typeNode, errorNode, null, null, null, false, false); + generateMethods(typeNode, errorNode, null, null, null, false, FieldAccess.GETTER); } @Override public boolean handle(AnnotationValues<EqualsAndHashCode> annotation, @@ -135,11 +135,13 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); } - return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true, ann.doNotUseGetters()); + FieldAccess fieldAccess = ann.doNotUseGetters() ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; + + return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true, fieldAccess); } public boolean generateMethods(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes, - Boolean callSuper, boolean whineIfExists, boolean useFieldsDirectly) { + Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { assert excludes == null || includes == null; TypeDeclaration typeDecl = null; @@ -209,7 +211,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA boolean isFinal = (typeDecl.modifiers & ClassFileConstants.AccFinal) != 0; needsCanEqual = !isDirectDescendantOfObject || !isFinal; - MethodDeclaration equals = createEquals(typeNode, nodesForEquality, callSuper, errorNode.get(), useFieldsDirectly, needsCanEqual); + MethodDeclaration equals = createEquals(typeNode, nodesForEquality, callSuper, errorNode.get(), fieldAccess, needsCanEqual); injectMethod(typeNode, equals); break; case EXISTS_BY_LOMBOK: @@ -237,7 +239,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA switch (methodExists("hashCode", typeNode)) { case NOT_EXISTS: - MethodDeclaration hashCode = createHashCode(typeNode, nodesForEquality, callSuper, errorNode.get(), useFieldsDirectly); + MethodDeclaration hashCode = createHashCode(typeNode, nodesForEquality, callSuper, errorNode.get(), fieldAccess); injectMethod(typeNode, hashCode); break; case EXISTS_BY_LOMBOK: @@ -253,7 +255,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA return true; } - private MethodDeclaration createHashCode(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, boolean useFieldsDirectly) { + private MethodDeclaration createHashCode(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; @@ -318,9 +320,9 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA int tempCounter = 0; for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, useFieldsDirectly); + TypeReference fType = getFieldType(field, fieldAccess); char[] token = fType.getLastToken(); - Expression fieldAccessor = createFieldAccessor(field, useFieldsDirectly, source); + Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source); if (fType.dimensions() == 0 && token != null) { if (Arrays.equals(TypeConstants.FLOAT, token)) { /* Float.floatToIntBits(fieldName) */ @@ -363,7 +365,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA Eclipse.setGeneratedBy(int1231or1237, source); intoResult.add(int1231or1237); } else if (Arrays.equals(TypeConstants.LONG, token)) { - intoResult.add(longToIntForHashCode(fieldAccessor, createFieldAccessor(field, useFieldsDirectly, source), source)); + intoResult.add(longToIntForHashCode(fieldAccessor, createFieldAccessor(field, fieldAccess, source), source)); } else if (BUILT_IN_TYPES.contains(new String(token))) { intoResult.add(fieldAccessor); } else /* objects */ { @@ -371,7 +373,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA MessageSend hashCodeCall = new MessageSend(); hashCodeCall.sourceStart = pS; hashCodeCall.sourceEnd = pE; Eclipse.setGeneratedBy(hashCodeCall, source); - hashCodeCall.receiver = createFieldAccessor(field, useFieldsDirectly, source); + hashCodeCall.receiver = createFieldAccessor(field, fieldAccess, source); hashCodeCall.selector = "hashCode".toCharArray(); NullLiteral nullLiteral = new NullLiteral(pS, pE); Eclipse.setGeneratedBy(nullLiteral, source); @@ -433,7 +435,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA return method; } - private MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, boolean useFieldsDirectly, boolean needsCanEqual) { + private MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, FieldAccess fieldAccess, boolean needsCanEqual) { int pS = source.sourceStart; int pE = source.sourceEnd; long p = (long)pS << 32 | pE; TypeDeclaration typeDecl = (TypeDeclaration)type.get(); @@ -589,10 +591,10 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA } for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, useFieldsDirectly); + TypeReference fType = getFieldType(field, fieldAccess); char[] token = fType.getLastToken(); - Expression thisFieldAccessor = createFieldAccessor(field, useFieldsDirectly, source); - Expression otherFieldAccessor = createFieldAccessor(field, useFieldsDirectly, source, otherName); + Expression thisFieldAccessor = createFieldAccessor(field, fieldAccess, source); + Expression otherFieldAccessor = createFieldAccessor(field, fieldAccess, source, otherName); if (fType.dimensions() == 0 && token != null) { if (Arrays.equals(TypeConstants.FLOAT, token)) { @@ -619,9 +621,9 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA MessageSend equalsCall = new MessageSend(); equalsCall.sourceStart = pS; equalsCall.sourceEnd = pE; Eclipse.setGeneratedBy(equalsCall, source); - equalsCall.receiver = createFieldAccessor(field, useFieldsDirectly, source); + equalsCall.receiver = createFieldAccessor(field, fieldAccess, source); equalsCall.selector = "equals".toCharArray(); - equalsCall.arguments = new Expression[] { createFieldAccessor(field, useFieldsDirectly, source, otherName) }; + equalsCall.arguments = new Expression[] { createFieldAccessor(field, fieldAccess, source, otherName) }; UnaryExpression fieldsNotEqual = new UnaryExpression(equalsCall, OperatorIds.NOT); fieldsNotEqual.sourceStart = pS; fieldsNotEqual.sourceEnd = pE; Eclipse.setGeneratedBy(fieldsNotEqual, source); diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 38a6b468..56d0ba6c 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -25,6 +25,9 @@ import static lombok.eclipse.Eclipse.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import lombok.AccessLevel; import lombok.Getter; @@ -34,14 +37,30 @@ import lombok.core.handlers.TransformationsUtil; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -76,7 +95,7 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { } for (EclipseNode field : typeNode.down()) { - if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, pos.get(), level, null); + if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, pos.get(), level, null, false); } return true; } @@ -103,7 +122,7 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { * If not, the getter is still generated if it isn't already there, though there will not * be a warning if its already there. The default access level is used. */ - public void generateGetterForField(EclipseNode fieldNode, ASTNode pos, AccessLevel level, Annotation[] onMethod) { + public void generateGetterForField(EclipseNode fieldNode, ASTNode pos, AccessLevel level, Annotation[] onMethod, boolean lazy) { for (EclipseNode child : fieldNode.down()) { if (child.getKind() == Kind.ANNOTATION) { if (annotationTypeMatches(Getter.class, child)) { @@ -113,42 +132,61 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { } } - createGetterForField(level, fieldNode, fieldNode, pos, false, onMethod); + createGetterForField(level, fieldNode, fieldNode, pos, false, onMethod, lazy); } public boolean handle(AnnotationValues<Getter> annotation, Annotation ast, EclipseNode annotationNode) { EclipseNode node = annotationNode.up(); - AccessLevel level = annotation.getInstance().value(); - if (level == AccessLevel.NONE) return true; + Getter annotationInstance = annotation.getInstance(); + AccessLevel level = annotationInstance.value(); + boolean lazy = annotationInstance.lazy(); + if (level == AccessLevel.NONE) { + if (lazy) { + annotationNode.addWarning("'lazy' does not work with AccessLevel.NONE."); + } + return true; + } if (node == null) return false; Annotation[] onMethod = getAndRemoveAnnotationParameter(ast, "onMethod"); if (node.getKind() == Kind.FIELD) { - return createGetterForFields(level, annotationNode.upFromAnnotationToFields(), annotationNode, annotationNode.get(), true, onMethod); + return createGetterForFields(level, annotationNode.upFromAnnotationToFields(), annotationNode, annotationNode.get(), true, onMethod, lazy); } if (node.getKind() == Kind.TYPE) { if (onMethod != null && onMethod.length != 0) annotationNode.addError("'onMethod' is not supported for @Getter on a type."); + if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type."); return generateGetterForType(node, annotationNode, level, false); } return false; } - private boolean createGetterForFields(AccessLevel level, Collection<EclipseNode> fieldNodes, EclipseNode errorNode, ASTNode source, boolean whineIfExists, Annotation[] onMethod) { + private boolean createGetterForFields(AccessLevel level, Collection<EclipseNode> fieldNodes, EclipseNode errorNode, ASTNode source, boolean whineIfExists, Annotation[] onMethod, boolean lazy) { for (EclipseNode fieldNode : fieldNodes) { - createGetterForField(level, fieldNode, errorNode, source, whineIfExists, onMethod); + createGetterForField(level, fieldNode, errorNode, source, whineIfExists, onMethod, lazy); } return true; } private boolean createGetterForField(AccessLevel level, - EclipseNode fieldNode, EclipseNode errorNode, ASTNode source, boolean whineIfExists, Annotation[] onMethod) { + EclipseNode fieldNode, EclipseNode errorNode, ASTNode source, boolean whineIfExists, Annotation[] onMethod, boolean lazy) { if (fieldNode.getKind() != Kind.FIELD) { errorNode.addError("@Getter is only supported on a class or a field."); return true; } FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + if (lazy) { + if ((field.modifiers & ClassFileConstants.AccPrivate) == 0 || (field.modifiers & ClassFileConstants.AccFinal) == 0) { + errorNode.addError("'lazy' requires the field to be private and final."); + return true; + } + if (field.initialization == null) { + errorNode.addError("'lazy' requires field initialization."); + return true; + } + } + TypeReference fieldType = copyType(field.type, source); String fieldName = new String(field.name); boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; @@ -174,7 +212,7 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { } } - MethodDeclaration method = generateGetter((TypeDeclaration) fieldNode.up().get(), fieldNode, getterName, modifier, source); + MethodDeclaration method = generateGetter((TypeDeclaration) fieldNode.up().get(), fieldNode, getterName, modifier, source, lazy); Annotation[] copiedAnnotations = copyAnnotations(source, findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN), findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN), onMethod); if (copiedAnnotations.length != 0) { method.annotations = copiedAnnotations; @@ -185,12 +223,22 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { return true; } - private MethodDeclaration generateGetter(TypeDeclaration parent, EclipseNode fieldNode, String name, int modifier, ASTNode source) { - FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + private MethodDeclaration generateGetter(TypeDeclaration parent, EclipseNode fieldNode, String name, int modifier, ASTNode source, boolean lazy) { + + // Remember the type; lazy will change it; + TypeReference returnType = copyType(((FieldDeclaration) fieldNode.get()).type, source); + + Statement[] statements; + if (lazy) { + statements = createLazyGetterBody(source, fieldNode); + } else { + statements = createSimpleGetterBody(source, fieldNode); + } + MethodDeclaration method = new MethodDeclaration(parent.compilationResult); Eclipse.setGeneratedBy(method, source); method.modifiers = modifier; - method.returnType = copyType(field.type, source); + method.returnType = returnType; method.annotations = null; method.arguments = null; method.selector = name.toCharArray(); @@ -198,12 +246,209 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { method.thrownExceptions = null; method.typeParameters = null; method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - Expression fieldRef = createFieldAccessor(fieldNode, true, source); - Statement returnStatement = new ReturnStatement(fieldRef, field.sourceStart, field.sourceEnd); - Eclipse.setGeneratedBy(returnStatement, source); method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; - method.statements = new Statement[] { returnStatement }; + method.statements = statements; return method; } + + private Statement[] createSimpleGetterBody(ASTNode source, EclipseNode fieldNode) { + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + Expression fieldRef = createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + Statement returnStatement = new ReturnStatement(fieldRef, field.sourceStart, field.sourceEnd); + Eclipse.setGeneratedBy(returnStatement, source); + return new Statement[] {returnStatement}; + } + + private static final char[][] AR = fromQualifiedName("java.util.concurrent.atomic.AtomicReference"); + private static final TypeReference[][] AR_PARAMS = new TypeReference[5][]; + + private static final java.util.Map<String, char[][]> TYPE_MAP; + static { + Map<String, char[][]> m = new HashMap<String, char[][]>(); + m.put("int", fromQualifiedName("java.lang.Integer")); + m.put("double", fromQualifiedName("java.lang.Double")); + m.put("float", fromQualifiedName("java.lang.Float")); + m.put("short", fromQualifiedName("java.lang.Short")); + m.put("byte", fromQualifiedName("java.lang.Byte")); + m.put("long", fromQualifiedName("java.lang.Long")); + m.put("boolean", fromQualifiedName("java.lang.Boolean")); + m.put("char", fromQualifiedName("java.lang.Character")); + TYPE_MAP = Collections.unmodifiableMap(m); + } + + private static char[] valueName = "value".toCharArray(); + + private Statement[] createLazyGetterBody(ASTNode source, EclipseNode fieldNode) { + /* + java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + if (value == null) { + synchronized (this.fieldName) { + value = this.fieldName.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<ValueType>(new ValueType()); + this.fieldName.set(value); + } + } + } + return value.get(); + */ + + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + TypeReference componentType = copyType(field.type, source); + if (field.type instanceof SingleTypeReference && !(field.type instanceof ArrayTypeReference)) { + char[][] newType = TYPE_MAP.get(new String(((SingleTypeReference)field.type).token)); + if (newType != null) { + componentType = new QualifiedTypeReference(newType, poss(source, 3)); + Eclipse.setGeneratedBy(componentType, source); + } + } + + Statement[] statements = new Statement[3]; + + /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); */ { + LocalDeclaration valueDecl = new LocalDeclaration(valueName, pS, pE); + Eclipse.setGeneratedBy(valueDecl, source); + TypeReference[][] typeParams = AR_PARAMS.clone(); + typeParams[4] = new TypeReference[] {copyType(componentType, source)}; + valueDecl.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + valueDecl.type.sourceStart = pS; valueDecl.type.sourceEnd = pE; + Eclipse.setGeneratedBy(valueDecl.type, source); + + MessageSend getter = new MessageSend(); + Eclipse.setGeneratedBy(getter, source); + getter.sourceStart = pS; getter.sourceEnd = pE; + getter.selector = new char[] {'g', 'e', 't'}; + getter.receiver = EclipseHandlerUtil.createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + + valueDecl.initialization = getter; + Eclipse.setGeneratedBy(valueDecl.initialization, source); + statements[0] = valueDecl; + } + + /* + if (value == null) { + synchronized (this.fieldName) { + value = this.fieldName.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<ValueType>(new ValueType()); + this.fieldName.set(value); + } + } + } + */ { + EqualExpression cond = new EqualExpression( + new SingleNameReference(valueName, p), new NullLiteral(pS, pE), + BinaryExpression.EQUAL_EQUAL); + Eclipse.setGeneratedBy(cond.left, source); + Eclipse.setGeneratedBy(cond.right, source); + Eclipse.setGeneratedBy(cond, source); + Block then = new Block(0); + Eclipse.setGeneratedBy(then, source); + Expression lock = EclipseHandlerUtil.createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + Block inner = new Block(0); + Eclipse.setGeneratedBy(inner, source); + inner.statements = new Statement[2]; + /* value = this.fieldName.get(); */ { + MessageSend getter = new MessageSend(); + Eclipse.setGeneratedBy(getter, source); + getter.sourceStart = pS; getter.sourceEnd = pE; + getter.selector = new char[] {'g', 'e', 't'}; + getter.receiver = EclipseHandlerUtil.createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + Assignment assign = new Assignment(new SingleNameReference(valueName, p), getter, pE); + Eclipse.setGeneratedBy(assign, source); + Eclipse.setGeneratedBy(assign.lhs, source); + inner.statements[0] = assign; + } + /* if (value == null) */ { + EqualExpression innerCond = new EqualExpression( + new SingleNameReference(valueName, p), new NullLiteral(pS, pE), + BinaryExpression.EQUAL_EQUAL); + Eclipse.setGeneratedBy(innerCond.left, source); + Eclipse.setGeneratedBy(innerCond.right, source); + Eclipse.setGeneratedBy(innerCond, source); + Block innerThen = new Block(0); + Eclipse.setGeneratedBy(innerThen, source); + innerThen.statements = new Statement[2]; + /*value = new java.util.concurrent.atomic.AtomicReference<ValueType>(new ValueType()); */ { + AllocationExpression create = new AllocationExpression(); + Eclipse.setGeneratedBy(create, source); + create.sourceStart = pS; create.sourceEnd = pE; + TypeReference[][] typeParams = AR_PARAMS.clone(); + typeParams[4] = new TypeReference[] {copyType(componentType, source)}; + create.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + create.type.sourceStart = pS; create.type.sourceEnd = pE; + Eclipse.setGeneratedBy(create.type, source); + create.arguments = new Expression[] {field.initialization}; + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), create, pE); + Eclipse.setGeneratedBy(innerAssign, source); + Eclipse.setGeneratedBy(innerAssign.lhs, source); + innerThen.statements[0] = innerAssign; + } + + /*this.fieldName.set(value);*/ { + MessageSend setter = new MessageSend(); + Eclipse.setGeneratedBy(setter, source); + setter.sourceStart = pS; setter.sourceEnd = pE; + setter.receiver = EclipseHandlerUtil.createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + setter.selector = new char[] { 's', 'e', 't' }; + setter.arguments = new Expression[] { + new SingleNameReference(valueName, p)}; + Eclipse.setGeneratedBy(setter.arguments[0], source); + innerThen.statements[1] = setter; + } + + IfStatement innerIf = new IfStatement(innerCond, innerThen, pS, pE); + Eclipse.setGeneratedBy(innerIf, source); + inner.statements[1] = innerIf; + } + + SynchronizedStatement sync = new SynchronizedStatement(lock, inner, pS, pE); + Eclipse.setGeneratedBy(sync, source); + then.statements = new Statement[] {sync}; + + IfStatement ifStatement = new IfStatement(cond, then, pS, pE); + Eclipse.setGeneratedBy(ifStatement, source); + statements[1] = ifStatement; + } + + /* return value.get(); */ { + MessageSend getter = new MessageSend(); + Eclipse.setGeneratedBy(getter, source); + getter.sourceStart = pS; getter.sourceEnd = pE; + getter.selector = new char[] {'g', 'e', 't'}; + getter.receiver = new SingleNameReference(valueName, p); + Eclipse.setGeneratedBy(getter.receiver, source); + + statements[2] = new ReturnStatement(getter, pS, pE); + Eclipse.setGeneratedBy(statements[2], source); + } + + + // update the field type and init last + + /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ { + + LocalDeclaration first = (LocalDeclaration) statements[0]; + TypeReference innerType = copyType(first.type, source); + + TypeReference[][] typeParams = AR_PARAMS.clone(); + typeParams[4] = new TypeReference[] {copyType(innerType, source)}; + TypeReference type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + // Some magic here + type.sourceStart = -1; type.sourceEnd = -2; + Eclipse.setGeneratedBy(type, source); + + field.type = type; + AllocationExpression init = new AllocationExpression(); + // Some magic here + init.sourceStart = field.initialization.sourceStart; init.sourceEnd = field.initialization.sourceEnd; + init.type = copyType(type, source); + field.initialization = init; + } + return statements; + } } diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 48e688fd..2c3ca6ed 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -35,6 +35,7 @@ import lombok.core.handlers.TransformationsUtil; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; @@ -206,7 +207,7 @@ public class HandleSetter implements EclipseAnnotationHandler<Setter> { method.thrownExceptions = null; method.typeParameters = null; method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - Expression fieldRef = createFieldAccessor(fieldNode, true, source); + Expression fieldRef = createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); NameReference fieldNameRef = new SingleNameReference(field.name, p); Eclipse.setGeneratedBy(fieldNameRef, source); Assignment assignment = new Assignment(fieldRef, fieldNameRef, (int)p); diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index b3f4abaa..f39cb129 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -38,6 +38,7 @@ import lombok.core.AST.Kind; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil.FieldAccess; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; @@ -94,7 +95,7 @@ public class HandleToString implements EclipseAnnotationHandler<ToString> { try { includeFieldNames = ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue(); } catch (Exception ignore) {} - generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, false); + generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, FieldAccess.GETTER); } public boolean handle(AnnotationValues<ToString> annotation, Annotation ast, EclipseNode annotationNode) { @@ -115,11 +116,13 @@ public class HandleToString implements EclipseAnnotationHandler<ToString> { checkForBogusFieldNames(typeNode, annotation); - return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true, ann.doNotUseGetters()); + FieldAccess fieldAccess = ann.doNotUseGetters() ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; + + return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true, fieldAccess); } public boolean generateToString(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes, - boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, boolean useFieldsDirectly) { + boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { TypeDeclaration typeDecl = null; if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); @@ -161,7 +164,7 @@ public class HandleToString implements EclipseAnnotationHandler<ToString> { switch (methodExists("toString", typeNode)) { case NOT_EXISTS: - MethodDeclaration toString = createToString(typeNode, nodesForToString, includeFieldNames, callSuper, errorNode.get(), useFieldsDirectly); + MethodDeclaration toString = createToString(typeNode, nodesForToString, includeFieldNames, callSuper, errorNode.get(), fieldAccess); injectMethod(typeNode, toString); return true; case EXISTS_BY_LOMBOK: @@ -176,7 +179,7 @@ public class HandleToString implements EclipseAnnotationHandler<ToString> { } private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, - boolean includeFieldNames, boolean callSuper, ASTNode source, boolean useFieldsDirectly) { + boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); String infixS = ", "; @@ -214,8 +217,8 @@ public class HandleToString implements EclipseAnnotationHandler<ToString> { } for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, useFieldsDirectly); - Expression fieldAccessor = createFieldAccessor(field, useFieldsDirectly, source); + TypeReference fType = getFieldType(field, fieldAccess); + Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source); Expression ex; if (fType.dimensions() > 0) { diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 8d524964..8799f2e1 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -90,7 +90,9 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); } - return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true, ann.doNotUseGetters()); + FieldAccess fieldAccess = ann.doNotUseGetters() ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; + + return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true, fieldAccess); } public void generateEqualsAndHashCodeForType(JavacNode typeNode, JavacNode errorNode) { @@ -103,11 +105,11 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd } } - generateMethods(typeNode, errorNode, null, null, null, false, false); + generateMethods(typeNode, errorNode, null, null, null, false, FieldAccess.GETTER); } private boolean generateMethods(JavacNode typeNode, JavacNode errorNode, List<String> excludes, List<String> includes, - Boolean callSuper, boolean whineIfExists, boolean useFieldsDirectly) { + Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { boolean notAClass = true; if (typeNode.get() instanceof JCClassDecl) { long flags = ((JCClassDecl)typeNode.get()).mods.flags; @@ -173,7 +175,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd boolean isFinal = (((JCClassDecl)typeNode.get()).mods.flags & Flags.FINAL) != 0; needsCanEqual = !isFinal || !isDirectDescendantOfObject; - JCMethodDecl method = createEquals(typeNode, nodesForEquality, callSuper, useFieldsDirectly, needsCanEqual); + JCMethodDecl method = createEquals(typeNode, nodesForEquality, callSuper, fieldAccess, needsCanEqual); injectMethod(typeNode, method); break; case EXISTS_BY_LOMBOK: @@ -200,7 +202,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd } switch (methodExists("hashCode", typeNode)) { case NOT_EXISTS: - JCMethodDecl method = createHashCode(typeNode, nodesForEquality, callSuper, useFieldsDirectly); + JCMethodDecl method = createHashCode(typeNode, nodesForEquality, callSuper, fieldAccess); injectMethod(typeNode, method); break; case EXISTS_BY_LOMBOK: @@ -215,7 +217,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd return true; } - private JCMethodDecl createHashCode(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, boolean useFieldsDirectly) { + private JCMethodDecl createHashCode(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, FieldAccess fieldAccess) { TreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.<JCExpression>nil()); @@ -247,8 +249,8 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd int tempCounter = 0; for (JavacNode fieldNode : fields) { - JCExpression fType = getFieldType(fieldNode, useFieldsDirectly); - JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, useFieldsDirectly); + JCExpression fType = getFieldType(fieldNode, fieldAccess); + JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); if (fType instanceof JCPrimitiveTypeTree) { switch (((JCPrimitiveTypeTree)fType).getPrimitiveTypeKind()) { case BOOLEAN: @@ -256,7 +258,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd intoResult = intoResult.append(maker.Conditional(fieldAccessor, maker.Literal(1231), maker.Literal(1237))); break; case LONG: - intoResult = intoResult.append(longToIntForHashCode(maker, fieldAccessor, createFieldAccessor(maker, fieldNode, useFieldsDirectly))); + intoResult = intoResult.append(longToIntForHashCode(maker, fieldAccessor, createFieldAccessor(maker, fieldNode, fieldAccess))); break; case FLOAT: /* Float.floatToIntBits(this.fieldName) */ @@ -296,7 +298,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd maker.Apply(List.<JCExpression>nil(), hcMethod, List.of(fieldAccessor))); } else /* objects */ { /* this.fieldName == null ? 0 : this.fieldName.hashCode() */ - JCExpression hcCall = maker.Apply(List.<JCExpression>nil(), maker.Select(createFieldAccessor(maker, fieldNode, useFieldsDirectly), typeNode.toName("hashCode")), + JCExpression hcCall = maker.Apply(List.<JCExpression>nil(), maker.Select(createFieldAccessor(maker, fieldNode, fieldAccess), typeNode.toName("hashCode")), List.<JCExpression>nil()); JCExpression thisEqualsNull = maker.Binary(JCTree.EQ, fieldAccessor, maker.Literal(TypeTags.BOT, null)); intoResult = intoResult.append( @@ -329,7 +331,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd return maker.TypeCast(maker.TypeIdent(TypeTags.INT), xorBits); } - private JCMethodDecl createEquals(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, boolean useFieldsDirectly, boolean needsCanEqual) { + private JCMethodDecl createEquals(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, FieldAccess fieldAccess, boolean needsCanEqual) { TreeMaker maker = typeNode.getTreeMaker(); JCClassDecl type = (JCClassDecl) typeNode.get(); @@ -398,9 +400,9 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd } for (JavacNode fieldNode : fields) { - JCExpression fType = getFieldType(fieldNode, useFieldsDirectly); - JCExpression thisFieldAccessor = createFieldAccessor(maker, fieldNode, useFieldsDirectly); - JCExpression otherFieldAccessor = createFieldAccessor(maker, fieldNode, useFieldsDirectly, maker.Ident(otherName)); + JCExpression fType = getFieldType(fieldNode, fieldAccess); + JCExpression thisFieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); + JCExpression otherFieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess, maker.Ident(otherName)); if (fType instanceof JCPrimitiveTypeTree) { switch (((JCPrimitiveTypeTree)fType).getPrimitiveTypeKind()) { case FLOAT: @@ -432,8 +434,8 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd JCExpression thisEqualsNull = maker.Binary(JCTree.EQ, thisFieldAccessor, maker.Literal(TypeTags.BOT, null)); JCExpression otherNotEqualsNull = maker.Binary(JCTree.NE, otherFieldAccessor, maker.Literal(TypeTags.BOT, null)); JCExpression thisEqualsThat = maker.Apply(List.<JCExpression>nil(), - maker.Select(createFieldAccessor(maker, fieldNode, useFieldsDirectly), typeNode.toName("equals")), - List.of(createFieldAccessor(maker, fieldNode, useFieldsDirectly, maker.Ident(otherName)))); + maker.Select(createFieldAccessor(maker, fieldNode, fieldAccess), typeNode.toName("equals")), + List.of(createFieldAccessor(maker, fieldNode, fieldAccess, maker.Ident(otherName)))); JCExpression fieldsAreNotEqual = maker.Conditional(thisEqualsNull, otherNotEqualsNull, maker.Unary(JCTree.NOT, thisEqualsThat)); statements = statements.append(maker.If(fieldsAreNotEqual, returnBool(maker, false), null)); } diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index e58069e1..be1bc49a 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -24,6 +24,9 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import lombok.AccessLevel; import lombok.Getter; @@ -33,17 +36,28 @@ import lombok.core.handlers.TransformationsUtil; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.TypeTags; +import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBinary; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; +import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCNewClass; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCSynchronized; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; @@ -78,7 +92,7 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> { } for (JavacNode field : typeNode.down()) { - if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, errorNode.get(), level, List.<JCExpression>nil()); + if (fieldQualifiesForGetterGeneration(field)) generateGetterForField(field, errorNode.get(), level, List.<JCExpression>nil(), false); } return true; @@ -108,8 +122,9 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> { * * @param fieldNode The node representing the field you want a getter for. * @param pos The node responsible for generating the getter (the {@code @Data} or {@code @Getter} annotation). + * @param lazy */ - public void generateGetterForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, List<JCExpression> onMethod) { + public void generateGetterForField(JavacNode fieldNode, DiagnosticPosition pos, AccessLevel level, List<JCExpression> onMethod, boolean lazy) { for (JavacNode child : fieldNode.down()) { if (child.getKind() == Kind.ANNOTATION) { if (Javac.annotationTypeMatches(Getter.class, child)) { @@ -119,7 +134,7 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> { } } - createGetterForField(level, fieldNode, fieldNode, false, onMethod); + createGetterForField(level, fieldNode, fieldNode, false, onMethod, lazy); } @Override public boolean handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacNode annotationNode) { @@ -127,38 +142,58 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> { markAnnotationAsProcessed(annotationNode, Getter.class); deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); JavacNode node = annotationNode.up(); - AccessLevel level = annotation.getInstance().value(); - if (level == AccessLevel.NONE) return true; + Getter annotationInstance = annotation.getInstance(); + AccessLevel level = annotationInstance.value(); + boolean lazy = annotationInstance.lazy(); + if (level == AccessLevel.NONE) { + if (lazy) { + annotationNode.addWarning("'lazy' does not work with AccessLevel.NONE."); + } + return true; + } if (node == null) return false; List<JCExpression> onMethod = getAndRemoveAnnotationParameter(ast, "onMethod"); if (node.getKind() == Kind.FIELD) { - return createGetterForFields(level, fields, annotationNode, true, onMethod); + return createGetterForFields(level, fields, annotationNode, true, onMethod, lazy); } if (node.getKind() == Kind.TYPE) { if (!onMethod.isEmpty()) annotationNode.addError("'onMethod' is not supported for @Getter on a type."); + if (lazy) annotationNode.addError("'lazy' is not supported for @Getter on a type."); return generateGetterForType(node, annotationNode, level, false); } return false; } - private boolean createGetterForFields(AccessLevel level, Collection<JavacNode> fieldNodes, JavacNode errorNode, boolean whineIfExists, List<JCExpression> onMethod) { + private boolean createGetterForFields(AccessLevel level, Collection<JavacNode> fieldNodes, JavacNode errorNode, boolean whineIfExists, List<JCExpression> onMethod, boolean lazy) { for (JavacNode fieldNode : fieldNodes) { - createGetterForField(level, fieldNode, errorNode, whineIfExists, onMethod); + createGetterForField(level, fieldNode, errorNode, whineIfExists, onMethod, lazy); } return true; } private boolean createGetterForField(AccessLevel level, - JavacNode fieldNode, JavacNode errorNode, boolean whineIfExists, List<JCExpression> onMethod) { + JavacNode fieldNode, JavacNode errorNode, boolean whineIfExists, List<JCExpression> onMethod, boolean lazy) { if (fieldNode.getKind() != Kind.FIELD) { errorNode.addError("@Getter is only supported on a class or a field."); return true; } JCVariableDecl fieldDecl = (JCVariableDecl)fieldNode.get(); + + if (lazy) { + if ((fieldDecl.mods.flags & Flags.PRIVATE) == 0 || (fieldDecl.mods.flags & Flags.FINAL) == 0) { + errorNode.addError("'lazy' requires the field to be private and final."); + return true; + } + if (fieldDecl.init == null) { + errorNode.addError("'lazy' requires field initialization."); + return true; + } + } + String methodName = toGetterName(fieldDecl); for (String altName : toAllGetterNames(fieldDecl)) { @@ -181,19 +216,26 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> { long access = toJavacModifier(level) | (fieldDecl.mods.flags & Flags.STATIC); - injectMethod(fieldNode.up(), createGetter(access, fieldNode, fieldNode.getTreeMaker(), onMethod)); + injectMethod(fieldNode.up(), createGetter(access, fieldNode, fieldNode.getTreeMaker(), onMethod, lazy)); return true; } - private JCMethodDecl createGetter(long access, JavacNode field, TreeMaker treeMaker, List<JCExpression> onMethod) { + private JCMethodDecl createGetter(long access, JavacNode field, TreeMaker treeMaker, List<JCExpression> onMethod, boolean lazy) { JCVariableDecl fieldNode = (JCVariableDecl) field.get(); - JCExpression fieldRef = createFieldAccessor(treeMaker, field, true); - JCStatement returnStatement = treeMaker.Return(fieldRef); - JCBlock methodBody = treeMaker.Block(0, List.of(returnStatement)); + // Remember the type; lazy will change it; + JCExpression methodType = copyType(treeMaker, fieldNode); + + List<JCStatement> statements; + if (lazy) { + statements = createLazyGetterBody(treeMaker, field); + } else { + statements = createSimpleGetterBody(treeMaker, field); + } + + JCBlock methodBody = treeMaker.Block(0, statements); Name methodName = field.toName(toGetterName(fieldNode)); - JCExpression methodType = fieldNode.type != null ? treeMaker.Type(fieldNode.type) : fieldNode.vartype; List<JCTypeParameter> methodGenericParams = List.nil(); List<JCVariableDecl> parameters = List.nil(); @@ -209,6 +251,122 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> { methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue); } + private List<JCStatement> createSimpleGetterBody(TreeMaker treeMaker, JavacNode field) { + return List.<JCStatement>of(treeMaker.Return(createFieldAccessor(treeMaker, field, FieldAccess.ALWAYS_FIELD))); + } + + private static final String AR = "java.util.concurrent.atomic.AtomicReference"; + private static final List<JCExpression> NIL_EXPRESSION = List.nil(); + + private static final java.util.Map<Integer, String> TYPE_MAP; + static { + Map<Integer, String> m = new HashMap<Integer, String>(); + m.put(TypeTags.INT, "java.lang.Integer"); + m.put(TypeTags.DOUBLE, "java.lang.Double"); + m.put(TypeTags.FLOAT, "java.lang.Float"); + m.put(TypeTags.SHORT, "java.lang.Short"); + m.put(TypeTags.BYTE, "java.lang.Byte"); + m.put(TypeTags.LONG, "java.lang.Long"); + m.put(TypeTags.BOOLEAN, "java.lang.Boolean"); + m.put(TypeTags.CHAR, "java.lang.Character"); + TYPE_MAP = Collections.unmodifiableMap(m); + } + + private List<JCStatement> createLazyGetterBody(TreeMaker maker, JavacNode fieldNode) { + /* + java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + if (value == null) { + synchronized (this.fieldName) { + value = this.fieldName.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<ValueType>(new ValueType()); + this.fieldName.set(value); + } + } + } + return value.get(); + */ + + List<JCStatement> statements = List.nil(); + + JCVariableDecl field = (JCVariableDecl) fieldNode.get(); + field.type = null; + if (field.vartype instanceof JCPrimitiveTypeTree) { + String boxed = TYPE_MAP.get(((JCPrimitiveTypeTree)field.vartype).typetag); + if (boxed != null) { + field.vartype = chainDotsString(maker, fieldNode, boxed); + } + } + + Name valueName = fieldNode.toName("value"); + + /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get();*/ { + JCTypeApply valueVarType = maker.TypeApply(chainDotsString(maker, fieldNode, AR), List.of(copyType(maker, field))); + statements = statements.append(maker.VarDef(maker.Modifiers(0), valueName, valueVarType, callGet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)))); + } + + /* if (value == null) { */ { + JCSynchronized synchronizedStatement; + /* synchronized (this.fieldName) { */ { + List<JCStatement> synchronizedStatements = List.nil(); + /* value = this.fieldName.get(); */ { + JCExpressionStatement newAssign = maker.Exec(maker.Assign(maker.Ident(valueName), callGet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)))); + synchronizedStatements = synchronizedStatements.append(newAssign); + } + + /* if (value == null) { */ { + List<JCStatement> innerIfStatements = List.nil(); + /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(new ValueType());*/ { + JCTypeApply valueVarType = maker.TypeApply(chainDotsString(maker, fieldNode, AR), List.of(copyType(maker, field))); + JCNewClass newInstance = maker.NewClass(null, NIL_EXPRESSION, valueVarType, List.<JCExpression>of(field.init), null); + + JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), newInstance)); + innerIfStatements = innerIfStatements.append(statement); + } + /* this.fieldName.set(value); */ { + JCStatement statement = callSet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD), maker.Ident(valueName)); + innerIfStatements = innerIfStatements.append(statement); + } + + JCBinary isNull = maker.Binary(JCTree.EQ, maker.Ident(valueName), maker.Literal(TypeTags.BOT, null)); + JCIf ifStatement = maker.If(isNull, maker.Block(0, innerIfStatements), null); + synchronizedStatements = synchronizedStatements.append(ifStatement); + } + + synchronizedStatement = maker.Synchronized(createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD), maker.Block(0, synchronizedStatements)); + } + + JCBinary isNull = maker.Binary(JCTree.EQ, maker.Ident(valueName), maker.Literal(TypeTags.BOT, null)); + JCIf ifStatement = maker.If(isNull, maker.Block(0, List.<JCStatement>of(synchronizedStatement)), null); + statements = statements.append(ifStatement); + } + /* return value.get(); */ + statements = statements.append(maker.Return(callGet(fieldNode, maker.Ident(valueName)))); + + // update the field type and init last + + /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ { + field.vartype = maker.TypeApply(chainDotsString(maker, fieldNode, AR), List.<JCExpression>of(maker.TypeApply(chainDotsString(maker, fieldNode, AR), List.of(copyType(maker, field))))); + field.init = maker.NewClass(null, NIL_EXPRESSION, copyType(maker, field), NIL_EXPRESSION, null); + } + + return statements; + } + + private JCMethodInvocation callGet(JavacNode source, JCExpression receiver) { + TreeMaker maker = source.getTreeMaker(); + return maker.Apply(NIL_EXPRESSION, maker.Select(receiver, source.toName("get")), NIL_EXPRESSION); + } + + private JCStatement callSet(JavacNode source, JCExpression receiver, JCExpression value) { + TreeMaker maker = source.getTreeMaker(); + return maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(receiver, source.toName("set")), List.<JCExpression>of(value))); + } + + private JCExpression copyType(TreeMaker treeMaker, JCVariableDecl fieldNode) { + return fieldNode.type != null ? treeMaker.Type(fieldNode.type) : fieldNode.vartype; + } + @Override public boolean isResolutionBased() { return false; } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 68de9ebe..875c63dd 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -38,6 +38,7 @@ import lombok.core.handlers.TransformationsUtil; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; @@ -197,7 +198,7 @@ public class HandleSetter implements JavacAnnotationHandler<Setter> { private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, List<JCExpression> onMethod, List<JCExpression> onParam) { JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); - JCExpression fieldRef = createFieldAccessor(treeMaker, field, true); + JCExpression fieldRef = createFieldAccessor(treeMaker, field, FieldAccess.ALWAYS_FIELD); JCAssign assign = treeMaker.Assign(fieldRef, treeMaker.Ident(fieldDecl.name)); List<JCStatement> statements; diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index f52602c3..82e32c06 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -29,6 +29,7 @@ import lombok.core.AST.Kind; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; @@ -88,7 +89,9 @@ public class HandleToString implements JavacAnnotationHandler<ToString> { annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); } - return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true, ann.doNotUseGetters()); + FieldAccess fieldAccess = ann.doNotUseGetters() ? FieldAccess.PREFER_FIELD : FieldAccess.GETTER; + + return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true, fieldAccess); } public void generateToStringForType(JavacNode typeNode, JavacNode errorNode) { @@ -105,11 +108,11 @@ public class HandleToString implements JavacAnnotationHandler<ToString> { try { includeFieldNames = ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue(); } catch (Exception ignore) {} - generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, false); + generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, FieldAccess.GETTER); } private boolean generateToString(JavacNode typeNode, JavacNode errorNode, List<String> excludes, List<String> includes, - boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, boolean useFieldsDirectly) { + boolean includeFieldNames, Boolean callSuper, boolean whineIfExists, FieldAccess fieldAccess) { boolean notAClass = true; if (typeNode.get() instanceof JCClassDecl) { long flags = ((JCClassDecl)typeNode.get()).mods.flags; @@ -150,7 +153,7 @@ public class HandleToString implements JavacAnnotationHandler<ToString> { switch (methodExists("toString", typeNode)) { case NOT_EXISTS: - JCMethodDecl method = createToString(typeNode, nodesForToString, includeFieldNames, callSuper, useFieldsDirectly); + JCMethodDecl method = createToString(typeNode, nodesForToString, includeFieldNames, callSuper, fieldAccess); injectMethod(typeNode, method); return true; case EXISTS_BY_LOMBOK: @@ -165,7 +168,7 @@ public class HandleToString implements JavacAnnotationHandler<ToString> { } - private JCMethodDecl createToString(JavacNode typeNode, List<JavacNode> fields, boolean includeFieldNames, boolean callSuper, boolean useFieldsDirectly) { + private JCMethodDecl createToString(JavacNode typeNode, List<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess) { TreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(chainDots(maker, typeNode, "java", "lang", "Override"), List.<JCExpression>nil()); @@ -202,9 +205,9 @@ public class HandleToString implements JavacAnnotationHandler<ToString> { JCVariableDecl field = (JCVariableDecl) fieldNode.get(); JCExpression expr; - JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, useFieldsDirectly); + JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); - if (getFieldType(fieldNode, useFieldsDirectly) instanceof JCArrayTypeTree) { + if (getFieldType(fieldNode, fieldAccess) instanceof JCArrayTypeTree) { boolean multiDim = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCArrayTypeTree; boolean primitiveArray = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCPrimitiveTypeTree; boolean useDeepTS = multiDim || !primitiveArray; diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index bf356853..d1884e5c 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -345,13 +345,34 @@ public class JavacHandlerUtil { return null; } + enum FieldAccess { + GETTER, PREFER_FIELD, ALWAYS_FIELD; + } + + static boolean lookForGetter(JavacNode field, FieldAccess fieldAccess) { + if (fieldAccess == FieldAccess.GETTER) return true; + if (fieldAccess == FieldAccess.ALWAYS_FIELD) return false; + + // If @Getter(lazy = true) is used, then using it is mandatory. + for (JavacNode child : field.down()) { + if (child.getKind() != Kind.ANNOTATION) continue; + if (Javac.annotationTypeMatches(Getter.class, child)) { + AnnotationValues<Getter> ann = Javac.createAnnotation(Getter.class, child); + if (ann.getInstance().lazy()) return true; + } + } + return false; + } + /** * Returns the type of the field, unless a getter exists for this field, in which case the return type of the getter is returned. * * @see #createFieldAccessor(TreeMaker, JavacNode) */ - static JCExpression getFieldType(JavacNode field, boolean useFieldsDirectly) { - GetterMethod getter = useFieldsDirectly ? null : findGetter(field); + static JCExpression getFieldType(JavacNode field, FieldAccess fieldAccess) { + boolean lookForGetter = lookForGetter(field, fieldAccess); + + GetterMethod getter = lookForGetter ? findGetter(field) : null; if (getter == null) { return ((JCVariableDecl)field.get()).vartype; @@ -363,12 +384,14 @@ public class JavacHandlerUtil { /** * Creates an expression that reads the field. Will either be {@code this.field} or {@code this.getField()} depending on whether or not there's a getter. */ - static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, boolean useFieldsDirectly) { - return createFieldAccessor(maker, field, useFieldsDirectly, null); + static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, FieldAccess fieldAccess) { + return createFieldAccessor(maker, field, fieldAccess, null); } - static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, boolean useFieldsDirectly, JCExpression receiver) { - GetterMethod getter = useFieldsDirectly ? null : findGetter(field); + static JCExpression createFieldAccessor(TreeMaker maker, JavacNode field, FieldAccess fieldAccess, JCExpression receiver) { + boolean lookForGetter = lookForGetter(field, fieldAccess); + + GetterMethod getter = lookForGetter ? findGetter(field) : null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); if (getter == null) { diff --git a/test/transform/resource/after-delombok/GetterLazy.java b/test/transform/resource/after-delombok/GetterLazy.java new file mode 100644 index 00000000..d7f97f0d --- /dev/null +++ b/test/transform/resource/after-delombok/GetterLazy.java @@ -0,0 +1,19 @@ +class GetterLazy { + static class ValueType { + } + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); + @java.lang.SuppressWarnings("all") + public ValueType getFieldName() { + java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + if (value == null) { + synchronized (this.fieldName) { + value = this.fieldName.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<ValueType>(new ValueType()); + this.fieldName.set(value); + } + } + } + return value.get(); + } +} diff --git a/test/transform/resource/after-delombok/GetterLazyEahcToString.java b/test/transform/resource/after-delombok/GetterLazyEahcToString.java new file mode 100644 index 00000000..f085722d --- /dev/null +++ b/test/transform/resource/after-delombok/GetterLazyEahcToString.java @@ -0,0 +1,58 @@ +class GetterLazyEahcToString { + + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>> value = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>>(); + private final String value2 = ""; + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof GetterLazyEahcToString)) return false; + final GetterLazyEahcToString other = (GetterLazyEahcToString)o; + if (!other.canEqual(this)) return false; + if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false; + if (this.value2 == null ? other.value2 != null : !this.value2.equals(other.value2)) return false; + return true; + } + + @java.lang.SuppressWarnings("all") + public boolean canEqual(final java.lang.Object other) { + return other instanceof GetterLazyEahcToString; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public int hashCode() { + final int PRIME = 31; + int result = 1; + result = result * PRIME + (this.getValue() == null ? 0 : this.getValue().hashCode()); + result = result * PRIME + (this.value2 == null ? 0 : this.value2.hashCode()); + return result; + } + + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "GetterLazyEahcToString(value=" + this.getValue() + ", value2=" + this.value2 + ")"; + } + + @java.lang.SuppressWarnings("all") + public String getValue() { + java.util.concurrent.atomic.AtomicReference<String> value = this.value.get(); + if (value == null) { + synchronized (this.value) { + value = this.value.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<String>(""); + this.value.set(value); + } + } + } + return value.get(); + } + + @java.lang.SuppressWarnings("all") + public String getValue2() { + return this.value2; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/GetterLazyInvalid.java b/test/transform/resource/after-delombok/GetterLazyInvalid.java new file mode 100644 index 00000000..25963921 --- /dev/null +++ b/test/transform/resource/after-delombok/GetterLazyInvalid.java @@ -0,0 +1,25 @@ +class GetterLazyInvalidNotFinal { + private String fieldName = ""; +} +class GetterLazyInvalidNotPrivate { + final String fieldName = ""; +} +class GetterLazyInvalidNotPrivateFinal { + String fieldName = ""; +} +class GetterLazyInvalidNone { + private final String fieldName = ""; +} +class GetterLazyInvalidClass { + private final String fieldName = ""; + @java.lang.SuppressWarnings("all") + public String getFieldName() { + return this.fieldName; + } +} +class GetterLazyInvalidNoInit { + private final String fieldName; + GetterLazyInvalidNoInit() { + this.fieldName = "foo"; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/GetterLazyNative.java b/test/transform/resource/after-delombok/GetterLazyNative.java new file mode 100644 index 00000000..650d0496 --- /dev/null +++ b/test/transform/resource/after-delombok/GetterLazyNative.java @@ -0,0 +1,137 @@ +class GetterLazyNative { + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> booleanField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>> byteField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>> shortField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>> intField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>> longField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>> floatField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>> doubleField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>> charField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>>(); + private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>> intArrayField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>>(); + @java.lang.SuppressWarnings("all") + public boolean getBooleanField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.booleanField.get(); + if (value == null) { + synchronized (this.booleanField) { + value = this.booleanField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(true); + this.booleanField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public byte getByteField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Byte> value = this.byteField.get(); + if (value == null) { + synchronized (this.byteField) { + value = this.byteField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Byte>(1); + this.byteField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public short getShortField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Short> value = this.shortField.get(); + if (value == null) { + synchronized (this.shortField) { + value = this.shortField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Short>(1); + this.shortField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public int getIntField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Integer> value = this.intField.get(); + if (value == null) { + synchronized (this.intField) { + value = this.intField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Integer>(1); + this.intField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public long getLongField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Long> value = this.longField.get(); + if (value == null) { + synchronized (this.longField) { + value = this.longField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Long>(1); + this.longField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public float getFloatField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Float> value = this.floatField.get(); + if (value == null) { + synchronized (this.floatField) { + value = this.floatField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Float>(1.0F); + this.floatField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public double getDoubleField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Double> value = this.doubleField.get(); + if (value == null) { + synchronized (this.doubleField) { + value = this.doubleField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Double>(1.0); + this.doubleField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public char getCharField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Character> value = this.charField.get(); + if (value == null) { + synchronized (this.charField) { + value = this.charField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Character>('1'); + this.charField.set(value); + } + } + } + return value.get(); + } + @java.lang.SuppressWarnings("all") + public int[] getIntArrayField() { + java.util.concurrent.atomic.AtomicReference<int[]> value = this.intArrayField.get(); + if (value == null) { + synchronized (this.intArrayField) { + value = this.intArrayField.get(); + if (value == null) { + value = new java.util.concurrent.atomic.AtomicReference<int[]>(new int[]{1}); + this.intArrayField.set(value); + } + } + } + return value.get(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/GetterLazy.java b/test/transform/resource/after-ecj/GetterLazy.java new file mode 100644 index 00000000..669a9a81 --- /dev/null +++ b/test/transform/resource/after-ecj/GetterLazy.java @@ -0,0 +1,27 @@ +class GetterLazy { + static class ValueType { + ValueType() { + super(); + } + } + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); + public @java.lang.SuppressWarnings("all") ValueType getFieldName() { + java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + if ((value == null)) + { + synchronized (this.fieldName) + { + value = this.fieldName.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<ValueType>(new ValueType()); + this.fieldName.set(value); + } + } + } + return value.get(); + } + GetterLazy() { + super(); + } +} diff --git a/test/transform/resource/after-ecj/GetterLazyEahcToString.java b/test/transform/resource/after-ecj/GetterLazyEahcToString.java new file mode 100644 index 00000000..6261ce38 --- /dev/null +++ b/test/transform/resource/after-ecj/GetterLazyEahcToString.java @@ -0,0 +1,53 @@ +@lombok.EqualsAndHashCode(doNotUseGetters = true) @lombok.ToString(doNotUseGetters = true) class GetterLazyEahcToString { + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>> value = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<String>>(); + private final @lombok.Getter String value2 = ""; + public @java.lang.SuppressWarnings("all") String getValue() { + java.util.concurrent.atomic.AtomicReference<String> value = this.value.get(); + if ((value == null)) + { + synchronized (this.value) + { + value = this.value.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<String>(""); + this.value.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") String getValue2() { + return this.value2; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof GetterLazyEahcToString))) + return false; + final GetterLazyEahcToString other = (GetterLazyEahcToString) o; + if ((! other.canEqual(this))) + return false; + if (((this.getValue() == null) ? (other.getValue() != null) : (! this.getValue().equals(other.getValue())))) + return false; + if (((this.value2 == null) ? (other.value2 != null) : (! this.value2.equals(other.value2)))) + return false; + return true; + } + public @java.lang.SuppressWarnings("all") boolean canEqual(final java.lang.Object other) { + return (other instanceof GetterLazyEahcToString); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") int hashCode() { + final int PRIME = 31; + int result = 1; + result = ((result * PRIME) + ((this.getValue() == null) ? 0 : this.getValue().hashCode())); + result = ((result * PRIME) + ((this.value2 == null) ? 0 : this.value2.hashCode())); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("GetterLazyEahcToString(value=" + this.getValue()) + ", value2=") + this.value2) + ")"); + } + GetterLazyEahcToString() { + super(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/GetterLazyInvalid.java b/test/transform/resource/after-ecj/GetterLazyInvalid.java new file mode 100644 index 00000000..eaa22d71 --- /dev/null +++ b/test/transform/resource/after-ecj/GetterLazyInvalid.java @@ -0,0 +1,40 @@ +class GetterLazyInvalidNotFinal { + private @lombok.Getter(lazy = true) String fieldName = ""; + GetterLazyInvalidNotFinal() { + super(); + } +} +class GetterLazyInvalidNotPrivate { + final @lombok.Getter(lazy = true) String fieldName = ""; + GetterLazyInvalidNotPrivate() { + super(); + } +} +class GetterLazyInvalidNotPrivateFinal { + @lombok.Getter(lazy = true) String fieldName = ""; + GetterLazyInvalidNotPrivateFinal() { + super(); + } +} +class GetterLazyInvalidNone { + private final @lombok.Getter(lazy = true,value = lombok.AccessLevel.NONE) String fieldName = ""; + GetterLazyInvalidNone() { + super(); + } +} +@lombok.Getter(lazy = true) class GetterLazyInvalidClass { + private final String fieldName = ""; + public @java.lang.SuppressWarnings("all") String getFieldName() { + return this.fieldName; + } + GetterLazyInvalidClass() { + super(); + } +} +class GetterLazyInvalidNoInit { + private final @lombok.Getter(lazy = true) String fieldName; + GetterLazyInvalidNoInit() { + super(); + this.fieldName = "foo"; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-ecj/GetterLazyNative.java b/test/transform/resource/after-ecj/GetterLazyNative.java new file mode 100644 index 00000000..6c90a101 --- /dev/null +++ b/test/transform/resource/after-ecj/GetterLazyNative.java @@ -0,0 +1,158 @@ +class GetterLazyNative { + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>> booleanField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>> byteField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Byte>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>> shortField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Short>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>> intField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Integer>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>> longField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Long>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>> floatField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Float>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>> doubleField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Double>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>> charField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<java.lang.Character>>(); + private final @lombok.Getter(lazy = true) java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>> intArrayField = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<int[]>>(); + public @java.lang.SuppressWarnings("all") boolean isBooleanField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Boolean> value = this.booleanField.get(); + if ((value == null)) + { + synchronized (this.booleanField) + { + value = this.booleanField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Boolean>(true); + this.booleanField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") byte getByteField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Byte> value = this.byteField.get(); + if ((value == null)) + { + synchronized (this.byteField) + { + value = this.byteField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Byte>(1); + this.byteField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") short getShortField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Short> value = this.shortField.get(); + if ((value == null)) + { + synchronized (this.shortField) + { + value = this.shortField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Short>(1); + this.shortField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") int getIntField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Integer> value = this.intField.get(); + if ((value == null)) + { + synchronized (this.intField) + { + value = this.intField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Integer>(1); + this.intField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") long getLongField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Long> value = this.longField.get(); + if ((value == null)) + { + synchronized (this.longField) + { + value = this.longField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Long>(1); + this.longField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") float getFloatField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Float> value = this.floatField.get(); + if ((value == null)) + { + synchronized (this.floatField) + { + value = this.floatField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Float>(1.0f); + this.floatField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") double getDoubleField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Double> value = this.doubleField.get(); + if ((value == null)) + { + synchronized (this.doubleField) + { + value = this.doubleField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Double>(1.0); + this.doubleField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") char getCharField() { + java.util.concurrent.atomic.AtomicReference<java.lang.Character> value = this.charField.get(); + if ((value == null)) + { + synchronized (this.charField) + { + value = this.charField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<java.lang.Character>('1'); + this.charField.set(value); + } + } + } + return value.get(); + } + public @java.lang.SuppressWarnings("all") int[] getIntArrayField() { + java.util.concurrent.atomic.AtomicReference<int[]> value = this.intArrayField.get(); + if ((value == null)) + { + synchronized (this.intArrayField) + { + value = this.intArrayField.get(); + if ((value == null)) + { + value = new java.util.concurrent.atomic.AtomicReference<int[]>(new int[]{1}); + this.intArrayField.set(value); + } + } + } + return value.get(); + } + GetterLazyNative() { + super(); + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/GetterLazy.java b/test/transform/resource/before/GetterLazy.java new file mode 100644 index 00000000..51c7a921 --- /dev/null +++ b/test/transform/resource/before/GetterLazy.java @@ -0,0 +1,7 @@ +class GetterLazy { + static class ValueType { + } + + @lombok.Getter(lazy=true) + private final ValueType fieldName = new ValueType(); +} diff --git a/test/transform/resource/before/GetterLazyEahcToString.java b/test/transform/resource/before/GetterLazyEahcToString.java new file mode 100644 index 00000000..642f8deb --- /dev/null +++ b/test/transform/resource/before/GetterLazyEahcToString.java @@ -0,0 +1,8 @@ +@lombok.EqualsAndHashCode(doNotUseGetters = true) +@lombok.ToString(doNotUseGetters = true) +class GetterLazyEahcToString { + @lombok.Getter(lazy=true) + private final String value = ""; + @lombok.Getter + private final String value2 = ""; +} diff --git a/test/transform/resource/before/GetterLazyInvalid.java b/test/transform/resource/before/GetterLazyInvalid.java new file mode 100644 index 00000000..cc9f9501 --- /dev/null +++ b/test/transform/resource/before/GetterLazyInvalid.java @@ -0,0 +1,27 @@ +class GetterLazyInvalidNotFinal { + @lombok.Getter(lazy=true) + private String fieldName = ""; +} +class GetterLazyInvalidNotPrivate { + @lombok.Getter(lazy=true) + final String fieldName = ""; +} +class GetterLazyInvalidNotPrivateFinal { + @lombok.Getter(lazy=true) + String fieldName = ""; +} +class GetterLazyInvalidNone { + @lombok.Getter(lazy=true, value=lombok.AccessLevel.NONE) + private final String fieldName = ""; +} +@lombok.Getter(lazy = true) +class GetterLazyInvalidClass { + private final String fieldName = ""; +} +class GetterLazyInvalidNoInit { + @lombok.Getter(lazy = true) + private final String fieldName; + GetterLazyInvalidNoInit() { + this.fieldName = "foo"; + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/GetterLazyNative.java b/test/transform/resource/before/GetterLazyNative.java new file mode 100644 index 00000000..9b290bc7 --- /dev/null +++ b/test/transform/resource/before/GetterLazyNative.java @@ -0,0 +1,28 @@ +class GetterLazyNative { + @lombok.Getter(lazy=true) + private final boolean booleanField = true; + + @lombok.Getter(lazy=true) + private final byte byteField = 1; + + @lombok.Getter(lazy=true) + private final short shortField = 1; + + @lombok.Getter(lazy=true) + private final int intField = 1; + + @lombok.Getter(lazy=true) + private final long longField = 1; + + @lombok.Getter(lazy=true) + private final float floatField = 1.0f; + + @lombok.Getter(lazy=true) + private final double doubleField = 1.0; + + @lombok.Getter(lazy=true) + private final char charField = '1'; + + @lombok.Getter(lazy=true) + private final int[] intArrayField = new int[] {1}; +} diff --git a/test/transform/resource/messages-delombok/GetterLazyInvalid.java.messages b/test/transform/resource/messages-delombok/GetterLazyInvalid.java.messages new file mode 100644 index 00000000..4f7e3df8 --- /dev/null +++ b/test/transform/resource/messages-delombok/GetterLazyInvalid.java.messages @@ -0,0 +1,6 @@ +2:9 ERROR 'lazy' requires the field to be private and final. +6:9 ERROR 'lazy' requires the field to be private and final. +10:9 ERROR 'lazy' requires the field to be private and final. +14:9 WARNING 'lazy' does not work with AccessLevel.NONE. +17:1 ERROR 'lazy' is not supported for @Getter on a type. +22:9 ERROR 'lazy' requires field initialization.
\ No newline at end of file diff --git a/test/transform/resource/messages-ecj/GetterLazyInvalid.java.messages b/test/transform/resource/messages-ecj/GetterLazyInvalid.java.messages new file mode 100644 index 00000000..25641930 --- /dev/null +++ b/test/transform/resource/messages-ecj/GetterLazyInvalid.java.messages @@ -0,0 +1,6 @@ +2 error 'lazy' requires the field to be private and final. +6 error 'lazy' requires the field to be private and final. +10 error 'lazy' requires the field to be private and final. +14 warning 'lazy' does not work with AccessLevel.NONE. +17 error 'lazy' is not supported for @Getter on a type. +22 error 'lazy' requires field initialization. |