From 1a0e611a9c5e1ee518670647ce1a44beae559b44 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Wed, 25 Nov 2009 07:32:49 +0100 Subject: Refactored the source folders. --- src/core/lombok/eclipse/Eclipse.java | 479 ++++++++++++++ src/core/lombok/eclipse/EclipseAST.java | 366 +++++++++++ src/core/lombok/eclipse/EclipseASTAdapter.java | 101 +++ src/core/lombok/eclipse/EclipseASTVisitor.java | 295 +++++++++ .../lombok/eclipse/EclipseAnnotationHandler.java | 54 ++ src/core/lombok/eclipse/EclipseNode.java | 175 +++++ src/core/lombok/eclipse/HandlerLibrary.java | 200 ++++++ src/core/lombok/eclipse/TransformEclipseAST.java | 196 ++++++ .../eclipse/handlers/EclipseHandlerUtil.java | 385 +++++++++++ .../lombok/eclipse/handlers/HandleCleanup.java | 200 ++++++ src/core/lombok/eclipse/handlers/HandleData.java | 243 +++++++ .../eclipse/handlers/HandleEqualsAndHashCode.java | 718 +++++++++++++++++++++ src/core/lombok/eclipse/handlers/HandleGetter.java | 154 +++++ .../lombok/eclipse/handlers/HandlePrintAST.java | 57 ++ src/core/lombok/eclipse/handlers/HandleSetter.java | 172 +++++ .../eclipse/handlers/HandleSneakyThrows.java | 224 +++++++ .../eclipse/handlers/HandleSynchronized.java | 132 ++++ .../lombok/eclipse/handlers/HandleToString.java | 304 +++++++++ src/core/lombok/eclipse/handlers/package-info.java | 26 + src/core/lombok/eclipse/package-info.java | 26 + 20 files changed, 4507 insertions(+) create mode 100644 src/core/lombok/eclipse/Eclipse.java create mode 100644 src/core/lombok/eclipse/EclipseAST.java create mode 100644 src/core/lombok/eclipse/EclipseASTAdapter.java create mode 100644 src/core/lombok/eclipse/EclipseASTVisitor.java create mode 100644 src/core/lombok/eclipse/EclipseAnnotationHandler.java create mode 100644 src/core/lombok/eclipse/EclipseNode.java create mode 100644 src/core/lombok/eclipse/HandlerLibrary.java create mode 100644 src/core/lombok/eclipse/TransformEclipseAST.java create mode 100644 src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java create mode 100644 src/core/lombok/eclipse/handlers/HandleCleanup.java create mode 100644 src/core/lombok/eclipse/handlers/HandleData.java create mode 100644 src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java create mode 100644 src/core/lombok/eclipse/handlers/HandleGetter.java create mode 100644 src/core/lombok/eclipse/handlers/HandlePrintAST.java create mode 100644 src/core/lombok/eclipse/handlers/HandleSetter.java create mode 100644 src/core/lombok/eclipse/handlers/HandleSneakyThrows.java create mode 100644 src/core/lombok/eclipse/handlers/HandleSynchronized.java create mode 100644 src/core/lombok/eclipse/handlers/HandleToString.java create mode 100644 src/core/lombok/eclipse/handlers/package-info.java create mode 100644 src/core/lombok/eclipse/package-info.java (limited to 'src/core/lombok/eclipse') 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 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 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 AnnotationValues + createAnnotation(Class type, final EclipseNode annotationNode) { + final Annotation annotation = (Annotation) annotationNode.get(); + Map values = new HashMap(); + + final MemberValuePair[] pairs = annotation.memberValuePairs(); + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + List raws = new ArrayList(); + List guesses = new ArrayList(); + 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(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 { + /** + * 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 getImportStatements() { + List imports = new ArrayList(); + 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 queuedProblems = new ArrayList(); + + 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 children = buildTypes(top.types); + return putInMap(new EclipseNode(this, top, children, Kind.COMPILATION_UNIT)); + } + + private void addIfNotNull(Collection collection, EclipseNode n) { + if (n != null) collection.add(n); + } + + private List buildTypes(TypeDeclaration[] children) { + List childNodes = new ArrayList(); + if (children != null) for (TypeDeclaration type : children) addIfNotNull(childNodes, buildType(type)); + return childNodes; + } + + private EclipseNode buildType(TypeDeclaration type) { + if (setAndGetAsHandled(type)) return null; + List childNodes = new ArrayList(); + 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 buildFields(FieldDeclaration[] children) { + List childNodes = new ArrayList(); + if (children != null) for (FieldDeclaration child : children) addIfNotNull(childNodes, buildField(child)); + return childNodes; + } + + private static List singleton(T item) { + List list = new ArrayList(); + 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 childNodes = new ArrayList(); + 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 buildMethods(AbstractMethodDeclaration[] children) { + List childNodes = new ArrayList(); + if (children != null) for (AbstractMethodDeclaration method : children) addIfNotNull(childNodes, buildMethod(method)); + return childNodes; + } + + private EclipseNode buildMethod(AbstractMethodDeclaration method) { + if (setAndGetAsHandled(method)) return null; + List childNodes = new ArrayList(); + 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 buildArguments(Argument[] children) { + List childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + addIfNotNull(childNodes, buildStatement(local.initialization)); + childNodes.addAll(buildAnnotations(local.annotations)); + return putInMap(new EclipseNode(this, local, childNodes, kind)); + } + + private Collection buildAnnotations(Annotation[] annotations) { + List elements = new ArrayList(); + 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 buildStatements(Statement[] children) { + List childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); + 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> getStatementTypes() { + return Collections.>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("", node.getFileName(), Eclipse.isGenerated(unit) ? " (GENERATED)" : ""); + indent++; + } + + public void endVisitCompilationUnit(EclipseNode node, CompilationUnitDeclaration unit) { + indent--; + print(""); + } + + public void visitType(EclipseNode node, TypeDeclaration type) { + print("", str(type.name), Eclipse.isGenerated(type) ? " (GENERATED)" : ""); + indent++; + if (printContent) { + print("%s", type); + disablePrinting++; + } + } + + public void visitAnnotationOnType(TypeDeclaration type, EclipseNode node, Annotation annotation) { + forcePrint("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitType(EclipseNode node, TypeDeclaration type) { + if (printContent) disablePrinting--; + indent--; + print("", 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("", (initializer.modifiers & Modifier.STATIC) != 0 ? "static" : "instance"); + } + + public void visitField(EclipseNode node, FieldDeclaration field) { + print("", 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("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitField(EclipseNode node, FieldDeclaration field) { + if (printContent) disablePrinting--; + indent--; + print("", 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("", Eclipse.isGenerated(method) ? " (GENERATED)" : "", annotation); + } + + public void endVisitMethod(EclipseNode node, AbstractMethodDeclaration method) { + if (printContent) disablePrinting--; + String type = method instanceof ConstructorDeclaration ? "CONSTRUCTOR" : "METHOD"; + indent--; + print("", type, str(method.selector)); + } + + public void visitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { + print("", 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("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitMethodArgument(EclipseNode node, Argument arg, AbstractMethodDeclaration method) { + indent--; + print("", str(arg.type), str(arg.name)); + } + + public void visitLocal(EclipseNode node, LocalDeclaration local) { + print("", Eclipse.isGenerated(local) ? " (GENERATED)" : "", str(local.type), str(local.name), local.initialization); + indent++; + } + + public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode node, Annotation annotation) { + print("", Eclipse.isGenerated(annotation) ? " (GENERATED)" : "", annotation); + } + + public void endVisitLocal(EclipseNode node, LocalDeclaration local) { + indent--; + print("", 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("", 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} + * + * 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 { + /** + * 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 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 { + /** {@inheritDoc} */ + EclipseNode(EclipseAST ast, ASTNode node, List 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