private static Expression initializeInstance( ClassNode buildee, List<FieldNode> fields, BlockStatement body) { Expression instance = varX("_the" + buildee.getNameWithoutPackage(), buildee); body.addStatement(declS(instance, ctorX(buildee))); for (FieldNode field : fields) { body.addStatement(stmt(assignX(propX(instance, field.getName()), varX(field)))); } return instance; }
private boolean validateConstructors(ClassNode cNode) { if (cNode.getDeclaredConstructors().size() != 0) { // TODO: allow constructors which only call provided constructor? addError( "Explicit constructors not allowed for " + ImmutableASTTransformation.MY_TYPE_NAME + " class: " + cNode.getNameWithoutPackage(), cNode.getDeclaredConstructors().get(0)); } return true; }
private String createClassLabel(ClassNode node) { StringBuilder sb = new StringBuilder(); node = node.redirect(); if (ClassHelper.DYNAMIC_TYPE == node) { return "def"; } sb.append(node.getNameWithoutPackage()); GenericsType[] genericsTypes = node.getGenericsTypes(); if (genericsTypes != null && genericsTypes.length > 0) { sb.append(" <"); for (int i = 0; i < genericsTypes.length; i++) { sb.append(genericsTypes[i].getName()); if (i < genericsTypes.length - 1) { sb.append(", "); } } sb.append("> "); } return sb.toString(); }
private void printConstructor(PrintWriter out, ClassNode clazz, ConstructorNode constructorNode) { printAnnotations(out, constructorNode); // printModifiers(out, constructorNode.getModifiers()); out.print("public "); // temporary hack String className = clazz.getNameWithoutPackage(); if (clazz instanceof InnerClassNode) className = className.substring(className.lastIndexOf("$") + 1); out.println(className); printParams(out, constructorNode); ConstructorCallExpression constrCall = getConstructorCallExpression(constructorNode); if (constrCall == null || !constrCall.isSpecialCall()) { out.println(" {}"); } else { out.println(" {"); printSpecialConstructorArgs(out, constructorNode, constrCall); out.println("}"); } }
/** * Handles generation of code for the @Immutable annotation. * * @author Paul King * @author Andre Steingress */ @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) public class ImmutableASTTransformation extends AbstractASTTransformation { /* Currently leaving BigInteger and BigDecimal in list but see: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6348370 Also, Color is not final so while not normally used with child classes, it isn't strictly immutable. Use at your own risk. This list can by extended by providing "known immutable" classes via Immutable.knownImmutableClasses */ private static List<String> immutableList = Arrays.asList( "java.lang.Boolean", "java.lang.Byte", "java.lang.Character", "java.lang.Double", "java.lang.Float", "java.lang.Integer", "java.lang.Long", "java.lang.Short", "java.lang.String", "java.math.BigInteger", "java.math.BigDecimal", "java.awt.Color", "java.net.URI", "java.util.UUID"); private static final Class MY_CLASS = groovy.transform.Immutable.class; static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS); static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); static final String MEMBER_KNOWN_IMMUTABLE_CLASSES = "knownImmutableClasses"; static final String MEMBER_KNOWN_IMMUTABLES = "knownImmutables"; private static final ClassNode DATE_TYPE = ClassHelper.make(Date.class); private static final ClassNode CLONEABLE_TYPE = ClassHelper.make(Cloneable.class); private static final ClassNode COLLECTION_TYPE = ClassHelper.makeWithoutCaching(Collection.class, false); private static final ClassNode READONLYEXCEPTION_TYPE = ClassHelper.make(ReadOnlyPropertyException.class); private static final ClassNode DGM_TYPE = ClassHelper.make(DefaultGroovyMethods.class); private static final ClassNode SELF_TYPE = ClassHelper.make(ImmutableASTTransformation.class); private static final ClassNode HASHMAP_TYPE = ClassHelper.makeWithoutCaching(HashMap.class, false); private static final ClassNode MAP_TYPE = ClassHelper.makeWithoutCaching(Map.class, false); private static final ClassNode REFLECTION_INVOKER_TYPE = ClassHelper.make(ReflectionMethodInvoker.class); private static final ClassNode SORTEDSET_CLASSNODE = ClassHelper.make(SortedSet.class); private static final ClassNode SORTEDMAP_CLASSNODE = ClassHelper.make(SortedMap.class); private static final ClassNode SET_CLASSNODE = ClassHelper.make(Set.class); private static final ClassNode MAP_CLASSNODE = ClassHelper.make(Map.class); public void visit(ASTNode[] nodes, SourceUnit source) { init(nodes, source); AnnotatedNode parent = (AnnotatedNode) nodes[1]; AnnotationNode node = (AnnotationNode) nodes[0]; // temporarily have weaker check which allows for old Deprecated Annotation // if (!MY_TYPE.equals(node.getClassNode())) return; if (!node.getClassNode().getName().endsWith(".Immutable")) return; List<PropertyNode> newProperties = new ArrayList<PropertyNode>(); if (parent instanceof ClassNode) { final List<String> knownImmutableClasses = getKnownImmutableClasses(node); final List<String> knownImmutables = getKnownImmutables(node); ClassNode cNode = (ClassNode) parent; String cName = cNode.getName(); checkNotInterface(cNode, MY_TYPE_NAME); makeClassFinal(cNode); final List<PropertyNode> pList = getInstanceProperties(cNode); for (PropertyNode pNode : pList) { adjustPropertyForImmutability(pNode, newProperties); } for (PropertyNode pNode : newProperties) { cNode.getProperties().remove(pNode); addProperty(cNode, pNode); } final List<FieldNode> fList = cNode.getFields(); for (FieldNode fNode : fList) { ensureNotPublic(cName, fNode); } createConstructors(cNode, knownImmutableClasses, knownImmutables); if (!hasAnnotation(cNode, EqualsAndHashCodeASTTransformation.MY_TYPE)) { createHashCode(cNode, true, false, false, null, null); createEquals(cNode, false, false, false, null, null); } if (!hasAnnotation(cNode, ToStringASTTransformation.MY_TYPE)) { createToString(cNode, false, false, null, null, false, true); } } } private void doAddConstructor(final ClassNode cNode, final ConstructorNode constructorNode) { cNode.addConstructor(constructorNode); // GROOVY-5814: Immutable is not compatible with @CompileStatic Parameter argsParam = null; for (Parameter p : constructorNode.getParameters()) { if ("args".equals(p.getName())) { argsParam = p; break; } } if (argsParam != null) { final Parameter arg = argsParam; ClassCodeVisitorSupport variableExpressionFix = new ClassCodeVisitorSupport() { @Override protected SourceUnit getSourceUnit() { return cNode.getModule().getContext(); } @Override public void visitVariableExpression(final VariableExpression expression) { super.visitVariableExpression(expression); if ("args".equals(expression.getName())) { expression.setAccessedVariable(arg); } } }; variableExpressionFix.visitConstructor(constructorNode); } } private List<String> getKnownImmutableClasses(AnnotationNode node) { final ArrayList<String> immutableClasses = new ArrayList<String>(); final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLE_CLASSES); if (expression == null) return immutableClasses; if (!(expression instanceof ListExpression)) { addError( "Use the Groovy list notation [el1, el2] to specify known immutable classes via \"" + MEMBER_KNOWN_IMMUTABLE_CLASSES + "\"", node); return immutableClasses; } final ListExpression listExpression = (ListExpression) expression; for (Expression listItemExpression : listExpression.getExpressions()) { if (listItemExpression instanceof ClassExpression) { immutableClasses.add(listItemExpression.getType().getName()); } } return immutableClasses; } private List<String> getKnownImmutables(AnnotationNode node) { final ArrayList<String> immutables = new ArrayList<String>(); final Expression expression = node.getMember(MEMBER_KNOWN_IMMUTABLES); if (expression == null) return immutables; if (!(expression instanceof ListExpression)) { addError( "Use the Groovy list notation [el1, el2] to specify known immutable property names via \"" + MEMBER_KNOWN_IMMUTABLES + "\"", node); return immutables; } final ListExpression listExpression = (ListExpression) expression; for (Expression listItemExpression : listExpression.getExpressions()) { if (listItemExpression instanceof ConstantExpression) { immutables.add((String) ((ConstantExpression) listItemExpression).getValue()); } } return immutables; } private void makeClassFinal(ClassNode cNode) { if ((cNode.getModifiers() & ACC_FINAL) == 0) { cNode.setModifiers(cNode.getModifiers() | ACC_FINAL); } } private void createConstructors( ClassNode cNode, List<String> knownImmutableClasses, List<String> knownImmutables) { if (!validateConstructors(cNode)) return; List<PropertyNode> list = getInstanceProperties(cNode); boolean specialHashMapCase = list.size() == 1 && list.get(0).getField().getType().equals(HASHMAP_TYPE); if (specialHashMapCase) { createConstructorMapSpecial(cNode, list); } else { createConstructorMap(cNode, list, knownImmutableClasses, knownImmutables); createConstructorOrdered(cNode, list); } } private void createConstructorOrdered(ClassNode cNode, List<PropertyNode> list) { final MapExpression argMap = new MapExpression(); final Parameter[] orderedParams = new Parameter[list.size()]; int index = 0; for (PropertyNode pNode : list) { Parameter param = new Parameter(pNode.getField().getType(), pNode.getField().getName()); orderedParams[index++] = param; argMap.addMapEntryExpression( new ConstantExpression(pNode.getName()), new VariableExpression(pNode.getName())); } final BlockStatement orderedBody = new BlockStatement(); orderedBody.addStatement( new ExpressionStatement( new ConstructorCallExpression( ClassNode.THIS, new ArgumentListExpression(new CastExpression(HASHMAP_TYPE, argMap))))); doAddConstructor( cNode, new ConstructorNode(ACC_PUBLIC, orderedParams, ClassNode.EMPTY_ARRAY, orderedBody)); } private Statement createGetterBodyDefault(FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); return new ExpressionStatement(fieldExpr); } private Expression cloneCollectionExpr(Expression fieldExpr) { TernaryExpression expression = createIfInstanceOfAsImmutableStatement( fieldExpr, SORTEDSET_CLASSNODE, createIfInstanceOfAsImmutableStatement( fieldExpr, SORTEDMAP_CLASSNODE, createIfInstanceOfAsImmutableStatement( fieldExpr, SET_CLASSNODE, createIfInstanceOfAsImmutableStatement( fieldExpr, MAP_CLASSNODE, createIfInstanceOfAsImmutableStatement( fieldExpr, ClassHelper.LIST_TYPE, createAsImmutableExpression(fieldExpr, COLLECTION_TYPE)))))); return expression; } private TernaryExpression createIfInstanceOfAsImmutableStatement( Expression expr, ClassNode type, Expression elseStatement) { return new TernaryExpression( new BooleanExpression( new BinaryExpression( expr, Token.newSymbol(Types.KEYWORD_INSTANCEOF, -1, -1), new ClassExpression(type))), createAsImmutableExpression(expr, type), elseStatement); } private Expression createAsImmutableExpression(final Expression expr, final ClassNode type) { return new StaticMethodCallExpression(DGM_TYPE, "asImmutable", new CastExpression(type, expr)); } private Expression cloneArrayOrCloneableExpr(Expression fieldExpr, ClassNode type) { StaticMethodCallExpression smce = new StaticMethodCallExpression( REFLECTION_INVOKER_TYPE, "invoke", new ArgumentListExpression( fieldExpr, new ConstantExpression("clone"), new ArrayExpression( ClassHelper.OBJECT_TYPE.makeArray(), Collections.<Expression>emptyList()))); return new CastExpression(type, smce); } private void createConstructorMapSpecial(ClassNode cNode, List<PropertyNode> list) { final BlockStatement body = new BlockStatement(); body.addStatement(createConstructorStatementMapSpecial(list.get(0).getField())); createConstructorMapCommon(cNode, body); } private void createConstructorMap( ClassNode cNode, List<PropertyNode> list, List<String> knownImmutableClasses, List<String> knownImmutables) { final BlockStatement body = new BlockStatement(); for (PropertyNode pNode : list) { body.addStatement( createConstructorStatement(cNode, pNode, knownImmutableClasses, knownImmutables)); } // check for missing properties Expression checkArgs = new ArgumentListExpression(new VariableExpression("this"), new VariableExpression("args")); body.addStatement( new ExpressionStatement( new StaticMethodCallExpression(SELF_TYPE, "checkPropNames", checkArgs))); createConstructorMapCommon(cNode, body); } private void createConstructorMapCommon(ClassNode cNode, BlockStatement body) { final List<FieldNode> fList = cNode.getFields(); for (FieldNode fNode : fList) { if (fNode.isPublic()) continue; // public fields will be rejected elsewhere if (cNode.getProperty(fNode.getName()) != null) continue; // a property if (fNode.isFinal() && fNode.isStatic()) continue; if (fNode.getName().contains("$")) continue; // internal field if (fNode.isFinal() && fNode.getInitialExpression() != null) body.addStatement(checkFinalArgNotOverridden(cNode, fNode)); body.addStatement(createConstructorStatementDefault(fNode)); } final Parameter[] params = new Parameter[] {new Parameter(HASHMAP_TYPE, "args")}; doAddConstructor( cNode, new ConstructorNode( ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, new IfStatement( equalsNullExpr(new VariableExpression("args")), new EmptyStatement(), body))); } private Statement checkFinalArgNotOverridden(ClassNode cNode, FieldNode fNode) { final String name = fNode.getName(); Expression value = findArg(name); return new IfStatement( equalsNullExpr(value), new EmptyStatement(), new ThrowStatement( new ConstructorCallExpression( READONLYEXCEPTION_TYPE, new ArgumentListExpression( new ConstantExpression(name), new ConstantExpression(cNode.getName()))))); } private Statement createConstructorStatementMapSpecial(FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); Expression initExpr = fNode.getInitialValueExpression(); if (initExpr == null) initExpr = new ConstantExpression(null); Expression namedArgs = findArg(fNode.getName()); Expression baseArgs = new VariableExpression("args"); return new IfStatement( equalsNullExpr(baseArgs), new IfStatement( equalsNullExpr(initExpr), new EmptyStatement(), assignStatement(fieldExpr, cloneCollectionExpr(initExpr))), new IfStatement( equalsNullExpr(namedArgs), new IfStatement( isTrueExpr( new MethodCallExpression( baseArgs, "containsKey", new ConstantExpression(fNode.getName()))), assignStatement(fieldExpr, namedArgs), assignStatement(fieldExpr, cloneCollectionExpr(baseArgs))), new IfStatement( isOneExpr( new MethodCallExpression(baseArgs, "size", MethodCallExpression.NO_ARGUMENTS)), assignStatement(fieldExpr, cloneCollectionExpr(namedArgs)), assignStatement(fieldExpr, cloneCollectionExpr(baseArgs))))); } private void ensureNotPublic(String cNode, FieldNode fNode) { String fName = fNode.getName(); // TODO: do we need to lock down things like: $ownClass if (fNode.isPublic() && !fName.contains("$") && !(fNode.isStatic() && fNode.isFinal())) { addError( "Public field '" + fName + "' not allowed for " + MY_TYPE_NAME + " class '" + cNode + "'.", fNode); } } private void addProperty(ClassNode cNode, PropertyNode pNode) { final FieldNode fn = pNode.getField(); cNode.getFields().remove(fn); cNode.addProperty( pNode.getName(), pNode.getModifiers() | ACC_FINAL, pNode.getType(), pNode.getInitialExpression(), pNode.getGetterBlock(), pNode.getSetterBlock()); final FieldNode newfn = cNode.getField(fn.getName()); cNode.getFields().remove(newfn); cNode.addField(fn); } private boolean validateConstructors(ClassNode cNode) { if (cNode.getDeclaredConstructors().size() != 0) { // TODO: allow constructors which only call provided constructor? addError( "Explicit constructors not allowed for " + ImmutableASTTransformation.MY_TYPE_NAME + " class: " + cNode.getNameWithoutPackage(), cNode.getDeclaredConstructors().get(0)); } return true; } private Statement createConstructorStatement( ClassNode cNode, PropertyNode pNode, List<String> knownImmutableClasses, List<String> knownImmutables) { FieldNode fNode = pNode.getField(); final ClassNode fieldType = fNode.getType(); Statement statement = null; if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) { statement = createConstructorStatementArrayOrCloneable(fNode); } else if (isKnownImmutableClass(fieldType, knownImmutableClasses) || isKnownImmutable(pNode.getName(), knownImmutables)) { statement = createConstructorStatementDefault(fNode); } else if (fieldType.isDerivedFrom(DATE_TYPE)) { statement = createConstructorStatementDate(fNode); } else if (isOrImplements(fieldType, COLLECTION_TYPE) || fieldType.isDerivedFrom(COLLECTION_TYPE) || isOrImplements(fieldType, MAP_TYPE) || fieldType.isDerivedFrom(MAP_TYPE)) { statement = createConstructorStatementCollection(fNode); } else if (fieldType.isResolved()) { addError( createErrorMessage(cNode.getName(), fNode.getName(), fieldType.getName(), "compiling"), fNode); statement = EmptyStatement.INSTANCE; } else { statement = createConstructorStatementGuarded(cNode, fNode); } return statement; } private Statement createConstructorStatementGuarded(ClassNode cNode, FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); Expression initExpr = fNode.getInitialValueExpression(); if (initExpr == null) initExpr = new ConstantExpression(null); Expression unknown = findArg(fNode.getName()); return new IfStatement( equalsNullExpr(unknown), new IfStatement( equalsNullExpr(initExpr), new EmptyStatement(), assignStatement(fieldExpr, checkUnresolved(cNode, fNode, initExpr))), assignStatement(fieldExpr, checkUnresolved(cNode, fNode, unknown))); } private Expression checkUnresolved(ClassNode cNode, FieldNode fNode, Expression value) { Expression args = new TupleExpression( new MethodCallExpression( new VariableExpression("this"), "getClass", ArgumentListExpression.EMPTY_ARGUMENTS), new ConstantExpression(fNode.getName()), value); return new StaticMethodCallExpression(SELF_TYPE, "checkImmutable", args); } private Statement createConstructorStatementCollection(FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); Expression initExpr = fNode.getInitialValueExpression(); if (initExpr == null) initExpr = new ConstantExpression(null); Expression collection = findArg(fNode.getName()); ClassNode fieldType = fieldExpr.getType(); return new IfStatement( equalsNullExpr(collection), new IfStatement( equalsNullExpr(initExpr), new EmptyStatement(), assignStatement(fieldExpr, cloneCollectionExpr(initExpr))), new IfStatement( isInstanceOf(collection, CLONEABLE_TYPE), assignStatement( fieldExpr, cloneCollectionExpr(cloneArrayOrCloneableExpr(collection, fieldType))), assignStatement(fieldExpr, cloneCollectionExpr(collection)))); } private boolean isKnownImmutableClass(ClassNode fieldType, List<String> knownImmutableClasses) { if (!fieldType.isResolved()) return false; return fieldType.isEnum() || ClassHelper.isPrimitiveType(fieldType) || fieldType.getAnnotations(MY_TYPE).size() != 0 || inImmutableList(fieldType.getName()) || knownImmutableClasses.contains(fieldType.getName()); } private boolean isKnownImmutable(String fieldName, List<String> knownImmutables) { return knownImmutables.contains(fieldName); } private static boolean inImmutableList(String typeName) { return immutableList.contains(typeName); } private Statement createConstructorStatementArrayOrCloneable(FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); Expression initExpr = fNode.getInitialValueExpression(); ClassNode fieldType = fNode.getType(); if (initExpr == null) initExpr = new ConstantExpression(null); final Expression array = findArg(fNode.getName()); return new IfStatement( equalsNullExpr(array), new IfStatement( equalsNullExpr(initExpr), assignStatement(fieldExpr, new ConstantExpression(null)), assignStatement(fieldExpr, cloneArrayOrCloneableExpr(initExpr, fieldType))), assignStatement(fieldExpr, cloneArrayOrCloneableExpr(array, fieldType))); } private Statement createConstructorStatementDate(FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); Expression initExpr = fNode.getInitialValueExpression(); if (initExpr == null) initExpr = new ConstantExpression(null); final Expression date = findArg(fNode.getName()); return new IfStatement( equalsNullExpr(date), new IfStatement( equalsNullExpr(initExpr), assignStatement(fieldExpr, new ConstantExpression(null)), assignStatement(fieldExpr, cloneDateExpr(initExpr))), assignStatement(fieldExpr, cloneDateExpr(date))); } private Expression cloneDateExpr(Expression origDate) { return new ConstructorCallExpression( DATE_TYPE, new MethodCallExpression(origDate, "getTime", MethodCallExpression.NO_ARGUMENTS)); } private void adjustPropertyForImmutability(PropertyNode pNode, List<PropertyNode> newNodes) { final FieldNode fNode = pNode.getField(); fNode.setModifiers((pNode.getModifiers() & (~ACC_PUBLIC)) | ACC_FINAL | ACC_PRIVATE); adjustPropertyNode(pNode, createGetterBody(fNode)); newNodes.add(pNode); } private void adjustPropertyNode(PropertyNode pNode, Statement getterBody) { pNode.setSetterBlock(null); pNode.setGetterBlock(getterBody); } private Statement createGetterBody(FieldNode fNode) { BlockStatement body = new BlockStatement(); final ClassNode fieldType = fNode.getType(); final Statement statement; if (fieldType.isArray() || isOrImplements(fieldType, CLONEABLE_TYPE)) { statement = createGetterBodyArrayOrCloneable(fNode); } else if (fieldType.isDerivedFrom(DATE_TYPE)) { statement = createGetterBodyDate(fNode); } else { statement = createGetterBodyDefault(fNode); } body.addStatement(statement); return body; } private static String createErrorMessage( String className, String fieldName, String typeName, String mode) { return MY_TYPE_NAME + " processor doesn't know how to handle field '" + fieldName + "' of type '" + prettyTypeName(typeName) + "' while " + mode + " class " + className + ".\n" + MY_TYPE_NAME + " classes only support properties with effectively immutable types including:\n" + "- Strings, primitive types, wrapper types, BigInteger and BigDecimal, enums\n" + "- other " + MY_TYPE_NAME + " classes and known immutables (java.awt.Color, java.net.URI)\n" + "- Cloneable classes, collections, maps and arrays, and other classes with special handling (java.util.Date)\n" + "Other restrictions apply, please see the groovydoc for " + MY_TYPE_NAME + " for further details"; } private static String prettyTypeName(String name) { return name.equals("java.lang.Object") ? name + " or def" : name; } private Statement createGetterBodyArrayOrCloneable(FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); final Expression expression = cloneArrayOrCloneableExpr(fieldExpr, fNode.getType()); return safeExpression(fieldExpr, expression); } private Statement createGetterBodyDate(FieldNode fNode) { final Expression fieldExpr = new VariableExpression(fNode); final Expression expression = cloneDateExpr(fieldExpr); return safeExpression(fieldExpr, expression); } /** This method exists to be binary compatible with 1.7 - 1.8.6 compiled code. */ @SuppressWarnings("Unchecked") public static Object checkImmutable(String className, String fieldName, Object field) { if (field == null || field instanceof Enum || inImmutableList(field.getClass().getName())) return field; if (field instanceof Collection) return DefaultGroovyMethods.asImmutable((Collection) field); if (field.getClass().getAnnotation(MY_CLASS) != null) return field; final String typeName = field.getClass().getName(); throw new RuntimeException(createErrorMessage(className, fieldName, typeName, "constructing")); } @SuppressWarnings("Unchecked") public static Object checkImmutable(Class<?> clazz, String fieldName, Object field) { Immutable immutable = (Immutable) clazz.getAnnotation(MY_CLASS); List<Class> knownImmutableClasses = new ArrayList<Class>(); if (immutable != null && immutable.knownImmutableClasses().length > 0) { knownImmutableClasses = Arrays.asList(immutable.knownImmutableClasses()); } if (field == null || field instanceof Enum || inImmutableList(field.getClass().getName()) || knownImmutableClasses.contains(field.getClass())) return field; if (field instanceof Collection) return DefaultGroovyMethods.asImmutable((Collection) field); if (field.getClass().getAnnotation(MY_CLASS) != null) return field; final String typeName = field.getClass().getName(); throw new RuntimeException( createErrorMessage(clazz.getName(), fieldName, typeName, "constructing")); } public static void checkPropNames(Object instance, Map<String, Object> args) { final MetaClass metaClass = InvokerHelper.getMetaClass(instance); for (String k : args.keySet()) { if (metaClass.hasProperty(instance, k) == null) throw new MissingPropertyException(k, instance.getClass()); } } }
/** * Handles transformation for the @BaseScript annotation. * * @author Paul King * @author Cedric Champeau * @author Vladimir Orany * @author Jim White */ @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class BaseScriptASTTransformation extends AbstractASTTransformation { private static final Class<BaseScript> MY_CLASS = BaseScript.class; public static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS); private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); private static final Parameter[] CONTEXT_CTOR_PARAMETERS = { new Parameter(ClassHelper.BINDING_TYPE, "context") }; public void visit(ASTNode[] nodes, SourceUnit source) { init(nodes, source); AnnotatedNode parent = (AnnotatedNode) nodes[1]; AnnotationNode node = (AnnotationNode) nodes[0]; if (!MY_TYPE.equals(node.getClassNode())) return; if (parent instanceof DeclarationExpression) { changeBaseScriptTypeFromDeclaration((DeclarationExpression) parent, node); } else if (parent instanceof ImportNode || parent instanceof PackageNode) { changeBaseScriptTypeFromPackageOrImport(source, parent, node); } else if (parent instanceof ClassNode) { changeBaseScriptTypeFromClass((ClassNode) parent, node); } } private void changeBaseScriptTypeFromPackageOrImport( final SourceUnit source, final AnnotatedNode parent, final AnnotationNode node) { Expression value = node.getMember("value"); if (!(value instanceof ClassExpression)) { addError("Annotation " + MY_TYPE_NAME + " member 'value' should be a class literal.", value); return; } List<ClassNode> classes = source.getAST().getClasses(); for (ClassNode classNode : classes) { if (classNode.isScriptBody()) { changeBaseScriptType(parent, classNode, value.getType()); } } } private void changeBaseScriptTypeFromClass(final ClassNode parent, final AnnotationNode node) { // Expression value = node.getMember("value"); // if (!(value instanceof ClassExpression)) { // addError("Annotation " + MY_TYPE_NAME + " member 'value' should be a class // literal.", value); // return; // } changeBaseScriptType(parent, parent, parent.getSuperClass()); } private void changeBaseScriptTypeFromDeclaration( final DeclarationExpression de, final AnnotationNode node) { if (de.isMultipleAssignmentDeclaration()) { addError( "Annotation " + MY_TYPE_NAME + " not supported with multiple assignment notation.", de); return; } if (!(de.getRightExpression() instanceof EmptyExpression)) { addError("Annotation " + MY_TYPE_NAME + " not supported with variable assignment.", de); return; } Expression value = node.getMember("value"); if (value != null) { addError( "Annotation " + MY_TYPE_NAME + " cannot have member 'value' if used on a declaration.", value); return; } ClassNode cNode = de.getDeclaringClass(); ClassNode baseScriptType = de.getVariableExpression().getType().getPlainNodeReference(); de.setRightExpression(new VariableExpression("this")); changeBaseScriptType(de, cNode, baseScriptType); } private void changeBaseScriptType( final AnnotatedNode parent, final ClassNode cNode, final ClassNode baseScriptType) { if (!cNode.isScriptBody()) { addError("Annotation " + MY_TYPE_NAME + " can only be used within a Script.", parent); return; } if (!baseScriptType.isScript()) { addError( "Declared type " + baseScriptType + " does not extend groovy.lang.Script class!", parent); return; } cNode.setSuperClass(baseScriptType); // Method in base script that will contain the script body code. MethodNode runScriptMethod = ClassHelper.findSAM(baseScriptType); // If they want to use a name other than than "run", then make the change. if (isCustomScriptBodyMethod(runScriptMethod)) { MethodNode defaultMethod = cNode.getDeclaredMethod("run", Parameter.EMPTY_ARRAY); // GROOVY-6706: Sometimes an NPE is thrown here. // The reason is that our transform is getting called more than once sometimes. if (defaultMethod != null) { cNode.removeMethod(defaultMethod); MethodNode methodNode = new MethodNode( runScriptMethod.getName(), runScriptMethod.getModifiers() & ~ACC_ABSTRACT, runScriptMethod.getReturnType(), runScriptMethod.getParameters(), runScriptMethod.getExceptions(), defaultMethod.getCode()); // The AST node metadata has the flag that indicates that this method is a script body. // It may also be carrying data for other AST transforms. methodNode.copyNodeMetaData(defaultMethod); cNode.addMethod(methodNode); } } // If the new script base class does not have a contextual constructor (g.l.Binding), then we // won't either. // We have to do things this way (and rely on just default constructors) because the logic that // generates // the constructors for our script class have already run. if (cNode.getSuperClass().getDeclaredConstructor(CONTEXT_CTOR_PARAMETERS) == null) { ConstructorNode orphanedConstructor = cNode.getDeclaredConstructor(CONTEXT_CTOR_PARAMETERS); cNode.removeConstructor(orphanedConstructor); } } private boolean isCustomScriptBodyMethod(MethodNode node) { return node != null && !(node.getDeclaringClass().equals(ClassHelper.SCRIPT_TYPE) && "run".equals(node.getName()) && node.getParameters().length == 0); } }
/** * Transformation used by the {@link grails.test.mixin.TestFor} annotation to signify the class * under test. * * @author Graeme Rocher * @since 2.0 */ @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) @SuppressWarnings("rawtypes") public class TestForTransformation extends TestMixinTransformation { private static final ClassNode MY_TYPE = new ClassNode(TestFor.class); private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); private static final Token ASSIGN = Token.newSymbol("=", -1, -1); protected static final Map<String, Class> artefactTypeToTestMap = new HashMap<String, Class>(); static { artefactTypeToTestMap.put(ControllerArtefactHandler.TYPE, ControllerUnitTestMixin.class); artefactTypeToTestMap.put(TagLibArtefactHandler.TYPE, GroovyPageUnitTestMixin.class); artefactTypeToTestMap.put( FiltersConfigArtefactHandler.getTYPE().toString(), FiltersUnitTestMixin.class); artefactTypeToTestMap.put(UrlMappingsArtefactHandler.TYPE, UrlMappingsUnitTestMixin.class); artefactTypeToTestMap.put(ServiceArtefactHandler.TYPE, ServiceUnitTestMixin.class); } public static final String DOMAIN_TYPE = "Domain"; public static final ClassNode BEFORE_CLASS_NODE = new ClassNode(Before.class); public static final AnnotationNode BEFORE_ANNOTATION = new AnnotationNode(BEFORE_CLASS_NODE); public static final AnnotationNode TEST_ANNOTATION = new AnnotationNode(new ClassNode(Test.class)); public static final ClassNode GROOVY_TEST_CASE_CLASS = new ClassNode(GroovyTestCase.class); private ResourceLocator resourceLocator; public ResourceLocator getResourceLocator() { if (resourceLocator == null) { resourceLocator = new DefaultResourceLocator(); BuildSettings buildSettings = BuildSettingsHolder.getSettings(); String basedir; if (buildSettings != null) { basedir = buildSettings.getBaseDir().getAbsolutePath(); } else { basedir = "."; } resourceLocator.setSearchLocation(basedir); } return resourceLocator; } @Override public void visit(ASTNode[] astNodes, SourceUnit source) { if (!(astNodes[0] instanceof AnnotationNode) || !(astNodes[1] instanceof AnnotatedNode)) { throw new RuntimeException("Internal error: wrong types: $node.class / $parent.class"); } AnnotatedNode parent = (AnnotatedNode) astNodes[1]; AnnotationNode node = (AnnotationNode) astNodes[0]; if (!MY_TYPE.equals(node.getClassNode()) || !(parent instanceof ClassNode)) { return; } ClassNode classNode = (ClassNode) parent; if (classNode.isInterface() || Modifier.isAbstract(classNode.getModifiers())) { return; } boolean junit3Test = isJunit3Test(classNode); boolean spockTest = isSpockTest(classNode); boolean isJunit = classNode.getName().endsWith("Tests"); if (!junit3Test && !spockTest && !isJunit) return; Expression value = node.getMember("value"); ClassExpression ce; if (value instanceof ClassExpression) { ce = (ClassExpression) value; testFor(classNode, ce); } else { if (!junit3Test) { List<AnnotationNode> annotations = classNode.getAnnotations(MY_TYPE); if (annotations.size() > 0) return; // bail out, in this case it was already applied as a local transform // no explicit class specified try by convention String fileName = source.getName(); String className = GrailsResourceUtils.getClassName(new FileSystemResource(fileName)); if (className != null) { boolean isSpock = className.endsWith("Spec"); String targetClassName = null; if (isJunit) { targetClassName = className.substring(0, className.indexOf("Tests")); } else if (isSpock) { targetClassName = className.substring(0, className.indexOf("Spec")); } if (targetClassName != null) { Resource targetResource = getResourceLocator().findResourceForClassName(targetClassName); if (targetResource != null) { try { if (GrailsResourceUtils.isDomainClass(targetResource.getURL())) { testFor( classNode, new ClassExpression( new ClassNode(targetClassName, 0, ClassHelper.OBJECT_TYPE))); } else { for (String artefactType : artefactTypeToTestMap.keySet()) { if (targetClassName.endsWith(artefactType)) { testFor( classNode, new ClassExpression( new ClassNode(targetClassName, 0, ClassHelper.OBJECT_TYPE))); break; } } } } catch (IOException e) { // ignore } } } } } } } /** * Main entry point for the calling the TestForTransformation programmatically. * * @param classNode The class node that represents th test * @param ce The class expression that represents the class to test */ public void testFor(ClassNode classNode, ClassExpression ce) { boolean junit3Test = isJunit3Test(classNode); // make sure the 'log' property is not the one from GroovyTestCase FieldNode log = classNode.getField("log"); if (log == null || log.getDeclaringClass().equals(GROOVY_TEST_CASE_CLASS)) { LoggingTransformer.addLogField(classNode, classNode.getName()); } boolean isSpockTest = isSpockTest(classNode); if (!isSpockTest && !junit3Test) { // assume JUnit 4 Map<String, MethodNode> declaredMethodsMap = classNode.getDeclaredMethodsMap(); for (String methodName : declaredMethodsMap.keySet()) { MethodNode methodNode = declaredMethodsMap.get(methodName); if (isCandidateMethod(methodNode) && methodNode.getName().startsWith("test")) { if (methodNode.getAnnotations().size() == 0) { methodNode.addAnnotation(TEST_ANNOTATION); } } } } final MethodNode methodToAdd = weaveMock(classNode, ce, true); if (methodToAdd != null && junit3Test) { addMethodCallsToMethod(classNode, SET_UP_METHOD, Arrays.asList(methodToAdd)); } } private Map<ClassNode, List<Class>> wovenMixins = new HashMap<ClassNode, List<Class>>(); protected MethodNode weaveMock( ClassNode classNode, ClassExpression value, boolean isClassUnderTest) { ClassNode testTarget = value.getType(); String className = testTarget.getName(); MethodNode testForMethod = null; for (String artefactType : artefactTypeToTestMap.keySet()) { if (className.endsWith(artefactType)) { Class mixinClass = artefactTypeToTestMap.get(artefactType); if (!isAlreadyWoven(classNode, mixinClass)) { weaveMixinClass(classNode, mixinClass); if (isClassUnderTest) { testForMethod = addClassUnderTestMethod(classNode, value, artefactType); } else { addMockCollaboratorToSetup(classNode, value, artefactType); } return testForMethod; } addMockCollaboratorToSetup(classNode, value, artefactType); return null; } } // must be a domain class weaveMixinClass(classNode, DomainClassUnitTestMixin.class); if (isClassUnderTest) { testForMethod = addClassUnderTestMethod(classNode, value, DOMAIN_TYPE); } else { addMockCollaboratorToSetup(classNode, value, DOMAIN_TYPE); } return testForMethod; } private void addMockCollaboratorToSetup( ClassNode classNode, ClassExpression targetClassExpression, String artefactType) { BlockStatement methodBody; if (isJunit3Test(classNode)) { methodBody = getJunit3Setup(classNode); addMockCollaborator(artefactType, targetClassExpression, methodBody); } else { addToJunit4BeforeMethods(classNode, artefactType, targetClassExpression); } } private void addToJunit4BeforeMethods( ClassNode classNode, String artefactType, ClassExpression targetClassExpression) { Map<String, MethodNode> declaredMethodsMap = classNode.getDeclaredMethodsMap(); boolean weavedIntoBeforeMethods = false; for (MethodNode methodNode : declaredMethodsMap.values()) { if (isDeclaredBeforeMethod(methodNode)) { Statement code = getMethodBody(methodNode); addMockCollaborator(artefactType, targetClassExpression, (BlockStatement) code); weavedIntoBeforeMethods = true; } } if (!weavedIntoBeforeMethods) { BlockStatement junit4Setup = getJunit4Setup(classNode); addMockCollaborator(artefactType, targetClassExpression, junit4Setup); } } private Statement getMethodBody(MethodNode methodNode) { Statement code = methodNode.getCode(); if (!(code instanceof BlockStatement)) { BlockStatement body = new BlockStatement(); body.addStatement(code); code = body; } return code; } private boolean isDeclaredBeforeMethod(MethodNode methodNode) { return isPublicInstanceMethod(methodNode) && hasAnnotation(methodNode, Before.class) && !hasAnnotation(methodNode, MixinMethod.class); } private boolean isPublicInstanceMethod(MethodNode methodNode) { return !methodNode.isSynthetic() && !methodNode.isStatic() && methodNode.isPublic(); } private BlockStatement getJunit4Setup(ClassNode classNode) { MethodNode setupMethod = classNode.getMethod(SET_UP_METHOD, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (setupMethod == null) { setupMethod = new MethodNode( SET_UP_METHOD, Modifier.PUBLIC, ClassHelper.VOID_TYPE, GrailsArtefactClassInjector.ZERO_PARAMETERS, null, new BlockStatement()); setupMethod.addAnnotation(MIXIN_METHOD_ANNOTATION); classNode.addMethod(setupMethod); } if (setupMethod.getAnnotations(BEFORE_CLASS_NODE).size() == 0) { setupMethod.addAnnotation(BEFORE_ANNOTATION); } return getOrCreateMethodBody(classNode, setupMethod, SET_UP_METHOD); } private BlockStatement getJunit3Setup(ClassNode classNode) { return getOrCreateNoArgsMethodBody(classNode, SET_UP_METHOD); } private boolean isAlreadyWoven(ClassNode classNode, Class mixinClass) { List<Class> mixinClasses = wovenMixins.get(classNode); if (mixinClasses == null) { mixinClasses = new ArrayList<Class>(); mixinClasses.add(mixinClass); wovenMixins.put(classNode, mixinClasses); } else { if (mixinClasses.contains(mixinClass)) { return true; } mixinClasses.add(mixinClass); } return false; } protected void weaveMixinClass(ClassNode classNode, Class mixinClass) { ListExpression listExpression = new ListExpression(); listExpression.addExpression(new ClassExpression(new ClassNode(mixinClass))); weaveMixinsIntoClass(classNode, listExpression); } protected MethodNode addClassUnderTestMethod( ClassNode classNode, ClassExpression targetClass, String type) { String methodName = "setup" + type + "UnderTest"; String fieldName = GrailsNameUtils.getPropertyName(type); String getterName = GrailsNameUtils.getGetterName(fieldName); fieldName = '$' + fieldName; if (classNode.getField(fieldName) == null) { classNode.addField(fieldName, Modifier.PRIVATE, targetClass.getType(), null); } MethodNode methodNode = classNode.getMethod(methodName, GrailsArtefactClassInjector.ZERO_PARAMETERS); VariableExpression fieldExpression = new VariableExpression(fieldName); if (methodNode == null) { BlockStatement setupMethodBody = new BlockStatement(); addMockCollaborator(type, targetClass, setupMethodBody); methodNode = new MethodNode( methodName, Modifier.PUBLIC, ClassHelper.VOID_TYPE, GrailsArtefactClassInjector.ZERO_PARAMETERS, null, setupMethodBody); methodNode.addAnnotation(BEFORE_ANNOTATION); methodNode.addAnnotation(MIXIN_METHOD_ANNOTATION); classNode.addMethod(methodNode); } MethodNode getter = classNode.getMethod(getterName, GrailsArtefactClassInjector.ZERO_PARAMETERS); if (getter == null) { BlockStatement getterBody = new BlockStatement(); getter = new MethodNode( getterName, Modifier.PUBLIC, targetClass.getType().getPlainNodeReference(), GrailsArtefactClassInjector.ZERO_PARAMETERS, null, getterBody); BinaryExpression testTargetAssignment = new BinaryExpression( fieldExpression, ASSIGN, new ConstructorCallExpression( targetClass.getType(), GrailsArtefactClassInjector.ZERO_ARGS)); IfStatement autowiringIfStatement = getAutowiringIfStatement(targetClass, fieldExpression, testTargetAssignment); getterBody.addStatement(autowiringIfStatement); getterBody.addStatement(new ReturnStatement(fieldExpression)); classNode.addMethod(getter); } return methodNode; } private IfStatement getAutowiringIfStatement( ClassExpression targetClass, VariableExpression fieldExpression, BinaryExpression testTargetAssignment) { VariableExpression appCtxVar = new VariableExpression("applicationContext"); BooleanExpression applicationContextCheck = new BooleanExpression( new BinaryExpression( new BinaryExpression( fieldExpression, GrailsASTUtils.EQUALS_OPERATOR, GrailsASTUtils.NULL_EXPRESSION), Token.newSymbol("&&", 0, 0), new BinaryExpression( appCtxVar, GrailsASTUtils.NOT_EQUALS_OPERATOR, GrailsASTUtils.NULL_EXPRESSION))); BlockStatement performAutowireBlock = new BlockStatement(); ArgumentListExpression arguments = new ArgumentListExpression(); arguments.addExpression(fieldExpression); arguments.addExpression(new ConstantExpression(1)); arguments.addExpression(new ConstantExpression(false)); BlockStatement assignFromApplicationContext = new BlockStatement(); ArgumentListExpression argWithClassName = new ArgumentListExpression(); MethodCallExpression getClassNameMethodCall = new MethodCallExpression(targetClass, "getName", new ArgumentListExpression()); argWithClassName.addExpression(getClassNameMethodCall); assignFromApplicationContext.addStatement( new ExpressionStatement( new BinaryExpression( fieldExpression, ASSIGN, new MethodCallExpression(appCtxVar, "getBean", argWithClassName)))); BlockStatement elseBlock = new BlockStatement(); elseBlock.addStatement(new ExpressionStatement(testTargetAssignment)); performAutowireBlock.addStatement( new IfStatement( new BooleanExpression( new MethodCallExpression(appCtxVar, "containsBean", argWithClassName)), assignFromApplicationContext, elseBlock)); performAutowireBlock.addStatement( new ExpressionStatement( new MethodCallExpression( new PropertyExpression(appCtxVar, "autowireCapableBeanFactory"), "autowireBeanProperties", arguments))); return new IfStatement(applicationContextCheck, performAutowireBlock, new BlockStatement()); } protected void addMockCollaborator( String mockType, ClassExpression targetClass, BlockStatement methodBody) { ArgumentListExpression args = new ArgumentListExpression(); args.addExpression(targetClass); methodBody .getStatements() .add( 0, new ExpressionStatement( new MethodCallExpression(THIS_EXPRESSION, "mock" + mockType, args))); } }
/** * Handles transformation for the @PackageScope annotation. * * <p>Both the deprecated groovy.lang.PackageScope and groovy.transform.PackageScope annotations are * supported. The former will be removed in a future version of Groovy. * * @author Paul King */ @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class PackageScopeASTTransformation implements ASTTransformation, Opcodes { private static final Class MY_CLASS = PackageScope.class; private static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS); private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); private static final String LEGACY_TYPE_NAME = "groovy.lang.PackageScope"; private static final Class TARGET_CLASS = groovy.transform.PackageScopeTarget.class; private static final String TARGET_CLASS_NAME = ClassHelper.make(TARGET_CLASS).getNameWithoutPackage(); public void visit(ASTNode[] nodes, SourceUnit source) { if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) { throw new RuntimeException( "Internal error: expecting [AnnotationNode, AnnotatedNode] but got: " + Arrays.asList(nodes)); } AnnotatedNode parent = (AnnotatedNode) nodes[1]; AnnotationNode node = (AnnotationNode) nodes[0]; boolean legacyMode = LEGACY_TYPE_NAME.equals(node.getClassNode().getName()); if (!MY_TYPE.equals(node.getClassNode()) && !legacyMode) return; Expression value = node.getMember("value"); if (parent instanceof ClassNode) { List<groovy.transform.PackageScopeTarget> targets; if (value == null) targets = Arrays.asList(legacyMode ? PackageScopeTarget.FIELDS : PackageScopeTarget.CLASS); else targets = determineTargets(value); visitClassNode((ClassNode) parent, targets); parent.getAnnotations(); } else { if (value != null) throw new RuntimeException( "Error during " + MY_TYPE_NAME + " processing: " + TARGET_CLASS_NAME + " only allowed at class level."); if (parent instanceof MethodNode) { visitMethodNode((MethodNode) parent); } else if (parent instanceof FieldNode) { visitFieldNode((FieldNode) parent); } } } private void visitMethodNode(MethodNode methodNode) { if (methodNode.isSyntheticPublic()) revertVisibility(methodNode); else throw new RuntimeException( "Can't use " + MY_TYPE_NAME + " for method '" + methodNode.getName() + "' which has explicit visibility."); } private void visitClassNode(ClassNode cNode, List<PackageScopeTarget> value) { String cName = cNode.getName(); if (cNode.isInterface() && value.size() != 1 && value.get(0) != PackageScopeTarget.CLASS) { throw new RuntimeException( "Error processing interface '" + cName + "'. " + MY_TYPE_NAME + " not allowed for interfaces except when targeting Class level."); } if (value.contains(groovy.transform.PackageScopeTarget.CLASS)) { if (cNode.isSyntheticPublic()) revertVisibility(cNode); else throw new RuntimeException( "Can't use " + MY_TYPE_NAME + " for class '" + cNode.getName() + "' which has explicit visibility."); } if (value.contains(groovy.transform.PackageScopeTarget.METHODS)) { final List<MethodNode> mList = cNode.getMethods(); for (MethodNode mNode : mList) { if (mNode.isSyntheticPublic()) revertVisibility(mNode); } } if (value.contains(PackageScopeTarget.FIELDS)) { final List<PropertyNode> pList = cNode.getProperties(); List<PropertyNode> foundProps = new ArrayList<PropertyNode>(); List<String> foundNames = new ArrayList<String>(); for (PropertyNode pNode : pList) { foundProps.add(pNode); foundNames.add(pNode.getName()); } for (PropertyNode pNode : foundProps) { pList.remove(pNode); } final List<FieldNode> fList = cNode.getFields(); for (FieldNode fNode : fList) { if (foundNames.contains(fNode.getName())) { revertVisibility(fNode); } } } } private void visitFieldNode(FieldNode fNode) { final ClassNode cNode = fNode.getDeclaringClass(); final List<PropertyNode> pList = cNode.getProperties(); PropertyNode foundProp = null; for (PropertyNode pNode : pList) { if (pNode.getName().equals(fNode.getName())) { foundProp = pNode; break; } } if (foundProp != null) { revertVisibility(fNode); pList.remove(foundProp); } } private void revertVisibility(FieldNode fNode) { fNode.setModifiers(fNode.getModifiers() & ~ACC_PRIVATE); } private void revertVisibility(MethodNode mNode) { mNode.setModifiers(mNode.getModifiers() & ~ACC_PUBLIC); } private void revertVisibility(ClassNode cNode) { cNode.setModifiers(cNode.getModifiers() & ~ACC_PUBLIC); } private List<groovy.transform.PackageScopeTarget> determineTargets(Expression expr) { List<groovy.transform.PackageScopeTarget> list = new ArrayList<groovy.transform.PackageScopeTarget>(); if (expr instanceof PropertyExpression) { list.add(extractTarget((PropertyExpression) expr)); } else if (expr instanceof ListExpression) { final ListExpression expressionList = (ListExpression) expr; final List<Expression> expressions = expressionList.getExpressions(); for (Expression ex : expressions) { if (ex instanceof PropertyExpression) { list.add(extractTarget((PropertyExpression) ex)); } } } return list; } private groovy.transform.PackageScopeTarget extractTarget(PropertyExpression expr) { Expression oe = expr.getObjectExpression(); if (oe instanceof ClassExpression) { ClassExpression ce = (ClassExpression) oe; if (ce.getType().getName().equals("groovy.transform.PackageScopeTarget")) { Expression prop = expr.getProperty(); if (prop instanceof ConstantExpression) { String propName = (String) ((ConstantExpression) prop).getValue(); try { return PackageScopeTarget.valueOf(propName); } catch (IllegalArgumentException iae) { /* ignore */ } } } } throw new RuntimeException( "Internal error during " + MY_TYPE_NAME + " processing. Annotation parameters must be of type: " + TARGET_CLASS_NAME + "."); } }
/** * Handles transformation for the @ScriptURI annotation. * * @author Paul King * @author Cedric Champeau * @author Vladimir Orany * @author Jim White */ @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) public class SourceURIASTTransformation extends AbstractASTTransformation { private static final Class<SourceURI> MY_CLASS = SourceURI.class; private static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS); private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage(); private static final ClassNode URI_TYPE = ClassHelper.make(java.net.URI.class); public void visit(ASTNode[] nodes, SourceUnit source) { init(nodes, source); AnnotatedNode parent = (AnnotatedNode) nodes[1]; AnnotationNode node = (AnnotationNode) nodes[0]; if (!MY_TYPE.equals(node.getClassNode())) return; if (parent instanceof DeclarationExpression) { setScriptURIOnDeclaration((DeclarationExpression) parent, node); } else if (parent instanceof FieldNode) { setScriptURIOnField((FieldNode) parent, node); } else { addError( "Expected to find the annotation " + MY_TYPE_NAME + " on an declaration statement.", parent); } } private void setScriptURIOnDeclaration( final DeclarationExpression de, final AnnotationNode node) { if (de.isMultipleAssignmentDeclaration()) { addError( "Annotation " + MY_TYPE_NAME + " not supported with multiple assignment notation.", de); return; } if (!(de.getRightExpression() instanceof EmptyExpression)) { addError("Annotation " + MY_TYPE_NAME + " not supported with variable assignment.", de); return; } URI uri = getSourceURI(node); if (uri == null) { addError("Unable to get the URI for the source of this script!", de); } else { // Set the RHS to '= URI.create("string for this URI")'. // That may throw an IllegalArgumentExpression wrapping the URISyntaxException. de.setRightExpression(getExpression(uri)); } } private void setScriptURIOnField(final FieldNode fieldNode, final AnnotationNode node) { if (fieldNode.hasInitialExpression()) { addError( "Annotation " + MY_TYPE_NAME + " not supported with variable assignment.", fieldNode); return; } URI uri = getSourceURI(node); if (uri == null) { addError("Unable to get the URI for the source of this class!", fieldNode); } else { // Set the RHS to '= URI.create("string for this URI")'. // That may throw an IllegalArgumentExpression wrapping the URISyntaxException. fieldNode.setInitialValueExpression(getExpression(uri)); } } private Expression getExpression(URI uri) { return callX(URI_TYPE, "create", args(constX(uri.toString()))); } protected URI getSourceURI(AnnotationNode node) { URI uri = sourceUnit.getSource().getURI(); if (uri != null) { if (!(uri.isAbsolute() || memberHasValue(node, "allowRelative", true))) { // FIXME: What should we use as the base URI? // It is unlikely we get to this point with a relative URI since making a URL // from will make it absolute I think. But lets handle the simple case of // using file paths and turning that into an absolute file URI. // So we will use the current working directory as the base. URI baseURI = new File(".").toURI(); uri = uri.resolve(baseURI); } } return uri; } }
private void printClassContents(PrintWriter out, ClassNode classNode) throws FileNotFoundException { if (classNode instanceof InnerClassNode && ((InnerClassNode) classNode).isAnonymous()) { // if it is an anonymous inner class, don't generate the stub code for it. return; } try { Verifier verifier = new Verifier() { public void addCovariantMethods(ClassNode cn) {} protected void addTimeStamp(ClassNode node) {} protected void addInitialization(ClassNode node) {} protected void addPropertyMethod(MethodNode method) { doAddMethod(method); } protected void addReturnIfNeeded(MethodNode node) {} protected void addMethod( ClassNode node, boolean shouldBeSynthetic, String name, int modifiers, ClassNode returnType, Parameter[] parameters, ClassNode[] exceptions, Statement code) { doAddMethod( new MethodNode(name, modifiers, returnType, parameters, exceptions, code)); } protected void addConstructor( Parameter[] newParams, ConstructorNode ctor, Statement code, ClassNode node) { if (code instanceof ExpressionStatement) { // GROOVY-4508 Statement temp = code; code = new BlockStatement(); ((BlockStatement) code).addStatement(temp); } ConstructorNode ctrNode = new ConstructorNode(ctor.getModifiers(), newParams, ctor.getExceptions(), code); ctrNode.setDeclaringClass(node); constructors.add(ctrNode); } protected void addDefaultParameters(DefaultArgsAction action, MethodNode method) { final Parameter[] parameters = method.getParameters(); final Expression[] saved = new Expression[parameters.length]; for (int i = 0; i < parameters.length; i++) { if (parameters[i].hasInitialExpression()) saved[i] = parameters[i].getInitialExpression(); } super.addDefaultParameters(action, method); for (int i = 0; i < parameters.length; i++) { if (saved[i] != null) parameters[i].setInitialExpression(saved[i]); } } private void doAddMethod(MethodNode method) { String sig = method.getTypeDescriptor(); if (propertyMethodsWithSigs.containsKey(sig)) return; propertyMethods.add(method); propertyMethodsWithSigs.put(sig, method); } @Override protected void addDefaultConstructor(ClassNode node) { // not required for stub generation } }; verifier.visitClass(classNode); currentModule = classNode.getModule(); boolean isInterface = classNode.isInterface(); boolean isEnum = (classNode.getModifiers() & Opcodes.ACC_ENUM) != 0; boolean isAnnotationDefinition = classNode.isAnnotationDefinition(); printAnnotations(out, classNode); printModifiers( out, classNode.getModifiers() & ~(isInterface ? Opcodes.ACC_ABSTRACT : 0) & ~(isEnum ? Opcodes.ACC_FINAL : 0)); if (isInterface) { if (isAnnotationDefinition) { out.print("@"); } out.print("interface "); } else if (isEnum) { out.print("enum "); } else { out.print("class "); } String className = classNode.getNameWithoutPackage(); if (classNode instanceof InnerClassNode) className = className.substring(className.lastIndexOf("$") + 1); out.println(className); printGenericsBounds(out, classNode, true); ClassNode superClass = classNode.getUnresolvedSuperClass(false); if (!isInterface && !isEnum) { out.print(" extends "); printType(out, superClass); } ClassNode[] interfaces = classNode.getInterfaces(); if (interfaces != null && interfaces.length > 0 && !isAnnotationDefinition) { if (isInterface) { out.println(" extends"); } else { out.println(" implements"); } for (int i = 0; i < interfaces.length - 1; ++i) { out.print(" "); printType(out, interfaces[i]); out.print(","); } out.print(" "); printType(out, interfaces[interfaces.length - 1]); } out.println(" {"); printFields(out, classNode); printMethods(out, classNode, isEnum); for (Iterator<InnerClassNode> inner = classNode.getInnerClasses(); inner.hasNext(); ) { // GROOVY-4004: Clear the methods from the outer class so that they don't get duplicated in // inner ones propertyMethods.clear(); propertyMethodsWithSigs.clear(); constructors.clear(); printClassContents(out, inner.next()); } out.println("}"); } finally { propertyMethods.clear(); propertyMethodsWithSigs.clear(); constructors.clear(); currentModule = null; } }
private String getFullName(AnnotationNode anno, ClassNode buildee) { String builderClassName = getMemberStringValue(anno, "builderClassName", buildee.getNameWithoutPackage() + "Builder"); return buildee.getName() + "$" + builderClassName; }