diff options
Diffstat (limited to 'src')
36 files changed, 2596 insertions, 351 deletions
diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index 7e71b248..8ea554a1 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -65,7 +65,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, this.imports = Collections.unmodifiableCollection(new ArrayList<String>(imports)); } - protected void setChanged() { + public void setChanged() { this.changed = true; } diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index db4c6d09..6cecedc7 100644 --- a/src/core/lombok/core/AnnotationValues.java +++ b/src/core/lombok/core/AnnotationValues.java @@ -53,29 +53,20 @@ public class AnnotationValues<A extends Annotation> { /** Guesses for each raw expression. If the raw expression is a literal expression, the guess will * likely be right. If not, it'll be wrong. */ public final List<Object> valueGuesses; + + /** A list of the actual expressions. List is size 1 unless an array is provided. */ + public final List<Object> expressions; + private final LombokNode<?, ?, ?> node; private final boolean isExplicit; /** - * 'raw' should be the exact expression, for example '5+7', 'AccessLevel.PUBLIC', or 'int.class'. - * 'valueGuess' should be a likely guess at the real value intended. - * - * For classes, supply the class name (qualified or not) as a string.<br /> - * For enums, supply the simple name part (everything after the last dot) as a string.<br /> - */ - public AnnotationValue(LombokNode<?, ?, ?> node, String raw, Object valueGuess, boolean isExplicit) { - this.node = node; - this.raws = Collections.singletonList(raw); - this.valueGuesses = Collections.singletonList(valueGuess); - this.isExplicit = isExplicit; - } - - /** * Like the other constructor, but used for when the annotation method is initialized with an array value. */ - public AnnotationValue(LombokNode<?, ?, ?> node, List<String> raws, List<Object> valueGuesses, boolean isExplicit) { + public AnnotationValue(LombokNode<?, ?, ?> node, List<String> raws, List<Object> expressions, List<Object> valueGuesses, boolean isExplicit) { this.node = node; this.raws = raws; + this.expressions = expressions; this.valueGuesses = valueGuesses; this.isExplicit = isExplicit; } @@ -310,6 +301,14 @@ public class AnnotationValues<A extends Annotation> { return v == null ? Collections.<String>emptyList() : v.raws; } + /** + * Returns the actual expressions used for the provided {@code annotationMethodName}. + */ + public List<Object> getActualExpressions(String annotationMethodName) { + AnnotationValue v = values.get(annotationMethodName); + return v == null ? Collections.<Object>emptyList() : v.expressions; + } + public boolean isExplicit(String annotationMethodName) { AnnotationValue annotationValue = values.get(annotationMethodName); return annotationValue != null && annotationValue.isExplicit(); @@ -325,6 +324,16 @@ public class AnnotationValues<A extends Annotation> { return l.isEmpty() ? null : l.get(0); } + /** + * Convenience method to return the first result in a {@link #getActualExpressions(String)} call. + * + * You should use this method if the annotation method is not an array type. + */ + public Object getActualExpression(String annotationMethodName) { + List<Object> l = getActualExpressions(annotationMethodName); + return l.isEmpty() ? null : l.get(0); + } + /** Generates an error message on the stated annotation value (you should only call this method if you know it's there!) */ public void setError(String annotationMethodName, String message) { setError(annotationMethodName, message, -1); diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index 6d0f3147..4a57e080 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -78,6 +78,10 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, this.isStructurallySignificant = calculateIsStructurallySignificant(null); } + public A getAst() { + return ast; + } + /** {@inheritDoc} */ @Override public String toString() { return String.format("NODE %s (%s) %s%s", diff --git a/src/core/lombok/core/handlers/SneakyThrowsAndCleanupDependencyInfo.java b/src/core/lombok/core/handlers/SneakyThrowsAndCleanupDependencyInfo.java new file mode 100644 index 00000000..4370d218 --- /dev/null +++ b/src/core/lombok/core/handlers/SneakyThrowsAndCleanupDependencyInfo.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2009-2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.handlers; + +import java.util.Arrays; +import java.util.List; + +import lombok.core.runtimeDependencies.RuntimeDependencyInfo; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(RuntimeDependencyInfo.class) +public class SneakyThrowsAndCleanupDependencyInfo implements RuntimeDependencyInfo { + @Override public List<String> getRuntimeDependencies() { + return Arrays.asList( + "lombok/Lombok.class" + ); + } + + @Override public List<String> getRuntimeDependentsDescriptions() { + return Arrays.asList( + "@SneakyThrows (only when delomboking - using @SneakyThrows in code that is compiled with lombok on the classpath does not create the dependency)", + "@Cleanup (only when delomboking - using @Cleanup in code that is compiled with lombok on the classpath does not create the dependency)" + ); + } +} diff --git a/src/core/lombok/eclipse/Eclipse.java b/src/core/lombok/eclipse/Eclipse.java index c70660f4..f5b80552 100644 --- a/src/core/lombok/eclipse/Eclipse.java +++ b/src/core/lombok/eclipse/Eclipse.java @@ -25,10 +25,12 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.WeakHashMap; import lombok.core.AnnotationValues; import lombok.core.TypeLibrary; @@ -62,7 +64,13 @@ import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.Wildcard; +import org.eclipse.jdt.internal.compiler.lookup.CaptureBinding; +import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; import org.osgi.framework.Bundle; public class Eclipse { @@ -156,7 +164,6 @@ public class Eclipse { return result; } - /** * You can't share TypeParameter objects or bad things happen; for example, one 'T' resolves differently * from another 'T', even for the same T in a single class file. Unfortunately the TypeParameter type hierarchy @@ -292,6 +299,159 @@ public class Eclipse { return ref; } + private static long pos(ASTNode node) { + return ((long) node.sourceStart << 32) | (node.sourceEnd & 0xFFFFFFFFL); + } + + public static long[] poss(ASTNode node, int repeat) { + long p = ((long) node.sourceStart << 32) | (node.sourceEnd & 0xFFFFFFFFL); + long[] out = new long[repeat]; + Arrays.fill(out, p); + return out; + } + + public static TypeReference makeType(TypeBinding binding, ASTNode pos, boolean allowCompound) { + int dims = binding.dimensions(); + binding = binding.leafComponentType(); + + // Primitives + + char[] base = null; + + switch (binding.id) { + case TypeIds.T_int: + base = TypeConstants.INT; + break; + case TypeIds.T_long: + base = TypeConstants.LONG; + break; + case TypeIds.T_short: + base = TypeConstants.SHORT; + break; + case TypeIds.T_byte: + base = TypeConstants.BYTE; + break; + case TypeIds.T_double: + base = TypeConstants.DOUBLE; + break; + case TypeIds.T_float: + base = TypeConstants.FLOAT; + break; + case TypeIds.T_boolean: + base = TypeConstants.BOOLEAN; + break; + case TypeIds.T_char: + base = TypeConstants.CHAR; + break; + case TypeIds.T_void: + base = TypeConstants.VOID; + break; + case TypeIds.T_null: + return null; + } + + if (base != null) { + if (dims > 0) { + return new ArrayTypeReference(base, dims, pos(pos)); + } + return new SingleTypeReference(base, pos(pos)); + } + + 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)); + return makeType(supers[0], pos, false); + } + + if (binding instanceof CaptureBinding) { + return makeType(((CaptureBinding)binding).wildcard, pos, allowCompound); + } + + if (binding.isUnboundWildcard()) { + if (!allowCompound) { + return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(pos, 3)); + } else { + Wildcard out = new Wildcard(Wildcard.UNBOUND); + out.sourceStart = pos.sourceStart; + out.sourceEnd = pos.sourceEnd; + return out; + } + } + + if (binding.isWildcard()) { + WildcardBinding wildcard = (WildcardBinding) binding; + if (wildcard.boundKind == Wildcard.EXTENDS) { + if (!allowCompound) { + return makeType(wildcard.bound, pos, false); + } else { + Wildcard out = new Wildcard(Wildcard.EXTENDS); + out.bound = makeType(wildcard.bound, pos, false); + out.sourceStart = pos.sourceStart; + out.sourceEnd = pos.sourceEnd; + return out; + } + } else if (allowCompound && wildcard.boundKind == Wildcard.SUPER) { + Wildcard out = new Wildcard(Wildcard.SUPER); + 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)); + } + } + + char[][] parts; + + if (binding.isLocalType() || binding.isTypeVariable()) { + parts = new char[][] { binding.shortReadableName() }; + } else { + String[] pkg = new String(binding.qualifiedPackageName()).split("\\."); + String[] name = new String(binding.qualifiedSourceName()).split("\\."); + if (pkg.length == 1 && pkg[0].isEmpty()) pkg = new String[0]; + parts = new char[pkg.length + name.length][]; + int ptr; + for (ptr = 0; ptr < pkg.length; ptr++) parts[ptr] = pkg[ptr].toCharArray(); + for (; ptr < pkg.length + name.length; ptr++) parts[ptr] = name[ptr - pkg.length].toCharArray(); + } + + TypeReference[] params = new TypeReference[0]; + + if (binding instanceof ParameterizedTypeBinding) { + ParameterizedTypeBinding paramized = (ParameterizedTypeBinding) binding; + if (paramized.arguments != null) { + params = new TypeReference[paramized.arguments.length]; + for (int i = 0; i < params.length; i++) { + params[i] = makeType(paramized.arguments[i], pos, true); + } + } + } + + if (params.length > 0) { + 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)); + } + return new ParameterizedSingleTypeReference(parts[0], params, dims, pos(pos)); + } + + if (dims > 0) { + if (parts.length > 1) { + return new ArrayQualifiedTypeReference(parts, dims, poss(pos, parts.length)); + } + return new ArrayTypeReference(parts[0], dims, pos(pos)); + } + + if (parts.length > 1) { + return new QualifiedTypeReference(parts, poss(pos, parts.length)); + } + return new SingleTypeReference(parts[0], pos(pos)); + + } + public static Annotation[] copyAnnotations(Annotation[] annotations, ASTNode source) { return copyAnnotations(annotations, null, source); } @@ -377,6 +537,7 @@ public class Eclipse { if (!Modifier.isPublic(m.getModifiers())) continue; String name = m.getName(); List<String> raws = new ArrayList<String>(); + List<Object> expressionValues = new ArrayList<Object>(); List<Object> guesses = new ArrayList<Object>(); Expression fullExpression = null; Expression[] expressions = null; @@ -397,6 +558,7 @@ public class Eclipse { StringBuffer sb = new StringBuffer(); ex.print(0, sb); raws.add(sb.toString()); + expressionValues.add(ex); guesses.add(calculateValue(ex)); } } @@ -404,7 +566,7 @@ public class Eclipse { final Expression fullExpr = fullExpression; final Expression[] exprs = expressions; - values.put(name, new AnnotationValue(annotationNode, raws, guesses, isExplicit) { + values.put(name, new AnnotationValue(annotationNode, raws, expressionValues, guesses, isExplicit) { @Override public void setError(String message, int valueIdx) { Expression ex; if (valueIdx == -1) ex = fullExpr; @@ -474,12 +636,16 @@ public class Eclipse { } } + private static Map<ASTNode, ASTNode> generatedNodes = new WeakHashMap<ASTNode, ASTNode>(); + public static ASTNode getGeneratedBy(ASTNode node) { - try { - return (ASTNode) generatedByField.get(node); - } catch (Exception t) { - //ignore - no $generatedBy exists when running in ecj. - return null; + if (generatedByField != null) { + try { + return (ASTNode) generatedByField.get(node); + } catch (Exception e) {} + } + synchronized (generatedNodes) { + return generatedNodes.get(node); } } @@ -488,12 +654,15 @@ public class Eclipse { } public static ASTNode setGeneratedBy(ASTNode node, ASTNode source) { - try { - generatedByField.set(node, source); - } catch (Exception t) { - //ignore - no $generatedBy exists when running in ecj. + if (generatedByField != null) { + try { + generatedByField.set(node, source); + return node; + } catch (Exception e) {} + } + synchronized (generatedNodes) { + generatedNodes.put(node, source); } - return node; } } diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 5005752b..019ae637 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -23,6 +23,7 @@ package lombok.eclipse.handlers; import static lombok.eclipse.Eclipse.*; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -42,6 +43,7 @@ import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Clinit; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; @@ -406,9 +408,17 @@ public class EclipseHandlerUtil { /** * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. + * The field carries the @{@link SuppressWarnings}("all") annotation. */ - public static void injectField(EclipseNode type, FieldDeclaration field) { + public static void injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { field.annotations = createSuppressWarningsAll(field, field.annotations); + injectField(type, field); + } + + /** + * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. + */ + public static void injectField(EclipseNode type, FieldDeclaration field) { TypeDeclaration parent = (TypeDeclaration) type.get(); if (parent.fields == null) { @@ -421,9 +431,24 @@ public class EclipseHandlerUtil { parent.fields = newArray; } + if ((field.modifiers & Modifier.STATIC) != 0) { + if (!hasClinit(parent)) { + parent.addClinit(); + } + } + type.add(field, Kind.FIELD).recursiveSetHandled(); } + private static boolean hasClinit(TypeDeclaration parent) { + if (parent.methods == null) return false; + + for (AbstractMethodDeclaration method : parent.methods) { + if (method instanceof Clinit) return true; + } + return false; + } + /** * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. */ @@ -435,25 +460,42 @@ public class EclipseHandlerUtil { parent.methods = new AbstractMethodDeclaration[1]; parent.methods[0] = method; } else { - boolean injectionComplete = false; if (method instanceof ConstructorDeclaration) { for (int i = 0 ; i < parent.methods.length ; i++) { if (parent.methods[i] instanceof ConstructorDeclaration && (parent.methods[i].bits & ASTNode.IsDefaultConstructor) != 0) { EclipseNode tossMe = type.getNodeFor(parent.methods[i]); - parent.methods[i] = method; + + AbstractMethodDeclaration[] withoutGeneratedConstructor = new AbstractMethodDeclaration[parent.methods.length - 1]; + + System.arraycopy(parent.methods, 0, withoutGeneratedConstructor, 0, i); + System.arraycopy(parent.methods, i + 1, withoutGeneratedConstructor, i, parent.methods.length - i - 1); + + parent.methods = withoutGeneratedConstructor; if (tossMe != null) tossMe.up().removeChild(tossMe); - injectionComplete = true; break; } } } - if (!injectionComplete) { - AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[parent.methods.length + 1]; - System.arraycopy(parent.methods, 0, newArray, 0, parent.methods.length); - newArray[parent.methods.length] = method; - parent.methods = newArray; + int insertionPoint; + for (insertionPoint = 0; insertionPoint < parent.methods.length; insertionPoint++) { + AbstractMethodDeclaration current = parent.methods[insertionPoint]; + if (current instanceof Clinit) continue; + if (method instanceof ConstructorDeclaration) { + if (current instanceof ConstructorDeclaration) continue; + break; + } + if (Eclipse.isGenerated(current)) continue; + break; + } + AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[parent.methods.length + 1]; + System.arraycopy(parent.methods, 0, newArray, 0, insertionPoint); + if (insertionPoint <= parent.methods.length) { + System.arraycopy(parent.methods, insertionPoint, newArray, insertionPoint + 1, parent.methods.length - insertionPoint); } + + newArray[insertionPoint] = method; + parent.methods = newArray; } type.add(method, Kind.METHOD).recursiveSetHandled(); diff --git a/src/core/lombok/eclipse/handlers/HandleCleanup.java b/src/core/lombok/eclipse/handlers/HandleCleanup.java index d296e96b..33ab7fb9 100644 --- a/src/core/lombok/eclipse/handlers/HandleCleanup.java +++ b/src/core/lombok/eclipse/handlers/HandleCleanup.java @@ -37,9 +37,13 @@ import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.CaseStatement; import org.eclipse.jdt.internal.compiler.ast.CastExpression; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; @@ -157,7 +161,29 @@ public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> { } unsafeClose.nameSourcePosition = nameSourcePosition; unsafeClose.selector = cleanupName.toCharArray(); - finallyBlock[0] = unsafeClose; + + + int pS = ast.sourceStart, pE = ast.sourceEnd; + long p = (long)pS << 32 | pE; + + SingleNameReference varName = new SingleNameReference(decl.name, p); + Eclipse.setGeneratedBy(varName, ast); + NullLiteral nullLiteral = new NullLiteral(pS, pE); + Eclipse.setGeneratedBy(nullLiteral, ast); + EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.NOT_EQUAL); + equalExpression.sourceStart = pS; equalExpression.sourceEnd = pE; + Eclipse.setGeneratedBy(equalExpression, ast); + + Block closeBlock = new Block(0); + closeBlock.statements = new Statement[1]; + closeBlock.statements[0] = unsafeClose; + Eclipse.setGeneratedBy(closeBlock, ast); + IfStatement ifStatement = new IfStatement(equalExpression, closeBlock, 0, 0); + Eclipse.setGeneratedBy(ifStatement, ast); + + + + finallyBlock[0] = ifStatement; tryStatement.finallyBlock = new Block(0); Eclipse.setGeneratedBy(tryStatement.finallyBlock, ast); tryStatement.finallyBlock.statements = finallyBlock; diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index e501c3f1..ed13fd1b 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -47,6 +47,7 @@ import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression; import org.eclipse.jdt.internal.compiler.ast.IntLiteral; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; import org.eclipse.jdt.internal.compiler.ast.MessageSend; @@ -202,9 +203,13 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA } } + boolean needsCanEqual = false; switch (methodExists("equals", typeNode)) { case NOT_EXISTS: - MethodDeclaration equals = createEquals(typeNode, nodesForEquality, callSuper, errorNode.get(), useFieldsDirectly); + boolean isFinal = (typeDecl.modifiers & ClassFileConstants.AccFinal) != 0; + needsCanEqual = !isDirectDescendantOfObject || !isFinal; + + MethodDeclaration equals = createEquals(typeNode, nodesForEquality, callSuper, errorNode.get(), useFieldsDirectly, needsCanEqual); injectMethod(typeNode, equals); break; case EXISTS_BY_LOMBOK: @@ -217,6 +222,19 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA break; } + if (needsCanEqual) { + switch (methodExists("canEqual", typeNode)) { + case NOT_EXISTS: + MethodDeclaration equals = createCanEqual(typeNode, errorNode.get()); + injectMethod(typeNode, equals); + break; + case EXISTS_BY_LOMBOK: + case EXISTS_BY_USER: + default: + break; + } + } + switch (methodExists("hashCode", typeNode)) { case NOT_EXISTS: MethodDeclaration hashCode = createHashCode(typeNode, nodesForEquality, callSuper, errorNode.get(), useFieldsDirectly); @@ -415,9 +433,10 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA return method; } - private MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, boolean useFieldsDirectly) { + private MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, boolean useFieldsDirectly, boolean needsCanEqual) { int pS = source.sourceStart; int pE = source.sourceEnd; long p = (long)pS << 32 | pE; + TypeDeclaration typeDecl = (TypeDeclaration)type.get(); MethodDeclaration method = new MethodDeclaration( ((CompilationUnitDeclaration) type.top().get()).compilationResult); @@ -458,75 +477,36 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA statements.add(ifOtherEqualsThis); } - /* if (o == null) return false; */ { + /* if (!(o instanceof MyType) return false; */ { SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p); Eclipse.setGeneratedBy(oRef, source); - NullLiteral nullLiteral = new NullLiteral(pS, pE); - Eclipse.setGeneratedBy(nullLiteral, source); - EqualExpression otherEqualsNull = new EqualExpression(oRef, nullLiteral, OperatorIds.EQUAL_EQUAL); - Eclipse.setGeneratedBy(otherEqualsNull, source); + + SingleTypeReference typeReference = new SingleTypeReference(typeDecl.name, p); + Eclipse.setGeneratedBy(typeReference, source); + + InstanceOfExpression instanceOf = new InstanceOfExpression(oRef, typeReference); + instanceOf.sourceStart = pS; instanceOf.sourceEnd = pE; + Eclipse.setGeneratedBy(instanceOf, source); + + Expression notInstanceOf = new UnaryExpression(instanceOf, OperatorIds.NOT); + Eclipse.setGeneratedBy(notInstanceOf, source); FalseLiteral falseLiteral = new FalseLiteral(pS, pE); Eclipse.setGeneratedBy(falseLiteral, source); + ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE); Eclipse.setGeneratedBy(returnFalse, source); - IfStatement ifOtherEqualsNull = new IfStatement(otherEqualsNull, returnFalse, pS, pE); - Eclipse.setGeneratedBy(ifOtherEqualsNull, source); - statements.add(ifOtherEqualsNull); - } - - /* if (o.getClass() != getClass()) return false; */ { - MessageSend otherGetClass = new MessageSend(); - otherGetClass.sourceStart = pS; otherGetClass.sourceEnd = pE; - Eclipse.setGeneratedBy(otherGetClass, source); - otherGetClass.receiver = new SingleNameReference(new char[] { 'o' }, p); - Eclipse.setGeneratedBy(otherGetClass.receiver, source); - otherGetClass.selector = "getClass".toCharArray(); - MessageSend thisGetClass = new MessageSend(); - thisGetClass.sourceStart = pS; thisGetClass.sourceEnd = pE; - Eclipse.setGeneratedBy(thisGetClass, source); - thisGetClass.receiver = new ThisReference(pS, pE); - Eclipse.setGeneratedBy(thisGetClass.receiver, source); - thisGetClass.selector = "getClass".toCharArray(); - EqualExpression classesNotEqual = new EqualExpression(otherGetClass, thisGetClass, OperatorIds.NOT_EQUAL); - Eclipse.setGeneratedBy(classesNotEqual, source); - FalseLiteral falseLiteral = new FalseLiteral(pS, pE); - Eclipse.setGeneratedBy(falseLiteral, source); - ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE); - Eclipse.setGeneratedBy(returnFalse, source); - IfStatement ifClassesNotEqual = new IfStatement(classesNotEqual, returnFalse, pS, pE); - Eclipse.setGeneratedBy(ifClassesNotEqual, source); - statements.add(ifClassesNotEqual); + + IfStatement ifNotInstanceOf = new IfStatement(notInstanceOf, returnFalse, pS, pE); + Eclipse.setGeneratedBy(ifNotInstanceOf, source); + statements.add(ifNotInstanceOf); } - char[] otherN = "other".toCharArray(); - - /* if (!super.equals(o)) return false; */ - if (callSuper) { - MessageSend callToSuper = new MessageSend(); - callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE; - Eclipse.setGeneratedBy(callToSuper, source); - callToSuper.receiver = new SuperReference(pS, pE); - Eclipse.setGeneratedBy(callToSuper.receiver, source); - callToSuper.selector = "equals".toCharArray(); - SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p); - Eclipse.setGeneratedBy(oRef, source); - callToSuper.arguments = new Expression[] {oRef}; - Expression superNotEqual = new UnaryExpression(callToSuper, OperatorIds.NOT); - Eclipse.setGeneratedBy(superNotEqual, source); - FalseLiteral falseLiteral = new FalseLiteral(pS, pE); - Eclipse.setGeneratedBy(falseLiteral, source); - ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE); - Eclipse.setGeneratedBy(returnFalse, source); - IfStatement ifSuperEquals = new IfStatement(superNotEqual, returnFalse, pS, pE); - Eclipse.setGeneratedBy(ifSuperEquals, source); - statements.add(ifSuperEquals); - } + char[] otherName = "other".toCharArray(); - TypeDeclaration typeDecl = (TypeDeclaration)type.get(); /* MyType<?> other = (MyType<?>) o; */ { if (!fields.isEmpty()) { - LocalDeclaration other = new LocalDeclaration(otherN, pS, pE); + LocalDeclaration other = new LocalDeclaration(otherName, pS, pE); other.modifiers |= ClassFileConstants.AccFinal; Eclipse.setGeneratedBy(other, source); char[] typeName = typeDecl.name; @@ -556,11 +536,63 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA } } + /* if (!other.canEqual(this)) return false; */ { + if (needsCanEqual) { + MessageSend otherCanEqual = new MessageSend(); + otherCanEqual.sourceStart = pS; otherCanEqual.sourceEnd = pE; + Eclipse.setGeneratedBy(otherCanEqual, source); + otherCanEqual.receiver = new SingleNameReference(otherName, p); + Eclipse.setGeneratedBy(otherCanEqual.receiver, source); + otherCanEqual.selector = "canEqual".toCharArray(); + + ThisReference thisReference = new ThisReference(pS, pE); + Eclipse.setGeneratedBy(thisReference, source); + + otherCanEqual.arguments = new Expression[] {thisReference}; + + Expression notOtherCanEqual = new UnaryExpression(otherCanEqual, OperatorIds.NOT); + Eclipse.setGeneratedBy(notOtherCanEqual, source); + + FalseLiteral falseLiteral = new FalseLiteral(pS, pE); + Eclipse.setGeneratedBy(falseLiteral, source); + + ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE); + Eclipse.setGeneratedBy(returnFalse, source); + + IfStatement ifNotCanEqual = new IfStatement(notOtherCanEqual, returnFalse, pS, pE); + Eclipse.setGeneratedBy(ifNotCanEqual, source); + + statements.add(ifNotCanEqual); + } + } + + /* if (!super.equals(o)) return false; */ + if (callSuper) { + MessageSend callToSuper = new MessageSend(); + callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE; + Eclipse.setGeneratedBy(callToSuper, source); + callToSuper.receiver = new SuperReference(pS, pE); + Eclipse.setGeneratedBy(callToSuper.receiver, source); + callToSuper.selector = "equals".toCharArray(); + SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p); + Eclipse.setGeneratedBy(oRef, source); + callToSuper.arguments = new Expression[] {oRef}; + Expression superNotEqual = new UnaryExpression(callToSuper, OperatorIds.NOT); + Eclipse.setGeneratedBy(superNotEqual, source); + FalseLiteral falseLiteral = new FalseLiteral(pS, pE); + Eclipse.setGeneratedBy(falseLiteral, source); + ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE); + Eclipse.setGeneratedBy(returnFalse, source); + IfStatement ifSuperEquals = new IfStatement(superNotEqual, returnFalse, pS, pE); + Eclipse.setGeneratedBy(ifSuperEquals, source); + statements.add(ifSuperEquals); + } + for (EclipseNode field : fields) { TypeReference fType = getFieldType(field, useFieldsDirectly); char[] token = fType.getLastToken(); Expression thisFieldAccessor = createFieldAccessor(field, useFieldsDirectly, source); - Expression otherFieldAccessor = createFieldAccessor(field, useFieldsDirectly, source, otherN); + Expression otherFieldAccessor = createFieldAccessor(field, useFieldsDirectly, source, otherName); if (fType.dimensions() == 0 && token != null) { if (Arrays.equals(TypeConstants.FLOAT, token)) { @@ -589,7 +621,7 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA Eclipse.setGeneratedBy(equalsCall, source); equalsCall.receiver = createFieldAccessor(field, useFieldsDirectly, source); equalsCall.selector = "equals".toCharArray(); - equalsCall.arguments = new Expression[] { createFieldAccessor(field, useFieldsDirectly, source, otherN) }; + equalsCall.arguments = new Expression[] { createFieldAccessor(field, useFieldsDirectly, source, otherName) }; UnaryExpression fieldsNotEqual = new UnaryExpression(equalsCall, OperatorIds.NOT); fieldsNotEqual.sourceStart = pS; fieldsNotEqual.sourceEnd = pE; Eclipse.setGeneratedBy(fieldsNotEqual, source); @@ -639,6 +671,54 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA return method; } + + private MethodDeclaration createCanEqual(EclipseNode type, ASTNode source) { + /* public boolean canEquals(final java.lang.Object other) { + * return other instanceof MyType; + * } + */ + int pS = source.sourceStart; int pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + char[] otherName = "other".toCharArray(); + + MethodDeclaration method = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + Eclipse.setGeneratedBy(method, source); + method.modifiers = EclipseHandlerUtil.toEclipseModifier(AccessLevel.PUBLIC); + method.returnType = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + method.returnType.sourceStart = pS; method.returnType.sourceEnd = pE; + Eclipse.setGeneratedBy(method.returnType, source); + method.selector = "canEqual".toCharArray(); + method.thrownExceptions = null; + method.typeParameters = null; + method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; + TypeReference objectRef = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { p, p, p }); + Eclipse.setGeneratedBy(objectRef, source); + method.arguments = new Argument[] {new Argument(otherName, 0, objectRef, Modifier.FINAL)}; + method.arguments[0].sourceStart = pS; method.arguments[0].sourceEnd = pE; + Eclipse.setGeneratedBy(method.arguments[0], source); + + SingleNameReference otherRef = new SingleNameReference(otherName, p); + Eclipse.setGeneratedBy(otherRef, source); + + SingleTypeReference typeReference = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); + Eclipse.setGeneratedBy(typeReference, source); + + InstanceOfExpression instanceOf = new InstanceOfExpression(otherRef, typeReference); + instanceOf.sourceStart = pS; instanceOf.sourceEnd = pE; + Eclipse.setGeneratedBy(instanceOf, source); + + ReturnStatement returnStatement = new ReturnStatement(instanceOf, pS, pE); + Eclipse.setGeneratedBy(returnStatement, source); + + method.statements = new Statement[] {returnStatement}; + return method; + } + + private IfStatement generateCompareFloatOrDouble(Expression thisRef, Expression otherRef, char[] floatOrDouble, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; /* if (Float.compare(fieldName, other.fieldName) != 0) return false */ diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java new file mode 100644 index 00000000..736e6e43 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -0,0 +1,284 @@ +/* + * Copyright © 2009 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers; + +import static lombok.eclipse.Eclipse.fromQualifiedName; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.lang.reflect.Modifier; +import java.util.Arrays; + +import lombok.core.AnnotationValues; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult; + +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.mangosdk.spi.ProviderFor; + +public class HandleLog { + + private HandleLog() { + throw new UnsupportedOperationException(); + } + + public static boolean processAnnotation(LoggingFramework framework, AnnotationValues<? extends java.lang.annotation.Annotation> annotation, Annotation source, EclipseNode annotationNode) { + Expression annotationValue = (Expression) annotation.getActualExpression("value"); + if (annotationValue != null && !(annotationValue instanceof ClassLiteralAccess)) { + return true; + } + ClassLiteralAccess loggingType = (ClassLiteralAccess)annotationValue; + + EclipseNode owner = annotationNode.up(); + switch (owner.getKind()) { + case TYPE: + TypeDeclaration typeDecl = null; + if (owner.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) owner.get(); + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + + boolean notAClass = (modifiers & + (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0; + + if (typeDecl == null || notAClass) { + annotationNode.addError("@Log is legal only on classes and enums."); + return false; + } + + if (fieldExists("log", owner) != MemberExistsResult.NOT_EXISTS) { + annotationNode.addWarning("Field 'log' already exists."); + return true; + } + + if (loggingType == null) { + loggingType = selfType(owner, source); + } + + injectField(owner, createField(framework, source, loggingType)); + owner.rebuild(); + return true; + default: + annotationNode.addError("@Log is legal only on types."); + return true; + } + } + + private static ClassLiteralAccess selfType(EclipseNode type, Annotation source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); + TypeReference typeReference = new SingleTypeReference(typeDeclaration.name, p); + Eclipse.setGeneratedBy(typeReference, source); + + ClassLiteralAccess result = new ClassLiteralAccess(source.sourceEnd, typeReference); + Eclipse.setGeneratedBy(result, source); + + return result; + } + + private static FieldDeclaration createField(LoggingFramework framework, Annotation source, ClassLiteralAccess loggingType) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + // private static final <loggerType> log = <factoryMethod>(<parameter>); + + FieldDeclaration fieldDecl = new FieldDeclaration("log".toCharArray(), 0, -1); + Eclipse.setGeneratedBy(fieldDecl, source); + fieldDecl.declarationSourceEnd = -1; + fieldDecl.modifiers = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL; + + fieldDecl.type = createTypeReference(framework.getLoggerTypeName(), source); + + MessageSend factoryMethodCall = new MessageSend(); + Eclipse.setGeneratedBy(factoryMethodCall, source); + + factoryMethodCall.receiver = createNameReference(framework.getLoggerFactoryTypeName(), source); + factoryMethodCall.selector = framework.getLoggerFactoryMethodName().toCharArray(); + + Expression parameter = framework.createFactoryParameter(loggingType, source); + + factoryMethodCall.arguments = new Expression[] { parameter }; + factoryMethodCall.nameSourcePosition = p; + factoryMethodCall.sourceStart = pS; + factoryMethodCall.sourceEnd = factoryMethodCall.statementEnd = pE; + + fieldDecl.initialization = factoryMethodCall; + + return fieldDecl; + } + + private static TypeReference createTypeReference(String typeName, Annotation source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + TypeReference typeReference; + if (typeName.contains(".")) { + + char[][] typeNameTokens = fromQualifiedName(typeName); + long[] pos = new long[typeNameTokens.length]; + Arrays.fill(pos, p); + + typeReference = new QualifiedTypeReference(typeNameTokens, pos); + } + else { + typeReference = null; + } + + Eclipse.setGeneratedBy(typeReference, source); + return typeReference; + } + + private static NameReference createNameReference(String name, Annotation source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + char[][] nameTokens = fromQualifiedName(name); + long[] pos = new long[nameTokens.length]; + Arrays.fill(pos, p); + + QualifiedNameReference nameReference = new QualifiedNameReference(nameTokens, pos, pS, pE); + nameReference.statementEnd = pE; + + Eclipse.setGeneratedBy(nameReference, source); + return nameReference; + } + + /** + * Handles the {@link lombok.extern.apachecommons.Log} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleCommonsLog implements EclipseAnnotationHandler<lombok.extern.apachecommons.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.apachecommons.Log> annotation, Annotation source, EclipseNode annotationNode) { + return processAnnotation(LoggingFramework.COMMONS, annotation, source, annotationNode); + } + } + + /** + * Handles the {@link lombok.extern.jul.Log} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleJulLog implements EclipseAnnotationHandler<lombok.extern.jul.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.jul.Log> annotation, Annotation source, EclipseNode annotationNode) { + return processAnnotation(LoggingFramework.JUL, annotation, source, annotationNode); + } + } + + /** + * Handles the {@link lombok.extern.log4j.Log} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleLog4jLog implements EclipseAnnotationHandler<lombok.extern.log4j.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.log4j.Log> annotation, Annotation source, EclipseNode annotationNode) { + return processAnnotation(LoggingFramework.LOG4J, annotation, source, annotationNode); + } + } + + /** + * Handles the {@link lombok.extern.slf4j.Log} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleSlf4jLog implements EclipseAnnotationHandler<lombok.extern.slf4j.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.slf4j.Log> annotation, Annotation source, EclipseNode annotationNode) { + return processAnnotation(LoggingFramework.SLF4J, annotation, source, annotationNode); + } + } + + enum LoggingFramework { + // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); + COMMONS(lombok.extern.jul.Log.class, "org.apache.commons.logging.Log", "org.apache.commons.logging.LogFactory", "getLog"), + + // private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(TargetType.class.getName()); + JUL(lombok.extern.jul.Log.class, "java.util.logging.Logger", "java.util.logging.Logger", "getLogger") { + @Override public Expression createFactoryParameter(ClassLiteralAccess type, Annotation source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + MessageSend factoryParameterCall = new MessageSend(); + Eclipse.setGeneratedBy(factoryParameterCall, source); + + factoryParameterCall.receiver = super.createFactoryParameter(type, source); + factoryParameterCall.selector = "getName".toCharArray(); + + factoryParameterCall.nameSourcePosition = p; + factoryParameterCall.sourceStart = pS; + factoryParameterCall.sourceEnd = factoryParameterCall.statementEnd = pE; + + return factoryParameterCall; + } + }, + + // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); + LOG4J(lombok.extern.jul.Log.class, "org.apache.log4j.Logger", "org.apache.log4j.Logger", "getLogger"), + + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); + SLF4J(lombok.extern.slf4j.Log.class, "org.slf4j.Logger", "org.slf4j.LoggerFactory", "getLogger"), + + ; + + private final Class<? extends java.lang.annotation.Annotation> annotationClass; + private final String loggerTypeName; + private final String loggerFactoryTypeName; + private final String loggerFactoryMethodName; + + LoggingFramework(Class<? extends java.lang.annotation.Annotation> annotationClass, String loggerTypeName, String loggerFactoryTypeName, String loggerFactoryMethodName) { + this.annotationClass = annotationClass; + this.loggerTypeName = loggerTypeName; + this.loggerFactoryTypeName = loggerFactoryTypeName; + this.loggerFactoryMethodName = loggerFactoryMethodName; + } + + final Class<? extends java.lang.annotation.Annotation> getAnnotationClass() { + return annotationClass; + } + + final String getLoggerTypeName() { + return loggerTypeName; + } + + final String getLoggerFactoryTypeName() { + return loggerFactoryTypeName; + } + + final String getLoggerFactoryMethodName() { + return loggerFactoryMethodName; + } + + Expression createFactoryParameter(ClassLiteralAccess loggingType, Annotation source){ + TypeReference copy = Eclipse.copyType(loggingType.type, source); + ClassLiteralAccess result = new ClassLiteralAccess(source.sourceEnd, copy); + Eclipse.setGeneratedBy(result, source); + return result; + }; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleSynchronized.java b/src/core/lombok/eclipse/handlers/HandleSynchronized.java index fde36192..b77099b5 100644 --- a/src/core/lombok/eclipse/handlers/HandleSynchronized.java +++ b/src/core/lombok/eclipse/handlers/HandleSynchronized.java @@ -101,7 +101,7 @@ public class HandleSynchronized implements EclipseAnnotationHandler<Synchronized fieldDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { 0, 0, 0 }); Eclipse.setGeneratedBy(fieldDecl.type, source); fieldDecl.initialization = arrayAlloc; - injectField(annotationNode.up().up(), fieldDecl); + injectFieldSuppressWarnings(annotationNode.up().up(), fieldDecl); } if (method.statements == null) return false; diff --git a/src/core/lombok/eclipse/handlers/HandleVal.java b/src/core/lombok/eclipse/handlers/HandleVal.java new file mode 100644 index 00000000..b3cfa879 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleVal.java @@ -0,0 +1,61 @@ +/* + * Copyright © 2009-2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers; + +import lombok.eclipse.EclipseASTAdapter; +import lombok.eclipse.EclipseASTVisitor; +import lombok.eclipse.EclipseNode; + +import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; +import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.mangosdk.spi.ProviderFor; + +/* + * This class just handles 2 basic error cases. The real meat of eclipse 'val' support is in {@code EclipsePatcher}'s {@code patchHandleVal} method. + */ +@ProviderFor(EclipseASTVisitor.class) +public class HandleVal extends EclipseASTAdapter { + @Override public void visitLocal(EclipseNode localNode, LocalDeclaration local) { + if (local.type instanceof SingleTypeReference) { + char[] token = ((SingleTypeReference)local.type).token; + if (token == null || token.length != 3) return; + else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return; + + boolean variableOfForEach = false; + + if (localNode.directUp().get() instanceof ForeachStatement) { + ForeachStatement fs = (ForeachStatement) localNode.directUp().get(); + variableOfForEach = fs.elementVariable == local; + } + + if (local.initialization == null && !variableOfForEach) { + localNode.addError("'val' on a local variable requires an initializer expression"); + } + + if (local.initialization instanceof ArrayInitializer) { + localNode.addError("'val' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); + } + } + } +} diff --git a/src/core/lombok/extern/apachecommons/Log.java b/src/core/lombok/extern/apachecommons/Log.java new file mode 100644 index 00000000..4c82a2a7 --- /dev/null +++ b/src/core/lombok/extern/apachecommons/Log.java @@ -0,0 +1,75 @@ +/* + * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.extern.apachecommons; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Causes lombok to generate a logger field. + * Example: + * <pre> + * @Log + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); + * } + * </pre> + * + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class. + * Example: + * <pre> + * @Log(java.util.List.class) + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(java.util.List.class); + * } + * </pre> + * + * This annotation is valid for classes and enumerations.<br /> + * + * @see lombok.extern.jul.Log lombok.extern.jul.Log + * @see lombok.extern.log4j.Log lombok.extern.log4j.Log + * @see lombok.extern.slf4j.Log lombok.extern.slf4j.Log + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Log { + /** + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class here. + */ + Class<?> value() default void.class; +}
\ No newline at end of file diff --git a/src/core/lombok/extern/jul/Log.java b/src/core/lombok/extern/jul/Log.java new file mode 100644 index 00000000..a66db671 --- /dev/null +++ b/src/core/lombok/extern/jul/Log.java @@ -0,0 +1,75 @@ +/* + * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.extern.jul; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Causes lombok to generate a logger field. + * Example: + * <pre> + * @Log + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); + * } + * </pre> + * + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class. + * Example: + * <pre> + * @Log(java.util.List.class) + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(java.util.List.class.getName()); + * } + * </pre> + * + * This annotation is valid for classes and enumerations.<br /> + * + * @see lombok.extern.apachecommons.Log lombok.extern.apachecommons.Log + * @see lombok.extern.log4j.Log lombok.extern.log4j.Log + * @see lombok.extern.slf4j.Log lombok.extern.slf4j.Log + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Log { + /** + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class here. + */ + Class<?> value() default void.class; +}
\ No newline at end of file diff --git a/src/core/lombok/extern/log4j/Log.java b/src/core/lombok/extern/log4j/Log.java new file mode 100644 index 00000000..151b5e98 --- /dev/null +++ b/src/core/lombok/extern/log4j/Log.java @@ -0,0 +1,75 @@ +/* + * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.extern.log4j; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Causes lombok to generate a logger field. + * Example: + * <pre> + * @Log + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); + * } + * </pre> + * + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class. + * Example: + * <pre> + * @Log(java.util.List.class) + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(java.util.List.class); + * } + * </pre> + * + * This annotation is valid for classes and enumerations.<br /> + * + * @see lombok.extern.apachecommons.Log lombok.extern.apachecommons.Log + * @see lombok.extern.jul.Log lombok.extern.jul.Log + * @see lombok.extern.slf4j.Log lombok.extern.slf4j.Log + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Log { + /** + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class here. + */ + Class<?> value() default void.class; +}
\ No newline at end of file diff --git a/src/core/lombok/extern/slf4j/Log.java b/src/core/lombok/extern/slf4j/Log.java new file mode 100644 index 00000000..5431847a --- /dev/null +++ b/src/core/lombok/extern/slf4j/Log.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.extern.slf4j; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * Causes lombok to generate a logger field. + * Example: + * <pre> + * @Log + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); + * } + * </pre> + * + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class. + * Example: + * <pre> + * @Log(java.util.List.class) + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(java.util.List.class); + * } + * </pre> + * + * This annotation is valid for classes and enumerations.<br /> + * + * @see lombok.extern.apachecommons.Log lombok.extern.apachecommons.Log + * @see lombok.extern.jul.Log lombok.extern.jul.Log + * @see lombok.extern.log4j.Log lombok.extern.log4j.Log + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Log { + /** + * If you do not want to use the annotated class as the logger parameter, you can specify an alternate class here. + */ + Class<?> value() default void.class; +} diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java index bbbdacd0..5b792874 100644 --- a/src/core/lombok/javac/HandlerLibrary.java +++ b/src/core/lombok/javac/HandlerLibrary.java @@ -183,7 +183,13 @@ public class HandlerLibrary { */ public void callASTVisitors(JavacAST ast) { for (JavacASTVisitor visitor : visitorHandlers) try { - ast.traverse(visitor); + if (!visitor.isResolutionBased()) ast.traverse(visitor); + } catch (Throwable t) { + javacError(String.format("Lombok visitor handler %s failed", visitor.getClass()), t); + } + + for (JavacASTVisitor visitor : visitorHandlers) try { + if (visitor.isResolutionBased()) ast.traverse(visitor); } catch (Throwable t) { javacError(String.format("Lombok visitor handler %s failed", visitor.getClass()), t); } diff --git a/src/core/lombok/javac/Javac.java b/src/core/lombok/javac/Javac.java index 58a24207..6d9800ab 100644 --- a/src/core/lombok/javac/Javac.java +++ b/src/core/lombok/javac/Javac.java @@ -90,6 +90,7 @@ public class Javac { String name = m.getName(); List<String> raws = new ArrayList<String>(); List<Object> guesses = new ArrayList<Object>(); + List<Object> expressions = new ArrayList<Object>(); final List<DiagnosticPosition> positions = new ArrayList<DiagnosticPosition>(); boolean isExplicit = false; @@ -112,17 +113,19 @@ public class Javac { List<JCExpression> elems = ((JCNewArray)rhs).elems; for (JCExpression inner : elems) { raws.add(inner.toString()); + expressions.add(inner); guesses.add(calculateGuess(inner)); positions.add(inner.pos()); } } else { raws.add(rhs.toString()); + expressions.add(rhs); guesses.add(calculateGuess(rhs)); positions.add(rhs.pos()); } } - values.put(name, new AnnotationValue(node, raws, guesses, isExplicit) { + values.put(name, new AnnotationValue(node, raws, expressions, guesses, isExplicit) { @Override public void setError(String message, int valueIdx) { if (valueIdx < 0) node.addError(message); else node.addError(message, positions.get(valueIdx)); diff --git a/src/core/lombok/javac/JavacASTAdapter.java b/src/core/lombok/javac/JavacASTAdapter.java index 41bc46d3..bbdb6876 100644 --- a/src/core/lombok/javac/JavacASTAdapter.java +++ b/src/core/lombok/javac/JavacASTAdapter.java @@ -35,6 +35,11 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; */ public class JavacASTAdapter implements JavacASTVisitor { /** {@inheritDoc} */ + @Override public boolean isResolutionBased() { + return false; + } + + /** {@inheritDoc} */ @Override public void visitCompilationUnit(JavacNode top, JCCompilationUnit unit) {} /** {@inheritDoc} */ diff --git a/src/core/lombok/javac/JavacASTVisitor.java b/src/core/lombok/javac/JavacASTVisitor.java index 3c5887a7..18376037 100644 --- a/src/core/lombok/javac/JavacASTVisitor.java +++ b/src/core/lombok/javac/JavacASTVisitor.java @@ -38,6 +38,12 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; */ public interface JavacASTVisitor { /** + * If true, you'll be called after all the non-resolution based visitors. + * NB: Temporary solution - will be rewritten to a different style altogether in a future release. + */ + boolean isResolutionBased(); + + /** * Called at the very beginning and end. */ void visitCompilationUnit(JavacNode top, JCCompilationUnit unit); @@ -101,6 +107,10 @@ public interface JavacASTVisitor { private int disablePrinting = 0; private int indent = 0; + @Override public boolean isResolutionBased() { + return false; + } + /** * @param printContent if true, bodies are printed directly, as java code, * instead of a tree listing of every AST node inside it. diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java new file mode 100644 index 00000000..e0eb436d --- /dev/null +++ b/src/core/lombok/javac/JavacResolution.java @@ -0,0 +1,414 @@ +package lombok.javac; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.util.ArrayDeque; +import java.util.Map; + +import javax.lang.model.type.TypeKind; +import javax.tools.DiagnosticListener; + +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type.ArrayType; +import com.sun.tools.javac.code.Type.CapturedType; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTags; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.comp.Attr; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Enter; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.comp.MemberEnter; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; + +public class JavacResolution { + private final Attr attr; + private final LogDisabler logDisabler; + + public JavacResolution(Context context) { + attr = Attr.instance(context); + logDisabler = new LogDisabler(context); + } + /** + * During resolution, the resolver will emit resolution errors, but without appropriate file names and line numbers. If these resolution errors stick around + * then they will be generated AGAIN, this time with proper names and line numbers, at the end. Therefore, we want to suppress the logger. + */ + private static final class LogDisabler { + private final Log log; + private static final Field errWriterField, warnWriterField, noticeWriterField, dumpOnErrorField, promptOnErrorField, diagnosticListenerField; + private PrintWriter errWriter, warnWriter, noticeWriter; + private Boolean dumpOnError, promptOnError; + private DiagnosticListener<?> contextDiagnosticListener, logDiagnosticListener; + private final Context context; + + // If this is true, the fields changed. Better to print weird error messages than to fail outright. + private static final boolean dontBother; + + static { + boolean z; + Field a = null, b = null, c = null, d = null, e = null, f = null; + try { + a = Log.class.getDeclaredField("errWriter"); + b = Log.class.getDeclaredField("warnWriter"); + c = Log.class.getDeclaredField("noticeWriter"); + d = Log.class.getDeclaredField("dumpOnError"); + e = Log.class.getDeclaredField("promptOnError"); + f = Log.class.getDeclaredField("diagListener"); + z = false; + a.setAccessible(true); + b.setAccessible(true); + c.setAccessible(true); + d.setAccessible(true); + e.setAccessible(true); + f.setAccessible(true); + } catch (Exception x) { + z = true; + } + + errWriterField = a; + warnWriterField = b; + noticeWriterField = c; + dumpOnErrorField = d; + promptOnErrorField = e; + diagnosticListenerField = f; + dontBother = z; + } + + LogDisabler(Context context) { + this.log = Log.instance(context); + this.context = context; + } + + boolean disableLoggers() { + contextDiagnosticListener = context.get(DiagnosticListener.class); + context.put(DiagnosticListener.class, (DiagnosticListener<?>) null); + if (dontBother) return false; + boolean dontBotherInstance = false; + + PrintWriter dummyWriter = new PrintWriter(new OutputStream() { + @Override public void write(int b) throws IOException { + // Do nothing on purpose + } + }); + + if (!dontBotherInstance) try { + errWriter = (PrintWriter) errWriterField.get(log); + errWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + warnWriter = (PrintWriter) warnWriterField.get(log); + warnWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + noticeWriter = (PrintWriter) noticeWriterField.get(log); + noticeWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + dumpOnError = (Boolean) dumpOnErrorField.get(log); + dumpOnErrorField.set(log, false); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + promptOnError = (Boolean) promptOnErrorField.get(log); + promptOnErrorField.set(log, false); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + logDiagnosticListener = (DiagnosticListener<?>) diagnosticListenerField.get(log); + diagnosticListenerField.set(log, null); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (dontBotherInstance) enableLoggers(); + return !dontBotherInstance; + } + + void enableLoggers() { + if (contextDiagnosticListener != null) { + context.put(DiagnosticListener.class, contextDiagnosticListener); + contextDiagnosticListener = null; + } + if (errWriter != null) try { + errWriterField.set(log, errWriter); + errWriter = null; + } catch (Exception e) {} + + if (warnWriter != null) try { + warnWriterField.set(log, warnWriter); + warnWriter = null; + } catch (Exception e) {} + + if (noticeWriter != null) try { + noticeWriterField.set(log, noticeWriter); + noticeWriter = null; + } catch (Exception e) {} + + if (dumpOnError != null) try { + dumpOnErrorField.set(log, dumpOnError); + dumpOnError = null; + } catch (Exception e) {} + + if (promptOnError != null) try { + promptOnErrorField.set(log, promptOnError); + promptOnError = null; + } catch (Exception e) {} + + if (logDiagnosticListener != null) try { + diagnosticListenerField.set(log, logDiagnosticListener); + logDiagnosticListener = null; + } catch (Exception e) {} + } + } + + private static final class EnvFinder extends JCTree.Visitor { + private Env<AttrContext> env = null; + private Enter enter; + private MemberEnter memberEnter; + private JCTree copyAt = null; + + EnvFinder(Context context) { + this.enter = Enter.instance(context); + this.memberEnter = MemberEnter.instance(context); + } + + Env<AttrContext> get() { + return env; + } + + JCTree copyAt() { + return copyAt; + } + + @Override public void visitTopLevel(JCCompilationUnit tree) { + if (copyAt != null) return; + env = enter.getTopLevelEnv(tree); + } + + @Override public void visitClassDef(JCClassDecl tree) { + if (copyAt != null) return; + // The commented out one leaves the 'lint' field unset, which causes NPEs during attrib. So, we use the other one. + //env = enter.classEnv((JCClassDecl) tree, env); + env = enter.getClassEnv(tree.sym); + } + + @Override public void visitMethodDef(JCMethodDecl tree) { + if (copyAt != null) return; + env = memberEnter.getMethodEnv(tree, env); + copyAt = tree; + } + + public void visitVarDef(JCVariableDecl tree) { + if (copyAt != null) return; + env = memberEnter.getInitEnv(tree, env); + copyAt = tree; + } + + @Override public void visitBlock(JCBlock tree) { + if (copyAt != null) return; + copyAt = tree; + } + + @Override public void visitTree(JCTree that) { + } + } + + public Map<JCTree, JCTree> resolve(JavacNode node) { + ArrayDeque<JCTree> stack = new ArrayDeque<JCTree>(); + + { + JavacNode n = node; + while (n != null) { + stack.push(n.get()); + n = n.up(); + } + } + + logDisabler.disableLoggers(); + try { + EnvFinder finder = new EnvFinder(node.getContext()); + while (!stack.isEmpty()) stack.pop().accept(finder); + + TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node); + JCTree copy = mirrorMaker.copy(finder.copyAt()); + + attrib(copy, finder.get()); + return mirrorMaker.getOriginalToCopyMap(); + } finally { + logDisabler.enableLoggers(); + } + } + + private void attrib(JCTree tree, Env<AttrContext> env) { + if (tree instanceof JCBlock) attr.attribStat(tree, env); + else if (tree instanceof JCMethodDecl) attr.attribStat(((JCMethodDecl)tree).body, env); + else if (tree instanceof JCVariableDecl) attr.attribStat(tree, env); + else throw new IllegalStateException("Called with something that isn't a block, method decl, or variable decl"); + } + + public static class TypeNotConvertibleException extends Exception { + public TypeNotConvertibleException(String msg) { + super(msg); + } + } + + public static Type ifTypeIsIterableToComponent(Type type, JavacAST ast) { + Types types = Types.instance(ast.getContext()); + Symtab syms = Symtab.instance(ast.getContext()); + Type boundType = types.upperBound(type); + Type elemTypeIfArray = types.elemtype(boundType); + if (elemTypeIfArray != null) return elemTypeIfArray; + + Type base = types.asSuper(boundType, syms.iterableType.tsym); + if (base == null) return syms.objectType; + + List<Type> iterableParams = base.allparams(); + return iterableParams.isEmpty() ? syms.objectType : types.upperBound(iterableParams.head); + } + + public static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast) throws TypeNotConvertibleException { + return typeToJCTree(type, maker, ast, false); + } + + public static JCExpression createJavaLangObject(TreeMaker maker, JavacAST ast) { + JCExpression out = maker.Ident(ast.toName("java")); + out = maker.Select(out, ast.toName("lang")); + out = maker.Select(out, ast.toName("Object")); + return out; + } + + private static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) throws TypeNotConvertibleException { + int dims = 0; + Type type0 = type; + while (type0 instanceof ArrayType) { + dims++; + type0 = ((ArrayType)type0).elemtype; + } + + JCExpression result = typeToJCTree0(type0, maker, ast, allowCompound); + while (dims > 0) { + result = maker.TypeArray(result); + dims--; + } + return result; + } + + private static JCExpression typeToJCTree0(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) throws TypeNotConvertibleException { + // NB: There's such a thing as maker.Type(type), but this doesn't work very well; it screws up anonymous classes, captures, and adds an extra prefix dot for some reason too. + // -- so we write our own take on that here. + + if (type.isPrimitive()) return primitiveToJCTree(type.getKind(), maker); + if (type.isErroneous()) throw new TypeNotConvertibleException("Type cannot be resolved"); + + TypeSymbol symbol = type.asElement(); + List<Type> generics = type.getTypeArguments(); + + JCExpression replacement = null; + + if (symbol == null) throw new TypeNotConvertibleException("Null or compound type"); + + if (symbol.name.len == 0) { + // Anonymous inner class + if (type instanceof ClassType) { + List<Type> ifaces = ((ClassType)type).interfaces_field; + Type supertype = ((ClassType)type).supertype_field; + if (ifaces != null && ifaces.length() == 1) { + return typeToJCTree(ifaces.get(0), maker, ast, allowCompound); + } + if (supertype != null) return typeToJCTree(supertype, maker, ast, allowCompound); + } + throw new TypeNotConvertibleException("Anonymous inner class"); + } + + if (type instanceof CapturedType) { + if (allowCompound) { + if (type.getLowerBound() == null || type.getLowerBound().tag == TypeTags.BOT) { + if (type.getUpperBound().toString().equals("java.lang.Object")) { + return maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + } + return maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), typeToJCTree(type.getUpperBound(), maker, ast, false)); + } else { + return maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), typeToJCTree(type.getLowerBound(), maker, ast, false)); + } + } + if (type.getUpperBound() != null) { + return typeToJCTree(type.getUpperBound(), maker, ast, allowCompound); + } + + return createJavaLangObject(maker, ast); + } + + String qName = symbol.getQualifiedName().toString(); + if (qName.isEmpty()) throw new TypeNotConvertibleException("unknown type"); + if (qName.startsWith("<")) throw new TypeNotConvertibleException(qName); + String[] baseNames = symbol.getQualifiedName().toString().split("\\."); + replacement = maker.Ident(ast.toName(baseNames[0])); + for (int i = 1; i < baseNames.length; i++) { + replacement = maker.Select(replacement, ast.toName(baseNames[i])); + } + + if (generics != null && !generics.isEmpty()) { + List<JCExpression> args = List.nil(); + for (Type t : generics) args = args.append(typeToJCTree(t, maker, ast, true)); + replacement = maker.TypeApply(replacement, args); + } + + return replacement; + } + + private static JCExpression primitiveToJCTree(TypeKind kind, TreeMaker maker) throws TypeNotConvertibleException { + switch (kind) { + case BYTE: + return maker.TypeIdent(TypeTags.BYTE); + case CHAR: + return maker.TypeIdent(TypeTags.CHAR); + case SHORT: + return maker.TypeIdent(TypeTags.SHORT); + case INT: + return maker.TypeIdent(TypeTags.INT); + case LONG: + return maker.TypeIdent(TypeTags.LONG); + case FLOAT: + return maker.TypeIdent(TypeTags.FLOAT); + case DOUBLE: + return maker.TypeIdent(TypeTags.DOUBLE); + case BOOLEAN: + return maker.TypeIdent(TypeTags.BOOLEAN); + case VOID: + return maker.TypeIdent(TypeTags.VOID); + case NULL: + case NONE: + case OTHER: + default: + throw new TypeNotConvertibleException("Nulltype"); + } + } +} diff --git a/src/core/lombok/javac/TreeMirrorMaker.java b/src/core/lombok/javac/TreeMirrorMaker.java new file mode 100644 index 00000000..1c0b9311 --- /dev/null +++ b/src/core/lombok/javac/TreeMirrorMaker.java @@ -0,0 +1,50 @@ +package lombok.javac; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeCopier; +import com.sun.tools.javac.util.List; + +public class TreeMirrorMaker extends TreeCopier<Void> { + private final IdentityHashMap<JCTree, JCTree> originalToCopy = new IdentityHashMap<JCTree, JCTree>(); + + public TreeMirrorMaker(JavacNode node) { + super(node.getTreeMaker()); + } + + @Override public <T extends JCTree> T copy(T original) { + T copy = super.copy(original); + originalToCopy.put(original, copy); + return copy; + } + + @Override public <T extends JCTree> T copy(T original, Void p) { + T copy = super.copy(original, p); + originalToCopy.put(original, copy); + return copy; + } + + @Override public <T extends JCTree> List<T> copy(List<T> originals) { + List<T> copies = super.copy(originals); + Iterator<T> it1 = originals.iterator(); + Iterator<T> it2 = copies.iterator(); + while (it1.hasNext()) originalToCopy.put(it1.next(), it2.next()); + return copies; + } + + @Override public <T extends JCTree> List<T> copy(List<T> originals, Void p) { + List<T> copies = super.copy(originals, p); + Iterator<T> it1 = originals.iterator(); + Iterator<T> it2 = copies.iterator(); + while (it1.hasNext()) originalToCopy.put(it1.next(), it2.next()); + return copies; + } + + public Map<JCTree, JCTree> getOriginalToCopyMap() { + return Collections.unmodifiableMap(originalToCopy); + } +} diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index 1d3d0c34..037f5ba5 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -157,6 +157,8 @@ public class Processor extends AbstractProcessor { ClassLoader unwrapped = (ClassLoader) f.get(processingEnv); ClassLoader wrapped = new WrappingClassLoader(unwrapped); f.set(processingEnv, wrapped); + } catch (NoSuchFieldException e) { + // Some versions of javac have this (and call close on it), some don't. I guess this one doesn't have it. } catch (Throwable t) { throw Lombok.sneakyThrow(t); } diff --git a/src/core/lombok/javac/handlers/HandleCleanup.java b/src/core/lombok/javac/handlers/HandleCleanup.java index 2c89d9ad..779dd3ea 100644 --- a/src/core/lombok/javac/handlers/HandleCleanup.java +++ b/src/core/lombok/javac/handlers/HandleCleanup.java @@ -30,10 +30,12 @@ import lombok.javac.JavacNode; import org.mangosdk.spi.ProviderFor; +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.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCBinary; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCCase; import com.sun.tools.javac.tree.JCTree.JCCatch; @@ -41,6 +43,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTypeCast; @@ -108,11 +111,16 @@ public class HandleCleanup implements JavacAnnotationHandler<Cleanup> { doAssignmentCheck(annotationNode, tryBlock, decl.name); TreeMaker maker = annotationNode.getTreeMaker(); - JCFieldAccess cleanupCall = maker.Select(maker.Ident(decl.name), annotationNode.toName(cleanupName)); - List<JCStatement> finalizerBlock = List.<JCStatement>of(maker.Exec( - maker.Apply(List.<JCExpression>nil(), cleanupCall, List.<JCExpression>nil()))); + JCFieldAccess cleanupMethod = maker.Select(maker.Ident(decl.name), annotationNode.toName(cleanupName)); + List<JCStatement> cleanupCall = List.<JCStatement>of(maker.Exec( + maker.Apply(List.<JCExpression>nil(), cleanupMethod, List.<JCExpression>nil()))); + + JCBinary isNull = maker.Binary(JCTree.NE, maker.Ident(decl.name), maker.Literal(TypeTags.BOT, null)); + + JCIf ifNotNullCleanup = maker.If(isNull, maker.Block(0, cleanupCall), null); + + JCBlock finalizer = maker.Block(0, List.<JCStatement>of(ifNotNullCleanup)); - JCBlock finalizer = maker.Block(0, finalizerBlock); newStatements = newStatements.append(maker.Try(maker.Block(0, tryBlock), List.<JCCatch>nil(), finalizer)); if (blockNode instanceof JCBlock) { diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index ee049f1f..c5475fb1 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -167,9 +167,13 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd } } + boolean needsCanEqual = false; switch (methodExists("equals", typeNode)) { case NOT_EXISTS: - JCMethodDecl method = createEquals(typeNode, nodesForEquality, callSuper, useFieldsDirectly); + boolean isFinal = (((JCClassDecl)typeNode.get()).mods.flags & Flags.FINAL) != 0; + needsCanEqual = !isFinal || !isDirectDescendantOfObject; + + JCMethodDecl method = createEquals(typeNode, nodesForEquality, callSuper, useFieldsDirectly, needsCanEqual); injectMethod(typeNode, method); break; case EXISTS_BY_LOMBOK: @@ -182,6 +186,18 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd break; } + if (needsCanEqual) { + switch (methodExists("canEqual", typeNode)) { + case NOT_EXISTS: + JCMethodDecl method = createCanEqual(typeNode); + injectMethod(typeNode, method); + break; + case EXISTS_BY_LOMBOK: + case EXISTS_BY_USER: + default: + break; + } + } switch (methodExists("hashCode", typeNode)) { case NOT_EXISTS: JCMethodDecl method = createHashCode(typeNode, nodesForEquality, callSuper, useFieldsDirectly); @@ -196,7 +212,6 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd } break; } - return true; } @@ -314,7 +329,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) { + private JCMethodDecl createEquals(JavacNode typeNode, List<JavacNode> fields, boolean callSuper, boolean useFieldsDirectly, boolean needsCanEqual) { TreeMaker maker = typeNode.getTreeMaker(); JCClassDecl type = (JCClassDecl) typeNode.get(); @@ -335,27 +350,9 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd maker.Ident(thisName)), returnBool(maker, true), null)); } - /* if (o == null) return false; */ { - statements = statements.append(maker.If(maker.Binary(JCTree.EQ, maker.Ident(oName), - maker.Literal(TypeTags.BOT, null)), returnBool(maker, false), null)); - } - - /* if (o.getClass() != this.getClass()) return false; */ { - Name getClass = typeNode.toName("getClass"); - List<JCExpression> exprNil = List.nil(); - JCExpression oGetClass = maker.Apply(exprNil, maker.Select(maker.Ident(oName), getClass), exprNil); - JCExpression thisGetClass = maker.Apply(exprNil, maker.Select(maker.Ident(thisName), getClass), exprNil); - statements = statements.append( - maker.If(maker.Binary(JCTree.NE, oGetClass, thisGetClass), returnBool(maker, false), null)); - } - - /* if (!super.equals(o)) return false; */ - if (callSuper) { - JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), - maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), - List.<JCExpression>of(maker.Ident(oName))); - JCUnary superNotEqual = maker.Unary(JCTree.NOT, callToSuper); - statements = statements.append(maker.If(superNotEqual, returnBool(maker, false), null)); + /* if (!(o instanceof MyType) return false; */ { + JCUnary notInstanceOf = maker.Unary(JCTree.NOT, maker.TypeTest(maker.Ident(oName), maker.Ident(type.name))); + statements = statements.append(maker.If(notInstanceOf, returnBool(maker, false), null)); } /* MyType<?> other = (MyType<?>) o; */ { @@ -379,6 +376,25 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd maker.VarDef(maker.Modifiers(Flags.FINAL), otherName, selfType1, maker.TypeCast(selfType2, maker.Ident(oName)))); } + /* if (!other.canEqual(this)) return false; */ { + if (needsCanEqual) { + List<JCExpression> exprNil = List.nil(); + JCExpression equalityCheck = maker.Apply(exprNil, + maker.Select(maker.Ident(otherName), typeNode.toName("canEqual")), + List.<JCExpression>of(maker.Ident(thisName))); + statements = statements.append(maker.If(maker.Unary(JCTree.NOT, equalityCheck), returnBool(maker, false), null)); + } + } + + /* if (!super.equals(o)) return false; */ + if (callSuper) { + JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), + maker.Select(maker.Ident(typeNode.toName("super")), typeNode.toName("equals")), + List.<JCExpression>of(maker.Ident(oName))); + JCUnary superNotEqual = maker.Unary(JCTree.NOT, callToSuper); + statements = statements.append(maker.If(superNotEqual, returnBool(maker, false), null)); + } + for (JavacNode fieldNode : fields) { JCExpression fType = getFieldType(fieldNode, useFieldsDirectly); JCExpression thisFieldAccessor = createFieldAccessor(maker, fieldNode, useFieldsDirectly); @@ -428,6 +444,27 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd JCBlock body = maker.Block(0, statements); return maker.MethodDef(mods, typeNode.toName("equals"), returnType, List.<JCTypeParameter>nil(), params, List.<JCExpression>nil(), body, null); } + + private JCMethodDecl createCanEqual(JavacNode typeNode) { + /* public boolean canEquals(final java.lang.Object other) { + * return other instanceof MyType; + * } + */ + TreeMaker maker = typeNode.getTreeMaker(); + JCClassDecl type = (JCClassDecl) typeNode.get(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC, List.<JCAnnotation>nil()); + JCExpression returnType = maker.TypeIdent(TypeTags.BOOLEAN); + Name canEqualName = typeNode.toName("canEqual"); + JCExpression objectType = chainDots(maker, typeNode, "java", "lang", "Object"); + Name otherName = typeNode.toName("other"); + List<JCVariableDecl> params = List.of(maker.VarDef(maker.Modifiers(Flags.FINAL), otherName, objectType, null)); + + JCBlock body = maker.Block(0, List.<JCStatement>of( + maker.Return(maker.TypeTest(maker.Ident(otherName), maker.Ident(type.name))))); + + return maker.MethodDef(mods, canEqualName, returnType, List.<JCTypeParameter>nil(), params, List.<JCExpression>nil(), body, null); + } private JCStatement generateCompareFloatOrDouble(JCExpression thisDotField, JCExpression otherDotField, TreeMaker maker, JavacNode node, boolean isDouble) { @@ -442,5 +479,4 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd private JCStatement returnBool(TreeMaker maker, boolean bool) { return maker.Return(maker.Literal(TypeTags.BOOLEAN, bool ? 1 : 0)); } - } diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java new file mode 100644 index 00000000..03e40d7f --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -0,0 +1,203 @@ +/* + * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers; + +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.lang.annotation.Annotation; + +import lombok.core.AnnotationValues; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; + +public class HandleLog { + + private HandleLog() { + throw new UnsupportedOperationException(); + } + + public static boolean processAnnotation(LoggingFramework framework, AnnotationValues<?> annotation, JavacNode annotationNode) { + markAnnotationAsProcessed(annotationNode, framework.getAnnotationClass()); + +// String loggingClassName = annotation.getRawExpression("value"); +// if (loggingClassName == null) loggingClassName = "void"; +// if (loggingClassName.endsWith(".class")) loggingClassName = loggingClassName.substring(0, loggingClassName.length() - 6); + + JCExpression annotationValue = (JCExpression) annotation.getActualExpression("value"); + JCFieldAccess loggingType = null; + if (annotationValue != null) { + if (!(annotationValue instanceof JCFieldAccess)) return true; + loggingType = (JCFieldAccess) annotationValue; + if (!loggingType.name.contentEquals("class")) return true; + } + + + JavacNode typeNode = annotationNode.up(); + switch (typeNode.getKind()) { + case TYPE: + if ((((JCClassDecl)typeNode.get()).mods.flags & Flags.INTERFACE)!= 0) { + annotationNode.addError("@Log is legal only on classes and enums."); + return true; + } + + if (fieldExists("log", typeNode)!= MemberExistsResult.NOT_EXISTS) { + annotationNode.addWarning("Field 'log' already exists."); + return true; + } + + if (loggingType == null) { + loggingType = selfType(typeNode); + } + createField(framework, typeNode, loggingType); + return true; + default: + annotationNode.addError("@Log is legal only on types."); + return true; + } + } + + private static JCFieldAccess selfType(JavacNode typeNode) { + TreeMaker maker = typeNode.getTreeMaker(); + Name name = ((JCClassDecl) typeNode.get()).name; + return maker.Select(maker.Ident(name), typeNode.toName("class")); + } + + private static boolean createField(LoggingFramework framework, JavacNode typeNode, JCFieldAccess loggingType) { + TreeMaker maker = typeNode.getTreeMaker(); + + // private static final <loggerType> log = <factoryMethod>(<parameter>); + JCExpression loggerType = chainDotsString(maker, typeNode, framework.getLoggerTypeName()); + JCExpression factoryMethod = chainDotsString(maker, typeNode, framework.getLoggerFactoryMethodName()); + + JCExpression loggerName = framework.createFactoryParameter(typeNode, loggingType); + JCMethodInvocation factoryMethodCall = maker.Apply(List.<JCExpression>nil(), factoryMethod, List.<JCExpression>of(loggerName)); + + JCVariableDecl fieldDecl = maker.VarDef( + maker.Modifiers(Flags.PRIVATE | Flags.FINAL | Flags.STATIC), + typeNode.toName("log"), loggerType, factoryMethodCall); + + injectField(typeNode, fieldDecl); + return true; + } + + /** + * Handles the {@link lombok.extern.apachecommons.Log} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleCommonsLog implements JavacAnnotationHandler<lombok.extern.apachecommons.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.apachecommons.Log> annotation, JCAnnotation ast, JavacNode annotationNode) { + return processAnnotation(LoggingFramework.COMMONS, annotation, annotationNode); + } + } + + /** + * Handles the {@link lombok.extern.jul.Log} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleJulLog implements JavacAnnotationHandler<lombok.extern.jul.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.jul.Log> annotation, JCAnnotation ast, JavacNode annotationNode) { + return processAnnotation(LoggingFramework.JUL, annotation, annotationNode); + } + } + + /** + * Handles the {@link lombok.extern.log4j.Log} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleLog4jLog implements JavacAnnotationHandler<lombok.extern.log4j.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.log4j.Log> annotation, JCAnnotation ast, JavacNode annotationNode) { + return processAnnotation(LoggingFramework.LOG4J, annotation, annotationNode); + } + } + + /** + * Handles the {@link lombok.extern.slf4j.Log} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleSlf4jLog implements JavacAnnotationHandler<lombok.extern.slf4j.Log> { + @Override public boolean handle(AnnotationValues<lombok.extern.slf4j.Log> annotation, JCAnnotation ast, JavacNode annotationNode) { + return processAnnotation(LoggingFramework.SLF4J, annotation, annotationNode); + } + } + + enum LoggingFramework { + // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); + COMMONS(lombok.extern.jul.Log.class, "org.apache.commons.logging.Log", "org.apache.commons.logging.LogFactory.getLog"), + + // private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(TargetType.class.getName()); + JUL(lombok.extern.jul.Log.class, "java.util.logging.Logger", "java.util.logging.Logger.getLogger") { + @Override public JCExpression createFactoryParameter(JavacNode typeNode, JCFieldAccess loggingType) { + TreeMaker maker = typeNode.getTreeMaker(); + JCExpression method = maker.Select(loggingType, typeNode.toName("getName")); + return maker.Apply(List.<JCExpression>nil(), method, List.<JCExpression>nil()); + } + }, + + // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); + LOG4J(lombok.extern.jul.Log.class, "org.apache.log4j.Logger", "org.apache.log4j.Logger.getLogger"), + + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); + SLF4J(lombok.extern.slf4j.Log.class, "org.slf4j.Logger", "org.slf4j.LoggerFactory.getLogger"), + + ; + + private final Class<? extends Annotation> annotationClass; + private final String loggerTypeName; + private final String loggerFactoryName; + + LoggingFramework(Class<? extends Annotation> annotationClass, String loggerTypeName, String loggerFactoryName) { + this.annotationClass = annotationClass; + this.loggerTypeName = loggerTypeName; + this.loggerFactoryName = loggerFactoryName; + } + + final Class<? extends Annotation> getAnnotationClass() { + return annotationClass; + } + + final String getLoggerTypeName() { + return loggerTypeName; + } + + final String getLoggerFactoryMethodName() { + return loggerFactoryName; + } + + JCExpression createFactoryParameter(JavacNode typeNode, JCFieldAccess loggingType) { + return loggingType; + } + } +} diff --git a/src/core/lombok/javac/handlers/HandleSynchronized.java b/src/core/lombok/javac/handlers/HandleSynchronized.java index 2f900eb8..a095aaf1 100644 --- a/src/core/lombok/javac/handlers/HandleSynchronized.java +++ b/src/core/lombok/javac/handlers/HandleSynchronized.java @@ -89,7 +89,7 @@ public class HandleSynchronized implements JavacAnnotationHandler<Synchronized> JCVariableDecl fieldDecl = maker.VarDef( maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic ? Flags.STATIC : 0)), methodNode.toName(lockName), objectType, newObjectArray); - injectField(methodNode.up(), fieldDecl); + injectFieldSuppressWarnings(methodNode.up(), fieldDecl); } if (method.body == null) return false; diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java new file mode 100644 index 00000000..5da6fec0 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -0,0 +1,113 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers; + +import lombok.javac.JavacASTAdapter; +import lombok.javac.JavacASTVisitor; +import lombok.javac.JavacNode; +import lombok.javac.JavacResolution; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCNewArray; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; + +@ProviderFor(JavacASTVisitor.class) +public class HandleVal extends JavacASTAdapter { + @Override public boolean isResolutionBased() { + return true; + } + + @Override public void visitLocal(JavacNode localNode, JCVariableDecl local) { + if (local.vartype != null && local.vartype.toString().equals("val")) { + JCExpression rhsOfEnhancedForLoop = null; + if (local.init == null) { + JCTree parentRaw = localNode.directUp().get(); + if (parentRaw instanceof JCEnhancedForLoop) { + JCEnhancedForLoop efl = (JCEnhancedForLoop) parentRaw; + if (efl.var == local) rhsOfEnhancedForLoop = efl.expr; + } + } + + if (rhsOfEnhancedForLoop == null && local.init == null) { + localNode.addError("'val' on a local variable requires an initializer expression"); + return; + } + + if (local.init instanceof JCNewArray && ((JCNewArray)local.init).elemtype == null) { + localNode.addError("'val' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); + return; + } + + local.mods.flags |= Flags.FINAL; + local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst()); + + Type type; + try { + if (rhsOfEnhancedForLoop == null) { + if (local.init.type == null) { + JavacResolution resolver = new JavacResolution(localNode.getContext()); + type = ((JCExpression) resolver.resolve(localNode).get(local.init)).type; + } else { + type = local.init.type; + } + } else { + if (rhsOfEnhancedForLoop.type == null) { + JavacResolution resolver = new JavacResolution(localNode.getContext()); + type = ((JCExpression) resolver.resolve(localNode.directUp()).get(rhsOfEnhancedForLoop)).type; + } else { + type = rhsOfEnhancedForLoop.type; + } + } + + try { + JCExpression replacement; + + if (rhsOfEnhancedForLoop != null) { + Type componentType = JavacResolution.ifTypeIsIterableToComponent(type, localNode.getAst()); + if (componentType == null) replacement = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst()); + else replacement = JavacResolution.typeToJCTree(componentType, localNode.getTreeMaker(), localNode.getAst()); + } else { + replacement = JavacResolution.typeToJCTree(type, localNode.getTreeMaker(), localNode.getAst()); + } + + if (replacement != null) { + local.vartype = replacement; + localNode.getAst().setChanged(); + } + else local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst());; + } catch (JavacResolution.TypeNotConvertibleException e) { + localNode.addError("Cannot use 'val' here because initializer expression does not have a representable type: " + e.getMessage()); + local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst());; + } + } catch (RuntimeException e) { + local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst());; + throw e; + } + } + } +} diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 79436327..5dacf2ca 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -392,13 +392,26 @@ public class JavacHandlerUtil { /** * Adds the given new field declaration to the provided type AST Node. + * The field carries the @{@link SuppressWarnings}("all") annotation. + * Also takes care of updating the JavacAST. + */ + public static void injectFieldSuppressWarnings(JavacNode typeNode, JCVariableDecl field) { + injectField(typeNode, field, true); + } + + /** + * Adds the given new field declaration to the provided type AST Node. * * Also takes care of updating the JavacAST. */ public static void injectField(JavacNode typeNode, JCVariableDecl field) { + injectField(typeNode, field, false); + } + + private static void injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { JCClassDecl type = (JCClassDecl) typeNode.get(); - addSuppressWarningsAll(field.mods, typeNode, field.pos); + if (addSuppressWarnings) addSuppressWarningsAll(field.mods, typeNode, field.pos); type.defs = type.defs.append(field); typeNode.add(field, Kind.FIELD).recursiveSetHandled(); @@ -476,6 +489,21 @@ public class JavacHandlerUtil { return e; } + + + /** + * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} + * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by + * a {@code Ident} node. This method generates such an expression. + * + * For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]). + * + * @see com.sun.tools.javac.tree.JCTree.JCIdent + * @see com.sun.tools.javac.tree.JCTree.JCFieldAccess + */ + public static JCExpression chainDotsString(TreeMaker maker, JavacNode node, String elems) { + return chainDots(maker, node, elems.split("\\.")); + } /** * Searches the given field node for annotations and returns each one that matches the provided regular expression pattern. diff --git a/src/delombok/lombok/delombok/CommentCollectingScanner.java b/src/delombok/lombok/delombok/CommentCollectingScanner.java index 2552cbf7..c930bc62 100644 --- a/src/delombok/lombok/delombok/CommentCollectingScanner.java +++ b/src/delombok/lombok/delombok/CommentCollectingScanner.java @@ -25,7 +25,7 @@ import java.nio.CharBuffer; import lombok.delombok.Comment.EndConnection; import lombok.delombok.Comment.StartConnection; -import lombok.delombok.CommentPreservingParser.Comments; +import lombok.delombok.Delombok.Comments; import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.Context; diff --git a/src/delombok/lombok/delombok/CommentPreservingParser.java b/src/delombok/lombok/delombok/CommentPreservingParser.java deleted file mode 100644 index 87c02dcd..00000000 --- a/src/delombok/lombok/delombok/CommentPreservingParser.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package lombok.delombok; - -import java.io.IOException; -import java.io.Writer; -import java.util.Collections; -import java.util.Date; - -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.tools.DiagnosticListener; -import javax.tools.JavaFileObject; -import javax.tools.Diagnostic.Kind; - -import lombok.javac.DeleteLombokAnnotations; -import lombok.javac.JavacTransformer; - -import com.sun.tools.javac.main.JavaCompiler; -import com.sun.tools.javac.main.OptionName; -import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; -import com.sun.tools.javac.util.Context; -import com.sun.tools.javac.util.List; -import com.sun.tools.javac.util.Options; - -public class CommentPreservingParser { - private final String encoding; - private boolean deleteLombokAnnotations = false; - private DiagnosticListener<JavaFileObject> diagnostics = null; - - public CommentPreservingParser() { - this("utf-8"); - } - - public CommentPreservingParser(String encoding) { - this.encoding = encoding; - } - - public void setDeleteLombokAnnotations(boolean deleteLombokAnnotations) { - this.deleteLombokAnnotations = deleteLombokAnnotations; - } - - public void setDiagnosticsListener(DiagnosticListener<JavaFileObject> diagnostics) { - this.diagnostics = diagnostics; - } - - public ParseResult parse(JavaFileObject source, boolean forceProcessing) throws IOException { - return doParse(source, forceProcessing); - } - - public ParseResult parse(String fileName, boolean forceProcessing) throws IOException { - return doParse(fileName, forceProcessing); - } - - private ParseResult doParse(Object source, boolean forceProcessing) throws IOException { - Context context = new Context(); - - Options.instance(context).put(OptionName.ENCODING, encoding); - - if (diagnostics != null) context.put(DiagnosticListener.class, diagnostics); - - CommentCollectingScanner.Factory.preRegister(context); - - JavaCompiler compiler = new JavaCompiler(context) { - @Override - protected boolean keepComments() { - return true; - } - }; - - compiler.genEndPos = true; - - Comments comments = new Comments(); - context.put(Comments.class, comments); - if (deleteLombokAnnotations) context.put(DeleteLombokAnnotations.class, new DeleteLombokAnnotations(true)); - - comments.comments = List.nil(); - - JCCompilationUnit cu; - if (source instanceof JavaFileObject) { - cu = compiler.parse((JavaFileObject) source); - } else { - @SuppressWarnings("deprecation") - JCCompilationUnit unit = compiler.parse((String)source); - cu = unit; - } - - boolean changed = new JavacTransformer(messager).transform(context, Collections.singleton(cu)); - return new ParseResult(comments.comments, cu, forceProcessing || changed); - } - - private static final Messager messager = new Messager() { - @Override public void printMessage(Kind kind, CharSequence msg) { - System.out.printf("%s: %s\n", kind, msg); - } - - @Override public void printMessage(Kind kind, CharSequence msg, Element e) { - System.out.printf("%s: %s\n", kind, msg); - } - - @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a) { - System.out.printf("%s: %s\n", kind, msg); - } - - @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v) { - System.out.printf("%s: %s\n", kind, msg); - } - }; - - static class Comments { - List<Comment> comments = List.nil(); - - void add(Comment comment) { - comments = comments.append(comment); - } - } - - public static class ParseResult { - private final List<Comment> comments; - private final JCCompilationUnit compilationUnit; - private final boolean changed; - - private ParseResult(List<Comment> comments, JCCompilationUnit compilationUnit, boolean changed) { - this.comments = comments; - this.compilationUnit = compilationUnit; - this.changed = changed; - } - - public void print(Writer out) throws IOException { - if (!changed) { - JavaFileObject sourceFile = compilationUnit.getSourceFile(); - if (sourceFile != null) { - out.write(sourceFile.getCharContent(true).toString()); - return; - } - } - - out.write("// Generated by delombok at "); - out.write(String.valueOf(new Date())); - out.write(System.getProperty("line.separator")); - - compilationUnit.accept(new PrettyCommentsPrinter(out, compilationUnit, comments)); - } - - public boolean isChanged() { - return changed; - } - } -} diff --git a/src/delombok/lombok/delombok/Delombok.java b/src/delombok/lombok/delombok/Delombok.java index 56994f78..6866d97f 100644 --- a/src/delombok/lombok/delombok/Delombok.java +++ b/src/delombok/lombok/delombok/Delombok.java @@ -30,16 +30,33 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Writer; +import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; import javax.tools.DiagnosticListener; import javax.tools.JavaFileObject; +import javax.tools.Diagnostic.Kind; -import lombok.delombok.CommentPreservingParser.ParseResult; +import lombok.javac.DeleteLombokAnnotations; +import lombok.javac.JavacTransformer; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.OptionName; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Options; import com.zwitserloot.cmdreader.CmdReader; import com.zwitserloot.cmdreader.Description; import com.zwitserloot.cmdreader.Excludes; @@ -50,16 +67,24 @@ import com.zwitserloot.cmdreader.Shorthand; public class Delombok { private Charset charset = Charset.defaultCharset(); - private CommentPreservingParser parser = new CommentPreservingParser(); + private Context context = new Context(); + private Writer presetWriter; - { - parser.setDeleteLombokAnnotations(true); + public void setWriter(Writer writer) { + this.presetWriter = writer; + } + + public Delombok() { + context.put(DeleteLombokAnnotations.class, new DeleteLombokAnnotations(true)); } private PrintStream feedback = System.err; private boolean verbose; private boolean noCopy; private boolean force = false; + private String classpath, sourcepath; + private LinkedHashMap<File, File> fileToBase = new LinkedHashMap<File, File>(); + private List<File> filesToParse = new ArrayList<File>(); /** If null, output to standard out. */ private File output = null; @@ -88,6 +113,14 @@ public class Delombok { @Mandatory(onlyIfNot={"print", "help"}) private String target; + @Shorthand("c") + @Description("Classpath (analogous to javac -cp option)") + private String classpath; + + @Shorthand("s") + @Description("Sourcepath (analogous to javac -sourcepath option)") + private String sourcepath; + @Description("Files to delombok. Provide either a file, or a directory. If you use a directory, all files in it (recursive) are delombok-ed") @Sequential private List<String> input = new ArrayList<String>(); @@ -138,21 +171,29 @@ public class Delombok { if (args.verbose) delombok.setVerbose(true); if (args.nocopy) delombok.setNoCopy(true); - if (args.print) delombok.setOutputToStandardOut(); - else delombok.setOutput(new File(args.target)); + if (args.print) { + delombok.setOutputToStandardOut(); + } else { + delombok.setOutput(new File(args.target)); + } + + if (args.classpath != null) delombok.setClasspath(args.classpath); + if (args.sourcepath != null) delombok.setSourcepath(args.sourcepath); for (String in : args.input) { try { File f = new File(in); if (f.isFile()) { - delombok.delombok(f.getParentFile(), f.getName()); + delombok.addFile(f.getParentFile(), f.getName()); } else if (f.isDirectory()) { - delombok.delombok(f); + delombok.addDirectory(f); } else if (!f.exists()) { if (!args.quiet) System.err.println("WARNING: does not exist - skipping: " + f); } else { if (!args.quiet) System.err.println("WARNING: not a standard file or directory - skipping: " + f); } + + delombok.delombok(); } catch (Exception e) { if (!args.quiet) { String msg = e.getMessage(); @@ -168,12 +209,12 @@ public class Delombok { } public void setCharset(String charsetName) throws UnsupportedCharsetException { + charset = Charset.forName(charsetName); } - public void setDiagnosticsListener(DiagnosticListener<JavaFileObject> diagnostics) { - parser.setDiagnosticsListener(diagnostics); + if (diagnostics != null) context.put(DiagnosticListener.class, diagnostics); } public void setForceProcess(boolean force) { @@ -184,6 +225,14 @@ public class Delombok { this.feedback = feedback; } + public void setClasspath(String classpath) { + this.classpath = classpath; + } + + public void setSourcepath(String sourcepath) { + this.sourcepath = sourcepath; + } + public void setVerbose(boolean verbose) { this.verbose = verbose; } @@ -204,15 +253,15 @@ public class Delombok { this.output = null; } - public void delombok(File base) throws IOException { - delombok0(false, base, "", 0); + public void addDirectory(File base) throws IOException { + addDirectory0(false, base, "", 0); } - public void process(boolean copy, File base, String name) throws IOException { + public void addDirectory1(boolean copy, File base, String name) throws IOException { File f = new File(base, name); if (f.isFile()) { String extension = getExtension(f); - if (extension.equals("java")) delombok(base, name); + if (extension.equals("java")) addFile(base, name); else if (extension.equals("class")) skipClass(name); else copy(copy, base, name); } else if (!f.exists()) { @@ -222,7 +271,7 @@ public class Delombok { } } - private void delombok0(boolean inHiddenDir, File base, String suffix, int loop) throws IOException { + private void addDirectory0(boolean inHiddenDir, File base, String suffix, int loop) throws IOException { File dir = suffix.isEmpty() ? base : new File(base, suffix); if (dir.isDirectory()) { @@ -236,7 +285,7 @@ public class Delombok { feedback.printf("Only processing java files (not copying non-java files) in %s because it's a hidden directory.\n", canonical(dir)); } for (File f : list) { - delombok0(inHiddenDir || thisDirIsHidden, base, suffix + (suffix.isEmpty() ? "" : File.separator) + f.getName(), loop + 1); + addDirectory0(inHiddenDir || thisDirIsHidden, base, suffix + (suffix.isEmpty() ? "" : File.separator) + f.getName(), loop + 1); } } else { if (!thisDirIsHidden && !noCopy && !inHiddenDir && output != null && !suffix.isEmpty()) { @@ -247,7 +296,7 @@ public class Delombok { } } } else { - process(!inHiddenDir && !noCopy, base, suffix); + addDirectory1(!inHiddenDir && !noCopy, base, suffix); } } @@ -288,34 +337,96 @@ public class Delombok { } } - public void delombok(JavaFileObject file, Writer writer) throws IOException { - ParseResult result = parser.parse(file, force); - result.print(writer); + public void addFile(File base, String fileName) throws IOException { + if (output != null && canonical(base).equals(canonical(output))) throw new IOException( + "DELOMBOK: Output file and input file refer to the same filesystem location. Specify a separate path for output."); + + File f = new File(base, fileName); + filesToParse.add(f); + fileToBase.put(f, base); } - public void delombok(String file, Writer writer) throws IOException { - ParseResult result = parser.parse(file, force); - result.print(writer); + private static <T> com.sun.tools.javac.util.List<T> toJavacList(List<T> list) { + com.sun.tools.javac.util.List<T> out = com.sun.tools.javac.util.List.nil(); + ListIterator<T> li = list.listIterator(list.size()); + while (li.hasPrevious()) out = out.prepend(li.previous()); + return out; } - public void delombok(File base, String fileName) throws IOException { - if (output != null && canonical(base).equals(canonical(output))) throw new IOException( - "DELOMBOK: Output file and input file refer to the same filesystem location. Specify a separate path for output."); + public boolean delombok() throws IOException { + Options options = Options.instance(context); + options.put(OptionName.ENCODING, charset.name()); + if (classpath != null) options.put(OptionName.CLASSPATH, classpath); + if (sourcepath != null) options.put(OptionName.SOURCEPATH, sourcepath); + CommentCollectingScanner.Factory.preRegister(context); - ParseResult result = parser.parse(new File(base, fileName).getAbsolutePath(), force); + JavaCompiler compiler = new JavaCompiler(context); + compiler.keepComments = true; + compiler.genEndPos = true; - if (verbose) feedback.printf("File: %s [%s]\n", fileName, result.isChanged() ? "delombok-ed" : "unchanged"); + List<JCCompilationUnit> roots = new ArrayList<JCCompilationUnit>(); + Map<JCCompilationUnit, Comments> commentsMap = new IdentityHashMap<JCCompilationUnit, Comments>(); + Map<JCCompilationUnit, File> baseMap = new IdentityHashMap<JCCompilationUnit, File>(); + for (File fileToParse : filesToParse) { + Comments comments = new Comments(); + context.put(Comments.class, comments); + + @SuppressWarnings("deprecation") + JCCompilationUnit unit = compiler.parse(fileToParse.getAbsolutePath()); + + commentsMap.put(unit, comments); + baseMap.put(unit, fileToBase.get(fileToParse)); + roots.add(unit); + } - Writer rawWriter = output == null ? createStandardOutWriter() : createFileWriter(output, fileName); - BufferedWriter writer = new BufferedWriter(rawWriter); + if (compiler.errorCount() > 0) return false; + compiler.enterTrees(toJavacList(roots)); - try { - result.print(writer); - } finally { - writer.close(); + for (JCCompilationUnit unit : roots) { + boolean changed = new JavacTransformer(messager).transform(context, Collections.singleton(unit)); + DelombokResult result = new DelombokResult(commentsMap.get(unit).comments, unit, force || changed); + if (verbose) feedback.printf("File: %s [%s]\n", unit.sourcefile.getName(), result.isChanged() ? "delomboked" : "unchanged"); + Writer rawWriter; + if (presetWriter != null) rawWriter = presetWriter; + else if (output == null) rawWriter = createStandardOutWriter(); + else rawWriter = createFileWriter(output, baseMap.get(unit), unit.sourcefile.toUri()); + BufferedWriter writer = new BufferedWriter(rawWriter); + try { + result.print(writer); + } finally { + writer.close(); + } + } + + return true; + } + + public static class Comments { + public com.sun.tools.javac.util.List<Comment> comments = com.sun.tools.javac.util.List.nil(); + + void add(Comment comment) { + comments = comments.append(comment); } } + private static final Messager messager = new Messager() { + @Override public void printMessage(Kind kind, CharSequence msg) { + System.out.printf("%s: %s\n", kind, msg); + } + + @Override public void printMessage(Kind kind, CharSequence msg, Element e) { + System.out.printf("%s: %s\n", kind, msg); + } + + @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a) { + System.out.printf("%s: %s\n", kind, msg); + } + + @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v) { + System.out.printf("%s: %s\n", kind, msg); + } + }; + private static String canonical(File dir) { try { return dir.getCanonicalPath(); @@ -330,8 +441,15 @@ public class Delombok { return idx == -1 ? "" : name.substring(idx+1); } - private Writer createFileWriter(File base, String fileName) throws IOException { - File outFile = new File(base, fileName); + private Writer createFileWriter(File outBase, File inBase, URI file) throws IOException { + URI relative = inBase.toURI().relativize(file); + File outFile; + if (relative.isAbsolute()) { + outFile = new File(outBase, new File(relative).getName()); + } else { + outFile = new File(outBase, relative.getPath()); + } + outFile.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(outFile); return createUnicodeEscapeWriter(out); diff --git a/src/delombok/lombok/delombok/DelombokResult.java b/src/delombok/lombok/delombok/DelombokResult.java new file mode 100644 index 00000000..717a3bf1 --- /dev/null +++ b/src/delombok/lombok/delombok/DelombokResult.java @@ -0,0 +1,62 @@ +/* + * Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */package lombok.delombok; + +import java.io.IOException; +import java.io.Writer; +import java.util.Date; + +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.List; + +public class DelombokResult { + private final List<Comment> comments; + private final JCCompilationUnit compilationUnit; + private final boolean changed; + + public DelombokResult(List<Comment> comments, JCCompilationUnit compilationUnit, boolean changed) { + this.comments = comments; + this.compilationUnit = compilationUnit; + this.changed = changed; + } + + public void print(Writer out) throws IOException { + if (!changed) { + JavaFileObject sourceFile = compilationUnit.getSourceFile(); + if (sourceFile != null) { + out.write(sourceFile.getCharContent(true).toString()); + return; + } + } + + out.write("// Generated by delombok at "); + out.write(String.valueOf(new Date())); + out.write(System.getProperty("line.separator")); + + compilationUnit.accept(new PrettyCommentsPrinter(out, compilationUnit, comments)); + } + + public boolean isChanged() { + return changed; + } +}
\ No newline at end of file diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 55c0e1bc..57f0a1d8 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -337,7 +337,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { * Traversal methods *************************************************************************/ - /** Exception to propogate IOException through visitXXX methods */ + /** Exception to propagate IOException through visitXXX methods */ private static class UncheckedIOException extends Error { static final long serialVersionUID = -4032692679158424751L; UncheckedIOException(IOException e) { @@ -657,6 +657,11 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { } } print(" "); + // <Added for delombok by Reinier Zwitserloot> + if ((tree.mods.flags & INTERFACE) != 0) { + removeImplicitModifiersForInterfaceMembers(tree.defs); + } + // </Added for delombok by Reinier Zwitserloot> if ((tree.mods.flags & ENUM) != 0) { printEnumBody(tree.defs); } else { @@ -668,11 +673,28 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { } } + // Added for delombok by Reinier Zwitserloot + private void removeImplicitModifiersForInterfaceMembers(List<JCTree> defs) { + for (JCTree def :defs) { + if (def instanceof JCVariableDecl) { + ((JCVariableDecl) def).mods.flags &= ~(Flags.PUBLIC | Flags.STATIC | Flags.FINAL); + } + if (def instanceof JCMethodDecl) { + ((JCMethodDecl) def).mods.flags &= ~(Flags.PUBLIC | Flags.ABSTRACT); + } + if (def instanceof JCClassDecl) { + ((JCClassDecl) def).mods.flags &= ~(Flags.PUBLIC | Flags.STATIC); + } + } + } + public void visitMethodDef(JCMethodDecl tree) { try { + boolean isConstructor = tree.name == tree.name.table.fromChars("<init>".toCharArray(), 0, 6); // when producing source output, omit anonymous constructors - if (tree.name == tree.name.table.fromChars("<init>".toCharArray(), 0, 6) && - enclClassName == null) return; + if (isConstructor && enclClassName == null) return; + boolean isGeneratedConstructor = isConstructor && ((tree.mods.flags & Flags.GENERATEDCONSTR) != 0); + if (isGeneratedConstructor) return; println(); align(); printDocComment(tree); printExpr(tree.mods); @@ -1442,9 +1464,13 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { print("@"); printExpr(tree.annotationType); if (tree.args.nonEmpty()) { - print("("); - printExprs(tree.args); - print(")"); + print("("); + if (tree.args.length() == 1 && tree.args.get(0) instanceof JCAssign) { + JCExpression lhs = ((JCAssign)tree.args.get(0)).lhs; + if (lhs instanceof JCIdent && ((JCIdent)lhs).name.toString().equals("value")) tree.args = List.of(((JCAssign)tree.args.get(0)).rhs); + } + printExprs(tree.args); + print(")"); } } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/src/delombok/lombok/delombok/ant/DelombokTask.java b/src/delombok/lombok/delombok/ant/DelombokTask.java index 4a40b874..40c3c63e 100644 --- a/src/delombok/lombok/delombok/ant/DelombokTask.java +++ b/src/delombok/lombok/delombok/ant/DelombokTask.java @@ -32,14 +32,55 @@ import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; import org.apache.tools.ant.types.resources.FileResource; public class DelombokTask extends Task { private File fromDir, toDir; + private Path classpath; + private Path sourcepath; private boolean verbose; private String encoding; private Path path; + public void setClasspath(Path classpath) { + if (this.classpath == null) { + this.classpath = classpath; + } else { + this.classpath.append(classpath); + } + } + + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(getProject()); + } + return classpath.createPath(); + } + + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + + public void setSourcepath(Path sourcepath) { + if (this.sourcepath == null) { + this.sourcepath = sourcepath; + } else { + this.sourcepath.append(sourcepath); + } + } + + public Path createSourcepath() { + if (sourcepath == null) { + sourcepath = new Path(getProject()); + } + return sourcepath.createPath(); + } + + public void setSourcepathRef(Reference r) { + createSourcepath().setRefid(r); + } + public void setFrom(File dir) { this.fromDir = dir; } @@ -75,9 +116,12 @@ public class DelombokTask extends Task { throw new BuildException("Unknown charset: " + encoding, getLocation()); } + if (classpath != null) delombok.setClasspath(classpath.toString()); + if (sourcepath != null) delombok.setSourcepath(sourcepath.toString()); + delombok.setOutput(toDir); try { - if (fromDir != null) delombok.delombok(fromDir); + if (fromDir != null) delombok.addDirectory(fromDir); else { Iterator<?> it = path.iterator(); while (it.hasNext()) { @@ -85,12 +129,13 @@ public class DelombokTask extends Task { File baseDir = fileResource.getBaseDir(); if (baseDir == null) { File file = fileResource.getFile(); - delombok.process(false, file.getParentFile(), file.getName()); + delombok.addFile(file.getParentFile(), file.getName()); } else { - delombok.process(false, baseDir, fileResource.getName()); + delombok.addFile(baseDir, fileResource.getName()); } } } + delombok.delombok(); } catch (IOException e) { throw new BuildException("I/O problem during delombok", e, getLocation()); } diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index 2ac24a48..399ea357 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -69,6 +69,8 @@ public class EclipsePatcher extends Agent { patchPostCompileHookEcj(sm); } + patchEcjTransformers(sm, ecjOnly); + if (reloadExistingClasses) sm.reloadClasses(instrumentation); } @@ -253,4 +255,77 @@ public class EclipsePatcher extends Agent { .wrapMethod(new Hook("lombok.eclipse.TransformEclipseAST", "transform_swapped", "void", CUD_SIG, PARSER_SIG)) .request(StackRequest.THIS, StackRequest.RETURN_VALUE).build()); } + + private static void patchEcjTransformers(ScriptManager sm, boolean ecj) { + patchHandleVal(sm, ecj); + } + + // Creates a copy of the 'initialization' field on a LocalDeclaration if the type of the LocalDeclaration is 'val', because the completion parser will null this out, + // which in turn stops us from inferring the intended type for 'val x = 5;'. We look at the copy. + // Also patches local declaration to not call .resolveType() on the initializer expression if we've already done so (calling it twice causes weird errors), + // and patches .resolve() on LocalDeclaration itself to just-in-time replace the 'val' vartype with the right one. + + private static void patchHandleVal(ScriptManager sm, boolean ecj) { + final String LOCALDECLARATION_SIG = "org.eclipse.jdt.internal.compiler.ast.LocalDeclaration"; + final String FOREACHSTATEMENT_SIG = "org.eclipse.jdt.internal.compiler.ast.ForeachStatement"; + final String EXPRESSION_SIG = "org.eclipse.jdt.internal.compiler.ast.Expression"; + final String BLOCKSCOPE_SIG = "org.eclipse.jdt.internal.compiler.lookup.BlockScope"; + final String PARSER_SIG = "org.eclipse.jdt.internal.compiler.parser.Parser"; + final String TYPEBINDING_SIG = "org.eclipse.jdt.internal.compiler.lookup.TypeBinding"; + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .request(StackRequest.THIS, StackRequest.PARAM1) + .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleValForLocalDeclaration", "boolean", LOCALDECLARATION_SIG, BLOCKSCOPE_SIG)) + .build()); + + sm.addScript(ScriptBuilder.replaceMethodCall() + .target(new MethodTarget(LOCALDECLARATION_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) + .requestExtra(StackRequest.THIS) + .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "skipResolveInitializerIfAlreadyCalled2", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG, LOCALDECLARATION_SIG)) + .build()); + + sm.addScript(ScriptBuilder.exitEarly() + .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .request(StackRequest.THIS, StackRequest.PARAM1) + .decisionMethod(new Hook("lombok.eclipse.agent.PatchFixes", "handleValForForEach", "boolean", FOREACHSTATEMENT_SIG, BLOCKSCOPE_SIG)) + .build()); + + sm.addScript(ScriptBuilder.replaceMethodCall() + .target(new MethodTarget(FOREACHSTATEMENT_SIG, "resolve", "void", BLOCKSCOPE_SIG)) + .methodToReplace(new Hook(EXPRESSION_SIG, "resolveType", TYPEBINDING_SIG, BLOCKSCOPE_SIG)) + .replacementMethod(new Hook("lombok.eclipse.agent.PatchFixes", "skipResolveInitializerIfAlreadyCalled", TYPEBINDING_SIG, EXPRESSION_SIG, BLOCKSCOPE_SIG)) + .build()); + + if (!ecj) { + sm.addScript(ScriptBuilder.addField() + .fieldName("$initCopy") + .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") + .setPublic() + .setTransient() + .targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") + .build()); + + sm.addScript(ScriptBuilder.addField() + .fieldName("$iterableCopy") + .fieldType("Lorg/eclipse/jdt/internal/compiler/ast/ASTNode;") + .setPublic() + .setTransient() + .targetClass("org.eclipse.jdt.internal.compiler.ast.LocalDeclaration") + .build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG, "consumeExitVariableWithInitialization", "void")) + .request(StackRequest.THIS) + .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "copyInitializationOfLocalDeclarationForVal", "void", PARSER_SIG)) + .build()); + + sm.addScript(ScriptBuilder.wrapReturnValue() + .target(new MethodTarget(PARSER_SIG, "consumeEnhancedForStatementHeader", "void")) + .request(StackRequest.THIS) + .wrapMethod(new Hook("lombok.eclipse.agent.PatchFixes", "copyInitializationOfForEachIterable", "void", PARSER_SIG)) + .build()); + } + } } diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java index daf1cbf2..e2a52929 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchFixes.java @@ -31,10 +31,28 @@ import java.util.List; import lombok.core.DiagnosticsReceiver; import lombok.core.PostCompiler; +import lombok.eclipse.Eclipse; import org.eclipse.jdt.core.IMethod; -import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; +import org.eclipse.jdt.internal.compiler.lookup.Binding; +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.eclipse.jdt.internal.compiler.parser.Parser; public class PatchFixes { public static int fixRetrieveStartingCatchPosition(int in) { @@ -78,7 +96,7 @@ public class PatchFixes { boolean isGenerated = internalNode.getClass().getField("$generatedBy").get(internalNode) != null; if (isGenerated) { domNode.getClass().getField("$isGenerated").set(domNode, true); - domNode.setFlags(domNode.getFlags() & ~ASTNode.ORIGINAL); + domNode.setFlags(domNode.getFlags() & ~org.eclipse.jdt.core.dom.ASTNode.ORIGINAL); } } @@ -127,4 +145,178 @@ public class PatchFixes { String fileName = path + "/" + name; return new BufferedOutputStream(PostCompiler.wrapOutputStream(out, fileName, DiagnosticsReceiver.CONSOLE)); } + + private static Field astStackField, astPtrField; + + static { + try { + astStackField = Parser.class.getDeclaredField("astStack"); + astStackField.setAccessible(true); + astPtrField = Parser.class.getDeclaredField("astPtr"); + astPtrField.setAccessible(true); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + } + } + + public static void copyInitializationOfForEachIterable(Parser parser) { + ASTNode[] astStack; + int astPtr; + try { + astStack = (ASTNode[]) astStackField.get(parser); + astPtr = (Integer)astPtrField.get(parser); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + return; + } + + ForeachStatement foreachDecl = (ForeachStatement) astStack[astPtr]; + ASTNode init = foreachDecl.collection; + if (init == null) return; + if (foreachDecl.elementVariable != null && foreachDecl.elementVariable.type instanceof SingleTypeReference) { + SingleTypeReference ref = (SingleTypeReference) foreachDecl.elementVariable.type; + if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return; + } else return; + + try { + if (iterableCopyField != null) iterableCopyField.set(foreachDecl.elementVariable, init); + } catch (Exception e) { + // In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception. + } + } + + public static void copyInitializationOfLocalDeclarationForVal(Parser parser) { + ASTNode[] astStack; + int astPtr; + try { + astStack = (ASTNode[]) astStackField.get(parser); + astPtr = (Integer)astPtrField.get(parser); + } catch (Exception e) { + // Most likely we're in ecj or some other plugin usage of the eclipse compiler. No need for this. + return; + } + AbstractVariableDeclaration variableDecl = (AbstractVariableDeclaration) astStack[astPtr]; + if (!(variableDecl instanceof LocalDeclaration)) return; + ASTNode init = variableDecl.initialization; + if (init == null) return; + if (variableDecl.type instanceof SingleTypeReference) { + SingleTypeReference ref = (SingleTypeReference) variableDecl.type; + if (ref.token == null || ref.token.length != 3 || ref.token[0] != 'v' || ref.token[1] != 'a' || ref.token[2] != 'l') return; + } else return; + + try { + if (initCopyField != null) initCopyField.set(variableDecl, init); + } catch (Exception e) { + // In ecj mode this field isn't there and we don't need the copy anyway, so, we ignore the exception. + } + } + + private static Field initCopyField, iterableCopyField; + + static { + try { + initCopyField = LocalDeclaration.class.getDeclaredField("$initCopy"); + iterableCopyField = LocalDeclaration.class.getDeclaredField("$iterableCopy"); + } catch (Throwable t) { + //ignore - no $initCopy exists when running in ecj. + } + } + + public static boolean handleValForForEach(ForeachStatement forEach, BlockScope scope) { + if (forEach.elementVariable != null && forEach.elementVariable.type instanceof SingleTypeReference) { + char[] token = ((SingleTypeReference)forEach.elementVariable.type).token; + if (token == null || token.length != 3) return false; + else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return false; + } else return false; + + TypeBinding component = getForEachComponentType(forEach.collection, scope); + TypeReference replacement = Eclipse.makeType(component, forEach.elementVariable.type, false); + + forEach.elementVariable.modifiers |= ClassFileConstants.AccFinal; + forEach.elementVariable.type = replacement != null ? replacement : + new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(forEach.elementVariable.type, 3)); + + return false; + } + + private static TypeBinding getForEachComponentType(Expression collection, BlockScope scope) { + if (collection != null) { + TypeBinding resolved = collection.resolveType(scope); + if (resolved.isArrayType()) { + resolved = ((ArrayBinding) resolved).elementsType(); + return resolved; + } else if (resolved instanceof ReferenceBinding) { + ReferenceBinding iterableType = ((ReferenceBinding)resolved).findSuperTypeOriginatingFrom(TypeIds.T_JavaLangIterable, false); + + TypeBinding[] arguments = null; + if (iterableType != null) switch (iterableType.kind()) { + case Binding.GENERIC_TYPE : // for (T t : Iterable<T>) - in case used inside Iterable itself + arguments = iterableType.typeVariables(); + break; + case Binding.PARAMETERIZED_TYPE : // for(E e : Iterable<E>) + arguments = ((ParameterizedTypeBinding)iterableType).arguments; + break; + } + + if (arguments != null && arguments.length == 1) { + return arguments[0]; + } + } + } + + return null; + } + + public static boolean handleValForLocalDeclaration(LocalDeclaration local, BlockScope scope) { + if (local == null || !LocalDeclaration.class.equals(local.getClass())) return false; + boolean decomponent = false; + + if (local.type instanceof SingleTypeReference) { + char[] token = ((SingleTypeReference)local.type).token; + if (token == null || token.length != 3) return false; + else if (token[0] != 'v' || token[1] != 'a' || token[2] != 'l') return false; + } else return false; + + Expression init = local.initialization; + if (init == null && initCopyField != null) { + try { + init = (Expression) initCopyField.get(local); + } catch (Exception e) { + } + } + + if (init == null && iterableCopyField != null) { + try { + init = (Expression) iterableCopyField.get(local); + decomponent = true; + } catch (Exception e) { + } + } + + TypeReference replacement = null; + if (init != null && decomponent) { + } + + if (init != null) { + TypeBinding resolved = decomponent ? getForEachComponentType(init, scope) : init.resolveType(scope); + if (resolved != null) { + replacement = Eclipse.makeType(resolved, local.type, false); + } + } + + local.modifiers |= ClassFileConstants.AccFinal; + local.type = replacement != null ? replacement : new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, Eclipse.poss(local.type, 3)); + + return false; + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled(Expression expr, BlockScope scope) { + if (expr.resolvedType != null) return expr.resolvedType; + return expr.resolveType(scope); + } + + public static TypeBinding skipResolveInitializerIfAlreadyCalled2(Expression expr, BlockScope scope, LocalDeclaration decl) { + if (decl != null && LocalDeclaration.class.equals(decl.getClass()) && expr.resolvedType != null) return expr.resolvedType; + return expr.resolveType(scope); + } } |