diff options
author | Reinier Zwitserloot <reinier@tipit.to> | 2009-11-25 07:32:49 +0100 |
---|---|---|
committer | Reinier Zwitserloot <reinier@tipit.to> | 2009-11-25 07:32:49 +0100 |
commit | 1a0e611a9c5e1ee518670647ce1a44beae559b44 (patch) | |
tree | e5ef8f671bc6688f486e874d4e2e1a7813e4f0b2 /src/core/lombok/eclipse | |
parent | 7fd947ea40c25dad9ee543ebc4b92de9a2e05efc (diff) | |
download | lombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.tar.gz lombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.tar.bz2 lombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.zip |
Refactored the source folders.
Diffstat (limited to 'src/core/lombok/eclipse')
20 files changed, 4507 insertions, 0 deletions
diff --git a/src/core/lombok/eclipse/Eclipse.java b/src/core/lombok/eclipse/Eclipse.java new file mode 100644 index 00000000..41d9300f --- /dev/null +++ b/src/core/lombok/eclipse/Eclipse.java @@ -0,0 +1,479 @@ +/* + * Copyright © 2009 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.eclipse; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.Lombok; +import lombok.core.AnnotationValues; +import lombok.core.TypeLibrary; +import lombok.core.TypeResolver; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues.AnnotationValue; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; +import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.Literal; +import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation; +import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; +import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +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.TypeIds; +import org.osgi.framework.Bundle; + +public class Eclipse { + /** + * Eclipse's Parser class is instrumented to not attempt to fill in the body of any method or initializer + * or field initialization if this flag is set. Set it on the flag field of + * any method, field, or initializer you create! + */ + public static final int ECLIPSE_DO_NOT_TOUCH_FLAG = ASTNode.Bit24; + + private Eclipse() { + //Prevent instantiation + } + + private static final String DEFAULT_BUNDLE = "org.eclipse.jdt.core"; + + /** + * Generates an error in the Eclipse error log. Note that most people never look at it! + */ + public static void error(CompilationUnitDeclaration cud, String message) { + error(cud, message, DEFAULT_BUNDLE, null); + } + + /** + * Generates an error in the Eclipse error log. Note that most people never look at it! + */ + public static void error(CompilationUnitDeclaration cud, String message, Throwable error) { + error(cud, message, DEFAULT_BUNDLE, error); + } + + /** + * Generates an error in the Eclipse error log. Note that most people never look at it! + */ + public static void error(CompilationUnitDeclaration cud, String message, String bundleName) { + error(cud, message, bundleName, null); + } + + /** + * Generates an error in the Eclipse error log. Note that most people never look at it! + */ + public static void error(CompilationUnitDeclaration cud, String message, String bundleName, Throwable error) { + Bundle bundle = Platform.getBundle(bundleName); + if (bundle == null) { + System.err.printf("Can't find bundle %s while trying to report error:\n%s\n", bundleName, message); + return; + } + + ILog log = Platform.getLog(bundle); + + log.log(new Status(IStatus.ERROR, bundleName, message, error)); + if (cud != null) EclipseAST.addProblemToCompilationResult(cud, false, message + " - See error log.", 0, 0); + } + + /** + * For 'speed' reasons, Eclipse works a lot with char arrays. I have my doubts this was a fruitful exercise, + * but we need to deal with it. This turns [[java][lang][String]] into "java.lang.String". + */ + public static String toQualifiedName(char[][] typeName) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (char[] c : typeName) { + sb.append(first ? "" : ".").append(c); + first = false; + } + return sb.toString(); + } + + public static char[][] fromQualifiedName(String typeName) { + String[] split = typeName.split("\\."); + char[][] result = new char[split.length][]; + for (int i = 0; i < split.length; i++) { + result[i] = split[i].toCharArray(); + } + 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 + * is complicated and there's no clone method on TypeParameter itself. This method can clone them. + */ + public static TypeParameter[] copyTypeParams(TypeParameter[] params, ASTNode source) { + if (params == null) return null; + TypeParameter[] out = new TypeParameter[params.length]; + int idx = 0; + for (TypeParameter param : params) { + TypeParameter o = new TypeParameter(); + setGeneratedBy(o, source); + o.annotations = param.annotations; + o.bits = param.bits; + o.modifiers = param.modifiers; + o.name = param.name; + o.type = copyType(param.type, source); + o.sourceStart = param.sourceStart; + o.sourceEnd = param.sourceEnd; + o.declarationEnd = param.declarationEnd; + o.declarationSourceStart = param.declarationSourceStart; + o.declarationSourceEnd = param.declarationSourceEnd; + if (param.bounds != null) { + TypeReference[] b = new TypeReference[param.bounds.length]; + int idx2 = 0; + for (TypeReference ref : param.bounds) b[idx2++] = copyType(ref, source); + o.bounds = b; + } + out[idx++] = o; + } + return out; + } + + /** + * Convenience method that creates a new array and copies each TypeReference in the source array via + * {@link #copyType(TypeReference, ASTNode)}. + */ + public static TypeReference[] copyTypes(TypeReference[] refs, ASTNode source) { + if (refs == null) return null; + TypeReference[] outs = new TypeReference[refs.length]; + int idx = 0; + for (TypeReference ref : refs) { + outs[idx++] = copyType(ref, source); + } + return outs; + } + + /** + * You can't share TypeReference objects or subtle errors start happening. + * Unfortunately the TypeReference type hierarchy is complicated and there's no clone + * method on TypeReference itself. This method can clone them. + */ + public static TypeReference copyType(TypeReference ref, ASTNode source) { + if (ref instanceof ParameterizedQualifiedTypeReference) { + ParameterizedQualifiedTypeReference iRef = (ParameterizedQualifiedTypeReference) ref; + TypeReference[][] args = null; + if (iRef.typeArguments != null) { + args = new TypeReference[iRef.typeArguments.length][]; + int idx = 0; + for (TypeReference[] inRefArray : iRef.typeArguments) { + if (inRefArray == null) args[idx++] = null; + else { + TypeReference[] outRefArray = new TypeReference[inRefArray.length]; + int idx2 = 0; + for (TypeReference inRef : inRefArray) { + outRefArray[idx2++] = copyType(inRef, source); + } + args[idx++] = outRefArray; + } + } + } + TypeReference typeRef = new ParameterizedQualifiedTypeReference(iRef.tokens, args, iRef.dimensions(), iRef.sourcePositions); + setGeneratedBy(typeRef, source); + return typeRef; + } + + if (ref instanceof ArrayQualifiedTypeReference) { + ArrayQualifiedTypeReference iRef = (ArrayQualifiedTypeReference) ref; + TypeReference typeRef = new ArrayQualifiedTypeReference(iRef.tokens, iRef.dimensions(), iRef.sourcePositions); + setGeneratedBy(typeRef, source); + return typeRef; + } + + if (ref instanceof QualifiedTypeReference) { + QualifiedTypeReference iRef = (QualifiedTypeReference) ref; + TypeReference typeRef = new QualifiedTypeReference(iRef.tokens, iRef.sourcePositions); + setGeneratedBy(typeRef, source); + return typeRef; + } + + if (ref instanceof ParameterizedSingleTypeReference) { + ParameterizedSingleTypeReference iRef = (ParameterizedSingleTypeReference) ref; + TypeReference[] args = null; + if (iRef.typeArguments != null) { + args = new TypeReference[iRef.typeArguments.length]; + int idx = 0; + for (TypeReference inRef : iRef.typeArguments) { + if (inRef == null) args[idx++] = null; + else args[idx++] = copyType(inRef, source); + } + } + + TypeReference typeRef = new ParameterizedSingleTypeReference(iRef.token, args, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); + setGeneratedBy(typeRef, source); + return typeRef; + } + + if (ref instanceof ArrayTypeReference) { + ArrayTypeReference iRef = (ArrayTypeReference) ref; + TypeReference typeRef = new ArrayTypeReference(iRef.token, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); + setGeneratedBy(typeRef, source); + return typeRef; + } + + if (ref instanceof Wildcard) { + Wildcard wildcard = new Wildcard(((Wildcard)ref).kind); + wildcard.sourceStart = ref.sourceStart; + wildcard.sourceEnd = ref.sourceEnd; + setGeneratedBy(wildcard, source); + return wildcard; + } + + if (ref instanceof SingleTypeReference) { + SingleTypeReference iRef = (SingleTypeReference) ref; + TypeReference typeRef = new SingleTypeReference(iRef.token, (long)iRef.sourceStart << 32 | iRef.sourceEnd); + setGeneratedBy(typeRef, source); + return typeRef; + } + + return ref; + } + + public static Annotation[] copyAnnotations(Annotation[] annotations, ASTNode source) { + return copyAnnotations(annotations, null, source); + } + + public static Annotation[] copyAnnotations(Annotation[] annotations1, Annotation[] annotations2, ASTNode source) { + if (annotations1 == null && annotations2 == null) return null; + if (annotations1 == null) annotations1 = new Annotation[0]; + if (annotations2 == null) annotations2 = new Annotation[0]; + Annotation[] outs = new Annotation[annotations1.length + annotations2.length]; + int idx = 0; + for (Annotation annotation : annotations1) { + outs[idx++] = copyAnnotation(annotation, source); + } + for (Annotation annotation : annotations2) { + outs[idx++] = copyAnnotation(annotation, source); + } + return outs; + } + + public static Annotation copyAnnotation(Annotation annotation, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + + if (annotation instanceof MarkerAnnotation) { + MarkerAnnotation ann = new MarkerAnnotation(copyType(annotation.type, source), pS); + setGeneratedBy(ann, source); + ann.declarationSourceEnd = ann.sourceEnd = ann.statementEnd = pE; + return ann; + } + + if (annotation instanceof SingleMemberAnnotation) { + SingleMemberAnnotation ann = new SingleMemberAnnotation(copyType(annotation.type, source), pS); + setGeneratedBy(ann, source); + ann.declarationSourceEnd = ann.sourceEnd = ann.statementEnd = pE; + //TODO memberValue(s) need to be copied as well (same for copying a NormalAnnotation as below). + ann.memberValue = ((SingleMemberAnnotation)annotation).memberValue; + return ann; + } + + if (annotation instanceof NormalAnnotation) { + NormalAnnotation ann = new NormalAnnotation(copyType(annotation.type, source), pS); + setGeneratedBy(ann, source); + ann.declarationSourceEnd = ann.statementEnd = ann.sourceEnd = pE; + ann.memberValuePairs = ((NormalAnnotation)annotation).memberValuePairs; + return ann; + } + + return annotation; + } + + /** + * Checks if the provided annotation type is likely to be the intended type for the given annotation node. + * + * This is a guess, but a decent one. + */ + public static boolean annotationTypeMatches(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { + if (node.getKind() != Kind.ANNOTATION) return false; + TypeReference typeRef = ((Annotation)node.get()).type; + if (typeRef == null || typeRef.getTypeName() == null) return false; + String typeName = toQualifiedName(typeRef.getTypeName()); + + TypeLibrary library = new TypeLibrary(); + library.addType(type.getName()); + TypeResolver resolver = new TypeResolver(library, node.getPackageDeclaration(), node.getImportStatements()); + Collection<String> typeMatches = resolver.findTypeMatches(node, typeName); + + for (String match : typeMatches) { + if (match.equals(type.getName())) return true; + } + + return false; + } + + /** + * Provides AnnotationValues with the data it needs to do its thing. + */ + public static <A extends java.lang.annotation.Annotation> AnnotationValues<A> + createAnnotation(Class<A> type, final EclipseNode annotationNode) { + final Annotation annotation = (Annotation) annotationNode.get(); + Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); + + final MemberValuePair[] pairs = annotation.memberValuePairs(); + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + List<String> raws = new ArrayList<String>(); + List<Object> guesses = new ArrayList<Object>(); + Expression fullExpression = null; + Expression[] expressions = null; + + if (pairs != null) for (MemberValuePair pair : pairs) { + char[] n = pair.name; + String mName = n == null ? "value" : new String(pair.name); + if (mName.equals(name)) fullExpression = pair.value; + } + + boolean isExplicit = fullExpression != null; + + if (isExplicit) { + if (fullExpression instanceof ArrayInitializer) { + expressions = ((ArrayInitializer)fullExpression).expressions; + } else expressions = new Expression[] { fullExpression }; + if (expressions != null) for (Expression ex : expressions) { + StringBuffer sb = new StringBuffer(); + ex.print(0, sb); + raws.add(sb.toString()); + guesses.add(calculateValue(ex)); + } + } + + final Expression fullExpr = fullExpression; + final Expression[] exprs = expressions; + + values.put(name, new AnnotationValue(annotationNode, raws, guesses, isExplicit) { + @Override public void setError(String message, int valueIdx) { + Expression ex; + if (valueIdx == -1) ex = fullExpr; + else ex = exprs != null ? exprs[valueIdx] : null; + + if (ex == null) ex = annotation; + + int sourceStart = ex.sourceStart; + int sourceEnd = ex.sourceEnd; + + annotationNode.addError(message, sourceStart, sourceEnd); + } + + @Override public void setWarning(String message, int valueIdx) { + Expression ex; + if (valueIdx == -1) ex = fullExpr; + else ex = exprs != null ? exprs[valueIdx] : null; + + if (ex == null) ex = annotation; + + int sourceStart = ex.sourceStart; + int sourceEnd = ex.sourceEnd; + + annotationNode.addWarning(message, sourceStart, sourceEnd); + } + }); + } + + return new AnnotationValues<A>(type, values, annotationNode); + } + + private static Object calculateValue(Expression e) { + if (e instanceof Literal) { + ((Literal)e).computeConstant(); + switch (e.constant.typeID()) { + case TypeIds.T_int: return e.constant.intValue(); + case TypeIds.T_byte: return e.constant.byteValue(); + case TypeIds.T_short: return e.constant.shortValue(); + case TypeIds.T_char: return e.constant.charValue(); + case TypeIds.T_float: return e.constant.floatValue(); + case TypeIds.T_double: return e.constant.doubleValue(); + case TypeIds.T_boolean: return e.constant.booleanValue(); + case TypeIds.T_long: return e.constant.longValue(); + case TypeIds.T_JavaLangString: return e.constant.stringValue(); + default: return null; + } + } else if (e instanceof ClassLiteralAccess) { + return Eclipse.toQualifiedName(((ClassLiteralAccess)e).type.getTypeName()); + } else if (e instanceof SingleNameReference) { + return new String(((SingleNameReference)e).token); + } else if (e instanceof QualifiedNameReference) { + String qName = Eclipse.toQualifiedName(((QualifiedNameReference)e).tokens); + int idx = qName.lastIndexOf('.'); + return idx == -1 ? qName : qName.substring(idx+1); + } + + return null; + } + + private static Field generatedByField; + + static { + try { + generatedByField = ASTNode.class.getDeclaredField("$generatedBy"); + } catch (Throwable t) { + throw Lombok.sneakyThrow(t); + } + } + + public static ASTNode getGeneratedBy(ASTNode node) { + try { + return (ASTNode) generatedByField.get(node); + } catch (Exception t) { + throw Lombok.sneakyThrow(t); + } + } + + public static boolean isGenerated(ASTNode node) { + return getGeneratedBy(node) != null; + } + + public static ASTNode setGeneratedBy(ASTNode node, ASTNode source) { + try { + generatedByField.set(node, source); + } catch (Exception t) { + throw Lombok.sneakyThrow(t); + } + + return node; + } +} diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java new file mode 100644 index 00000000..e42e5de2 --- /dev/null +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -0,0 +1,366 @@ +/* + * Copyright © 2009 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.eclipse; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import lombok.core.AST; + +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ImportReference; +import org.eclipse.jdt.internal.compiler.ast.Initializer; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; +import org.eclipse.jdt.internal.compiler.util.Util; + +/** + * Wraps around Eclipse's internal AST view to add useful features as well as the ability to visit parents from children, + * something Eclipse own AST system does not offer. + */ +public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { + /** + * Creates a new EclipseAST of the provided Compilation Unit. + * + * @param ast The compilation unit, which serves as the top level node in the tree to be built. + */ + public EclipseAST(CompilationUnitDeclaration ast) { + super(toFileName(ast)); + this.compilationUnitDeclaration = ast; + setTop(buildCompilationUnit(ast)); + this.completeParse = isComplete(ast); + } + + /** {@inheritDoc} */ + @Override public String getPackageDeclaration() { + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + ImportReference pkg = cud.currentPackage; + return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName()); + } + + /** {@inheritDoc} */ + @Override public Collection<String> getImportStatements() { + List<String> imports = new ArrayList<String>(); + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + if (cud.imports == null) return imports; + for (ImportReference imp : cud.imports) { + if (imp == null) continue; + imports.add(Eclipse.toQualifiedName(imp.getImportName())); + } + + return imports; + } + + /** + * Runs through the entire AST, starting at the compilation unit, calling the provided visitor's visit methods + * for each node, depth first. + */ + public void traverse(EclipseASTVisitor visitor) { + top().traverse(visitor); + } + + void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) { + for (EclipseNode child : node.down()) { + child.traverse(visitor); + } + } + + /** + * Eclipse starts off with a 'diet' parse which leaves method bodies blank, amongst other shortcuts. + * + * For such diet parses, this method returns false, otherwise it returns true. Any lombok processor + * that needs the contents of methods should just do nothing (and return false so it gets another shot later!) + * when this is false. + */ + public boolean isCompleteParse() { + return completeParse; + } + + class ParseProblem { + final boolean isWarning; + final String message; + final int sourceStart; + final int sourceEnd; + + ParseProblem(boolean isWarning, String message, int sourceStart, int sourceEnd) { + this.isWarning = isWarning; + this.message = message; + this.sourceStart = sourceStart; + this.sourceEnd = sourceEnd; + } + + void addToCompilationResult() { + addProblemToCompilationResult((CompilationUnitDeclaration) top().get(), + isWarning, message, sourceStart, sourceEnd); + } + } + + private void propagateProblems() { + if (queuedProblems.isEmpty()) return; + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + if (cud.compilationResult == null) return; + for (ParseProblem problem : queuedProblems) problem.addToCompilationResult(); + queuedProblems.clear(); + } + + private final List<ParseProblem> queuedProblems = new ArrayList<ParseProblem>(); + + void addProblem(ParseProblem problem) { + queuedProblems.add(problem); + propagateProblems(); + } + + /** + * Adds a problem to the provided CompilationResult object so that it will show up + * in the Problems/Warnings view. + */ + static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + boolean isWarning, String message, int sourceStart, int sourceEnd) { + if (ast.compilationResult == null) return; + char[] fileNameArray = ast.getFileName(); + if (fileNameArray == null) fileNameArray = "(unknown).java".toCharArray(); + int lineNumber = 0; + int columnNumber = 1; + CompilationResult result = ast.compilationResult; + int[] lineEnds = null; + lineNumber = sourceStart >= 0 + ? Util.getLineNumber(sourceStart, lineEnds = result.getLineSeparatorPositions(), 0, lineEnds.length-1) + : 0; + columnNumber = sourceStart >= 0 + ? Util.searchColumnNumber(result.getLineSeparatorPositions(), lineNumber,sourceStart) + : 0; + + CategorizedProblem ecProblem = new LombokProblem( + fileNameArray, message, 0, new String[0], + isWarning ? ProblemSeverities.Warning : ProblemSeverities.Error, + sourceStart, sourceEnd, lineNumber, columnNumber); + ast.compilationResult.record(ecProblem, null); + } + + private static class LombokProblem extends DefaultProblem { + private static final String MARKER_ID = "org.eclipse.jdt.apt.pluggable.core.compileProblem"; //$NON-NLS-1$ + + public LombokProblem(char[] originatingFileName, String message, int id, + String[] stringArguments, int severity, + int startPosition, int endPosition, int line, int column) { + super(originatingFileName, message, id, stringArguments, severity, startPosition, endPosition, line, column); + } + + @Override public int getCategoryID() { + return CAT_UNSPECIFIED; + } + + @Override public String getMarkerType() { + return MARKER_ID; + } + } + + private final CompilationUnitDeclaration compilationUnitDeclaration; + private boolean completeParse; + + private static String toFileName(CompilationUnitDeclaration ast) { + return ast.compilationResult.fileName == null ? null : new String(ast.compilationResult.fileName); + } + + /** + * Call this method to move an EclipseAST generated for a diet parse to rebuild itself for the full parse - + * with filled in method bodies and such. Also propagates problems and errors, which in diet parse + * mode can't be reliably added to the problems/warnings view. + */ + public void reparse() { + propagateProblems(); + if (completeParse) return; + boolean newCompleteParse = isComplete(compilationUnitDeclaration); + if (!newCompleteParse) return; + + top().rebuild(); + + this.completeParse = true; + } + + private static boolean isComplete(CompilationUnitDeclaration unit) { + return (unit.bits & ASTNode.HasAllMethodBodies) != 0; + } + + /** {@inheritDoc} */ + @Override protected EclipseNode buildTree(ASTNode node, Kind kind) { + switch (kind) { + case COMPILATION_UNIT: + return buildCompilationUnit((CompilationUnitDeclaration) node); + case TYPE: + return buildType((TypeDeclaration) node); + case FIELD: + return buildField((FieldDeclaration) node); + case INITIALIZER: + return buildInitializer((Initializer) node); + case METHOD: + return buildMethod((AbstractMethodDeclaration) node); + case ARGUMENT: + return buildLocal((Argument) node, kind); + case LOCAL: + return buildLocal((LocalDeclaration) node, kind); + case STATEMENT: + return buildStatement((Statement) node); + case ANNOTATION: + return buildAnnotation((Annotation) node); + default: + throw new AssertionError("Did not expect to arrive here: " + kind); + } + } + + private EclipseNode buildCompilationUnit(CompilationUnitDeclaration top) { + if (setAndGetAsHandled(top)) return null; + List<EclipseNode> children = buildTypes(top.types); + return putInMap(new EclipseNode(this, top, children, Kind.COMPILATION_UNIT)); + } + + private void addIfNotNull(Collection<EclipseNode> collection, EclipseNode n) { + if (n != null) collection.add(n); + } + + private List<EclipseNode> buildTypes(TypeDeclaration[] children) { + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + if (children != null) for (TypeDeclaration type : children) addIfNotNull(childNodes, buildType(type)); + return childNodes; + } + + private EclipseNode buildType(TypeDeclaration type) { + if (setAndGetAsHandled(type)) return null; + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + childNodes.addAll(buildFields(type.fields)); + childNodes.addAll(buildTypes(type.memberTypes)); + childNodes.addAll(buildMethods(type.methods)); + childNodes.addAll(buildAnnotations(type.annotations)); + return putInMap(new EclipseNode(this, type, childNodes, Kind.TYPE)); + } + + private Collection<EclipseNode> buildFields(FieldDeclaration[] children) { + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + if (children != null) for (FieldDeclaration child : children) addIfNotNull(childNodes, buildField(child)); + return childNodes; + } + + private static <T> List<T> singleton(T item) { + List<T> list = new ArrayList<T>(); + if (item != null) list.add(item); + return list; + } + + private EclipseNode buildField(FieldDeclaration field) { + if (field instanceof Initializer) return buildInitializer((Initializer)field); + if (setAndGetAsHandled(field)) return null; + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + addIfNotNull(childNodes, buildStatement(field.initialization)); + childNodes.addAll(buildAnnotations(field.annotations)); + return putInMap(new EclipseNode(this, field, childNodes, Kind.FIELD)); + } + + private EclipseNode buildInitializer(Initializer initializer) { + if (setAndGetAsHandled(initializer)) return null; + return putInMap(new EclipseNode(this, initializer, singleton(buildStatement(initializer.block)), Kind.INITIALIZER)); + } + + private Collection<EclipseNode> buildMethods(AbstractMethodDeclaration[] children) { + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + if (children != null) for (AbstractMethodDeclaration method : children) addIfNotNull(childNodes, buildMethod(method)); + return childNodes; + } + + private EclipseNode buildMethod(AbstractMethodDeclaration method) { + if (setAndGetAsHandled(method)) return null; + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + childNodes.addAll(buildArguments(method.arguments)); + childNodes.addAll(buildStatements(method.statements)); + childNodes.addAll(buildAnnotations(method.annotations)); + return putInMap(new EclipseNode(this, method, childNodes, Kind.METHOD)); + } + + //Arguments are a kind of LocalDeclaration. They can definitely contain lombok annotations, so we care about them. + private Collection<EclipseNode> buildArguments(Argument[] children) { + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + if (children != null) for (LocalDeclaration local : children) { + addIfNotNull(childNodes, buildLocal(local, Kind.ARGUMENT)); + } + return childNodes; + } + + private EclipseNode buildLocal(LocalDeclaration local, Kind kind) { + if (setAndGetAsHandled(local)) return null; + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + addIfNotNull(childNodes, buildStatement(local.initialization)); + childNodes.addAll(buildAnnotations(local.annotations)); + return putInMap(new EclipseNode(this, local, childNodes, kind)); + } + + private Collection<EclipseNode> buildAnnotations(Annotation[] annotations) { + List<EclipseNode> elements = new ArrayList<EclipseNode>(); + if (annotations != null) for (Annotation an : annotations) addIfNotNull(elements, buildAnnotation(an)); + return elements; + } + + private EclipseNode buildAnnotation(Annotation annotation) { + if (annotation == null) return null; + if (setAndGetAsHandled(annotation)) return null; + return putInMap(new EclipseNode(this, annotation, null, Kind.ANNOTATION)); + } + + private Collection<EclipseNode> buildStatements(Statement[] children) { + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + if (children != null) for (Statement child : children) addIfNotNull(childNodes, buildStatement(child)); + return childNodes; + } + + private EclipseNode buildStatement(Statement child) { + if (child == null) return null; + if (child instanceof TypeDeclaration) return buildType((TypeDeclaration)child); + + if (child instanceof LocalDeclaration) return buildLocal((LocalDeclaration)child, Kind.LOCAL); + + if (setAndGetAsHandled(child)) return null; + + return drill(child); + } + + private EclipseNode drill(Statement statement) { + List<EclipseNode> childNodes = new ArrayList<EclipseNode>(); + for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(EclipseNode.class, statement, fa)); + return putInMap(new EclipseNode(this, statement, childNodes, Kind.STATEMENT)); + } + + /** For Eclipse, only Statement counts, as Expression is a subclass of it, even though this isn't + * entirely correct according to the JLS spec (only some expressions can be used as statements, not all of them). */ + @Override protected Collection<Class<? extends ASTNode>> getStatementTypes() { + return Collections.<Class<? extends ASTNode>>singleton(Statement.class); + } +} diff --git a/src/core/lombok/eclipse/EclipseASTAdapter.java b/src/core/lombok/eclipse/EclipseASTAdapter.java new file mode 100644 index 00000000..2062619c --- /dev/null +++ b/src/core/lombok/eclipse/EclipseASTAdapter.java @@ -0,0 +1,101 @@ +/* + * Copyright © 2009 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.eclipse; + +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Initializer; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; + +/** + * Standard adapter for the {@link EclipseASTVisitor} interface. Every method on that interface + * has been implemented with an empty body. Override whichever methods you need. + */ +public abstract class EclipseASTAdapter implements EclipseASTVisitor { + /** {@inheritDoc} */ + public void visitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit) {} + + /** {@inheritDoc} */ + public void endVisitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit) {} + + /** {@inheritDoc} */ + public void visitType(EclipseNode typeNode, TypeDeclaration type) {} + + /** {@inheritDoc} */ + public void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation) {} + + /** {@inheritDoc} */ + public void endVisitType(EclipseNode typeNode, TypeDeclaration type) {} + + /** {@inheritDoc} */ + public void visitInitializer(EclipseNode initializerNode, Initializer initializer) {} + + /** {@inheritDoc} */ + public void endVisitInitializer(EclipseNode initializerNode, Initializer initializer) {} + + /** {@inheritDoc} */ + public void visitField(EclipseNode fieldNode, FieldDeclaration field) {} + + /** {@inheritDoc} */ + public void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation) {} + + /** {@inheritDoc} */ + public void endVisitField(EclipseNode fieldNode, FieldDeclaration field) {} + + /** {@inheritDoc} */ + public void visitMethod(EclipseNode methodNode, AbstractMethodDeclaration method) {} + + /** {@inheritDoc} */ + public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {} + + /** {@inheritDoc} */ + public void endVisitMethod(EclipseNode methodNode, AbstractMethodDeclaration method) {} + + /** {@inheritDoc} */ + public void visitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method) {} + + /** {@inheritDoc} */ + public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {} + + /** {@inheritDoc} */ + public void endVisitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method) {} + + /** {@inheritDoc} */ + public void visitLocal(EclipseNode localNode, LocalDeclaration local) {} + + /** {@inheritDoc} */ + public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation) {} + + /** {@inheritDoc} */ + public void endVisitLocal(EclipseNode localNode, LocalDeclaration local) {} + + /** {@inheritDoc} */ + public void visitStatement(EclipseNode statementNode, Statement statement) {} + + /** {@inheritDoc} */ + public void endVisitStatement(EclipseNode statementNode, Statement statement) {} +} diff --git a/src/core/lombok/eclipse/EclipseASTVisitor.java b/src/core/lombok/eclipse/EclipseASTVisitor.java new file mode 100644 index 00000000..793166d7 --- /dev/null +++ b/src/core/lombok/eclipse/EclipseASTVisitor.java @@ -0,0 +1,295 @@ +/* + * Copyright © 2009 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.eclipse; + +import java.io.PrintStream; +import java.lang.reflect.Modifier; + +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Initializer; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; + +/** + * Implement so you can ask any JavacAST.Node to traverse depth-first through all children, + * calling the appropriate visit and endVisit methods. + */ +public interface EclipseASTVisitor { + /** + * Called at the very beginning and end. + */ + void visitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit); + void endVisitCompilationUnit(EclipseNode top, CompilationUnitDeclaration unit); + + /** + * Called when visiting a type (a class, interface, annotation, enum, etcetera). + */ + void visitType(EclipseNode typeNode, TypeDeclaration type); + void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation); + void endVisitType(EclipseNode typeNode, TypeDeclaration type); + + /** + * Called when visiting a field of a class. + * Even though in Eclipse initializers (both instance and static) are represented as Initializer objects, + * which are a subclass of FieldDeclaration, those do NOT result in a call to this method. They result + * in a call to the visitInitializer method. + */ + void visitField(EclipseNode fieldNode, FieldDeclaration field); + void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation); + void endVisitField(EclipseNode fieldNode, FieldDeclaration field); + + /** + * Called for static and instance initializers. You can tell the difference via the modifier flag on the + * ASTNode (8 for static, 0 for not static). The content is in the 'block', not in the 'initialization', + * which would always be null for an initializer instance. + */ + void visitInitializer(EclipseNode initializerNode, Initializer initializer); + void endVisitInitializer(EclipseNode initializerNode, Initializer initializer); + + /** + * Called for both methods (MethodDeclaration) and constructors (ConstructorDeclaration), but not for + * Clinit objects, which are a vestigial Eclipse thing that never contain anything. Static initializers + * show up as 'Initializer', in the visitInitializer method, with modifier bit STATIC set. + */ + void visitMethod(EclipseNode methodNode, AbstractMethodDeclaration method); + void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation); + void endVisitMethod(EclipseNode methodNode, AbstractMethodDeclaration method); + + /** + * Visits a method argument + */ + void visitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method); + void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation); + void endVisitMethodArgument(EclipseNode argNode, Argument arg, AbstractMethodDeclaration method); + + /** + * Visits a local declaration - that is, something like 'int x = 10;' on the method level. + */ + void visitLocal(EclipseNode localNode, LocalDeclaration local); + void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation); + void endVisitLocal(EclipseNode localNode, LocalDeclaration local); + + /** + * Visits a statement that isn't any of the other visit methods (e.g. TypeDeclaration). + */ + void visitStatement(EclipseNode statementNode, Statement statement); + void endVisitStatement(EclipseNode statementNode, Statement statement); + + /** + * Prints the structure of an AST. + */ + public static class Printer implements EclipseASTVisitor { + private final PrintStream out; + private final boolean printContent; + private int disablePrinting = 0; + private int indent = 0; + + /** + * @param printContent if true, bodies are printed directly, as java code, + * instead of a tree listing of every AST node inside it. + */ + public Printer(boolean printContent) { + this(printContent, System.out); + } + + /** + * @param printContent if true, bodies are printed directly, as java code, + * instead of a tree listing of every AST node inside it. + * @param out write output to this stream. You must close it yourself. flush() is called after every line. + * + * @see java.io.PrintStream#flush() + */ + public Printer(boolean printContent, PrintStream out) { + this.printContent = printContent; + this.out = out; + } + + private void forcePrint(String text, Object... params) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < indent; i++) sb.append(" "); + out.printf(sb.append(text).append('\n').toString(), params); + out.flush(); + } + + private void print(String text, Object... params) { + if (disablePrinting == 0) forcePrint(text, params); + } + + private String str(char[] c) { + if (c == null) return "(NULL)"; + return new String(c); + } + + private String str(TypeReference type) { + if (type == null) return "(NULL)"; + char[][] c = type.getTypeName(); + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (char[] d : c) { + sb.append(first ? "" : ".").append(new String(d)); + first = false; + } + return sb.toString(); + } + + public void visitCompilationUnit(EclipseNode node, CompilationUnitDeclaration unit) { + out.println("---------------------------------------------------------"); + out.println(node.isCompleteParse() ? "COMPLETE" : "incomplete"); + + print("<CUD %s%s>", node.getFileName(), Eclipse.isGenerated(unit) ? " (GENERATED)" : ""); + indent++; + } + + public void endVisitCompilationUnit(EclipseNode node, CompilationUnitDeclaration unit) { + indent--; + print("</CUD>"); + } + + public void visitType(EclipseNode node, TypeDeclaration type) { + print("<TYPE %s%s>", str(type.name), Eclipse.isGenerated(type) ? " (GENERATED)" : ""); + indent++; + if (printContent) { + print("%s", type); + disablePrinting++; + } + } + + public void visitAnnotationOnType(TypeDeclaration type, EclipseNode node, Annotation annotation) { + forcePrint("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitType(EclipseNode node, TypeDeclaration type) { + if (printContent) disablePrinting--; + indent--; + print("</TYPE %s>", str(type.name)); + } + + public void visitInitializer(EclipseNode node, Initializer initializer) { + Block block = initializer.block; + boolean s = (block != null && block.statements != null); + print("<%s INITIALIZER: %s%s>", + (initializer.modifiers & Modifier.STATIC) != 0 ? "static" : "instance", + s ? "filled" : "blank", + Eclipse.isGenerated(initializer) ? " (GENERATED)" : ""); + indent++; + if (printContent) { + if (initializer.block != null) print("%s", initializer.block); + disablePrinting++; + } + } + + public void endVisitInitializer(EclipseNode node, Initializer initializer) { + if (printContent) disablePrinting--; + indent--; + print("</%s INITIALIZER>", (initializer.modifiers & Modifier.STATIC) != 0 ? "static" : "instance"); + } + + public void visitField(EclipseNode node, FieldDeclaration field) { + print("<FIELD%s %s %s = %s>", Eclipse.isGenerated(field) ? " (GENERATED)" : "", + str(field.type), str(field.name), field.initialization); + indent++; + if (printContent) { + if (field.initialization != null) print("%s", field.initialization); + disablePrinting++; + } + } + + public void visitAnnotationOnField(FieldDeclaration field, EclipseNode node, Annotation annotation) { + forcePrint("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitField(EclipseNode node, FieldDeclaration field) { + if (printContent) disablePrinting--; + indent--; + print("</FIELD %s %s>", str(field.type), str(field.name)); + } + + public void visitMethod(EclipseNode node, AbstractMethodDeclaration method) { + String type = method instanceof ConstructorDeclaration ? "CONSTRUCTOR" : "METHOD"; + print("<%s %s: %s%s>", type, str(method.selector), method.statements != null ? "filled" : "blank", + Eclipse.isGenerated(method) ? " (GENERATED)" : ""); + indent++; + if (printContent) { + if (method.statements != null) print("%s", method); + disablePrinting++; + } + } + + public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode node, Annotation annotation) { + forcePrint("<ANNOTATION%s: %s />", Eclipse.isGenerated(method) ? " (GENERATED)" : "", annotation); + } + + public void endVisitMethod(EclipseNode node, AbstractMethodDeclaration method) { + if (printContent) disablePrinting--; + String type = method instanceof ConstructorDeclaration ? "CONSTRUCTOR" : "METHOD"; + indent--; + print("</%s %s>", type, str(method.selector)); + } + + public void visitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { + print("<METHODARG%s %s %s = %s>", Eclipse.isGenerated(arg) ? " (GENERATED)" : "", str(arg.type), str(arg.name), arg.initialization); + indent++; + } + + public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode node, Annotation annotation) { + print("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { + indent--; + print("</METHODARG %s %s>", str(arg.type), str(arg.name)); + } + + public void visitLocal(EclipseNode node, LocalDeclaration local) { + print("<LOCAL%s %s %s = %s>", Eclipse.isGenerated(local) ? " (GENERATED)" : "", str(local.type), str(local.name), local.initialization); + indent++; + } + + public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode node, Annotation annotation) { + print("<ANNOTATION%s: %s />", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitLocal(EclipseNode node, LocalDeclaration local) { + indent--; + print("</LOCAL %s %s>", str(local.type), str(local.name)); + } + + public void visitStatement(EclipseNode node, Statement statement) { + print("<%s%s>", statement.getClass(), Eclipse.isGenerated(statement) ? " (GENERATED)" : ""); + indent++; + print("%s", statement); + } + + public void endVisitStatement(EclipseNode node, Statement statement) { + indent--; + print("</%s>", statement.getClass()); + } + } +} diff --git a/src/core/lombok/eclipse/EclipseAnnotationHandler.java b/src/core/lombok/eclipse/EclipseAnnotationHandler.java new file mode 100644 index 00000000..aaa57603 --- /dev/null +++ b/src/core/lombok/eclipse/EclipseAnnotationHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2009 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.eclipse; + +import lombok.core.AnnotationValues; + +/** + * Implement this interface if you want to be triggered for a specific annotation. + * + * You MUST replace 'T' with a specific annotation type, such as: + * + * {@code public class HandleGetter implements EclipseAnnotationHandler<Getter>} + * + * Because this generics parameter is inspected to figure out which class you're interested in. + * + * You also need to register yourself via SPI discovery as being an implementation of {@code EclipseAnnotationHandler}. + */ +public interface EclipseAnnotationHandler<T extends java.lang.annotation.Annotation> { + /** + * Called when an annotation is found that is likely to match the annotation you're interested in. + * + * Be aware that you'll be called for ANY annotation node in the source that looks like a match. There is, + * for example, no guarantee that the annotation node belongs to a method, even if you set your + * TargetType in the annotation to methods only. + * + * @param annotation The actual annotation - use this object to retrieve the annotation parameters. + * @param ast The Eclipse AST node representing the annotation. + * @param annotationNode The Lombok AST wrapper around the 'ast' parameter. You can use this object + * to travel back up the chain (something javac AST can't do) to the parent of the annotation, as well + * as access useful methods such as generating warnings or errors focused on the annotation. + * @return {@code true} if you don't want to be called again about this annotation during this + * compile session (you've handled it), or {@code false} to indicate you aren't done yet. + */ + boolean handle(AnnotationValues<T> annotation, org.eclipse.jdt.internal.compiler.ast.Annotation ast, EclipseNode annotationNode); +} diff --git a/src/core/lombok/eclipse/EclipseNode.java b/src/core/lombok/eclipse/EclipseNode.java new file mode 100644 index 00000000..668e6a6e --- /dev/null +++ b/src/core/lombok/eclipse/EclipseNode.java @@ -0,0 +1,175 @@ +/* + * Copyright © 2009 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.eclipse; + +import java.util.List; + +import lombok.core.AST.Kind; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Clinit; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Initializer; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; + +/** + * Eclipse specific version of the LombokNode class. + */ +public class EclipseNode extends lombok.core.LombokNode<EclipseAST, EclipseNode, ASTNode> { + /** {@inheritDoc} */ + EclipseNode(EclipseAST ast, ASTNode node, List<EclipseNode> children, Kind kind) { + super(ast, node, children, kind); + } + + /** + * Visits this node and all child nodes depth-first, calling the provided visitor's visit methods. + */ + public void traverse(EclipseASTVisitor visitor) { + switch (getKind()) { + case COMPILATION_UNIT: + visitor.visitCompilationUnit(this, (CompilationUnitDeclaration)get()); + ast.traverseChildren(visitor, this); + visitor.endVisitCompilationUnit(this, (CompilationUnitDeclaration)get()); + break; + case TYPE: + visitor.visitType(this, (TypeDeclaration)get()); + ast.traverseChildren(visitor, this); + visitor.endVisitType(this, (TypeDeclaration)get()); + break; + case FIELD: + visitor.visitField(this, (FieldDeclaration)get()); + ast.traverseChildren(visitor, this); + visitor.endVisitField(this, (FieldDeclaration)get()); + break; + case INITIALIZER: + visitor.visitInitializer(this, (Initializer)get()); + ast.traverseChildren(visitor, this); + visitor.endVisitInitializer(this, (Initializer)get()); + break; + case METHOD: + if (get() instanceof Clinit) return; + visitor.visitMethod(this, (AbstractMethodDeclaration)get()); + ast.traverseChildren(visitor, this); + visitor.endVisitMethod(this, (AbstractMethodDeclaration)get()); + break; + case ARGUMENT: + AbstractMethodDeclaration method = (AbstractMethodDeclaration)up().get(); + visitor.visitMethodArgument(this, (Argument)get(), method); + ast.traverseChildren(visitor, this); + visitor.endVisitMethodArgument(this, (Argument)get(), method); + break; + case LOCAL: + visitor.visitLocal(this, (LocalDeclaration)get()); + ast.traverseChildren(visitor, this); + visitor.endVisitLocal(this, (LocalDeclaration)get()); + break; + case ANNOTATION: + switch (up().getKind()) { + case TYPE: + visitor.visitAnnotationOnType((TypeDeclaration)up().get(), this, (Annotation)get()); + break; + case FIELD: + visitor.visitAnnotationOnField((FieldDeclaration)up().get(), this, (Annotation)get()); + break; + case METHOD: + visitor.visitAnnotationOnMethod((AbstractMethodDeclaration)up().get(), this, (Annotation)get()); + break; + case ARGUMENT: + visitor.visitAnnotationOnMethodArgument( + (Argument)parent.get(), + (AbstractMethodDeclaration)parent.directUp().get(), + this, (Annotation)get()); + break; + case LOCAL: + visitor.visitAnnotationOnLocal((LocalDeclaration)parent.get(), this, (Annotation)get()); + break; + default: + throw new AssertionError("Annotion not expected as child of a " + up().getKind()); + } + break; + case STATEMENT: + visitor.visitStatement(this, (Statement)get()); + ast.traverseChildren(visitor, this); + visitor.endVisitStatement(this, (Statement)get()); + break; + default: + throw new AssertionError("Unexpected kind during node traversal: " + getKind()); + } + } + + /** {@inheritDoc} */ + @Override public String getName() { + final char[] n; + if (node instanceof TypeDeclaration) n = ((TypeDeclaration)node).name; + else if (node instanceof FieldDeclaration) n = ((FieldDeclaration)node).name; + else if (node instanceof AbstractMethodDeclaration) n = ((AbstractMethodDeclaration)node).selector; + else if (node instanceof LocalDeclaration) n = ((LocalDeclaration)node).name; + else n = null; + + return n == null ? null : new String(n); + } + + /** {@inheritDoc} */ + @Override public void addError(String message) { + this.addError(message, this.get().sourceStart, this.get().sourceEnd); + } + + /** Generate a compiler error that shows the wavy underline from-to the stated character positions. */ + public void addError(String message, int sourceStart, int sourceEnd) { + ast.addProblem(ast.new ParseProblem(false, message, sourceStart, sourceEnd)); + } + + /** {@inheritDoc} */ + @Override public void addWarning(String message) { + this.addWarning(message, this.get().sourceStart, this.get().sourceEnd); + } + + /** Generate a compiler warning that shows the wavy underline from-to the stated character positions. */ + public void addWarning(String message, int sourceStart, int sourceEnd) { + ast.addProblem(ast.new ParseProblem(true, message, sourceStart, sourceEnd)); + } + + /** {@inheritDoc} */ + @Override protected boolean calculateIsStructurallySignificant() { + if (node instanceof TypeDeclaration) return true; + if (node instanceof AbstractMethodDeclaration) return true; + if (node instanceof FieldDeclaration) return true; + if (node instanceof LocalDeclaration) return true; + if (node instanceof CompilationUnitDeclaration) return true; + return false; + } + + /** + * Convenient shortcut to the owning EclipseAST object's isCompleteParse method. + * + * @see EclipseAST#isCompleteParse() + */ + public boolean isCompleteParse() { + return ast.isCompleteParse(); + } +} diff --git a/src/core/lombok/eclipse/HandlerLibrary.java b/src/core/lombok/eclipse/HandlerLibrary.java new file mode 100644 index 00000000..36c41504 --- /dev/null +++ b/src/core/lombok/eclipse/HandlerLibrary.java @@ -0,0 +1,200 @@ +/* + * Copyright © 2009 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.eclipse; + +import static lombok.eclipse.Eclipse.toQualifiedName; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import lombok.Lombok; +import lombok.core.AnnotationValues; +import lombok.core.PrintAST; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.core.TypeResolver; +import lombok.core.AnnotationValues.AnnotationValueDecodeFail; + +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; + +/** + * This class tracks 'handlers' and knows how to invoke them for any given AST node. + * + * This class can find the handlers (via SPI discovery) and will set up the given AST node, such as + * building an AnnotationValues instance. + */ +public class HandlerLibrary { + /** + * Creates a new HandlerLibrary. Errors will be reported to the Eclipse Error log. + * You probably want to use {@link #load()} instead. + */ + public HandlerLibrary() {} + + private TypeLibrary typeLibrary = new TypeLibrary(); + + private static class AnnotationHandlerContainer<T extends Annotation> { + private EclipseAnnotationHandler<T> handler; + private Class<T> annotationClass; + + AnnotationHandlerContainer(EclipseAnnotationHandler<T> handler, Class<T> annotationClass) { + this.handler = handler; + this.annotationClass = annotationClass; + } + + public boolean handle(org.eclipse.jdt.internal.compiler.ast.Annotation annotation, + final EclipseNode annotationNode) { + AnnotationValues<T> annValues = Eclipse.createAnnotation(annotationClass, annotationNode); + return handler.handle(annValues, annotation, annotationNode); + } + } + + private Map<String, AnnotationHandlerContainer<?>> annotationHandlers = + new HashMap<String, AnnotationHandlerContainer<?>>(); + + private Collection<EclipseASTVisitor> visitorHandlers = new ArrayList<EclipseASTVisitor>(); + + private boolean skipPrintAST; + + /** + * Creates a new HandlerLibrary. Errors will be reported to the Eclipse Error log. + * then uses SPI discovery to load all annotation and visitor based handlers so that future calls + * to the handle methods will defer to these handlers. + */ + public static HandlerLibrary load() { + HandlerLibrary lib = new HandlerLibrary(); + + loadAnnotationHandlers(lib); + loadVisitorHandlers(lib); + + return lib; + } + + /** Uses SPI Discovery to find implementations of {@link EclipseAnnotationHandler}. */ + @SuppressWarnings("unchecked") private static void loadAnnotationHandlers(HandlerLibrary lib) { + try { + for (EclipseAnnotationHandler<?> handler : SpiLoadUtil.findServices(EclipseAnnotationHandler.class)) { + try { + Class<? extends Annotation> annotationClass = + SpiLoadUtil.findAnnotationClass(handler.getClass(), EclipseAnnotationHandler.class); + AnnotationHandlerContainer<?> container = new AnnotationHandlerContainer(handler, annotationClass); + if (lib.annotationHandlers.put(container.annotationClass.getName(), container) != null) { + Eclipse.error(null, "Duplicate handlers for annotation type: " + container.annotationClass.getName()); + } + lib.typeLibrary.addType(container.annotationClass.getName()); + } catch (Throwable t) { + Eclipse.error(null, "Can't load Lombok annotation handler for Eclipse: ", t); + } + } + } catch (IOException e) { + Lombok.sneakyThrow(e); + } + } + + /** Uses SPI Discovery to find implementations of {@link EclipseASTVisitor}. */ + private static void loadVisitorHandlers(HandlerLibrary lib) { + try { + for (EclipseASTVisitor visitor : SpiLoadUtil.findServices(EclipseASTVisitor.class)) { + lib.visitorHandlers.add(visitor); + } + } catch (Throwable t) { + throw Lombok.sneakyThrow(t); + } + } + + /** + * Handles the provided annotation node by first finding a qualifying instance of + * {@link EclipseAnnotationHandler} and if one exists, calling it with a freshly cooked up + * instance of {@link AnnotationValues}. + * + * Note that depending on the printASTOnly flag, the {@link lombok.core.PrintAST} annotation + * will either be silently skipped, or everything that isn't {@code PrintAST} will be skipped. + * + * The HandlerLibrary will attempt to guess if the given annotation node represents a lombok annotation. + * For example, if {@code lombok.*} is in the import list, then this method will guess that + * {@code Getter} refers to {@code lombok.Getter}, presuming that {@link lombok.eclipse.handlers.HandleGetter} + * has been loaded. + * + * @param ast The Compilation Unit that contains the Annotation AST Node. + * @param annotationNode The Lombok AST Node representing the Annotation AST Node. + * @param annotation 'node.get()' - convenience parameter. + */ + public boolean handle(CompilationUnitDeclaration ast, EclipseNode annotationNode, + org.eclipse.jdt.internal.compiler.ast.Annotation annotation) { + String pkgName = annotationNode.getPackageDeclaration(); + Collection<String> imports = annotationNode.getImportStatements(); + + TypeResolver resolver = new TypeResolver(typeLibrary, pkgName, imports); + TypeReference rawType = annotation.type; + if (rawType == null) return false; + boolean handled = false; + for (String fqn : resolver.findTypeMatches(annotationNode, toQualifiedName(annotation.type.getTypeName()))) { + boolean isPrintAST = fqn.equals(PrintAST.class.getName()); + if (isPrintAST == skipPrintAST) continue; + AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); + + if (container == null) continue; + + try { + handled |= container.handle(annotation, annotationNode); + } catch (AnnotationValueDecodeFail fail) { + fail.owner.setError(fail.getMessage(), fail.idx); + } catch (Throwable t) { + Eclipse.error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); + } + } + + return handled; + } + + /** + * Will call all registered {@link EclipseASTVisitor} instances. + */ + public void callASTVisitors(EclipseAST ast) { + for (EclipseASTVisitor visitor : visitorHandlers) try { + ast.traverse(visitor); + } catch (Throwable t) { + Eclipse.error((CompilationUnitDeclaration) ast.top().get(), + String.format("Lombok visitor handler %s failed", visitor.getClass()), t); + } + } + + /** + * Lombok does not currently support triggering annotations in a specified order; the order is essentially + * random right now. This lack of order is particularly annoying for the {@code PrintAST} annotation, + * which is almost always intended to run last. Hence, this hack, which lets it in fact run last. + * + * @see #skipAllButPrintAST() + */ + public void skipPrintAST() { + skipPrintAST = true; + } + + /** @see #skipPrintAST() */ + public void skipAllButPrintAST() { + skipPrintAST = false; + } +} diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java new file mode 100644 index 00000000..3b5482ca --- /dev/null +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -0,0 +1,196 @@ +/* + * Copyright © 2009 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.eclipse; + +import java.lang.reflect.Field; + +import lombok.patcher.Symbols; + +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.parser.Parser; + +/** + * Entry point for the Eclipse Parser patch that lets lombok modify the Abstract Syntax Tree as generated by + * Eclipse's parser implementations. This class is injected into the appropriate OSGi ClassLoader and can thus + * use any classes that belong to org.eclipse.jdt.(apt.)core. + * + * Note that, for any Method body, if Bit24 is set, the Eclipse parser has been patched to never attempt to + * (re)parse it. You should set Bit24 on any MethodDeclaration object you inject into the AST: + * + * {@code methodDeclaration.bits |= ASTNode.Bit24; //0x800000} + * + * @author rzwitserloot + * @author rspilker + */ +public class TransformEclipseAST { + private final EclipseAST ast; + //The patcher hacks this field onto CUD. It's public. + private static final Field astCacheField; + private static final HandlerLibrary handlers; + + private static boolean disableLombok = false; + + static { + Field f = null; + HandlerLibrary l = null; + try { + l = HandlerLibrary.load(); + f = CompilationUnitDeclaration.class.getDeclaredField("$lombokAST"); + } catch (Throwable t) { + try { + Eclipse.error(null, "Problem initializing lombok", t); + } catch (Throwable t2) { + System.err.println("Problem initializing lombok"); + t.printStackTrace(); + } + disableLombok = true; + } + astCacheField = f; + handlers = l; + } + + public static void transform_swapped(CompilationUnitDeclaration ast, Parser parser) { + transform(parser, ast); + } + + /** + * This method is called immediately after Eclipse finishes building a CompilationUnitDeclaration, which is + * the top-level AST node when Eclipse parses a source file. The signature is 'magic' - you should not + * change it! + * + * Eclipse's parsers often operate in diet mode, which means many parts of the AST have been left blank. + * Be ready to deal with just about anything being null, such as the Statement[] arrays of the Method AST nodes. + * + * @param parser The Eclipse parser object that generated the AST. + * @param ast The AST node belonging to the compilation unit (java speak for a single source file). + */ + public static void transform(Parser parser, CompilationUnitDeclaration ast) { + if (disableLombok) return; + + if (Symbols.hasSymbol("lombok.disable")) return; + + try { + EclipseAST existing = getCache(ast); + if (existing == null) { + existing = new EclipseAST(ast); + setCache(ast, existing); + } else existing.reparse(); + new TransformEclipseAST(existing).go(); + } catch (Throwable t) { + try { + String message = "Lombok can't parse this source: " + t.toString(); + + EclipseAST.addProblemToCompilationResult(ast, false, message, 0, 0); + t.printStackTrace(); + } catch (Throwable t2) { + try { + Eclipse.error(ast, "Can't create an error in the problems dialog while adding: " + t.toString(), t2); + } catch (Throwable t3) { + //This seems risky to just silently turn off lombok, but if we get this far, something pretty + //drastic went wrong. For example, the eclipse help system's JSP compiler will trigger a lombok call, + //but due to class loader shenanigans we'll actually get here due to a cascade of + //ClassNotFoundErrors. This is the right action for the help system (no lombok needed for that JSP compiler, + //of course). 'disableLombok' is static, but each context classloader (e.g. each eclipse OSGi plugin) has + //it's own edition of this class, so this won't turn off lombok everywhere. + disableLombok = true; + } + } + } + } + + private static EclipseAST getCache(CompilationUnitDeclaration ast) { + if (astCacheField == null) return null; + try { + return (EclipseAST)astCacheField.get(ast); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static void setCache(CompilationUnitDeclaration ast, EclipseAST cache) { + if (astCacheField != null) try { + astCacheField.set(ast, cache); + } catch (Exception ignore) { + ignore.printStackTrace(); + } + } + + public TransformEclipseAST(EclipseAST ast) { + this.ast = ast; + } + + /** + * First handles all lombok annotations except PrintAST, then calls all non-annotation based handlers. + * then handles any PrintASTs. + */ + public void go() { + handlers.skipPrintAST(); + ast.traverse(new AnnotationVisitor()); + handlers.callASTVisitors(ast); + handlers.skipAllButPrintAST(); + ast.traverse(new AnnotationVisitor()); + } + + private static class AnnotationVisitor extends EclipseASTAdapter { + @Override public void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation) { + if (annotationNode.isHandled()) return; + CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); + boolean handled = handlers.handle(top, annotationNode, annotation); + if (handled) annotationNode.setHandled(); + } + + @Override public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) { + if (annotationNode.isHandled()) return; + CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); + boolean handled = handlers.handle(top, annotationNode, annotation); + if (handled) annotationNode.setHandled(); + } + + @Override public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation) { + if (annotationNode.isHandled()) return; + CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); + boolean handled = handlers.handle(top, annotationNode, annotation); + if (handled) annotationNode.setHandled(); + } + + @Override public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) { + if (annotationNode.isHandled()) return; + CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); + boolean handled = handlers.handle(top, annotationNode, annotation); + if (handled) annotationNode.setHandled(); + } + + @Override public void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation) { + if (annotationNode.isHandled()) return; + CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); + boolean handled = handlers.handle(top, annotationNode, annotation); + if (handled) annotationNode.setHandled(); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java new file mode 100644 index 00000000..2f676d09 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -0,0 +1,385 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import static lombok.eclipse.Eclipse.fromQualifiedName; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import lombok.AccessLevel; +import lombok.core.TransformationsUtil; +import lombok.core.AST.Kind; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseNode; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +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.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.StringLiteral; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; + +/** + * Container for static utility methods useful to handlers written for eclipse. + */ +public class EclipseHandlerUtil { + private EclipseHandlerUtil() { + //Prevent instantiation + } + + /** + * Checks if the given type reference represents a primitive type. + */ + public static boolean isPrimitive(TypeReference ref) { + if (ref.dimensions() > 0) return false; + return TransformationsUtil.PRIMITIVE_TYPE_NAME_PATTERN.matcher(Eclipse.toQualifiedName(ref.getTypeName())).matches(); + } + + /** + * Turns an {@code AccessLevel} instance into the flag bit used by eclipse. + */ + public static int toEclipseModifier(AccessLevel value) { + switch (value) { + case MODULE: + case PACKAGE: + return 0; + default: + case PUBLIC: + return ClassFileConstants.AccPublic; + case PROTECTED: + return ClassFileConstants.AccProtected; + case PRIVATE: + return ClassFileConstants.AccPrivate; + } + } + + /** + * Checks if an eclipse-style array-of-array-of-characters to represent a fully qualified name ('foo.bar.baz'), matches a plain + * string containing the same fully qualified name with dots in the string. + */ + public static boolean nameEquals(char[][] typeName, String string) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (char[] elem : typeName) { + if (first) first = false; + else sb.append('.'); + sb.append(elem); + } + + return string.contentEquals(sb); + } + + /** Serves as return value for the methods that check for the existence of fields and methods. */ + public enum MemberExistsResult { + NOT_EXISTS, EXISTS_BY_USER, EXISTS_BY_LOMBOK; + } + + /** + * Checks if there is a field with the provided name. + * + * @param fieldName the field name to check for. + * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof. + */ + public static MemberExistsResult fieldExists(String fieldName, EclipseNode node) { + while (node != null && !(node.get() instanceof TypeDeclaration)) { + node = node.up(); + } + + if (node != null && node.get() instanceof TypeDeclaration) { + TypeDeclaration typeDecl = (TypeDeclaration)node.get(); + if (typeDecl.fields != null) for (FieldDeclaration def : typeDecl.fields) { + char[] fName = def.name; + if (fName == null) continue; + if (fieldName.equals(new String(fName))) { + EclipseNode existing = node.getNodeFor(def); + if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER; + return MemberExistsResult.EXISTS_BY_LOMBOK; + } + } + } + + return MemberExistsResult.NOT_EXISTS; + } + + /** + * Checks if there is a method with the provided name. In case of multiple methods (overloading), only + * the first method decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned. + * + * @param methodName the method name to check for. + * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof. + */ + public static MemberExistsResult methodExists(String methodName, EclipseNode node) { + while (node != null && !(node.get() instanceof TypeDeclaration)) { + node = node.up(); + } + + if (node != null && node.get() instanceof TypeDeclaration) { + TypeDeclaration typeDecl = (TypeDeclaration)node.get(); + if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) { + char[] mName = def.selector; + if (mName == null) continue; + if (methodName.equals(new String(mName))) { + EclipseNode existing = node.getNodeFor(def); + if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER; + return MemberExistsResult.EXISTS_BY_LOMBOK; + } + } + } + + return MemberExistsResult.NOT_EXISTS; + } + + /** + * Checks if there is a (non-default) constructor. In case of multiple constructors (overloading), only + * the first constructor decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned. + * + * @param node Any node that represents the Type (TypeDeclaration) to look in, or any child node thereof. + */ + public static MemberExistsResult constructorExists(EclipseNode node) { + while (node != null && !(node.get() instanceof TypeDeclaration)) { + node = node.up(); + } + + if (node != null && node.get() instanceof TypeDeclaration) { + TypeDeclaration typeDecl = (TypeDeclaration)node.get(); + if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) { + if (def instanceof ConstructorDeclaration) { + if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue; + EclipseNode existing = node.getNodeFor(def); + if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER; + return MemberExistsResult.EXISTS_BY_LOMBOK; + } + } + } + + return MemberExistsResult.NOT_EXISTS; + } + + /** + * Returns the constructor that's already been generated by lombok. + * Provide any node that represents the type (TypeDeclaration) to look in, or any child node thereof. + */ + public static EclipseNode getExistingLombokConstructor(EclipseNode node) { + while (node != null && !(node.get() instanceof TypeDeclaration)) { + node = node.up(); + } + + if (node == null) return null; + + if (node.get() instanceof TypeDeclaration) { + for (AbstractMethodDeclaration def : ((TypeDeclaration)node.get()).methods) { + if (def instanceof ConstructorDeclaration) { + if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue; + EclipseNode existing = node.getNodeFor(def); + if (existing.isHandled()) return existing; + } + } + } + + return null; + } + + /** + * Returns the method that's already been generated by lombok with the given name. + * Provide any node that represents the type (TypeDeclaration) to look in, or any child node thereof. + */ + public static EclipseNode getExistingLombokMethod(String methodName, EclipseNode node) { + while (node != null && !(node.get() instanceof TypeDeclaration)) { + node = node.up(); + } + + if (node == null) return null; + + if (node.get() instanceof TypeDeclaration) { + for (AbstractMethodDeclaration def : ((TypeDeclaration)node.get()).methods) { + char[] mName = def.selector; + if (mName == null) continue; + if (methodName.equals(new String(mName))) { + EclipseNode existing = node.getNodeFor(def); + if (existing.isHandled()) return existing; + } + } + } + + return null; + } + + /** + * 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) { + parent.fields = new FieldDeclaration[1]; + parent.fields[0] = field; + } else { + FieldDeclaration[] newArray = new FieldDeclaration[parent.fields.length + 1]; + System.arraycopy(parent.fields, 0, newArray, 0, parent.fields.length); + newArray[parent.fields.length] = field; + parent.fields = newArray; + } + + type.add(field, Kind.FIELD).recursiveSetHandled(); + } + + /** + * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. + */ + public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) { + TypeDeclaration parent = (TypeDeclaration) type.get(); + + if (parent.methods == null) { + 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; + 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; + } + } + + type.add(method, Kind.METHOD).recursiveSetHandled(); + } + + /** + * Searches the given field node for annotations and returns each one that matches the provided regular expression pattern. + * + * Only the simple name is checked - the package and any containing class are ignored. + */ + public static Annotation[] findAnnotations(FieldDeclaration field, Pattern namePattern) { + List<Annotation> result = new ArrayList<Annotation>(); + if (field.annotations == null) return new Annotation[0]; + for (Annotation annotation : field.annotations) { + TypeReference typeRef = annotation.type; + if (typeRef != null && typeRef.getTypeName()!= null) { + char[][] typeName = typeRef.getTypeName(); + String suspect = new String(typeName[typeName.length - 1]); + if (namePattern.matcher(suspect).matches()) { + result.add(annotation); + } + } + } + return result.toArray(new Annotation[0]); + } + + /** + * Generates a new statement that checks if the given variable is null, and if so, throws a {@code NullPointerException} with the + * variable name as message. + */ + public static Statement generateNullCheck(AbstractVariableDeclaration variable, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + if (isPrimitive(variable.type)) return null; + AllocationExpression exception = new AllocationExpression(); + Eclipse.setGeneratedBy(exception, source); + exception.type = new QualifiedTypeReference(fromQualifiedName("java.lang.NullPointerException"), new long[]{p, p, p}); + Eclipse.setGeneratedBy(exception.type, source); + exception.arguments = new Expression[] { new StringLiteral(variable.name, pS, pE, 0)}; + Eclipse.setGeneratedBy(exception.arguments[0], source); + ThrowStatement throwStatement = new ThrowStatement(exception, pS, pE); + Eclipse.setGeneratedBy(throwStatement, source); + + SingleNameReference varName = new SingleNameReference(variable.name, p); + Eclipse.setGeneratedBy(varName, source); + NullLiteral nullLiteral = new NullLiteral(pS, pE); + Eclipse.setGeneratedBy(nullLiteral, source); + EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.EQUAL_EQUAL); + equalExpression.sourceStart = pS; equalExpression.sourceEnd = pE; + Eclipse.setGeneratedBy(equalExpression, source); + IfStatement ifStatement = new IfStatement(equalExpression, throwStatement, 0, 0); + Eclipse.setGeneratedBy(ifStatement, source); + return ifStatement; + } + + /** + * Create an annotation of the given name, and is marked as being generated by the given source. + */ + public static MarkerAnnotation makeMarkerAnnotation(char[][] name, ASTNode source) { + long pos = (long)source.sourceStart << 32 | source.sourceEnd; + TypeReference typeRef = new QualifiedTypeReference(name, new long[] {pos, pos, pos}); + Eclipse.setGeneratedBy(typeRef, source); + MarkerAnnotation ann = new MarkerAnnotation(typeRef, (int)(pos >> 32)); + ann.declarationSourceEnd = ann.sourceEnd = ann.statementEnd = (int)pos; + Eclipse.setGeneratedBy(ann, source); + return ann; + } + + /** + * Given a list of field names and a node referring to a type, finds each name in the list that does not match a field within the type. + */ + public static List<Integer> createListOfNonExistentFields(List<String> list, EclipseNode type, boolean excludeStandard, boolean excludeTransient) { + boolean[] matched = new boolean[list.size()]; + + for (EclipseNode child : type.down()) { + if (list.isEmpty()) break; + if (child.getKind() != Kind.FIELD) continue; + if (excludeStandard) { + if ((((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccStatic) != 0) continue; + if (child.getName().startsWith("$")) continue; + } + if (excludeTransient && (((FieldDeclaration)child.get()).modifiers & ClassFileConstants.AccTransient) != 0) continue; + int idx = list.indexOf(child.getName()); + if (idx > -1) matched[idx] = true; + } + + List<Integer> problematic = new ArrayList<Integer>(); + for (int i = 0 ; i < list.size() ; i++) { + if (!matched[i]) problematic.add(i); + } + + return problematic; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleCleanup.java b/src/core/lombok/eclipse/handlers/HandleCleanup.java new file mode 100644 index 00000000..d296e96b --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleCleanup.java @@ -0,0 +1,200 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import java.util.Arrays; + +import lombok.Cleanup; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +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.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; +import org.eclipse.jdt.internal.compiler.ast.TryStatement; +import org.mangosdk.spi.ProviderFor; + +/** + * Handles the {@code lombok.Cleanup} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleCleanup implements EclipseAnnotationHandler<Cleanup> { + public boolean handle(AnnotationValues<Cleanup> annotation, Annotation ast, EclipseNode annotationNode) { + String cleanupName = annotation.getInstance().value(); + if (cleanupName.length() == 0) { + annotationNode.addError("cleanupName cannot be the empty string."); + return true; + } + + if (annotationNode.up().getKind() != Kind.LOCAL) { + annotationNode.addError("@Cleanup is legal only on local variable declarations."); + return true; + } + + LocalDeclaration decl = (LocalDeclaration)annotationNode.up().get(); + + if (decl.initialization == null) { + annotationNode.addError("@Cleanup variable declarations need to be initialized."); + return true; + } + + EclipseNode ancestor = annotationNode.up().directUp(); + ASTNode blockNode = ancestor.get(); + + final boolean isSwitch; + final Statement[] statements; + if (blockNode instanceof AbstractMethodDeclaration) { + isSwitch = false; + statements = ((AbstractMethodDeclaration)blockNode).statements; + } else if (blockNode instanceof Block) { + isSwitch = false; + statements = ((Block)blockNode).statements; + } else if (blockNode instanceof SwitchStatement) { + isSwitch = true; + statements = ((SwitchStatement)blockNode).statements; + } else { + annotationNode.addError("@Cleanup is legal only on a local variable declaration inside a block."); + return true; + } + + if (statements == null) { + annotationNode.addError("LOMBOK BUG: Parent block does not contain any statements."); + return true; + } + + int start = 0; + for (; start < statements.length ; start++) { + if (statements[start] == decl) break; + } + + if (start == statements.length) { + annotationNode.addError("LOMBOK BUG: Can't find this local variable declaration inside its parent."); + return true; + } + + start++; //We start with try{} *AFTER* the var declaration. + + int end; + if (isSwitch) { + end = start + 1; + for (; end < statements.length ; end++) { + if (statements[end] instanceof CaseStatement) { + break; + } + } + } else end = statements.length; + + //At this point: + // start-1 = Local Declaration marked with @Cleanup + // start = first instruction that needs to be wrapped into a try block + // end = last intruction of the scope -OR- last instruction before the next case label in switch statements. + // hence: + // [start, end) = statements for the try block. + + Statement[] tryBlock = new Statement[end - start]; + System.arraycopy(statements, start, tryBlock, 0, end-start); + //Remove the stuff we just dumped into the tryBlock, and then leave room for the try node. + int newStatementsLength = statements.length - (end-start); //Remove room for every statement moved into try block... + newStatementsLength += 1; //But add room for the TryStatement node itself. + Statement[] newStatements = new Statement[newStatementsLength]; + System.arraycopy(statements, 0, newStatements, 0, start); //copy all statements before the try block verbatim. + System.arraycopy(statements, end, newStatements, start+1, statements.length - end); //For switch statements. + + doAssignmentCheck(annotationNode, tryBlock, decl.name); + + TryStatement tryStatement = new TryStatement(); + Eclipse.setGeneratedBy(tryStatement, ast); + tryStatement.tryBlock = new Block(0); + tryStatement.tryBlock.statements = tryBlock; + newStatements[start] = tryStatement; + + Statement[] finallyBlock = new Statement[1]; + MessageSend unsafeClose = new MessageSend(); + Eclipse.setGeneratedBy(unsafeClose, ast); + unsafeClose.sourceStart = ast.sourceStart; + unsafeClose.sourceEnd = ast.sourceEnd; + SingleNameReference receiver = new SingleNameReference(decl.name, 0); + Eclipse.setGeneratedBy(receiver, ast); + unsafeClose.receiver = receiver; + long nameSourcePosition = (long)ast.sourceStart << 32 | ast.sourceEnd; + if (ast.memberValuePairs() != null) for (MemberValuePair pair : ast.memberValuePairs()) { + if (pair.name != null && new String(pair.name).equals("value")) { + nameSourcePosition = (long)pair.value.sourceStart << 32 | pair.value.sourceEnd; + break; + } + } + unsafeClose.nameSourcePosition = nameSourcePosition; + unsafeClose.selector = cleanupName.toCharArray(); + finallyBlock[0] = unsafeClose; + tryStatement.finallyBlock = new Block(0); + Eclipse.setGeneratedBy(tryStatement.finallyBlock, ast); + tryStatement.finallyBlock.statements = finallyBlock; + + tryStatement.catchArguments = null; + tryStatement.catchBlocks = null; + + if (blockNode instanceof AbstractMethodDeclaration) { + ((AbstractMethodDeclaration)blockNode).statements = newStatements; + } else if (blockNode instanceof Block) { + ((Block)blockNode).statements = newStatements; + } else if (blockNode instanceof SwitchStatement) { + ((SwitchStatement)blockNode).statements = newStatements; + } + + ancestor.rebuild(); + + return true; + } + + private void doAssignmentCheck(EclipseNode node, Statement[] tryBlock, char[] varName) { + for (Statement statement : tryBlock) doAssignmentCheck0(node, statement, varName); + } + + private void doAssignmentCheck0(EclipseNode node, Statement statement, char[] varName) { + if (statement instanceof Assignment) + doAssignmentCheck0(node, ((Assignment)statement).expression, varName); + else if (statement instanceof LocalDeclaration) + doAssignmentCheck0(node, ((LocalDeclaration)statement).initialization, varName); + else if (statement instanceof CastExpression) + doAssignmentCheck0(node, ((CastExpression)statement).expression, varName); + else if (statement instanceof SingleNameReference) { + if (Arrays.equals(((SingleNameReference)statement).token, varName)) { + EclipseNode problemNode = node.getNodeFor(statement); + if (problemNode != null) problemNode.addWarning( + "You're assigning an auto-cleanup variable to something else. This is a bad idea."); + } + } + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java new file mode 100644 index 00000000..8c4e07ce --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleData.java @@ -0,0 +1,243 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.core.AnnotationValues; +import lombok.core.TransformationsUtil; +import lombok.core.AST.Kind; +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.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeParameter; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.mangosdk.spi.ProviderFor; + +/** + * Handles the {@code lombok.Data} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleData implements EclipseAnnotationHandler<Data> { + public boolean handle(AnnotationValues<Data> annotation, Annotation ast, EclipseNode annotationNode) { + Data ann = annotation.getInstance(); + EclipseNode typeNode = annotationNode.up(); + + TypeDeclaration typeDecl = null; + if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + boolean notAClass = (modifiers & + (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0; + + if (typeDecl == null || notAClass) { + annotationNode.addError("@Data is only supported on a class."); + return false; + } + + List<EclipseNode> nodesForConstructor = new ArrayList<EclipseNode>(); + for (EclipseNode child : typeNode.down()) { + if (child.getKind() != Kind.FIELD) continue; + FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); + //Skip fields that start with $ + if (fieldDecl.name.length > 0 && fieldDecl.name[0] == '$') continue; + //Skip static fields. + if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0) continue; + boolean isFinal = (fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0; + boolean isNonNull = findAnnotations(fieldDecl, TransformationsUtil.NON_NULL_PATTERN).length != 0; + if ((isFinal || isNonNull) && fieldDecl.initialization == null) nodesForConstructor.add(child); + new HandleGetter().generateGetterForField(child, annotationNode.get()); + if (!isFinal) new HandleSetter().generateSetterForField(child, annotationNode.get()); + } + + new HandleToString().generateToStringForType(typeNode, annotationNode); + new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); + + //Careful: Generate the public static constructor (if there is one) LAST, so that any attempt to + //'find callers' on the annotation node will find callers of the constructor, which is by far the + //most useful of the many methods built by @Data. This trick won't work for the non-static constructor, + //for whatever reason, though you can find callers of that one by focusing on the class name itself + //and hitting 'find callers'. + + if (constructorExists(typeNode) == MemberExistsResult.NOT_EXISTS) { + ConstructorDeclaration constructor = createConstructor( + ann.staticConstructor().length() == 0, typeNode, nodesForConstructor, ast); + injectMethod(typeNode, constructor); + } + + if (ann.staticConstructor().length() > 0) { + if (methodExists("of", typeNode) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration staticConstructor = createStaticConstructor( + ann.staticConstructor(), typeNode, nodesForConstructor, ast); + injectMethod(typeNode, staticConstructor); + } + } + + return false; + } + + private ConstructorDeclaration createConstructor(boolean isPublic, + EclipseNode type, Collection<EclipseNode> fields, ASTNode source) { + long p = (long)source.sourceStart << 32 | source.sourceEnd; + + ConstructorDeclaration constructor = new ConstructorDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + Eclipse.setGeneratedBy(constructor, source); + + constructor.modifiers = EclipseHandlerUtil.toEclipseModifier(isPublic ? AccessLevel.PUBLIC : AccessLevel.PRIVATE); + constructor.annotations = null; + constructor.selector = ((TypeDeclaration)type.get()).name; + constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper); + Eclipse.setGeneratedBy(constructor.constructorCall, source); + constructor.thrownExceptions = null; + constructor.typeParameters = null; + constructor.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; + constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; + constructor.arguments = null; + + List<Argument> args = new ArrayList<Argument>(); + List<Statement> assigns = new ArrayList<Statement>(); + List<Statement> nullChecks = new ArrayList<Statement>(); + + for (EclipseNode fieldNode : fields) { + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + FieldReference thisX = new FieldReference(("this." + new String(field.name)).toCharArray(), p); + Eclipse.setGeneratedBy(thisX, source); + thisX.receiver = new ThisReference((int)(p >> 32), (int)p); + Eclipse.setGeneratedBy(thisX.receiver, source); + thisX.token = field.name; + + SingleNameReference assignmentNameRef = new SingleNameReference(field.name, p); + Eclipse.setGeneratedBy(assignmentNameRef, source); + Assignment assignment = new Assignment(thisX, assignmentNameRef, (int)p); + Eclipse.setGeneratedBy(assignment, source); + assigns.add(assignment); + long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; + Argument argument = new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL); + Eclipse.setGeneratedBy(argument, source); + Annotation[] nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN); + Annotation[] nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN); + if (nonNulls.length != 0) { + Statement nullCheck = generateNullCheck(field, source); + if (nullCheck != null) nullChecks.add(nullCheck); + } + Annotation[] copiedAnnotations = copyAnnotations(nonNulls, nullables, source); + if (copiedAnnotations.length != 0) argument.annotations = copiedAnnotations; + args.add(argument); + } + + nullChecks.addAll(assigns); + constructor.statements = nullChecks.isEmpty() ? null : nullChecks.toArray(new Statement[nullChecks.size()]); + constructor.arguments = args.isEmpty() ? null : args.toArray(new Argument[args.size()]); + return constructor; + } + + private MethodDeclaration createStaticConstructor(String name, EclipseNode type, Collection<EclipseNode> fields, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + MethodDeclaration constructor = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + Eclipse.setGeneratedBy(constructor, source); + + constructor.modifiers = EclipseHandlerUtil.toEclipseModifier(AccessLevel.PUBLIC) | Modifier.STATIC; + TypeDeclaration typeDecl = (TypeDeclaration) type.get(); + if (typeDecl.typeParameters != null && typeDecl.typeParameters.length > 0) { + TypeReference[] refs = new TypeReference[typeDecl.typeParameters.length]; + int idx = 0; + for (TypeParameter param : typeDecl.typeParameters) { + TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd); + Eclipse.setGeneratedBy(typeRef, source); + refs[idx++] = typeRef; + } + constructor.returnType = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p); + } else constructor.returnType = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); + Eclipse.setGeneratedBy(constructor.returnType, source); + constructor.annotations = null; + constructor.selector = name.toCharArray(); + constructor.thrownExceptions = null; + constructor.typeParameters = copyTypeParams(((TypeDeclaration)type.get()).typeParameters, source); + constructor.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; + constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; + + List<Argument> args = new ArrayList<Argument>(); + List<Expression> assigns = new ArrayList<Expression>(); + AllocationExpression statement = new AllocationExpression(); + statement.sourceStart = pS; statement.sourceEnd = pE; + Eclipse.setGeneratedBy(statement, source); + statement.type = copyType(constructor.returnType, source); + + for (EclipseNode fieldNode : fields) { + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; + SingleNameReference nameRef = new SingleNameReference(field.name, fieldPos); + Eclipse.setGeneratedBy(nameRef, source); + assigns.add(nameRef); + + Argument argument = new Argument(field.name, fieldPos, copyType(field.type, source), 0); + Eclipse.setGeneratedBy(argument, source); + + Annotation[] copiedAnnotations = copyAnnotations( + findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN), + findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN), source); + if (copiedAnnotations.length != 0) argument.annotations = copiedAnnotations; + args.add(new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL)); + } + + statement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + constructor.arguments = args.isEmpty() ? null : args.toArray(new Argument[args.size()]); + constructor.statements = new Statement[] { new ReturnStatement(statement, (int)(p >> 32), (int)p) }; + Eclipse.setGeneratedBy(constructor.statements[0], source); + return constructor; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java new file mode 100644 index 00000000..7c0980c8 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -0,0 +1,718 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import static lombok.eclipse.Eclipse.copyTypes; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; +import org.eclipse.jdt.internal.compiler.ast.CastExpression; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +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.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NameReference; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Reference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SuperReference; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.ast.UnaryExpression; +import org.eclipse.jdt.internal.compiler.ast.Wildcard; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.mangosdk.spi.ProviderFor; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +/** + * Handles the {@code EqualsAndHashCode} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsAndHashCode> { + private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( + "byte", "short", "int", "long", "char", "boolean", "double", "float"))); + + private void checkForBogusFieldNames(EclipseNode type, AnnotationValues<EqualsAndHashCode> annotation) { + if (annotation.isExplicit("exclude")) { + for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, true)) { + annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i); + } + } + if (annotation.isExplicit("of")) { + for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().of()), type, false, false)) { + annotation.setWarning("of", "This field does not exist.", i); + } + } + } + + public void generateEqualsAndHashCodeForType(EclipseNode typeNode, EclipseNode errorNode) { + for (EclipseNode child : typeNode.down()) { + if (child.getKind() == Kind.ANNOTATION) { + if (Eclipse.annotationTypeMatches(EqualsAndHashCode.class, child)) { + //The annotation will make it happen, so we can skip it. + return; + } + } + } + + generateMethods(typeNode, errorNode, null, null, null, false); + } + + @Override public boolean handle(AnnotationValues<EqualsAndHashCode> annotation, + Annotation ast, EclipseNode annotationNode) { + EqualsAndHashCode ann = annotation.getInstance(); + List<String> excludes = Arrays.asList(ann.exclude()); + List<String> includes = Arrays.asList(ann.of()); + EclipseNode typeNode = annotationNode.up(); + + checkForBogusFieldNames(typeNode, annotation); + + Boolean callSuper = ann.callSuper(); + if (!annotation.isExplicit("callSuper")) callSuper = null; + if (!annotation.isExplicit("exclude")) excludes = null; + if (!annotation.isExplicit("of")) includes = null; + + if (excludes != null && includes != null) { + excludes = null; + annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); + } + + return generateMethods(typeNode, annotationNode, excludes, includes, callSuper, true); + } + + public boolean generateMethods(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes, + Boolean callSuper, boolean whineIfExists) { + assert excludes == null || includes == null; + + TypeDeclaration typeDecl = null; + + if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + boolean notAClass = (modifiers & + (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0; + + if (typeDecl == null || notAClass) { + errorNode.addError("@EqualsAndHashCode is only supported on a class."); + return false; + } + + boolean implicitCallSuper = callSuper == null; + + if (callSuper == null) { + try { + callSuper = ((Boolean)EqualsAndHashCode.class.getMethod("callSuper").getDefaultValue()).booleanValue(); + } catch (Exception ignore) { + throw new InternalError("Lombok bug - this cannot happen - can't find callSuper field in EqualsAndHashCode annotation."); + } + } + + boolean isDirectDescendantOfObject = true; + + if (typeDecl.superclass != null) { + String p = typeDecl.superclass.toString(); + isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object"); + } + + if (isDirectDescendantOfObject && callSuper) { + errorNode.addError("Generating equals/hashCode with a supercall to java.lang.Object is pointless."); + return true; + } + + if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) { + errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); + } + + List<EclipseNode> nodesForEquality = new ArrayList<EclipseNode>(); + if (includes != null) { + for (EclipseNode child : typeNode.down()) { + if (child.getKind() != Kind.FIELD) continue; + FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); + if (includes.contains(new String(fieldDecl.name))) nodesForEquality.add(child); + } + } else { + for (EclipseNode child : typeNode.down()) { + if (child.getKind() != Kind.FIELD) continue; + FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); + //Skip static fields. + if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0) continue; + //Skip transient fields. + if ((fieldDecl.modifiers & ClassFileConstants.AccTransient) != 0) continue; + //Skip excluded fields. + if (excludes != null && excludes.contains(new String(fieldDecl.name))) continue; + //Skip fields that start with $. + if (fieldDecl.name.length > 0 && fieldDecl.name[0] == '$') continue; + nodesForEquality.add(child); + } + } + + switch (methodExists("hashCode", typeNode)) { + case NOT_EXISTS: + MethodDeclaration hashCode = createHashCode(typeNode, nodesForEquality, callSuper, errorNode.get()); + injectMethod(typeNode, hashCode); + break; + case EXISTS_BY_LOMBOK: + break; + default: + case EXISTS_BY_USER: + if (whineIfExists) { + errorNode.addWarning("Not generating hashCode(): A method with that name already exists"); + } + break; + } + + switch (methodExists("equals", typeNode)) { + case NOT_EXISTS: + MethodDeclaration equals = createEquals(typeNode, nodesForEquality, callSuper, errorNode.get()); + injectMethod(typeNode, equals); + break; + case EXISTS_BY_LOMBOK: + break; + default: + case EXISTS_BY_USER: + if (whineIfExists) { + errorNode.addWarning("Not generating equals(Object other): A method with that name already exists"); + } + break; + } + + return true; + } + + private MethodDeclaration createHashCode(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + 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_int, 0); + Eclipse.setGeneratedBy(method.returnType, source); + method.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)}; + method.selector = "hashCode".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; + method.arguments = null; + + List<Statement> statements = new ArrayList<Statement>(); + List<Expression> intoResult = new ArrayList<Expression>(); + + final char[] PRIME = "PRIME".toCharArray(); + final char[] RESULT = "result".toCharArray(); + final boolean isEmpty = fields.isEmpty(); + + /* final int PRIME = 31; */ { + /* Without fields, PRIME isn't used, and that would trigger a 'local variable not used' warning. */ + if (!isEmpty || callSuper) { + LocalDeclaration primeDecl = new LocalDeclaration(PRIME, pS, pE); + Eclipse.setGeneratedBy(primeDecl, source); + primeDecl.modifiers |= Modifier.FINAL; + primeDecl.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); + primeDecl.type.sourceStart = pS; primeDecl.type.sourceEnd = pE; + Eclipse.setGeneratedBy(primeDecl.type, source); + primeDecl.initialization = new IntLiteral("31".toCharArray(), pS, pE); + Eclipse.setGeneratedBy(primeDecl.initialization, source); + statements.add(primeDecl); + } + } + + /* int result = 1; */ { + LocalDeclaration resultDecl = new LocalDeclaration(RESULT, pS, pE); + Eclipse.setGeneratedBy(resultDecl, source); + resultDecl.initialization = new IntLiteral("1".toCharArray(), pS, pE); + Eclipse.setGeneratedBy(resultDecl.initialization, source); + resultDecl.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); + resultDecl.type.sourceStart = pS; resultDecl.type.sourceEnd = pE; + Eclipse.setGeneratedBy(resultDecl.type, source); + statements.add(resultDecl); + } + + if (callSuper) { + MessageSend callToSuper = new MessageSend(); + Eclipse.setGeneratedBy(callToSuper, source); + callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE; + callToSuper.receiver = new SuperReference(pS, pE); + Eclipse.setGeneratedBy(callToSuper.receiver, source); + callToSuper.selector = "hashCode".toCharArray(); + intoResult.add(callToSuper); + } + + int tempCounter = 0; + for (EclipseNode field : fields) { + FieldDeclaration f = (FieldDeclaration) field.get(); + char[] token = f.type.getLastToken(); + if (f.type.dimensions() == 0 && token != null) { + if (Arrays.equals(TypeConstants.FLOAT, token)) { + /* Float.floatToIntBits(fieldName) */ + MessageSend floatToIntBits = new MessageSend(); + floatToIntBits.sourceStart = pS; floatToIntBits.sourceEnd = pE; + Eclipse.setGeneratedBy(floatToIntBits, source); + floatToIntBits.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA_LANG_FLOAT); + floatToIntBits.selector = "floatToIntBits".toCharArray(); + floatToIntBits.arguments = new Expression[] { generateFieldReference(f.name, source) }; + intoResult.add(floatToIntBits); + } else if (Arrays.equals(TypeConstants.DOUBLE, token)) { + /* longToIntForHashCode(Double.doubleToLongBits(fieldName)) */ + MessageSend doubleToLongBits = new MessageSend(); + doubleToLongBits.sourceStart = pS; doubleToLongBits.sourceEnd = pE; + Eclipse.setGeneratedBy(doubleToLongBits, source); + doubleToLongBits.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA_LANG_DOUBLE); + doubleToLongBits.selector = "doubleToLongBits".toCharArray(); + doubleToLongBits.arguments = new Expression[] { generateFieldReference(f.name, source) }; + final char[] tempName = ("temp" + ++tempCounter).toCharArray(); + LocalDeclaration tempVar = new LocalDeclaration(tempName, pS, pE); + Eclipse.setGeneratedBy(tempVar, source); + tempVar.initialization = doubleToLongBits; + tempVar.type = TypeReference.baseTypeReference(TypeIds.T_long, 0); + tempVar.type.sourceStart = pS; tempVar.type.sourceEnd = pE; + Eclipse.setGeneratedBy(tempVar.type, source); + tempVar.modifiers = Modifier.FINAL; + statements.add(tempVar); + SingleNameReference copy1 = new SingleNameReference(tempName, p); + Eclipse.setGeneratedBy(copy1, source); + SingleNameReference copy2 = new SingleNameReference(tempName, p); + Eclipse.setGeneratedBy(copy2, source); + intoResult.add(longToIntForHashCode(copy1, copy2, source)); + } else if (Arrays.equals(TypeConstants.BOOLEAN, token)) { + /* booleanField ? 1231 : 1237 */ + IntLiteral int1231 = new IntLiteral("1231".toCharArray(), pS, pE); + Eclipse.setGeneratedBy(int1231, source); + IntLiteral int1237 = new IntLiteral("1237".toCharArray(), pS, pE); + Eclipse.setGeneratedBy(int1237, source); + ConditionalExpression int1231or1237 = new ConditionalExpression( + generateFieldReference(f.name, source), int1231, int1237); + Eclipse.setGeneratedBy(int1231or1237, source); + intoResult.add(int1231or1237); + } else if (Arrays.equals(TypeConstants.LONG, token)) { + intoResult.add(longToIntForHashCode(generateFieldReference(f.name, source), generateFieldReference(f.name, source), source)); + } else if (BUILT_IN_TYPES.contains(new String(token))) { + intoResult.add(generateFieldReference(f.name, source)); + } else /* objects */ { + /* this.fieldName == null ? 0 : this.fieldName.hashCode() */ + MessageSend hashCodeCall = new MessageSend(); + hashCodeCall.sourceStart = pS; hashCodeCall.sourceEnd = pE; + Eclipse.setGeneratedBy(hashCodeCall, source); + hashCodeCall.receiver = generateFieldReference(f.name, source); + hashCodeCall.selector = "hashCode".toCharArray(); + NullLiteral nullLiteral = new NullLiteral(pS, pE); + Eclipse.setGeneratedBy(nullLiteral, source); + EqualExpression objIsNull = new EqualExpression( + generateFieldReference(f.name, source), nullLiteral, OperatorIds.EQUAL_EQUAL); + Eclipse.setGeneratedBy(objIsNull, source); + IntLiteral int0 = new IntLiteral("0".toCharArray(), pS, pE); + Eclipse.setGeneratedBy(int0, source); + ConditionalExpression nullOrHashCode = new ConditionalExpression(objIsNull, int0, hashCodeCall); + nullOrHashCode.sourceStart = pS; nullOrHashCode.sourceEnd = pE; + Eclipse.setGeneratedBy(nullOrHashCode, source); + intoResult.add(nullOrHashCode); + } + } else if (f.type.dimensions() > 0 && token != null) { + /* Arrays.deepHashCode(array) //just hashCode for simple arrays */ + MessageSend arraysHashCodeCall = new MessageSend(); + arraysHashCodeCall.sourceStart = pS; arraysHashCodeCall.sourceEnd = pE; + Eclipse.setGeneratedBy(arraysHashCodeCall, source); + arraysHashCodeCall.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray()); + if (f.type.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(token))) { + arraysHashCodeCall.selector = "deepHashCode".toCharArray(); + } else { + arraysHashCodeCall.selector = "hashCode".toCharArray(); + } + arraysHashCodeCall.arguments = new Expression[] { generateFieldReference(f.name, source) }; + intoResult.add(arraysHashCodeCall); + } + } + + /* fold each intoResult entry into: + result = result * PRIME + (item); */ { + for (Expression ex : intoResult) { + SingleNameReference resultRef = new SingleNameReference(RESULT, p); + Eclipse.setGeneratedBy(resultRef, source); + SingleNameReference primeRef = new SingleNameReference(PRIME, p); + Eclipse.setGeneratedBy(primeRef, source); + BinaryExpression multiplyByPrime = new BinaryExpression(resultRef, primeRef, OperatorIds.MULTIPLY); + multiplyByPrime.sourceStart = pS; multiplyByPrime.sourceEnd = pE; + Eclipse.setGeneratedBy(multiplyByPrime, source); + BinaryExpression addItem = new BinaryExpression(multiplyByPrime, ex, OperatorIds.PLUS); + addItem.sourceStart = pS; addItem.sourceEnd = pE; + Eclipse.setGeneratedBy(addItem, source); + resultRef = new SingleNameReference(RESULT, p); + Eclipse.setGeneratedBy(resultRef, source); + Assignment assignment = new Assignment(resultRef, addItem, pE); + assignment.sourceStart = pS; assignment.sourceEnd = pE; + Eclipse.setGeneratedBy(assignment, source); + statements.add(assignment); + } + } + + /* return result; */ { + SingleNameReference resultRef = new SingleNameReference(RESULT, p); + Eclipse.setGeneratedBy(resultRef, source); + ReturnStatement returnStatement = new ReturnStatement(resultRef, pS, pE); + Eclipse.setGeneratedBy(returnStatement, source); + statements.add(returnStatement); + } + method.statements = statements.toArray(new Statement[statements.size()]); + return method; + } + + private MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source) { + int pS = source.sourceStart; int pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + 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.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)}; + method.selector = "equals".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(new char[] { 'o' }, 0, objectRef, Modifier.FINAL)}; + method.arguments[0].sourceStart = pS; method.arguments[0].sourceEnd = pE; + Eclipse.setGeneratedBy(method.arguments[0], source); + + List<Statement> statements = new ArrayList<Statement>(); + + /* if (o == this) return true; */ { + SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p); + Eclipse.setGeneratedBy(oRef, source); + ThisReference thisRef = new ThisReference(pS, pE); + Eclipse.setGeneratedBy(thisRef, source); + EqualExpression otherEqualsThis = new EqualExpression(oRef, thisRef, OperatorIds.EQUAL_EQUAL); + Eclipse.setGeneratedBy(otherEqualsThis, source); + + TrueLiteral trueLiteral = new TrueLiteral(pS, pE); + Eclipse.setGeneratedBy(trueLiteral, source); + ReturnStatement returnTrue = new ReturnStatement(trueLiteral, pS, pE); + Eclipse.setGeneratedBy(returnTrue, source); + IfStatement ifOtherEqualsThis = new IfStatement(otherEqualsThis, returnTrue, pS, pE); + Eclipse.setGeneratedBy(ifOtherEqualsThis, source); + statements.add(ifOtherEqualsThis); + } + + /* if (o == null) 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); + + 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); + } + + 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); + } + + TypeDeclaration typeDecl = (TypeDeclaration)type.get(); + /* MyType<?> other = (MyType<?>) o; */ { + if (!fields.isEmpty()) { + LocalDeclaration other = new LocalDeclaration(otherN, pS, pE); + Eclipse.setGeneratedBy(other, source); + char[] typeName = typeDecl.name; + Expression targetType; + if (typeDecl.typeParameters == null || typeDecl.typeParameters.length == 0) { + targetType = new SingleNameReference(((TypeDeclaration)type.get()).name, p); + Eclipse.setGeneratedBy(targetType, source); + other.type = new SingleTypeReference(typeName, p); + Eclipse.setGeneratedBy(other.type, source); + } else { + TypeReference[] typeArgs = new TypeReference[typeDecl.typeParameters.length]; + for (int i = 0; i < typeArgs.length; i++) { + typeArgs[i] = new Wildcard(Wildcard.UNBOUND); + typeArgs[i].sourceStart = pS; typeArgs[i].sourceEnd = pE; + Eclipse.setGeneratedBy(typeArgs[i], source); + } + targetType = new ParameterizedSingleTypeReference(typeName, typeArgs, 0, p); + Eclipse.setGeneratedBy(targetType, source); + other.type = new ParameterizedSingleTypeReference(typeName, copyTypes(typeArgs, source), 0, p); + Eclipse.setGeneratedBy(other.type, source); + } + NameReference oRef = new SingleNameReference(new char[] { 'o' }, p); + Eclipse.setGeneratedBy(oRef, source); + other.initialization = new CastExpression(oRef, targetType); + Eclipse.setGeneratedBy(other.initialization, source); + statements.add(other); + } + } + + for (EclipseNode field : fields) { + FieldDeclaration f = (FieldDeclaration) field.get(); + char[] token = f.type.getLastToken(); + if (f.type.dimensions() == 0 && token != null) { + if (Arrays.equals(TypeConstants.FLOAT, token)) { + statements.add(generateCompareFloatOrDouble(otherN, "Float".toCharArray(), f.name, source)); + } else if (Arrays.equals(TypeConstants.DOUBLE, token)) { + statements.add(generateCompareFloatOrDouble(otherN, "Double".toCharArray(), f.name, source)); + } else if (BUILT_IN_TYPES.contains(new String(token))) { + NameReference fieldRef = new SingleNameReference(f.name, p); + Eclipse.setGeneratedBy(fieldRef, source); + EqualExpression fieldsNotEqual = new EqualExpression(fieldRef, + generateQualifiedNameRef(source, otherN, f.name), OperatorIds.NOT_EQUAL); + Eclipse.setGeneratedBy(fieldsNotEqual, source); + FalseLiteral falseLiteral = new FalseLiteral(pS, pE); + Eclipse.setGeneratedBy(falseLiteral, source); + ReturnStatement returnStatement = new ReturnStatement(falseLiteral, pS, pE); + Eclipse.setGeneratedBy(returnStatement, source); + IfStatement ifStatement = new IfStatement(fieldsNotEqual, returnStatement, pS, pE); + Eclipse.setGeneratedBy(ifStatement, source); + statements.add(ifStatement); + } else /* objects */ { + NameReference fieldNameRef = new SingleNameReference(f.name, p); + Eclipse.setGeneratedBy(fieldNameRef, source); + NullLiteral nullLiteral = new NullLiteral(pS, pE); + Eclipse.setGeneratedBy(nullLiteral, source); + EqualExpression fieldIsNull = new EqualExpression(fieldNameRef, nullLiteral, OperatorIds.EQUAL_EQUAL); + nullLiteral = new NullLiteral(pS, pE); + Eclipse.setGeneratedBy(nullLiteral, source); + EqualExpression otherFieldIsntNull = new EqualExpression( + generateQualifiedNameRef(source, otherN, f.name), + nullLiteral, OperatorIds.NOT_EQUAL); + MessageSend equalsCall = new MessageSend(); + equalsCall.sourceStart = pS; equalsCall.sourceEnd = pE; + Eclipse.setGeneratedBy(equalsCall, source); + equalsCall.receiver = new SingleNameReference(f.name, p); + Eclipse.setGeneratedBy(equalsCall.receiver, source); + equalsCall.selector = "equals".toCharArray(); + equalsCall.arguments = new Expression[] { generateQualifiedNameRef(source, otherN, f.name) }; + UnaryExpression fieldsNotEqual = new UnaryExpression(equalsCall, OperatorIds.NOT); + fieldsNotEqual.sourceStart = pS; fieldsNotEqual.sourceEnd = pE; + Eclipse.setGeneratedBy(fieldsNotEqual, source); + ConditionalExpression fullEquals = new ConditionalExpression(fieldIsNull, otherFieldIsntNull, fieldsNotEqual); + fullEquals.sourceStart = pS; fullEquals.sourceEnd = pE; + Eclipse.setGeneratedBy(fullEquals, source); + FalseLiteral falseLiteral = new FalseLiteral(pS, pE); + Eclipse.setGeneratedBy(falseLiteral, source); + ReturnStatement returnStatement = new ReturnStatement(falseLiteral, pS, pE); + Eclipse.setGeneratedBy(returnStatement, source); + IfStatement ifStatement = new IfStatement(fullEquals, returnStatement, pS, pE); + Eclipse.setGeneratedBy(ifStatement, source); + statements.add(ifStatement); + } + } else if (f.type.dimensions() > 0 && token != null) { + MessageSend arraysEqualCall = new MessageSend(); + arraysEqualCall.sourceStart = pS; arraysEqualCall.sourceEnd = pE; + Eclipse.setGeneratedBy(arraysEqualCall, source); + arraysEqualCall.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray()); + if (f.type.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(token))) { + arraysEqualCall.selector = "deepEquals".toCharArray(); + } else { + arraysEqualCall.selector = "equals".toCharArray(); + } + NameReference fieldNameRef = new SingleNameReference(f.name, p); + Eclipse.setGeneratedBy(fieldNameRef, source); + arraysEqualCall.arguments = new Expression[] { fieldNameRef, generateQualifiedNameRef(source, otherN, f.name) }; + UnaryExpression arraysNotEqual = new UnaryExpression(arraysEqualCall, OperatorIds.NOT); + arraysNotEqual.sourceStart = pS; arraysNotEqual.sourceEnd = pE; + Eclipse.setGeneratedBy(arraysNotEqual, source); + FalseLiteral falseLiteral = new FalseLiteral(pS, pE); + Eclipse.setGeneratedBy(falseLiteral, source); + ReturnStatement returnStatement = new ReturnStatement(falseLiteral, pS, pE); + Eclipse.setGeneratedBy(returnStatement, source); + IfStatement ifStatement = new IfStatement(arraysNotEqual, returnStatement, pS, pE); + Eclipse.setGeneratedBy(ifStatement, source); + statements.add(ifStatement); + } + } + + /* return true; */ { + TrueLiteral trueLiteral = new TrueLiteral(pS, pE); + Eclipse.setGeneratedBy(trueLiteral, source); + ReturnStatement returnStatement = new ReturnStatement(trueLiteral, pS, pE); + Eclipse.setGeneratedBy(returnStatement, source); + statements.add(returnStatement); + } + method.statements = statements.toArray(new Statement[statements.size()]); + return method; + } + + private IfStatement generateCompareFloatOrDouble(char[] otherN, char[] floatOrDouble, char[] fieldName, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + /* if (Float.compare(fieldName, other.fieldName) != 0) return false */ + MessageSend floatCompare = new MessageSend(); + floatCompare.sourceStart = pS; floatCompare.sourceEnd = pE; + Eclipse.setGeneratedBy(floatCompare, source); + floatCompare.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.LANG, floatOrDouble); + floatCompare.selector = "compare".toCharArray(); + NameReference fieldNameRef = new SingleNameReference(fieldName, p); + Eclipse.setGeneratedBy(fieldNameRef, source); + floatCompare.arguments = new Expression[] {fieldNameRef, generateQualifiedNameRef(source, otherN, fieldName)}; + IntLiteral int0 = new IntLiteral(new char[] {'0'}, pS, pE); + Eclipse.setGeneratedBy(int0, source); + EqualExpression ifFloatCompareIsNot0 = new EqualExpression(floatCompare, int0, OperatorIds.NOT_EQUAL); + ifFloatCompareIsNot0.sourceStart = pS; ifFloatCompareIsNot0.sourceEnd = pE; + Eclipse.setGeneratedBy(ifFloatCompareIsNot0, source); + FalseLiteral falseLiteral = new FalseLiteral(pS, pE); + Eclipse.setGeneratedBy(falseLiteral, source); + ReturnStatement returnFalse = new ReturnStatement(falseLiteral, pS, pE); + Eclipse.setGeneratedBy(returnFalse, source); + IfStatement ifStatement = new IfStatement(ifFloatCompareIsNot0, returnFalse, pS, pE); + Eclipse.setGeneratedBy(ifStatement, source); + return ifStatement; + } + + /** Give 2 clones! */ + private Expression longToIntForHashCode(Reference ref1, Reference ref2, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + /* (int)(ref >>> 32 ^ ref) */ + IntLiteral int32 = new IntLiteral("32".toCharArray(), pS, pE); + Eclipse.setGeneratedBy(int32, source); + BinaryExpression higherBits = new BinaryExpression(ref1, int32, OperatorIds.UNSIGNED_RIGHT_SHIFT); + Eclipse.setGeneratedBy(higherBits, source); + BinaryExpression xorParts = new BinaryExpression(ref2, higherBits, OperatorIds.XOR); + Eclipse.setGeneratedBy(xorParts, source); + TypeReference intRef = TypeReference.baseTypeReference(TypeIds.T_int, 0); + intRef.sourceStart = pS; intRef.sourceEnd = pE; + Eclipse.setGeneratedBy(intRef, source); + CastExpression expr = new CastExpression(xorParts, intRef); + expr.sourceStart = pS; expr.sourceEnd = pE; + Eclipse.setGeneratedBy(expr, source); + return expr; + } + + private Reference generateFieldReference(char[] fieldName, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + FieldReference thisX = new FieldReference(("this." + new String(fieldName)).toCharArray(), p); + Eclipse.setGeneratedBy(thisX, source); + thisX.receiver = new ThisReference(pS, pE); + Eclipse.setGeneratedBy(thisX.receiver, source); + thisX.token = fieldName; + return thisX; + } + + private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + + NameReference ref; + + if (varNames.length > 1) ref = new QualifiedNameReference(varNames, new long[varNames.length], pS, pE); + else ref = new SingleNameReference(varNames[0], p); + Eclipse.setGeneratedBy(ref, source); + return ref; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java new file mode 100644 index 00000000..4a9930e3 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -0,0 +1,154 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.core.AnnotationValues; +import lombok.core.TransformationsUtil; +import lombok.core.AST.Kind; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +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; + +/** + * Handles the {@code lombok.Getter} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleGetter implements EclipseAnnotationHandler<Getter> { + /** + * Generates a getter on the stated field. + * + * Used by {@link HandleData}. + * + * The difference between this call and the handle method is as follows: + * + * If there is a {@code lombok.Getter} annotation on the field, it is used and the + * same rules apply (e.g. warning if the method already exists, stated access level applies). + * If not, the getter is still generated if it isn't already there, though there will not + * be a warning if its already there. The default access level is used. + */ + public void generateGetterForField(EclipseNode fieldNode, ASTNode pos) { + for (EclipseNode child : fieldNode.down()) { + if (child.getKind() == Kind.ANNOTATION) { + if (annotationTypeMatches(Getter.class, child)) { + //The annotation will make it happen, so we can skip it. + return; + } + } + } + + createGetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, pos, false); + } + + public boolean handle(AnnotationValues<Getter> annotation, Annotation ast, EclipseNode annotationNode) { + EclipseNode fieldNode = annotationNode.up(); + AccessLevel level = annotation.getInstance().value(); + if (level == AccessLevel.NONE) return true; + + return createGetterForField(level, fieldNode, annotationNode, annotationNode.get(), true); + } + + private boolean createGetterForField(AccessLevel level, + EclipseNode fieldNode, EclipseNode errorNode, ASTNode source, boolean whineIfExists) { + if (fieldNode.getKind() != Kind.FIELD) { + errorNode.addError("@Getter is only supported on a field."); + return true; + } + + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + TypeReference fieldType = copyType(field.type, source); + String fieldName = new String(field.name); + boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + String getterName = TransformationsUtil.toGetterName(fieldName, isBoolean); + + int modifier = toEclipseModifier(level) | (field.modifiers & ClassFileConstants.AccStatic); + + for (String altName : TransformationsUtil.toAllGetterNames(fieldName, isBoolean)) { + switch (methodExists(altName, fieldNode)) { + case EXISTS_BY_LOMBOK: + return true; + case EXISTS_BY_USER: + if (whineIfExists) { + String altNameExpl = ""; + if (!altName.equals(getterName)) altNameExpl = String.format(" (%s)", altName); + errorNode.addWarning( + String.format("Not generating %s(): A method with that name already exists%s", getterName, altNameExpl)); + } + return true; + default: + case NOT_EXISTS: + //continue scanning the other alt names. + } + } + + MethodDeclaration method = generateGetter((TypeDeclaration) fieldNode.up().get(), field, getterName, modifier, source); + Annotation[] copiedAnnotations = copyAnnotations( + findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN), + findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN), source); + if (copiedAnnotations.length != 0) { + method.annotations = copiedAnnotations; + } + + injectMethod(fieldNode.up(), method); + + return true; + } + + private MethodDeclaration generateGetter(TypeDeclaration parent, FieldDeclaration field, String name, + int modifier, ASTNode source) { + MethodDeclaration method = new MethodDeclaration(parent.compilationResult); + Eclipse.setGeneratedBy(method, source); + method.modifiers = modifier; + method.returnType = copyType(field.type, source); + method.annotations = null; + method.arguments = null; + method.selector = name.toCharArray(); + method.binding = null; + method.thrownExceptions = null; + method.typeParameters = null; + method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + Expression fieldExpression = new SingleNameReference(field.name, ((long)field.declarationSourceStart << 32) | field.declarationSourceEnd); + Eclipse.setGeneratedBy(fieldExpression, source); + Statement returnStatement = new ReturnStatement(fieldExpression, field.sourceStart, field.sourceEnd); + Eclipse.setGeneratedBy(returnStatement, source); + method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; + method.statements = new Statement[] { returnStatement }; + return method; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandlePrintAST.java b/src/core/lombok/eclipse/handlers/HandlePrintAST.java new file mode 100644 index 00000000..580a54a2 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandlePrintAST.java @@ -0,0 +1,57 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintStream; + +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.mangosdk.spi.ProviderFor; + +import lombok.Lombok; +import lombok.core.AnnotationValues; +import lombok.core.PrintAST; +import lombok.eclipse.EclipseASTVisitor; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +/** + * Handles the {@code lombok.core.PrintAST} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandlePrintAST implements EclipseAnnotationHandler<PrintAST> { + public boolean handle(AnnotationValues<PrintAST> annotation, Annotation ast, EclipseNode annotationNode) { + if (!annotationNode.isCompleteParse()) return false; + + PrintStream stream = System.out; + String fileName = annotation.getInstance().outfile(); + if (fileName.length() > 0) try { + stream = new PrintStream(new File(fileName)); + } catch (FileNotFoundException e) { + Lombok.sneakyThrow(e); + } + + annotationNode.up().traverse(new EclipseASTVisitor.Printer(annotation.getInstance().printContent(), stream)); + return true; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java new file mode 100644 index 00000000..9bd10af3 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -0,0 +1,172 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.lang.reflect.Modifier; + +import lombok.AccessLevel; +import lombok.Setter; +import lombok.core.AnnotationValues; +import lombok.core.TransformationsUtil; +import lombok.core.AST.Kind; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +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.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.mangosdk.spi.ProviderFor; + +/** + * Handles the {@code lombok.Setter} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleSetter implements EclipseAnnotationHandler<Setter> { + /** + * Generates a setter on the stated field. + * + * Used by {@link HandleData}. + * + * The difference between this call and the handle method is as follows: + * + * If there is a {@code lombok.Setter} annotation on the field, it is used and the + * same rules apply (e.g. warning if the method already exists, stated access level applies). + * If not, the setter is still generated if it isn't already there, though there will not + * be a warning if its already there. The default access level is used. + */ + public void generateSetterForField(EclipseNode fieldNode, ASTNode pos) { + for (EclipseNode child : fieldNode.down()) { + if (child.getKind() == Kind.ANNOTATION) { + if (annotationTypeMatches(Setter.class, child)) { + //The annotation will make it happen, so we can skip it. + return; + } + } + } + + createSetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, pos, false); + } + + public boolean handle(AnnotationValues<Setter> annotation, Annotation ast, EclipseNode annotationNode) { + EclipseNode fieldNode = annotationNode.up(); + if (fieldNode.getKind() != Kind.FIELD) return false; + AccessLevel level = annotation.getInstance().value(); + if (level == AccessLevel.NONE) return true; + + return createSetterForField(level, fieldNode, annotationNode, annotationNode.get(), true); + } + + private boolean createSetterForField(AccessLevel level, + EclipseNode fieldNode, EclipseNode errorNode, ASTNode pos, boolean whineIfExists) { + if (fieldNode.getKind() != Kind.FIELD) { + errorNode.addError("@Setter is only supported on a field."); + return true; + } + + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); + String setterName = TransformationsUtil.toSetterName(new String(field.name)); + + int modifier = toEclipseModifier(level) | (field.modifiers & ClassFileConstants.AccStatic); + + switch (methodExists(setterName, fieldNode)) { + case EXISTS_BY_LOMBOK: + return true; + case EXISTS_BY_USER: + if (whineIfExists) errorNode.addWarning( + String.format("Not generating %s(%s %s): A method with that name already exists", + setterName, field.type, new String(field.name))); + return true; + default: + case NOT_EXISTS: + //continue with creating the setter + } + + MethodDeclaration method = generateSetter((TypeDeclaration) fieldNode.up().get(), field, setterName, modifier, pos); + + injectMethod(fieldNode.up(), method); + + return true; + } + + private MethodDeclaration generateSetter(TypeDeclaration parent, FieldDeclaration field, String name, + int modifier, ASTNode source) { + + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + MethodDeclaration method = new MethodDeclaration(parent.compilationResult); + Eclipse.setGeneratedBy(method, source); + method.modifiers = modifier; + method.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); + method.returnType.sourceStart = pS; method.returnType.sourceEnd = pE; + Eclipse.setGeneratedBy(method.returnType, source); + method.annotations = null; + Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL); + param.sourceStart = pS; param.sourceEnd = pE; + Eclipse.setGeneratedBy(param, source); + method.arguments = new Argument[] { param }; + method.selector = name.toCharArray(); + method.binding = null; + method.thrownExceptions = null; + method.typeParameters = null; + method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + FieldReference thisX = new FieldReference(field.name, p); + Eclipse.setGeneratedBy(thisX, source); + thisX.receiver = new ThisReference(source.sourceStart, source.sourceEnd); + Eclipse.setGeneratedBy(thisX.receiver, source); + NameReference fieldNameRef = new SingleNameReference(field.name, p); + Eclipse.setGeneratedBy(fieldNameRef, source); + Assignment assignment = new Assignment(thisX, fieldNameRef, (int)p); + assignment.sourceStart = pS; assignment.sourceEnd = pE; + Eclipse.setGeneratedBy(assignment, source); + method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; + + Annotation[] nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN); + Annotation[] nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN); + if (nonNulls.length == 0) { + method.statements = new Statement[] { assignment }; + } else { + Statement nullCheck = generateNullCheck(field, source); + if (nullCheck != null) method.statements = new Statement[] { nullCheck, assignment }; + else method.statements = new Statement[] { assignment }; + } + Annotation[] copiedAnnotations = copyAnnotations(nonNulls, nullables, source); + if (copiedAnnotations.length != 0) param.annotations = copiedAnnotations; + return method; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java new file mode 100644 index 00000000..38f22b2a --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java @@ -0,0 +1,224 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import lombok.SneakyThrows; +import lombok.core.AnnotationValues; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +import org.eclipse.jdt.internal.compiler.ast.TryStatement; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.mangosdk.spi.ProviderFor; + +/** + * Handles the {@code lombok.HandleSneakyThrows} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleSneakyThrows implements EclipseAnnotationHandler<SneakyThrows> { + private static class DeclaredException { + final String exceptionName; + final ASTNode node; + + DeclaredException(String exceptionName, ASTNode node) { + this.exceptionName = exceptionName; + this.node = node; + } + + public long getPos() { + return (long)node.sourceStart << 32 | node.sourceEnd; + } + } + + @Override public boolean handle(AnnotationValues<SneakyThrows> annotation, Annotation source, EclipseNode annotationNode) { + List<String> exceptionNames = annotation.getRawExpressions("value"); + List<DeclaredException> exceptions = new ArrayList<DeclaredException>(); + + MemberValuePair[] memberValuePairs = source.memberValuePairs(); + if (memberValuePairs == null || memberValuePairs.length == 0) { + exceptions.add(new DeclaredException("java.lang.Throwable", source)); + } else { + Expression arrayOrSingle = memberValuePairs[0].value; + final Expression[] exceptionNameNodes; + if (arrayOrSingle instanceof ArrayInitializer) { + exceptionNameNodes = ((ArrayInitializer)arrayOrSingle).expressions; + } else exceptionNameNodes = new Expression[] { arrayOrSingle }; + + if (exceptionNames.size() != exceptionNameNodes.length) { + annotationNode.addError( + "LOMBOK BUG: The number of exception classes in the annotation isn't the same pre- and post- guessing."); + } + + int idx = 0; + for (String exceptionName : exceptionNames) { + if (exceptionName.endsWith(".class")) exceptionName = exceptionName.substring(0, exceptionName.length() - 6); + exceptions.add(new DeclaredException(exceptionName, exceptionNameNodes[idx++])); + } + } + + + EclipseNode owner = annotationNode.up(); + switch (owner.getKind()) { +// case FIELD: +// return handleField(annotationNode, (FieldDeclaration)owner.get(), exceptions); + case METHOD: + return handleMethod(annotationNode, (AbstractMethodDeclaration)owner.get(), exceptions); + default: + annotationNode.addError("@SneakyThrows is legal only on methods and constructors."); + return true; + } + } + +// private boolean handleField(Node annotation, FieldDeclaration field, List<DeclaredException> exceptions) { +// if (field.initialization == null) { +// annotation.addError("@SneakyThrows can only be used on fields with an initialization statement."); +// return true; +// } +// +// Expression expression = field.initialization; +// Statement[] content = new Statement[] {new Assignment( +// new SingleNameReference(field.name, 0), expression, 0)}; +// field.initialization = null; +// +// for (DeclaredException exception : exceptions) { +// content = new Statement[] { buildTryCatchBlock(content, exception) }; +// } +// +// Block block = new Block(0); +// block.statements = content; +// +// Node typeNode = annotation.up().up(); +// +// Initializer initializer = new Initializer(block, field.modifiers & Modifier.STATIC); +// initializer.sourceStart = expression.sourceStart; +// initializer.sourceEnd = expression.sourceEnd; +// initializer.declarationSourceStart = expression.sourceStart; +// initializer.declarationSourceEnd = expression.sourceEnd; +// injectField(typeNode, initializer); +// +// typeNode.rebuild(); +// +// return true; +// } + + private boolean handleMethod(EclipseNode annotation, AbstractMethodDeclaration method, List<DeclaredException> exceptions) { + if (method.isAbstract()) { + annotation.addError("@SneakyThrows can only be used on concrete methods."); + return true; + } + + if (method.statements == null) return false; + + Statement[] contents = method.statements; + + for (DeclaredException exception : exceptions) { + contents = new Statement[] { buildTryCatchBlock(contents, exception, exception.node) }; + } + + method.statements = contents; + annotation.up().rebuild(); + + return true; + } + + private Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source) { + long p = exception.getPos(); + int pS = (int)(p >> 32), pE = (int)p; + + TryStatement tryStatement = new TryStatement(); + Eclipse.setGeneratedBy(tryStatement, source); + tryStatement.tryBlock = new Block(0); + tryStatement.tryBlock.sourceStart = pS; tryStatement.tryBlock.sourceEnd = pE; + Eclipse.setGeneratedBy(tryStatement.tryBlock, source); + tryStatement.tryBlock.statements = contents; + TypeReference typeReference; + if (exception.exceptionName.indexOf('.') == -1) { + typeReference = new SingleTypeReference(exception.exceptionName.toCharArray(), p); + typeReference.statementEnd = pE; + } else { + String[] x = exception.exceptionName.split("\\."); + char[][] elems = new char[x.length][]; + long[] poss = new long[x.length]; + int start = pS; + for (int i = 0; i < x.length; i++) { + elems[i] = x[i].trim().toCharArray(); + int end = start + x[i].length(); + poss[i] = (long)start << 32 | end; + start = end + 1; + } + typeReference = new QualifiedTypeReference(elems, poss); + } + Eclipse.setGeneratedBy(typeReference, source); + + Argument catchArg = new Argument("$ex".toCharArray(), p, typeReference, Modifier.FINAL); + Eclipse.setGeneratedBy(catchArg, source); + catchArg.declarationSourceEnd = catchArg.declarationEnd = catchArg.sourceEnd = pE; + catchArg.declarationSourceStart = catchArg.modifiersSourceStart = catchArg.sourceStart = pS; + + tryStatement.catchArguments = new Argument[] { catchArg }; + + MessageSend sneakyThrowStatement = new MessageSend(); + Eclipse.setGeneratedBy(sneakyThrowStatement, source); + sneakyThrowStatement.receiver = new QualifiedNameReference(new char[][] { "lombok".toCharArray(), "Lombok".toCharArray() }, new long[] { p, p }, pS, pE); + Eclipse.setGeneratedBy(sneakyThrowStatement.receiver, source); + sneakyThrowStatement.receiver.statementEnd = pE; + sneakyThrowStatement.selector = "sneakyThrow".toCharArray(); + SingleNameReference exRef = new SingleNameReference("$ex".toCharArray(), p); + Eclipse.setGeneratedBy(exRef, source); + exRef.statementEnd = pE; + sneakyThrowStatement.arguments = new Expression[] { exRef }; + sneakyThrowStatement.nameSourcePosition = p; + sneakyThrowStatement.sourceStart = pS; + sneakyThrowStatement.sourceEnd = sneakyThrowStatement.statementEnd = pE; + Statement rethrowStatement = new ThrowStatement(sneakyThrowStatement, pS, pE); + Eclipse.setGeneratedBy(rethrowStatement, source); + Block block = new Block(0); + block.sourceStart = pS; + block.sourceEnd = pE; + Eclipse.setGeneratedBy(block, source); + block.statements = new Statement[] { rethrowStatement }; + tryStatement.catchBlocks = new Block[] { block }; + tryStatement.sourceStart = pS; + tryStatement.sourceEnd = pE; + return tryStatement; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleSynchronized.java b/src/core/lombok/eclipse/handlers/HandleSynchronized.java new file mode 100644 index 00000000..fde36192 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleSynchronized.java @@ -0,0 +1,132 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.lang.reflect.Modifier; + +import lombok.Synchronized; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; +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.ArrayAllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.mangosdk.spi.ProviderFor; + +/** + * Handles the {@code lombok.Synchronized} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleSynchronized implements EclipseAnnotationHandler<Synchronized> { + private static final char[] INSTANCE_LOCK_NAME = "$lock".toCharArray(); + private static final char[] STATIC_LOCK_NAME = "$LOCK".toCharArray(); + + @Override public boolean handle(AnnotationValues<Synchronized> annotation, Annotation source, EclipseNode annotationNode) { + int p1 = source.sourceStart -1; + int p2 = source.sourceStart -2; + long pos = (((long)p1) << 32) | p2; + EclipseNode methodNode = annotationNode.up(); + if (methodNode == null || methodNode.getKind() != Kind.METHOD || !(methodNode.get() instanceof MethodDeclaration)) { + annotationNode.addError("@Synchronized is legal only on methods."); + return true; + } + + MethodDeclaration method = (MethodDeclaration)methodNode.get(); + if (method.isAbstract()) { + annotationNode.addError("@Synchronized is legal only on concrete methods."); + return true; + } + + char[] lockName = annotation.getInstance().value().toCharArray(); + boolean autoMake = false; + if (lockName.length == 0) { + autoMake = true; + lockName = method.isStatic() ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; + } + + if (fieldExists(new String(lockName), methodNode) == MemberExistsResult.NOT_EXISTS) { + if (!autoMake) { + annotationNode.addError("The field " + new String(lockName) + " does not exist."); + return true; + } + FieldDeclaration fieldDecl = new FieldDeclaration(lockName, 0, -1); + Eclipse.setGeneratedBy(fieldDecl, source); + fieldDecl.declarationSourceEnd = -1; + + fieldDecl.modifiers = (method.isStatic() ? Modifier.STATIC : 0) | Modifier.FINAL | Modifier.PRIVATE; + + //We use 'new Object[0];' because quite unlike 'new Object();', empty arrays *ARE* serializable! + ArrayAllocationExpression arrayAlloc = new ArrayAllocationExpression(); + Eclipse.setGeneratedBy(arrayAlloc, source); + arrayAlloc.dimensions = new Expression[] { new IntLiteral(new char[] { '0' }, 0, 0) }; + Eclipse.setGeneratedBy(arrayAlloc.dimensions[0], source); + arrayAlloc.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, new long[] { 0, 0, 0 }); + Eclipse.setGeneratedBy(arrayAlloc.type, source); + 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); + } + + if (method.statements == null) return false; + + Block block = new Block(0); + Eclipse.setGeneratedBy(block, source); + block.statements = method.statements; + Expression lockVariable; + if (method.isStatic()) lockVariable = new QualifiedNameReference(new char[][] { + methodNode.up().getName().toCharArray(), lockName }, new long[] { pos, pos }, p1, p2); + else { + lockVariable = new FieldReference(lockName, pos); + ThisReference thisReference = new ThisReference(p1, p2); + Eclipse.setGeneratedBy(thisReference, source); + ((FieldReference)lockVariable).receiver = thisReference; + } + Eclipse.setGeneratedBy(lockVariable, source); + + method.statements = new Statement[] { + new SynchronizedStatement(lockVariable, block, 0, 0) + }; + Eclipse.setGeneratedBy(method.statements[0], source); + + methodNode.rebuild(); + + return true; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java new file mode 100644 index 00000000..d5a4c398 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -0,0 +1,304 @@ +/* + * Copyright © 2009 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.eclipse.handlers; + +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import lombok.AccessLevel; +import lombok.ToString; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NameReference; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.StringLiteral; +import org.eclipse.jdt.internal.compiler.ast.SuperReference; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.mangosdk.spi.ProviderFor; + +/** + * Handles the {@code ToString} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleToString implements EclipseAnnotationHandler<ToString> { + private void checkForBogusFieldNames(EclipseNode type, AnnotationValues<ToString> annotation) { + if (annotation.isExplicit("exclude")) { + for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().exclude()), type, true, false)) { + annotation.setWarning("exclude", "This field does not exist, or would have been excluded anyway.", i); + } + } + if (annotation.isExplicit("of")) { + for (int i : createListOfNonExistentFields(Arrays.asList(annotation.getInstance().of()), type, false, false)) { + annotation.setWarning("of", "This field does not exist.", i); + } + } + } + + public void generateToStringForType(EclipseNode typeNode, EclipseNode errorNode) { + for (EclipseNode child : typeNode.down()) { + if (child.getKind() == Kind.ANNOTATION) { + if (Eclipse.annotationTypeMatches(ToString.class, child)) { + //The annotation will make it happen, so we can skip it. + return; + } + } + } + + boolean includeFieldNames = true; + try { + includeFieldNames = ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue(); + } catch (Exception ignore) {} + generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false); + } + + public boolean handle(AnnotationValues<ToString> annotation, Annotation ast, EclipseNode annotationNode) { + ToString ann = annotation.getInstance(); + List<String> excludes = Arrays.asList(ann.exclude()); + List<String> includes = Arrays.asList(ann.of()); + EclipseNode typeNode = annotationNode.up(); + Boolean callSuper = ann.callSuper(); + + if (!annotation.isExplicit("callSuper")) callSuper = null; + if (!annotation.isExplicit("exclude")) excludes = null; + if (!annotation.isExplicit("of")) includes = null; + + if (excludes != null && includes != null) { + excludes = null; + annotation.setWarning("exclude", "exclude and of are mutually exclusive; the 'exclude' parameter will be ignored."); + } + + checkForBogusFieldNames(typeNode, annotation); + + return generateToString(typeNode, annotationNode, excludes, includes, ann.includeFieldNames(), callSuper, true); + } + + public boolean generateToString(EclipseNode typeNode, EclipseNode errorNode, List<String> excludes, List<String> includes, + boolean includeFieldNames, Boolean callSuper, boolean whineIfExists) { + TypeDeclaration typeDecl = null; + + if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + boolean notAClass = (modifiers & + (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0; + + if (typeDecl == null || notAClass) { + errorNode.addError("@ToString is only supported on a class."); + return false; + } + + if (callSuper == null) { + try { + callSuper = ((Boolean)ToString.class.getMethod("callSuper").getDefaultValue()).booleanValue(); + } catch (Exception ignore) {} + } + + List<EclipseNode> nodesForToString = new ArrayList<EclipseNode>(); + if (includes != null) { + for (EclipseNode child : typeNode.down()) { + if (child.getKind() != Kind.FIELD) continue; + FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); + if (includes.contains(new String(fieldDecl.name))) nodesForToString.add(child); + } + } else { + for (EclipseNode child : typeNode.down()) { + if (child.getKind() != Kind.FIELD) continue; + FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); + //Skip static fields. + if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0) continue; + //Skip excluded fields. + if (excludes != null && excludes.contains(new String(fieldDecl.name))) continue; + //Skip fields that start with $ + if (fieldDecl.name.length > 0 && fieldDecl.name[0] == '$') continue; + nodesForToString.add(child); + } + } + + switch (methodExists("toString", typeNode)) { + case NOT_EXISTS: + MethodDeclaration toString = createToString(typeNode, nodesForToString, includeFieldNames, callSuper, errorNode.get()); + injectMethod(typeNode, toString); + return true; + case EXISTS_BY_LOMBOK: + return true; + default: + case EXISTS_BY_USER: + if (whineIfExists) { + errorNode.addWarning("Not generating toString(): A method with that name already exists"); + } + return true; + } + } + + private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, + boolean includeFieldNames, boolean callSuper, ASTNode source) { + TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); + char[] rawTypeName = typeDeclaration.name; + String typeName = rawTypeName == null ? "" : new String(rawTypeName); + char[] suffix = ")".toCharArray(); + String infixS = ", "; + char[] infix = infixS.toCharArray(); + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + final int PLUS = OperatorIds.PLUS; + + char[] prefix; + + if (callSuper) { + prefix = (typeName + "(super=").toCharArray(); + } else if (fields.isEmpty()) { + prefix = (typeName + "()").toCharArray(); + } else if (includeFieldNames) { + prefix = (typeName + "(" + new String(((FieldDeclaration)fields.iterator().next().get()).name) + "=").toCharArray(); + } else { + prefix = (typeName + "(").toCharArray(); + } + + boolean first = true; + Expression current = new StringLiteral(prefix, pS, pE, 0); + Eclipse.setGeneratedBy(current, source); + + 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, source); + callToSuper.selector = "toString".toCharArray(); + current = new BinaryExpression(current, callToSuper, PLUS); + Eclipse.setGeneratedBy(current, source); + first = false; + } + + for (EclipseNode field : fields) { + FieldDeclaration f = (FieldDeclaration)field.get(); + if (f.name == null || f.type == null) continue; + + Expression ex; + if (f.type.dimensions() > 0) { + MessageSend arrayToString = new MessageSend(); + arrayToString.sourceStart = pS; arrayToString.sourceEnd = pE; + arrayToString.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray()); + arrayToString.arguments = new Expression[] { new SingleNameReference(f.name, p) }; + Eclipse.setGeneratedBy(arrayToString.arguments[0], source); + if (f.type.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(f.type.getLastToken()))) { + arrayToString.selector = "deepToString".toCharArray(); + } else { + arrayToString.selector = "toString".toCharArray(); + } + ex = arrayToString; + } else { + FieldReference thisX = new FieldReference(f.name, p); + thisX.receiver = new ThisReference(source.sourceStart, source.sourceEnd); + Eclipse.setGeneratedBy(thisX.receiver, source); + ex = thisX; + } + Eclipse.setGeneratedBy(ex, source); + + if (first) { + current = new BinaryExpression(current, ex, PLUS); + current.sourceStart = pS; current.sourceEnd = pE; + Eclipse.setGeneratedBy(current, source); + first = false; + continue; + } + + StringLiteral fieldNameLiteral; + if (includeFieldNames) { + char[] namePlusEqualsSign = (infixS + new String(f.name) + "=").toCharArray(); + fieldNameLiteral = new StringLiteral(namePlusEqualsSign, pS, pE, 0); + } else { + fieldNameLiteral = new StringLiteral(infix, pS, pE, 0); + } + Eclipse.setGeneratedBy(fieldNameLiteral, source); + current = new BinaryExpression(current, fieldNameLiteral, PLUS); + Eclipse.setGeneratedBy(current, source); + current = new BinaryExpression(current, ex, PLUS); + Eclipse.setGeneratedBy(current, source); + } + if (!first) { + StringLiteral suffixLiteral = new StringLiteral(suffix, pS, pE, 0); + Eclipse.setGeneratedBy(suffixLiteral, source); + current = new BinaryExpression(current, suffixLiteral, PLUS); + Eclipse.setGeneratedBy(current, source); + } + + ReturnStatement returnStatement = new ReturnStatement(current, pS, pE); + Eclipse.setGeneratedBy(returnStatement, source); + + MethodDeclaration method = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); + Eclipse.setGeneratedBy(method, source); + method.modifiers = toEclipseModifier(AccessLevel.PUBLIC); + method.returnType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_STRING, new long[] {p, p, p}); + Eclipse.setGeneratedBy(method.returnType, source); + method.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, source)}; + method.arguments = null; + method.selector = "toString".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; + method.statements = new Statement[] { returnStatement }; + return method; + } + + private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( + "byte", "short", "int", "long", "char", "boolean", "double", "float"))); + + private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long)pS << 32 | pE; + NameReference ref; + if (varNames.length > 1) ref = new QualifiedNameReference(varNames, new long[varNames.length], pS, pE); + else ref = new SingleNameReference(varNames[0], p); + Eclipse.setGeneratedBy(ref, source); + return ref; + } +} diff --git a/src/core/lombok/eclipse/handlers/package-info.java b/src/core/lombok/eclipse/handlers/package-info.java new file mode 100644 index 00000000..062b73b3 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2009 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. + */ + +/** + * Contains the classes that implement the transformations for all of lombok's various features on the eclipse platform. + */ +package lombok.eclipse.handlers; diff --git a/src/core/lombok/eclipse/package-info.java b/src/core/lombok/eclipse/package-info.java new file mode 100644 index 00000000..0b5add4c --- /dev/null +++ b/src/core/lombok/eclipse/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright © 2009 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. + */ + +/** + * Includes the eclipse-specific implementations of the lombok AST and annotation introspection support. + */ +package lombok.eclipse; |