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