aboutsummaryrefslogtreecommitdiff
path: root/src/lombok/eclipse/handlers/EclipseHandlerUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/lombok/eclipse/handlers/EclipseHandlerUtil.java')
-rw-r--r--src/lombok/eclipse/handlers/EclipseHandlerUtil.java385
1 files changed, 385 insertions, 0 deletions
diff --git a/src/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/lombok/eclipse/handlers/EclipseHandlerUtil.java
new file mode 100644
index 00000000..88559436
--- /dev/null
+++ b/src/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 = 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;
+ }
+}