aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok/javac/handlers/JavacHandlerUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/lombok/javac/handlers/JavacHandlerUtil.java')
-rw-r--r--src/core/lombok/javac/handlers/JavacHandlerUtil.java335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
new file mode 100644
index 00000000..34d8b849
--- /dev/null
+++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
@@ -0,0 +1,335 @@
+/*
+ * 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.javac.handlers;
+
+import java.util.regex.Pattern;
+
+import lombok.AccessLevel;
+import lombok.core.TransformationsUtil;
+import lombok.core.AST.Kind;
+import lombok.javac.JavacNode;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.TypeTags;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Name;
+
+/**
+ * Container for static utility methods useful to handlers written for javac.
+ */
+public class JavacHandlerUtil {
+ private JavacHandlerUtil() {
+ //Prevent instantiation
+ }
+
+ /**
+ * Checks if the given expression (that really ought to refer to a type expression) represents a primitive type.
+ */
+ public static boolean isPrimitive(JCExpression ref) {
+ String typeName = ref.toString();
+ return TransformationsUtil.PRIMITIVE_TYPE_NAME_PATTERN.matcher(typeName).matches();
+ }
+
+ /**
+ * Translates the given field into all possible getter names.
+ * Convenient wrapper around {@link TransformationsUtil#toAllGetterNames(CharSequence, boolean)}.
+ */
+ public static java.util.List<String> toAllGetterNames(JCVariableDecl field) {
+ CharSequence fieldName = field.name;
+
+ boolean isBoolean = field.vartype.toString().equals("boolean");
+
+ return TransformationsUtil.toAllGetterNames(fieldName, isBoolean);
+ }
+
+ /**
+ * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo).
+ *
+ * Convenient wrapper around {@link TransformationsUtil#toGetterName(CharSequence, boolean)}.
+ */
+ public static String toGetterName(JCVariableDecl field) {
+ CharSequence fieldName = field.name;
+
+ boolean isBoolean = field.vartype.toString().equals("boolean");
+
+ return TransformationsUtil.toGetterName(fieldName, isBoolean);
+ }
+
+ /**
+ * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo).
+ *
+ * Convenient wrapper around {@link TransformationsUtil#toSetterName(CharSequence)}.
+ */
+ public static String toSetterName(JCVariableDecl field) {
+ CharSequence fieldName = field.name;
+
+ return TransformationsUtil.toSetterName(fieldName);
+ }
+
+ /** 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 (JCClassDecl) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult fieldExists(String fieldName, JavacNode node) {
+ while (node != null && !(node.get() instanceof JCClassDecl)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof JCClassDecl) {
+ for (JCTree def : ((JCClassDecl)node.get()).defs) {
+ if (def instanceof JCVariableDecl) {
+ if (((JCVariableDecl)def).name.contentEquals(fieldName)) {
+ JavacNode 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 (JCClassDecl) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult methodExists(String methodName, JavacNode node) {
+ while (node != null && !(node.get() instanceof JCClassDecl)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof JCClassDecl) {
+ for (JCTree def : ((JCClassDecl)node.get()).defs) {
+ if (def instanceof JCMethodDecl) {
+ if (((JCMethodDecl)def).name.contentEquals(methodName)) {
+ JavacNode 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 (JCClassDecl) to look in, or any child node thereof.
+ */
+ public static MemberExistsResult constructorExists(JavacNode node) {
+ while (node != null && !(node.get() instanceof JCClassDecl)) {
+ node = node.up();
+ }
+
+ if (node != null && node.get() instanceof JCClassDecl) {
+ for (JCTree def : ((JCClassDecl)node.get()).defs) {
+ if (def instanceof JCMethodDecl) {
+ if (((JCMethodDecl)def).name.contentEquals("<init>")) {
+ if ((((JCMethodDecl)def).mods.flags & Flags.GENERATEDCONSTR) != 0) continue;
+ JavacNode existing = node.getNodeFor(def);
+ if (existing == null || !existing.isHandled()) return MemberExistsResult.EXISTS_BY_USER;
+ return MemberExistsResult.EXISTS_BY_LOMBOK;
+ }
+ }
+ }
+ }
+
+ return MemberExistsResult.NOT_EXISTS;
+ }
+
+ /**
+ * Turns an {@code AccessLevel} instance into the flag bit used by javac.
+ */
+ public static int toJavacModifier(AccessLevel accessLevel) {
+ switch (accessLevel) {
+ case MODULE:
+ case PACKAGE:
+ return 0;
+ default:
+ case PUBLIC:
+ return Flags.PUBLIC;
+ case PRIVATE:
+ return Flags.PRIVATE;
+ case PROTECTED:
+ return Flags.PROTECTED;
+ }
+ }
+
+ /**
+ * Adds the given new field declaration to the provided type AST Node.
+ *
+ * Also takes care of updating the JavacAST.
+ */
+ public static void injectField(JavacNode typeNode, JCVariableDecl field) {
+ JCClassDecl type = (JCClassDecl) typeNode.get();
+
+ type.defs = type.defs.append(field);
+
+ typeNode.add(field, Kind.FIELD).recursiveSetHandled();
+ }
+
+ /**
+ * Adds the given new method declaration to the provided type AST Node.
+ * Can also inject constructors.
+ *
+ * Also takes care of updating the JavacAST.
+ */
+ public static void injectMethod(JavacNode typeNode, JCMethodDecl method) {
+ JCClassDecl type = (JCClassDecl) typeNode.get();
+
+ if (method.getName().contentEquals("<init>")) {
+ //Scan for default constructor, and remove it.
+ int idx = 0;
+ for (JCTree def : type.defs) {
+ if (def instanceof JCMethodDecl) {
+ if ((((JCMethodDecl)def).mods.flags & Flags.GENERATEDCONSTR) != 0) {
+ JavacNode tossMe = typeNode.getNodeFor(def);
+ if (tossMe != null) tossMe.up().removeChild(tossMe);
+ type.defs = addAllButOne(type.defs, idx);
+ break;
+ }
+ }
+ idx++;
+ }
+ }
+
+ type.defs = type.defs.append(method);
+
+ typeNode.add(method, Kind.METHOD).recursiveSetHandled();
+ }
+
+ private static List<JCTree> addAllButOne(List<JCTree> defs, int idx) {
+ List<JCTree> out = List.nil();
+ int i = 0;
+ for (JCTree def : defs) {
+ if (i++ != idx) out = out.append(def);
+ }
+ return out;
+ }
+
+ /**
+ * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
+ * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by
+ * a {@code Ident} node. This method generates such an expression.
+ *
+ * For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]).
+ *
+ * @see com.sun.tools.javac.tree.JCTree.JCIdent
+ * @see com.sun.tools.javac.tree.JCTree.JCFieldAccess
+ */
+ public static JCExpression chainDots(TreeMaker maker, JavacNode node, String... elems) {
+ assert elems != null;
+ assert elems.length > 0;
+
+ JCExpression e = maker.Ident(node.toName(elems[0]));
+ for (int i = 1 ; i < elems.length ; i++) {
+ e = maker.Select(e, node.toName(elems[i]));
+ }
+
+ return e;
+ }
+
+ /**
+ * 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 List<JCAnnotation> findAnnotations(JavacNode fieldNode, Pattern namePattern) {
+ List<JCAnnotation> result = List.nil();
+ for (JavacNode child : fieldNode.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ JCAnnotation annotation = (JCAnnotation) child.get();
+ String name = annotation.annotationType.toString();
+ int idx = name.lastIndexOf(".");
+ String suspect = idx == -1 ? name : name.substring(idx + 1);
+ if (namePattern.matcher(suspect).matches()) {
+ result = result.append(annotation);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 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 JCStatement generateNullCheck(TreeMaker treeMaker, JavacNode variable) {
+ JCVariableDecl varDecl = (JCVariableDecl) variable.get();
+ if (isPrimitive(varDecl.vartype)) return null;
+ Name fieldName = varDecl.name;
+ JCExpression npe = chainDots(treeMaker, variable, "java", "lang", "NullPointerException");
+ JCTree exception = treeMaker.NewClass(null, List.<JCExpression>nil(), npe, List.<JCExpression>of(treeMaker.Literal(fieldName.toString())), null);
+ JCStatement throwStatement = treeMaker.Throw(exception);
+ return treeMaker.If(treeMaker.Binary(JCTree.EQ, treeMaker.Ident(fieldName), treeMaker.Literal(TypeTags.BOT, null)), throwStatement, null);
+ }
+
+ /**
+ * 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, JavacNode type, boolean excludeStandard, boolean excludeTransient) {
+ boolean[] matched = new boolean[list.size()];
+
+ for (JavacNode child : type.down()) {
+ if (list.isEmpty()) break;
+ if (child.getKind() != Kind.FIELD) continue;
+ JCVariableDecl field = (JCVariableDecl)child.get();
+ if (excludeStandard) {
+ if ((field.mods.flags & Flags.STATIC) != 0) continue;
+ if (field.name.toString().startsWith("$")) continue;
+ }
+ if (excludeTransient && (field.mods.flags & Flags.TRANSIENT) != 0) continue;
+
+ int idx = list.indexOf(child.getName());
+ if (idx > -1) matched[idx] = true;
+ }
+
+ List<Integer> problematic = List.nil();
+ for (int i = 0 ; i < list.size() ; i++) {
+ if (!matched[i]) problematic = problematic.append(i);
+ }
+
+ return problematic;
+ }
+}