aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok/eclipse
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@tipit.to>2009-11-25 07:32:49 +0100
committerReinier Zwitserloot <reinier@tipit.to>2009-11-25 07:32:49 +0100
commit1a0e611a9c5e1ee518670647ce1a44beae559b44 (patch)
treee5ef8f671bc6688f486e874d4e2e1a7813e4f0b2 /src/core/lombok/eclipse
parent7fd947ea40c25dad9ee543ebc4b92de9a2e05efc (diff)
downloadlombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.tar.gz
lombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.tar.bz2
lombok-1a0e611a9c5e1ee518670647ce1a44beae559b44.zip
Refactored the source folders.
Diffstat (limited to 'src/core/lombok/eclipse')
-rw-r--r--src/core/lombok/eclipse/Eclipse.java479
-rw-r--r--src/core/lombok/eclipse/EclipseAST.java366
-rw-r--r--src/core/lombok/eclipse/EclipseASTAdapter.java101
-rw-r--r--src/core/lombok/eclipse/EclipseASTVisitor.java295
-rw-r--r--src/core/lombok/eclipse/EclipseAnnotationHandler.java54
-rw-r--r--src/core/lombok/eclipse/EclipseNode.java175
-rw-r--r--src/core/lombok/eclipse/HandlerLibrary.java200
-rw-r--r--src/core/lombok/eclipse/TransformEclipseAST.java196
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java385
-rw-r--r--src/core/lombok/eclipse/handlers/HandleCleanup.java200
-rw-r--r--src/core/lombok/eclipse/handlers/HandleData.java243
-rw-r--r--src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java718
-rw-r--r--src/core/lombok/eclipse/handlers/HandleGetter.java154
-rw-r--r--src/core/lombok/eclipse/handlers/HandlePrintAST.java57
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSetter.java172
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSneakyThrows.java224
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSynchronized.java132
-rw-r--r--src/core/lombok/eclipse/handlers/HandleToString.java304
-rw-r--r--src/core/lombok/eclipse/handlers/package-info.java26
-rw-r--r--src/core/lombok/eclipse/package-info.java26
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<Ec