/* * 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.lang.reflect.Modifier; import java.util.regex.Pattern; import lombok.AccessLevel; import lombok.core.TransformationsUtil; import lombok.core.AST.Kind; import lombok.javac.JavacAST; import lombok.javac.JavacAST.Node; 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 relevant to this package. */ class PKG { private PKG() { //Prevent instantiation } static boolean isPrimitive(JCExpression ref) { String typeName = ref.toString(); return TransformationsUtil.PRIMITIVE_TYPE_NAME_PATTERN.matcher(typeName).matches(); } static java.util.List 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). */ 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). */ 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. */ 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 check for, or any child node thereof. */ static MemberExistsResult fieldExists(String fieldName, JavacAST.Node 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) ) { JavacAST.Node 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 check for, or any child node thereof. */ static MemberExistsResult methodExists(String methodName, JavacAST.Node 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) ) { JavacAST.Node 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 check for, or any child node thereof. */ static MemberExistsResult constructorExists(JavacAST.Node 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("") ) { if ( (((JCMethodDecl)def).mods.flags & Flags.GENERATEDCONSTR) != 0 ) continue; JavacAST.Node 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 AccessLevel instance into the flag bit used by javac. * * @see java.lang.Modifier */ static int toJavacModifier(AccessLevel accessLevel) { switch ( accessLevel ) { case MODULE: case PACKAGE: return 0; default: case PUBLIC: return Modifier.PUBLIC; case PRIVATE: return Modifier.PRIVATE; case PROTECTED: return Modifier.PROTECTED; } } /** * Adds the given new field declaration to the provided type AST Node. * * Also takes care of updating the JavacAST. */ static void injectField(JavacAST.Node 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. */ static void injectMethod(JavacAST.Node typeNode, JCMethodDecl method) { JCClassDecl type = (JCClassDecl) typeNode.get(); if ( method.getName().contentEquals("") ) { //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 ) { JavacAST.Node 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 addAllButOne(List defs, int idx) { List 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 java.lang.String to var.methodName * is represented by a fold-left of Select nodes with the leftmost string represented by * a 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 */ static JCExpression chainDots(TreeMaker maker, JavacAST.Node 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; } static List findAnnotations(Node fieldNode, Pattern namePattern) { List result = List.nil(); for ( Node 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; } static JCStatement generateNullCheck(TreeMaker treeMaker, JavacAST.Node 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.nil(), npe, List.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); } static List createListOfNonExistentFields(List list, Node type, boolean excludeStandard, boolean excludeTransient) { boolean[] matched = new boolean[list.size()]; for ( Node 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 problematic = List.nil(); for ( int i = 0 ; i < list.size() ; i++ ) { if ( !matched[i] ) problematic = problematic.append(i); } return problematic; } }