http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java new file mode 100644 index 0000000..b2beb96 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java @@ -0,0 +1,805 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.ast.tools; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.PackageNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.VariableScope; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.AttributeExpression; +import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.BooleanExpression; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ClassExpression; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.DeclarationExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.FieldExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.NotExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; +import org.codehaus.groovy.ast.expr.TernaryExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.CatchStatement; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.ThrowStatement; +import org.codehaus.groovy.classgen.Verifier; +import org.codehaus.groovy.control.io.ReaderSource; +import org.codehaus.groovy.runtime.GeneratedClosure; +import org.codehaus.groovy.runtime.MetaClassHelper; +import org.codehaus.groovy.syntax.Token; +import org.codehaus.groovy.syntax.Types; +import org.codehaus.groovy.transform.AbstractASTTransformation; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IDENTICAL; + +/** + * Handy methods when working with the Groovy AST + */ +public class GeneralUtils { + public static final Token ASSIGN = Token.newSymbol(Types.ASSIGN, -1, -1); + public static final Token EQ = Token.newSymbol(Types.COMPARE_EQUAL, -1, -1); + public static final Token NE = Token.newSymbol(Types.COMPARE_NOT_EQUAL, -1, -1); + public static final Token NOT_IDENTICAL = Token.newSymbol(COMPARE_NOT_IDENTICAL, -1, -1); + public static final Token LT = Token.newSymbol(Types.COMPARE_LESS_THAN, -1, -1); + public static final Token AND = Token.newSymbol(Types.LOGICAL_AND, -1, -1); + public static final Token OR = Token.newSymbol(Types.LOGICAL_OR, -1, -1); + public static final Token CMP = Token.newSymbol(Types.COMPARE_TO, -1, -1); + private static final Token INSTANCEOF = Token.newSymbol(Types.KEYWORD_INSTANCEOF, -1, -1); + private static final Token PLUS = Token.newSymbol(Types.PLUS, -1, -1); + private static final Token INDEX = Token.newSymbol("[", -1, -1); + + public static BinaryExpression andX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, AND, rhv); + } + + public static ArgumentListExpression args(Expression... expressions) { + List<Expression> args = new ArrayList<Expression>(); + Collections.addAll(args, expressions); + return new ArgumentListExpression(args); + } + + public static ArgumentListExpression args(List<Expression> expressions) { + return new ArgumentListExpression(expressions); + } + + public static ArgumentListExpression args(Parameter[] parameters) { + return new ArgumentListExpression(parameters); + } + + public static ArgumentListExpression args(String... names) { + List<Expression> vars = new ArrayList<Expression>(); + for (String name : names) { + vars.add(varX(name)); + } + return new ArgumentListExpression(vars); + } + + public static Statement assignS(Expression target, Expression value) { + return new ExpressionStatement(assignX(target, value)); + } + + public static Expression assignX(Expression target, Expression value) { + return new BinaryExpression(target, ASSIGN, value); + } + + public static Expression attrX(Expression oe, Expression prop) { + return new AttributeExpression(oe, prop); + } + + public static BinaryExpression binX(Expression left, Token token, Expression right) { + return new BinaryExpression(left, token, right); + } + + public static BlockStatement block(VariableScope varScope, Statement... stmts) { + BlockStatement block = new BlockStatement(); + block.setVariableScope(varScope); + for (Statement stmt : stmts) block.addStatement(stmt); + return block; + } + + public static BlockStatement block(VariableScope varScope, List<Statement> stmts) { + BlockStatement block = new BlockStatement(); + block.setVariableScope(varScope); + for (Statement stmt : stmts) block.addStatement(stmt); + return block; + } + + public static BlockStatement block(Statement... stmts) { + BlockStatement block = new BlockStatement(); + for (Statement stmt : stmts) block.addStatement(stmt); + return block; + } + + public static MethodCallExpression callSuperX(String methodName, Expression args) { + return callX(varX("super"), methodName, args); + } + + public static MethodCallExpression callSuperX(String methodName) { + return callSuperX(methodName, MethodCallExpression.NO_ARGUMENTS); + } + + public static MethodCallExpression callThisX(String methodName, Expression args) { + return callX(varX("this"), methodName, args); + } + + public static MethodCallExpression callThisX(String methodName) { + return callThisX(methodName, MethodCallExpression.NO_ARGUMENTS); + } + + public static MethodCallExpression callX(Expression receiver, String methodName, Expression args) { + return new MethodCallExpression(receiver, methodName, args); + } + + public static MethodCallExpression callX(Expression receiver, Expression method, Expression args) { + return new MethodCallExpression(receiver, method, args); + } + + public static MethodCallExpression callX(Expression receiver, String methodName) { + return callX(receiver, methodName, MethodCallExpression.NO_ARGUMENTS); + } + + public static StaticMethodCallExpression callX(ClassNode receiver, String methodName, Expression args) { + return new StaticMethodCallExpression(receiver, methodName, args); + } + + public static StaticMethodCallExpression callX(ClassNode receiver, String methodName) { + return callX(receiver, methodName, MethodCallExpression.NO_ARGUMENTS); + } + + public static CastExpression castX(ClassNode type, Expression expression) { + return new CastExpression(type, expression); + } + + public static CastExpression castX(ClassNode type, Expression expression, boolean ignoreAutoboxing) { + return new CastExpression(type, expression, ignoreAutoboxing); + } + + public static ClassExpression classX(ClassNode clazz) { + return new ClassExpression(clazz); + } + + public static ClassExpression classX(Class clazz) { + return classX(ClassHelper.make(clazz).getPlainNodeReference()); + } + + public static ClosureExpression closureX(Parameter[] params, Statement code) { + return new ClosureExpression(params, code); + } + + public static ClosureExpression closureX(Statement code) { + return closureX(Parameter.EMPTY_ARRAY, code); + } + + public static Parameter[] cloneParams(Parameter[] source) { + Parameter[] result = new Parameter[source.length]; + for (int i = 0; i < source.length; i++) { + Parameter srcParam = source[i]; + Parameter dstParam = new Parameter(srcParam.getOriginType(), srcParam.getName()); + result[i] = dstParam; + } + return result; + } + + /** + * Build a binary expression that compares two values + * @param lhv expression for the value to compare from + * @param rhv expression for the value value to compare to + * @return the expression comparing two values + */ + public static BinaryExpression cmpX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, CMP, rhv); + } + + public static ConstantExpression constX(Object val) { + return new ConstantExpression(val); + } + + public static ConstantExpression constX(Object val, boolean keepPrimitive) { + return new ConstantExpression(val, keepPrimitive); + } + + /** + * Copies all <tt>candidateAnnotations</tt> with retention policy {@link java.lang.annotation.RetentionPolicy#RUNTIME} + * and {@link java.lang.annotation.RetentionPolicy#CLASS}. + * <p> + * Annotations with {@link org.codehaus.groovy.runtime.GeneratedClosure} members are not supported at present. + */ + public static void copyAnnotatedNodeAnnotations(final AnnotatedNode annotatedNode, final List<AnnotationNode> copied, List<AnnotationNode> notCopied) { + List<AnnotationNode> annotationList = annotatedNode.getAnnotations(); + for (AnnotationNode annotation : annotationList) { + + List<AnnotationNode> annotations = annotation.getClassNode().getAnnotations(AbstractASTTransformation.RETENTION_CLASSNODE); + if (annotations.isEmpty()) continue; + + if (hasClosureMember(annotation)) { + notCopied.add(annotation); + continue; + } + + AnnotationNode retentionPolicyAnnotation = annotations.get(0); + Expression valueExpression = retentionPolicyAnnotation.getMember("value"); + if (!(valueExpression instanceof PropertyExpression)) continue; + + PropertyExpression propertyExpression = (PropertyExpression) valueExpression; + boolean processAnnotation = + propertyExpression.getProperty() instanceof ConstantExpression && + ( + "RUNTIME".equals(((ConstantExpression) (propertyExpression.getProperty())).getValue()) || + "CLASS".equals(((ConstantExpression) (propertyExpression.getProperty())).getValue()) + ); + + if (processAnnotation) { + AnnotationNode newAnnotation = new AnnotationNode(annotation.getClassNode()); + for (Map.Entry<String, Expression> member : annotation.getMembers().entrySet()) { + newAnnotation.addMember(member.getKey(), member.getValue()); + } + newAnnotation.setSourcePosition(annotatedNode); + + copied.add(newAnnotation); + } + } + } + + public static Statement createConstructorStatementDefault(FieldNode fNode) { + final String name = fNode.getName(); + final ClassNode fType = fNode.getType(); + final Expression fieldExpr = propX(varX("this"), name); + Expression initExpr = fNode.getInitialValueExpression(); + Statement assignInit; + if (initExpr == null || (initExpr instanceof ConstantExpression && ((ConstantExpression)initExpr).isNullExpression())) { + if (ClassHelper.isPrimitiveType(fType)) { + assignInit = EmptyStatement.INSTANCE; + } else { + assignInit = assignS(fieldExpr, ConstantExpression.EMPTY_EXPRESSION); + } + } else { + assignInit = assignS(fieldExpr, initExpr); + } + fNode.setInitialValueExpression(null); + Expression value = findArg(name); + return ifElseS(equalsNullX(value), assignInit, assignS(fieldExpr, castX(fType, value))); + } + + public static ConstructorCallExpression ctorX(ClassNode type, Expression args) { + return new ConstructorCallExpression(type, args); + } + + public static ConstructorCallExpression ctorX(ClassNode type) { + return new ConstructorCallExpression(type, ArgumentListExpression.EMPTY_ARGUMENTS); + } + + public static Statement ctorSuperS(Expression args) { + return stmt(ctorX(ClassNode.SUPER, args)); + } + + public static Statement ctorThisS(Expression args) { + return stmt(ctorX(ClassNode.THIS, args)); + } + + public static Statement ctorSuperS() { + return stmt(ctorX(ClassNode.SUPER)); + } + + public static Statement ctorThisS() { + return stmt(ctorX(ClassNode.THIS)); + } + + public static Statement declS(Expression target, Expression init) { + return new ExpressionStatement(new DeclarationExpression(target, ASSIGN, init)); + } + + public static BinaryExpression eqX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, EQ, rhv); + } + + public static BooleanExpression equalsNullX(Expression argExpr) { + return new BooleanExpression(eqX(argExpr, new ConstantExpression(null))); + } + + public static FieldExpression fieldX(FieldNode fieldNode) { + return new FieldExpression(fieldNode); + } + + public static FieldExpression fieldX(ClassNode owner, String fieldName) { + return new FieldExpression(owner.getField(fieldName)); + } + + public static Expression findArg(String argName) { + return new PropertyExpression(new VariableExpression("args"), argName); + } + + public static List<MethodNode> getAllMethods(ClassNode type) { + ClassNode node = type; + List<MethodNode> result = new ArrayList<MethodNode>(); + while (node != null) { + result.addAll(node.getMethods()); + node = node.getSuperClass(); + } + return result; + } + + public static List<PropertyNode> getAllProperties(ClassNode type) { + ClassNode node = type; + List<PropertyNode> result = new ArrayList<PropertyNode>(); + while (node != null) { + result.addAll(node.getProperties()); + node = node.getSuperClass(); + } + return result; + } + + public static String getGetterName(PropertyNode pNode) { + return "get" + Verifier.capitalize(pNode.getName()); + } + + public static List<FieldNode> getInstanceNonPropertyFields(ClassNode cNode) { + final List<FieldNode> result = new ArrayList<FieldNode>(); + for (FieldNode fNode : cNode.getFields()) { + if (!fNode.isStatic() && cNode.getProperty(fNode.getName()) == null) { + result.add(fNode); + } + } + return result; + } + + public static List<String> getInstanceNonPropertyFieldNames(ClassNode cNode) { + List<FieldNode> fList = getInstanceNonPropertyFields(cNode); + List<String> result = new ArrayList<String>(fList.size()); + for (FieldNode fNode : fList) { + result.add(fNode.getName()); + } + return result; + } + + public static List<PropertyNode> getInstanceProperties(ClassNode cNode) { + final List<PropertyNode> result = new ArrayList<PropertyNode>(); + for (PropertyNode pNode : cNode.getProperties()) { + if (!pNode.isStatic()) { + result.add(pNode); + } + } + return result; + } + + public static List<String> getInstancePropertyNames(ClassNode cNode) { + List<PropertyNode> pList = BeanUtils.getAllProperties(cNode, false, false, true); + List<String> result = new ArrayList<String>(pList.size()); + for (PropertyNode pNode : pList) { + result.add(pNode.getName()); + } + return result; + } + + public static List<FieldNode> getInstancePropertyFields(ClassNode cNode) { + final List<FieldNode> result = new ArrayList<FieldNode>(); + for (PropertyNode pNode : cNode.getProperties()) { + if (!pNode.isStatic()) { + result.add(pNode.getField()); + } + } + return result; + } + + public static Set<ClassNode> getInterfacesAndSuperInterfaces(ClassNode type) { + Set<ClassNode> res = new LinkedHashSet<ClassNode>(); + if (type.isInterface()) { + res.add(type); + return res; + } + ClassNode next = type; + while (next != null) { + res.addAll(next.getAllInterfaces()); + next = next.getSuperClass(); + } + return res; + } + + public static List<FieldNode> getSuperNonPropertyFields(ClassNode cNode) { + final List<FieldNode> result; + if (cNode == ClassHelper.OBJECT_TYPE) { + result = new ArrayList<FieldNode>(); + } else { + result = getSuperNonPropertyFields(cNode.getSuperClass()); + } + for (FieldNode fNode : cNode.getFields()) { + if (!fNode.isStatic() && cNode.getProperty(fNode.getName()) == null) { + result.add(fNode); + } + } + return result; + } + + public static List<FieldNode> getSuperPropertyFields(ClassNode cNode) { + final List<FieldNode> result; + if (cNode == ClassHelper.OBJECT_TYPE) { + result = new ArrayList<FieldNode>(); + } else { + result = getSuperPropertyFields(cNode.getSuperClass()); + } + for (PropertyNode pNode : cNode.getProperties()) { + if (!pNode.isStatic()) { + result.add(pNode.getField()); + } + } + return result; + } + + public static BinaryExpression hasClassX(Expression instance, ClassNode cNode) { + return eqX(classX(cNode), callX(instance, "getClass")); + } + + private static boolean hasClosureMember(AnnotationNode annotation) { + + Map<String, Expression> members = annotation.getMembers(); + for (Map.Entry<String, Expression> member : members.entrySet()) { + if (member.getValue() instanceof ClosureExpression) return true; + + if (member.getValue() instanceof ClassExpression) { + ClassExpression classExpression = (ClassExpression) member.getValue(); + Class<?> typeClass = classExpression.getType().isResolved() ? classExpression.getType().redirect().getTypeClass() : null; + if (typeClass != null && GeneratedClosure.class.isAssignableFrom(typeClass)) return true; + } + } + + return false; + } + + public static boolean hasDeclaredMethod(ClassNode cNode, String name, int argsCount) { + List<MethodNode> ms = cNode.getDeclaredMethods(name); + for (MethodNode m : ms) { + Parameter[] paras = m.getParameters(); + if (paras != null && paras.length == argsCount) { + return true; + } + } + return false; + } + + public static BinaryExpression hasEqualFieldX(FieldNode fNode, Expression other) { + return eqX(varX(fNode), propX(other, fNode.getName())); + } + + public static BinaryExpression hasEqualPropertyX(ClassNode annotatedNode, PropertyNode pNode, VariableExpression other) { + return eqX(getterThisX(annotatedNode, pNode), getterX(other.getOriginType(), other, pNode)); + } + + @Deprecated + public static BinaryExpression hasEqualPropertyX(PropertyNode pNode, Expression other) { + String getterName = getGetterName(pNode); + return eqX(callThisX(getterName), callX(other, getterName)); + } + + public static BooleanExpression hasSameFieldX(FieldNode fNode, Expression other) { + return sameX(varX(fNode), propX(other, fNode.getName())); + } + + public static BooleanExpression hasSamePropertyX(PropertyNode pNode, Expression other) { + ClassNode cNode = pNode.getDeclaringClass(); + return sameX(getterThisX(cNode, pNode), getterX(cNode, other, pNode)); + } + + public static Statement ifElseS(Expression cond, Statement thenStmt, Statement elseStmt) { + return new IfStatement( + cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond), + thenStmt, + elseStmt + ); + } + + public static Statement ifS(Expression cond, Expression trueExpr) { + return ifS(cond, new ExpressionStatement(trueExpr)); + } + + public static Statement ifS(Expression cond, Statement trueStmt) { + return new IfStatement( + cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond), + trueStmt, + EmptyStatement.INSTANCE + ); + } + + public static Expression indexX(Expression target, Expression value) { + return new BinaryExpression(target, INDEX, value); + } + + public static BooleanExpression isInstanceOfX(Expression objectExpression, ClassNode cNode) { + return new BooleanExpression(new BinaryExpression(objectExpression, INSTANCEOF, classX(cNode))); + } + + public static BooleanExpression isOneX(Expression expr) { + return new BooleanExpression(new BinaryExpression(expr, EQ, new ConstantExpression(1))); + } + + public static boolean isOrImplements(ClassNode type, ClassNode interfaceType) { + return type.equals(interfaceType) || type.implementsInterface(interfaceType); + } + + public static BooleanExpression isTrueX(Expression argExpr) { + return new BooleanExpression(new BinaryExpression(argExpr, EQ, new ConstantExpression(Boolean.TRUE))); + } + + public static BooleanExpression isZeroX(Expression expr) { + return new BooleanExpression(new BinaryExpression(expr, EQ, new ConstantExpression(0))); + } + + public static BinaryExpression ltX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, LT, rhv); + } + + public static BinaryExpression notIdenticalX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, NOT_IDENTICAL, rhv); + } + + /** + * @deprecated use MethodNodeUtils#methodDescriptorWithoutReturnType(MethodNode) instead + */ + @Deprecated + public static String makeDescriptorWithoutReturnType(MethodNode mn) { + StringBuilder sb = new StringBuilder(); + sb.append(mn.getName()).append(':'); + for (Parameter p : mn.getParameters()) { + sb.append(p.getType()).append(','); + } + return sb.toString(); + } + + public static BinaryExpression neX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, NE, rhv); + } + + public static BooleanExpression notNullX(Expression argExpr) { + return new BooleanExpression(new BinaryExpression(argExpr, NE, new ConstantExpression(null))); + } + + public static NotExpression notX(Expression expr) { + return new NotExpression(expr instanceof BooleanExpression ? expr : new BooleanExpression(expr)); + } + + public static BinaryExpression orX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, OR, rhv); + } + + public static Parameter param(ClassNode type, String name) { + return param(type, name, null); + } + + public static Parameter param(ClassNode type, String name, Expression initialExpression) { + Parameter param = new Parameter(type, name); + if (initialExpression != null) { + param.setInitialExpression(initialExpression); + } + return param; + } + + public static Parameter[] params(Parameter... params) { + return params != null ? params : Parameter.EMPTY_ARRAY; + } + + public static BinaryExpression plusX(Expression lhv, Expression rhv) { + return new BinaryExpression(lhv, PLUS, rhv); + } + + public static Expression propX(Expression owner, String property) { + return new PropertyExpression(owner, property); + } + + public static Expression propX(Expression owner, Expression property) { + return new PropertyExpression(owner, property); + } + + public static Statement returnS(Expression expr) { + return new ReturnStatement(new ExpressionStatement(expr)); + } + + public static Statement safeExpression(Expression fieldExpr, Expression expression) { + return new IfStatement( + equalsNullX(fieldExpr), + new ExpressionStatement(fieldExpr), + new ExpressionStatement(expression)); + } + + public static BooleanExpression sameX(Expression self, Expression other) { + return new BooleanExpression(callX(self, "is", args(other))); + } + + public static Statement stmt(Expression expr) { + return new ExpressionStatement(expr); + } + + public static TernaryExpression ternaryX(Expression cond, Expression trueExpr, Expression elseExpr) { + return new TernaryExpression( + cond instanceof BooleanExpression ? (BooleanExpression) cond : new BooleanExpression(cond), + trueExpr, + elseExpr); + } + + public static VariableExpression varX(String name) { + return new VariableExpression(name); + } + + public static VariableExpression varX(Variable variable) { + return new VariableExpression(variable); + } + + public static VariableExpression varX(String name, ClassNode type) { + return new VariableExpression(name, type); + } + + public static ThrowStatement throwS(Expression expr) { + return new ThrowStatement(expr); + } + + public static CatchStatement catchS(Parameter variable, Statement code) { + return new CatchStatement(variable, code); + } + + /** + * This method is similar to {@link #propX(Expression, Expression)} but will make sure that if the property + * being accessed is defined inside the classnode provided as a parameter, then a getter call is generated + * instead of a field access. + * @param annotatedNode the class node where the property node is accessed from + * @param pNode the property being accessed + * @return a method call expression or a property expression + */ + public static Expression getterThisX(ClassNode annotatedNode, PropertyNode pNode) { + ClassNode owner = pNode.getDeclaringClass(); + if (annotatedNode.equals(owner)) { + return callThisX(getterName(annotatedNode, pNode)); + } + return propX(new VariableExpression("this"), pNode.getName()); + } + + private static String getterName(ClassNode annotatedNode, PropertyNode pNode) { + String getterName = "get" + MetaClassHelper.capitalize(pNode.getName()); + boolean existingExplicitGetter = annotatedNode.getMethod(getterName, Parameter.EMPTY_ARRAY) != null; + if (ClassHelper.boolean_TYPE.equals(pNode.getOriginType()) && !existingExplicitGetter) { + getterName = "is" + MetaClassHelper.capitalize(pNode.getName()); + } + return getterName; + } + + /** + * This method is similar to {@link #propX(Expression, Expression)} but will make sure that if the property + * being accessed is defined inside the classnode provided as a parameter, then a getter call is generated + * instead of a field access. + * @param annotatedNode the class node where the property node is accessed from + * @param receiver the object having the property + * @param pNode the property being accessed + * @return a method call expression or a property expression + */ + public static Expression getterX(ClassNode annotatedNode, Expression receiver, PropertyNode pNode) { + ClassNode owner = pNode.getDeclaringClass(); + if (annotatedNode.equals(owner)) { + return callX(receiver, getterName(annotatedNode, pNode)); + } + return propX(receiver, pNode.getName()); + } + + /** + * Converts an expression into the String source. Only some specific expressions like closure expression + * support this. + * + * @param readerSource a source + * @param expression an expression. Can't be null + * @return the source the closure was created from + * @throws java.lang.IllegalArgumentException when expression is null + * @throws java.lang.Exception when closure can't be read from source + */ + public static String convertASTToSource(ReaderSource readerSource, ASTNode expression) throws Exception { + if (expression == null) throw new IllegalArgumentException("Null: expression"); + + StringBuilder result = new StringBuilder(); + for (int x = expression.getLineNumber(); x <= expression.getLastLineNumber(); x++) { + String line = readerSource.getLine(x, null); + if (line == null) { + throw new Exception( + "Error calculating source code for expression. Trying to read line " + x + " from " + readerSource.getClass() + ); + } + if (x == expression.getLastLineNumber()) { + line = line.substring(0, expression.getLastColumnNumber() - 1); + } + if (x == expression.getLineNumber()) { + line = line.substring(expression.getColumnNumber() - 1); + } + //restoring line breaks is important b/c of lack of semicolons + result.append(line).append('\n'); + } + + + String source = result.toString().trim(); + + return source; + } + + public static boolean copyStatementsWithSuperAdjustment(ClosureExpression pre, BlockStatement body) { + Statement preCode = pre.getCode(); + boolean changed = false; + if (preCode instanceof BlockStatement) { + BlockStatement block = (BlockStatement) preCode; + List<Statement> statements = block.getStatements(); + for (int i = 0; i < statements.size(); i++) { + Statement statement = statements.get(i); + // adjust the first statement if it's a super call + if (i == 0 && statement instanceof ExpressionStatement) { + ExpressionStatement es = (ExpressionStatement) statement; + Expression preExp = es.getExpression(); + if (preExp instanceof MethodCallExpression) { + MethodCallExpression mce = (MethodCallExpression) preExp; + String name = mce.getMethodAsString(); + if ("super".equals(name)) { + es.setExpression(new ConstructorCallExpression(ClassNode.SUPER, mce.getArguments())); + changed = true; + } + } + } + body.addStatement(statement); + } + } + return changed; + } + + public static String getSetterName(String name) { + return "set" + Verifier.capitalize(name); + } + + public static boolean isDefaultVisibility(int modifiers) { + return (modifiers & (Modifier.PRIVATE | Modifier.PUBLIC | Modifier.PROTECTED)) == 0; + } + + public static boolean inSamePackage(ClassNode first, ClassNode second) { + PackageNode firstPackage = first.getPackage(); + PackageNode secondPackage = second.getPackage(); + return ((firstPackage == null && secondPackage == null) || + firstPackage != null && secondPackage != null && firstPackage.getName().equals(secondPackage.getName())); + } + + public static boolean inSamePackage(Class first, Class second) { + Package firstPackage = first.getPackage(); + Package secondPackage = second.getPackage(); + return ((firstPackage == null && secondPackage == null) || + firstPackage != null && secondPackage != null && firstPackage.getName().equals(secondPackage.getName())); + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java new file mode 100644 index 0000000..653f327 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -0,0 +1,614 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.ast.tools; + +import antlr.RecognitionException; +import antlr.TokenStreamException; +import groovy.transform.stc.IncorrectTypeHintException; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.antlr.AntlrParserPlugin; +import org.codehaus.groovy.antlr.parser.GroovyLexer; +import org.codehaus.groovy.antlr.parser.GroovyRecognizer; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.ResolveVisitor; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.syntax.ParserException; +import org.codehaus.groovy.syntax.Reduction; +import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Utility methods to deal with generic types. + * + * @author Cedric Champeau + * @author Paul King + */ +public class GenericsUtils { + public static final GenericsType[] EMPTY_GENERICS_ARRAY = new GenericsType[0]; + + /** + * Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a + * class uses generic type <pre><T,U,V></pre> (redirectGenericTypes), is used with actual type parameters + * <pre><java.lang.String, U,V></pre>, then a class or interface using generic types <pre><T,V></pre> + * will be aligned to <pre><java.lang.String,V></pre> + * @param redirectGenericTypes the type arguments or the redirect class node + * @param parameterizedTypes the actual type arguments used on this class node + * @param alignmentTarget the generic type arguments to which we want to align to + * @return aligned type arguments + * @deprecated You shouldn't call this method because it is inherently unreliable + */ + @Deprecated + public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGenericTypes, final GenericsType[] parameterizedTypes, final GenericsType[] alignmentTarget) { + if (alignmentTarget==null) return EMPTY_GENERICS_ARRAY; + if (parameterizedTypes==null || parameterizedTypes.length==0) return alignmentTarget; + GenericsType[] generics = new GenericsType[alignmentTarget.length]; + for (int i = 0, scgtLength = alignmentTarget.length; i < scgtLength; i++) { + final GenericsType currentTarget = alignmentTarget[i]; + GenericsType match = null; + if (redirectGenericTypes!=null) { + for (int j = 0; j < redirectGenericTypes.length && match == null; j++) { + GenericsType redirectGenericType = redirectGenericTypes[j]; + if (redirectGenericType.isCompatibleWith(currentTarget.getType())) { + if (currentTarget.isPlaceholder() && redirectGenericType.isPlaceholder() && !currentTarget.getName().equals(redirectGenericType.getName())) { + // check if there's a potential better match + boolean skip = false; + for (int k=j+1; k<redirectGenericTypes.length && !skip; k++) { + GenericsType ogt = redirectGenericTypes[k]; + if (ogt.isPlaceholder() && ogt.isCompatibleWith(currentTarget.getType()) && ogt.getName().equals(currentTarget.getName())) { + skip = true; + } + } + if (skip) continue; + } + match = parameterizedTypes[j]; + if (currentTarget.isWildcard()) { + // if alignment target is a wildcard type + // then we must make best effort to return a parameterized + // wildcard + ClassNode lower = currentTarget.getLowerBound()!=null?match.getType():null; + ClassNode[] currentUpper = currentTarget.getUpperBounds(); + ClassNode[] upper = currentUpper !=null?new ClassNode[currentUpper.length]:null; + if (upper!=null) { + for (int k = 0; k < upper.length; k++) { + upper[k] = currentUpper[k].isGenericsPlaceHolder()?match.getType():currentUpper[k]; + } + } + match = new GenericsType(ClassHelper.makeWithoutCaching("?"), upper, lower); + match.setWildcard(true); + } + } + } + } + if (match == null) { + match = currentTarget; + } + generics[i]=match; + } + return generics; + } + + /** + * Generates a wildcard generic type in order to be used for checks against class nodes. + * See {@link GenericsType#isCompatibleWith(org.codehaus.groovy.ast.ClassNode)}. + * @param types the type to be used as the wildcard upper bound + * @return a wildcard generics type + */ + public static GenericsType buildWildcardType(final ClassNode... types) { + ClassNode base = ClassHelper.makeWithoutCaching("?"); + GenericsType gt = new GenericsType(base, types, null); + gt.setWildcard(true); + return gt; + } + + public static Map<String, GenericsType> extractPlaceholders(ClassNode cn) { + Map<String, GenericsType> ret = new HashMap<String, GenericsType>(); + extractPlaceholders(cn, ret); + return ret; + } + + /** + * For a given classnode, fills in the supplied map with the parameterized + * types it defines. + * @param node + * @param map + */ + public static void extractPlaceholders(ClassNode node, Map<String, GenericsType> map) { + if (node == null) return; + + if (node.isArray()) { + extractPlaceholders(node.getComponentType(), map); + return; + } + + if (!node.isUsingGenerics() || !node.isRedirectNode()) return; + GenericsType[] parameterized = node.getGenericsTypes(); + if (parameterized == null || parameterized.length == 0) return; + GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes(); + if (redirectGenericsTypes==null) redirectGenericsTypes = parameterized; + for (int i = 0; i < redirectGenericsTypes.length; i++) { + GenericsType redirectType = redirectGenericsTypes[i]; + if (redirectType.isPlaceholder()) { + String name = redirectType.getName(); + if (!map.containsKey(name)) { + GenericsType value = parameterized[i]; + map.put(name, value); + if (value.isWildcard()) { + ClassNode lowerBound = value.getLowerBound(); + if (lowerBound!=null) { + extractPlaceholders(lowerBound, map); + } + ClassNode[] upperBounds = value.getUpperBounds(); + if (upperBounds!=null) { + for (ClassNode upperBound : upperBounds) { + extractPlaceholders(upperBound, map); + } + } + } else if (!value.isPlaceholder()) { + extractPlaceholders(value.getType(), map); + } + } + } + } + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + * @deprecated Use #parameterizeType instead + */ + @Deprecated + public static ClassNode parameterizeInterfaceGenerics(final ClassNode hint, final ClassNode target) { + return parameterizeType(hint, target); + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + */ + public static ClassNode parameterizeType(final ClassNode hint, final ClassNode target) { + if (hint.isArray()) { + if (target.isArray()) { + return parameterizeType(hint.getComponentType(), target.getComponentType()).makeArray(); + } + return target; + } + if (!target.equals(hint) && StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(target, hint)) { + ClassNode nextSuperClass = ClassHelper.getNextSuperClass(target, hint); + if (!hint.equals(nextSuperClass)) { + Map<String, ClassNode> genericsSpec = createGenericsSpec(hint); + extractSuperClassGenerics(hint, nextSuperClass, genericsSpec); + ClassNode result = correctToGenericsSpecRecurse(genericsSpec, nextSuperClass); + return parameterizeType(result, target); + } + } + Map<String, ClassNode> genericsSpec = createGenericsSpec(hint); + ClassNode targetRedirect = target.redirect(); + genericsSpec = createGenericsSpec(targetRedirect, genericsSpec); + extractSuperClassGenerics(hint, targetRedirect, genericsSpec); + return correctToGenericsSpecRecurse(genericsSpec, targetRedirect); + + } + + public static ClassNode nonGeneric(ClassNode type) { + if (type.isUsingGenerics()) { + final ClassNode nonGen = ClassHelper.makeWithoutCaching(type.getName()); + nonGen.setRedirect(type); + nonGen.setGenericsTypes(null); + nonGen.setUsingGenerics(false); + return nonGen; + } + if (type.isArray() && type.getComponentType().isUsingGenerics()) { + return type.getComponentType().getPlainNodeReference().makeArray(); + } + return type; + } + + public static ClassNode newClass(ClassNode type) { + return type.getPlainNodeReference(); + } + + public static ClassNode makeClassSafe(Class klass) { + return makeClassSafeWithGenerics(ClassHelper.make(klass)); + } + + public static ClassNode makeClassSafeWithGenerics(Class klass, ClassNode genericsType) { + GenericsType[] genericsTypes = new GenericsType[1]; + genericsTypes[0] = new GenericsType(genericsType); + return makeClassSafeWithGenerics(ClassHelper.make(klass), genericsTypes); + } + + public static ClassNode makeClassSafe0(ClassNode type, GenericsType... genericTypes) { + ClassNode plainNodeReference = newClass(type); + if (genericTypes != null && genericTypes.length > 0) { + plainNodeReference.setGenericsTypes(genericTypes); + if (type.isGenericsPlaceHolder()) plainNodeReference.setGenericsPlaceHolder(true); + } + return plainNodeReference; + } + + public static ClassNode makeClassSafeWithGenerics(ClassNode type, GenericsType... genericTypes) { + if (type.isArray()) { + return makeClassSafeWithGenerics(type.getComponentType(), genericTypes).makeArray(); + } + GenericsType[] gtypes = new GenericsType[0]; + if (genericTypes != null) { + gtypes = new GenericsType[genericTypes.length]; + System.arraycopy(genericTypes, 0, gtypes, 0, gtypes.length); + } + return makeClassSafe0(type, gtypes); + } + + public static MethodNode correctToGenericsSpec(Map<String,ClassNode> genericsSpec, MethodNode mn) { + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, mn.getReturnType()); + Parameter[] origParameters = mn.getParameters(); + Parameter[] newParameters = new Parameter[origParameters.length]; + for (int i = 0; i < origParameters.length; i++) { + Parameter origParameter = origParameters[i]; + newParameters[i] = new Parameter(correctToGenericsSpecRecurse(genericsSpec, origParameter.getType()), origParameter.getName(), origParameter.getInitialExpression()); + } + return new MethodNode(mn.getName(), mn.getModifiers(), correctedType, newParameters, mn.getExceptions(), mn.getCode()); + } + + public static ClassNode correctToGenericsSpecRecurse(Map<String,ClassNode> genericsSpec, ClassNode type) { + return correctToGenericsSpecRecurse(genericsSpec, type, new ArrayList<String>()); + } + + /** + * @since 2.4.1 + */ + public static ClassNode[] correctToGenericsSpecRecurse(Map<String,ClassNode> genericsSpec, ClassNode[] types) { + if (types==null || types.length==1) return types; + ClassNode[] newTypes = new ClassNode[types.length]; + boolean modified = false; + for (int i=0; i<types.length; i++) { + newTypes[i] = correctToGenericsSpecRecurse(genericsSpec, types[i], new ArrayList<String>()); + modified = modified || (types[i]!=newTypes[i]); + } + if (!modified) return types; + return newTypes; + } + + public static ClassNode correctToGenericsSpecRecurse(Map<String,ClassNode> genericsSpec, ClassNode type, List<String> exclusions) { + if (type.isArray()) { + return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray(); + } + if (type.isGenericsPlaceHolder() && !exclusions.contains(type.getUnresolvedName())) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + if (type != null && type.isGenericsPlaceHolder() && type.getGenericsTypes() == null) { + ClassNode placeholder = ClassHelper.makeWithoutCaching(type.getUnresolvedName()); + placeholder.setGenericsPlaceHolder(true); + type = makeClassSafeWithGenerics(type, new GenericsType(placeholder)); + } + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + GenericsType[] oldgTypes = type.getGenericsTypes(); + GenericsType[] newgTypes = GenericsType.EMPTY_ARRAY; + if (oldgTypes != null) { + newgTypes = new GenericsType[oldgTypes.length]; + for (int i = 0; i < newgTypes.length; i++) { + GenericsType oldgType = oldgTypes[i]; + if (oldgType.isPlaceholder() ) { + if (genericsSpec.get(oldgType.getName())!=null) { + newgTypes[i] = new GenericsType(genericsSpec.get(oldgType.getName())); + } else { + newgTypes[i] = new GenericsType(ClassHelper.OBJECT_TYPE); + } + } else if (oldgType.isWildcard()) { + ClassNode oldLower = oldgType.getLowerBound(); + ClassNode lower = oldLower!=null?correctToGenericsSpecRecurse(genericsSpec, oldLower, exclusions):null; + ClassNode[] oldUpper = oldgType.getUpperBounds(); + ClassNode[] upper = null; + if (oldUpper!=null) { + upper = new ClassNode[oldUpper.length]; + for (int j = 0; j < oldUpper.length; j++) { + upper[j] = correctToGenericsSpecRecurse(genericsSpec,oldUpper[j], exclusions); + } + } + GenericsType fixed = new GenericsType(oldgType.getType(), upper, lower); + fixed.setName(oldgType.getName()); + fixed.setWildcard(true); + newgTypes[i] = fixed; + } else { + newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec,correctToGenericsSpec(genericsSpec, oldgType), exclusions)); + } + } + } + return makeClassSafeWithGenerics(type, newgTypes); + } + + public static ClassNode correctToGenericsSpec(Map<String, ClassNode> genericsSpec, GenericsType type) { + ClassNode ret = null; + if (type.isPlaceholder()) { + String name = type.getName(); + ret = genericsSpec.get(name); + } + if (ret == null) ret = type.getType(); + return ret; + } + + public static ClassNode correctToGenericsSpec(Map<String,ClassNode> genericsSpec, ClassNode type) { + if (type.isArray()) { + return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray(); + } + if (type.isGenericsPlaceHolder()) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + return type; + } + + @SuppressWarnings("unchecked") + public static Map<String,ClassNode> createGenericsSpec(ClassNode current) { + return createGenericsSpec(current, Collections.EMPTY_MAP); + } + + public static Map<String,ClassNode> createGenericsSpec(ClassNode current, Map<String,ClassNode> oldSpec) { + Map<String,ClassNode> ret = new HashMap<String,ClassNode>(oldSpec); + // ret contains the type specs, what we now need is the type spec for the + // current class. To get that we first apply the type parameters to the + // current class and then use the type names of the current class to reset + // the map. Example: + // class A<V,W,X>{} + // class B<T extends Number> extends A<T,Long,String> {} + // first we have: T->Number + // we apply it to A<T,Long,String> -> A<Number,Long,String> + // resulting in: V->Number,W->Long,X->String + + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + ClassNode[] spec = new ClassNode[sgts.length]; + for (int i = 0; i < spec.length; i++) { + spec[i] = correctToGenericsSpec(ret, sgts[i]); + } + GenericsType[] newGts = current.redirect().getGenericsTypes(); + if (newGts == null) return ret; + ret.clear(); + for (int i = 0; i < spec.length; i++) { + ret.put(newGts[i].getName(), spec[i]); + } + } + return ret; + } + + public static Map<String,ClassNode> addMethodGenerics(MethodNode current, Map<String,ClassNode> oldSpec) { + Map<String,ClassNode> ret = new HashMap<String,ClassNode>(oldSpec); + // ret starts with the original type specs, now add gts for the current method if any + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + for (GenericsType sgt : sgts) { + ret.put(sgt.getName(), sgt.getType()); + } + } + return ret; + } + + public static void extractSuperClassGenerics(ClassNode type, ClassNode target, Map<String,ClassNode> spec) { + // TODO: this method is very similar to StaticTypesCheckingSupport#extractGenericsConnections, + // but operates on ClassNodes instead of GenericsType + if (target==null || type==target) return; + if (type.isArray() && target.isArray()) { + extractSuperClassGenerics(type.getComponentType(), target.getComponentType(), spec); + } else if (type.isArray() && target.getName().equals("java.lang.Object")) { + // Object is superclass of arrays but no generics involved + } else if (target.isGenericsPlaceHolder() || type.equals(target) || !StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(type, target)) { + // structural match route + if (target.isGenericsPlaceHolder()) { + spec.put(target.getGenericsTypes()[0].getName(),type); + } else { + extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); + } + } else { + // have first to find matching super class or interface + Map <String,ClassNode> genSpec = createGenericsSpec(type); + ClassNode superClass = ClassHelper.getNextSuperClass(type,target); + if (superClass!=null){ + ClassNode corrected = GenericsUtils.correctToGenericsSpecRecurse(genSpec, superClass); + extractSuperClassGenerics(corrected, target, spec); + } else { + // if we reach here, we have an unhandled case + throw new GroovyBugError("The type "+type+" seems not to normally extend "+target+". Sorry, I cannot handle this."); + } + } + } + + private static void extractSuperClassGenerics(GenericsType[] usage, GenericsType[] declaration, Map<String, ClassNode> spec) { + // if declaration does not provide generics, there is no connection to make + if (usage==null || declaration==null || declaration.length==0) return; + if (usage.length!=declaration.length) return; + + // both have generics + for (int i=0; i<usage.length; i++) { + GenericsType ui = usage[i]; + GenericsType di = declaration[i]; + if (di.isPlaceholder()) { + spec.put(di.getName(), ui.getType()); + } else if (di.isWildcard()){ + if (ui.isWildcard()) { + extractSuperClassGenerics(ui.getLowerBound(), di.getLowerBound(), spec); + extractSuperClassGenerics(ui.getUpperBounds(), di.getUpperBounds(), spec); + } else { + ClassNode cu = ui.getType(); + extractSuperClassGenerics(cu, di.getLowerBound(), spec); + ClassNode[] upperBounds = di.getUpperBounds(); + if (upperBounds!=null) { + for (ClassNode cn : upperBounds) { + extractSuperClassGenerics(cu, cn, spec); + } + } + } + } else { + extractSuperClassGenerics(ui.getType(), di.getType(), spec); + } + } + } + + private static void extractSuperClassGenerics(ClassNode[] usage, ClassNode[] declaration, Map<String, ClassNode> spec) { + if (usage==null || declaration==null || declaration.length==0) return; + // both have generics + for (int i=0; i<usage.length; i++) { + ClassNode ui = usage[i]; + ClassNode di = declaration[i]; + if (di.isGenericsPlaceHolder()) { + spec.put(di.getGenericsTypes()[0].getName(), di); + } else if (di.isUsingGenerics()){ + extractSuperClassGenerics(ui.getGenericsTypes(), di.getGenericsTypes(), spec); + } + } + } + + public static ClassNode[] parseClassNodesFromString( + final String option, + final SourceUnit sourceUnit, + final CompilationUnit compilationUnit, + final MethodNode mn, + final ASTNode usage) { + GroovyLexer lexer = new GroovyLexer(new StringReader("DummyNode<" + option + ">")); + final GroovyRecognizer rn = GroovyRecognizer.make(lexer); + try { + rn.classOrInterfaceType(true); + final AtomicReference<ClassNode> ref = new AtomicReference<ClassNode>(); + AntlrParserPlugin plugin = new AntlrParserPlugin() { + @Override + public ModuleNode buildAST(final SourceUnit sourceUnit, final ClassLoader classLoader, final Reduction cst) throws ParserException { + ref.set(makeTypeWithArguments(rn.getAST())); + return null; + } + }; + plugin.buildAST(null, null, null); + ClassNode parsedNode = ref.get(); + // the returned node is DummyNode<Param1, Param2, Param3, ...) + GenericsType[] parsedNodeGenericsTypes = parsedNode.getGenericsTypes(); + if (parsedNodeGenericsTypes == null) { + return null; + } + ClassNode[] signature = new ClassNode[parsedNodeGenericsTypes.length]; + for (int i = 0; i < parsedNodeGenericsTypes.length; i++) { + final GenericsType genericsType = parsedNodeGenericsTypes[i]; + signature[i] = resolveClassNode(sourceUnit, compilationUnit, mn, usage, genericsType.getType()); + } + return signature; + } catch (RecognitionException e) { + sourceUnit.addError(new IncorrectTypeHintException(mn, e, usage.getLineNumber(), usage.getColumnNumber())); + } catch (TokenStreamException e) { + sourceUnit.addError(new IncorrectTypeHintException(mn, e, usage.getLineNumber(), usage.getColumnNumber())); + } catch (ParserException e) { + sourceUnit.addError(new IncorrectTypeHintException(mn, e, usage.getLineNumber(), usage.getColumnNumber())); + } + return null; + } + + private static ClassNode resolveClassNode(final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final MethodNode mn, final ASTNode usage, final ClassNode parsedNode) { + ClassNode dummyClass = new ClassNode("dummy",0, ClassHelper.OBJECT_TYPE); + dummyClass.setModule(new ModuleNode(sourceUnit)); + dummyClass.setGenericsTypes(mn.getDeclaringClass().getGenericsTypes()); + MethodNode dummyMN = new MethodNode( + "dummy", + 0, + parsedNode, + Parameter.EMPTY_ARRAY, + ClassNode.EMPTY_ARRAY, + EmptyStatement.INSTANCE + ); + dummyMN.setGenericsTypes(mn.getGenericsTypes()); + dummyClass.addMethod(dummyMN); + ResolveVisitor visitor = new ResolveVisitor(compilationUnit) { + @Override + public void addError(final String msg, final ASTNode expr) { + sourceUnit.addError(new IncorrectTypeHintException(mn, msg, usage.getLineNumber(), usage.getColumnNumber())); + } + }; + visitor.startResolving(dummyClass, sourceUnit); + return dummyMN.getReturnType(); + } + + /** + * transforms generics types from an old context to a new context using the given spec. This method assumes + * all generics types will be placeholders. WARNING: The resulting generics types may or may not be placeholders + * after the transformation. + * @param genericsSpec the generics context information spec + * @param oldPlaceHolders the old placeholders + * @return the new generics types + */ + public static GenericsType[] applyGenericsContextToPlaceHolders(Map<String, ClassNode> genericsSpec, GenericsType[] oldPlaceHolders) { + if (oldPlaceHolders==null || oldPlaceHolders.length==0) return oldPlaceHolders; + if (genericsSpec.isEmpty()) return oldPlaceHolders; + GenericsType[] newTypes = new GenericsType[oldPlaceHolders.length]; + for (int i=0; i<oldPlaceHolders.length; i++) { + GenericsType old = oldPlaceHolders[i]; + if (!old.isPlaceholder()) throw new GroovyBugError("Given generics type "+old+" must be a placeholder!"); + ClassNode fromSpec = genericsSpec.get(old.getName()); + if (fromSpec!=null) { + if (fromSpec.isGenericsPlaceHolder()) { + ClassNode[] upper = new ClassNode[]{fromSpec.redirect()}; + newTypes[i] = new GenericsType(fromSpec, upper, null); + } else { + newTypes[i] = new GenericsType(fromSpec); + } + } else { + ClassNode[] upper = old.getUpperBounds(); + ClassNode[] newUpper = upper; + if (upper!=null && upper.length>0) { + ClassNode[] upperCorrected = new ClassNode[upper.length]; + for (int j=0;j<upper.length;j++) { + upperCorrected[i] = correctToGenericsSpecRecurse(genericsSpec,upper[j]); + } + upper = upperCorrected; + } + ClassNode lower = old.getLowerBound(); + ClassNode newLower = correctToGenericsSpecRecurse(genericsSpec,lower); + if (lower==newLower && upper==newUpper) { + newTypes[i] = oldPlaceHolders[i]; + } else { + ClassNode newPlaceHolder = ClassHelper.make(old.getName()); + GenericsType gt = new GenericsType(newPlaceHolder, newUpper, newLower); + gt.setPlaceholder(true); + newTypes[i] = gt; + } + } + } + return newTypes; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java new file mode 100644 index 0000000..4edf92e --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.codehaus.groovy.ast.tools; + +import org.codehaus.groovy.ast.Parameter; + +public class ParameterUtils { + public static boolean parametersEqual(Parameter[] a, Parameter[] b) { + if (a.length == b.length) { + boolean answer = true; + for (int i = 0; i < a.length; i++) { + if (!a[i].getType().equals(b[i].getType())) { + answer = false; + break; + } + } + return answer; + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java new file mode 100644 index 0000000..513144a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/ast/tools/PropertyNodeUtils.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.ast.tools; + +import org.codehaus.groovy.ast.PropertyNode; + +import java.lang.reflect.Modifier; + +public class PropertyNodeUtils { + /** + * Fields within the AST that have no explicit visibility are deemed to be properties + * and represented by a PropertyNode. The Groovy compiler creates accessor methods and + * a backing field for such property nodes. During this process, all modifiers + * from the property are carried over to the backing field (so a property marked as + * {@code transient} will have a {@code transient} backing field) but when creating + * the accessor methods we don't carry over modifier values which don't make sense for + * methods (this includes VOLATILE and TRANSIENT) but other modifiers are carried over, + * for example {@code static}. + * + * @param propNode the original property node + * @return the modifiers which make sense for an accessor method + */ + public static int adjustPropertyModifiersForMethod(PropertyNode propNode) { + // GROOVY-3726: clear volatile, transient modifiers so that they don't get applied to methods + return ~(Modifier.TRANSIENT | Modifier.VOLATILE) & propNode.getModifiers(); + } +}
