http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java new file mode 100644 index 0000000..a439197 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java @@ -0,0 +1,610 @@ +/* + * 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.control; + +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassCodeExpressionTransformer; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.DynamicVariable; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.ImportNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.AnnotationConstantExpression; +import org.codehaus.groovy.ast.expr.ArgumentListExpression; +import org.codehaus.groovy.ast.expr.BinaryExpression; +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.EmptyExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.expr.MapEntryExpression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.NamedArgumentListExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.syntax.Types; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.apache.groovy.ast.tools.ClassNodeUtils.getPropNameForAccessor; +import static org.apache.groovy.ast.tools.ClassNodeUtils.hasPossibleStaticMethod; +import static org.apache.groovy.ast.tools.ClassNodeUtils.hasPossibleStaticProperty; +import static org.apache.groovy.ast.tools.ClassNodeUtils.hasStaticProperty; +import static org.apache.groovy.ast.tools.ClassNodeUtils.isInnerClass; +import static org.apache.groovy.ast.tools.ClassNodeUtils.isValidAccessorName; +import static org.codehaus.groovy.runtime.MetaClassHelper.capitalize; + +/** + * Visitor to resolve constants and method calls from static Imports + */ +public class StaticImportVisitor extends ClassCodeExpressionTransformer { + private ClassNode currentClass; + private MethodNode currentMethod; + private SourceUnit source; + private boolean inSpecialConstructorCall; + private boolean inClosure; + private boolean inPropertyExpression; + private Expression foundConstant; + private Expression foundArgs; + private boolean inAnnotation; + private boolean inLeftExpression; + + public void visitClass(ClassNode node, SourceUnit source) { + this.currentClass = node; + this.source = source; + super.visitClass(node); + } + + @Override + protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { + this.currentMethod = node; + super.visitConstructorOrMethod(node, isConstructor); + this.currentMethod = null; + } + + @Override + public void visitAnnotations(AnnotatedNode node) { + boolean oldInAnnotation = inAnnotation; + inAnnotation = true; + super.visitAnnotations(node); + inAnnotation = oldInAnnotation; + } + + public Expression transform(Expression exp) { + if (exp == null) return null; + if (exp.getClass() == VariableExpression.class) { + return transformVariableExpression((VariableExpression) exp); + } + if (exp.getClass() == BinaryExpression.class) { + return transformBinaryExpression((BinaryExpression) exp); + } + if (exp.getClass() == PropertyExpression.class) { + return transformPropertyExpression((PropertyExpression) exp); + } + if (exp.getClass() == MethodCallExpression.class) { + return transformMethodCallExpression((MethodCallExpression) exp); + } + if (exp.getClass() == ClosureExpression.class) { + return transformClosureExpression((ClosureExpression) exp); + } + if (exp.getClass() == ConstructorCallExpression.class) { + return transformConstructorCallExpression((ConstructorCallExpression) exp); + } + if (exp.getClass() == ArgumentListExpression.class) { + Expression result = exp.transformExpression(this); + if (inPropertyExpression) { + foundArgs = result; + } + return result; + } + if (exp instanceof ConstantExpression) { + Expression result = exp.transformExpression(this); + if (inPropertyExpression) { + foundConstant = result; + } + if (inAnnotation && exp instanceof AnnotationConstantExpression) { + ConstantExpression ce = (ConstantExpression) result; + if (ce.getValue() instanceof AnnotationNode) { + // replicate a little bit of AnnotationVisitor here + // because we can't wait until later to do this + AnnotationNode an = (AnnotationNode) ce.getValue(); + Map<String, Expression> attributes = an.getMembers(); + for (Map.Entry<String, Expression> entry : attributes.entrySet()) { + Expression attrExpr = transform(entry.getValue()); + entry.setValue(attrExpr); + } + + } + } + return result; + } + return exp.transformExpression(this); + } + + // if you have a Bar class with a static foo property, and this: + // import static Bar.foo as baz + // then this constructor (not normal usage of statics): + // new Bar(baz:1) + // will become: + // new Bar(foo:1) + + private Expression transformMapEntryExpression(MapEntryExpression me, ClassNode constructorCallType) { + Expression key = me.getKeyExpression(); + Expression value = me.getValueExpression(); + ModuleNode module = currentClass.getModule(); + if (module != null && key instanceof ConstantExpression) { + Map<String, ImportNode> importNodes = module.getStaticImports(); + if (importNodes.containsKey(key.getText())) { + ImportNode importNode = importNodes.get(key.getText()); + if (importNode.getType().equals(constructorCallType)) { + String newKey = importNode.getFieldName(); + return new MapEntryExpression(new ConstantExpression(newKey), value.transformExpression(this)); + } + } + } + return me; + } + + protected Expression transformBinaryExpression(BinaryExpression be) { + int type = be.getOperation().getType(); + boolean oldInLeftExpression; + Expression right = transform(be.getRightExpression()); + be.setRightExpression(right); + Expression left; + if (type == Types.EQUAL && be.getLeftExpression() instanceof VariableExpression) { + oldInLeftExpression = inLeftExpression; + inLeftExpression = true; + left = transform(be.getLeftExpression()); + inLeftExpression = oldInLeftExpression; + if (left instanceof StaticMethodCallExpression) { + StaticMethodCallExpression smce = (StaticMethodCallExpression) left; + StaticMethodCallExpression result = new StaticMethodCallExpression(smce.getOwnerType(), smce.getMethod(), right); + setSourcePosition(result, be); + return result; + } + } else { + left = transform(be.getLeftExpression()); + } + be.setLeftExpression(left); + return be; + } + + protected Expression transformVariableExpression(VariableExpression ve) { + Variable v = ve.getAccessedVariable(); + if (v != null && v instanceof DynamicVariable) { + Expression result = findStaticFieldOrPropAccessorImportFromModule(v.getName()); + if (result != null) { + setSourcePosition(result, ve); + if (inAnnotation) { + result = transformInlineConstants(result); + } + return result; + } + } + return ve; + } + + /** + * Set the source position of toSet including its property expression if it has one. + * + * @param toSet resulting node + * @param origNode original node + */ + private static void setSourcePosition(Expression toSet, Expression origNode) { + toSet.setSourcePosition(origNode); + if (toSet instanceof PropertyExpression) { + ((PropertyExpression) toSet).getProperty().setSourcePosition(origNode); + } + } + + // resolve constant-looking expressions statically (do here as gets transformed away later) + + private Expression transformInlineConstants(Expression exp) { + if (exp instanceof PropertyExpression) { + PropertyExpression pe = (PropertyExpression) exp; + if (pe.getObjectExpression() instanceof ClassExpression) { + ClassExpression ce = (ClassExpression) pe.getObjectExpression(); + ClassNode type = ce.getType(); + if (type.isEnum()) return exp; + Expression constant = findConstant(getField(type, pe.getPropertyAsString())); + if (constant != null) return constant; + } + } else if (exp instanceof ListExpression) { + ListExpression le = (ListExpression) exp; + ListExpression result = new ListExpression(); + for (Expression e : le.getExpressions()) { + result.addExpression(transformInlineConstants(e)); + } + return result; + } + + return exp; + } + + private static Expression findConstant(FieldNode fn) { + if (fn != null && !fn.isEnum() && fn.isStatic() && fn.isFinal()) { + if (fn.getInitialValueExpression() instanceof ConstantExpression) { + return fn.getInitialValueExpression(); + } + } + return null; + } + + protected Expression transformMethodCallExpression(MethodCallExpression mce) { + Expression args = transform(mce.getArguments()); + Expression method = transform(mce.getMethod()); + Expression object = transform(mce.getObjectExpression()); + boolean isExplicitThisOrSuper = false; + boolean isExplicitSuper = false; + if (object instanceof VariableExpression) { + VariableExpression ve = (VariableExpression) object; + isExplicitThisOrSuper = !mce.isImplicitThis() && (ve.isThisExpression() || ve.isSuperExpression()); + isExplicitSuper = ve.isSuperExpression(); + } + + if (mce.isImplicitThis() || isExplicitThisOrSuper) { + if (mce.isImplicitThis()) { + Expression ret = findStaticMethodImportFromModule(method, args); + if (ret != null) { + setSourcePosition(ret, mce); + return ret; + } + if (method instanceof ConstantExpression && !inLeftExpression) { + // could be a closure field + String methodName = (String) ((ConstantExpression) method).getValue(); + ret = findStaticFieldOrPropAccessorImportFromModule(methodName); + if (ret != null) { + ret = new MethodCallExpression(ret, "call", args); + setSourcePosition(ret, mce); + return ret; + } + } + } else if (currentMethod!=null && currentMethod.isStatic() && isExplicitSuper) { + MethodCallExpression ret = new MethodCallExpression(new ClassExpression(currentClass.getSuperClass()), method, args); + setSourcePosition(ret, mce); + return ret; + } + + if (method instanceof ConstantExpression) { + ConstantExpression ce = (ConstantExpression) method; + Object value = ce.getValue(); + if (value instanceof String) { + boolean foundInstanceMethod = false; + String methodName = (String) value; + boolean inInnerClass = isInnerClass(currentClass); + if (currentMethod != null && !currentMethod.isStatic()) { + if (currentClass.hasPossibleMethod(methodName, args)) { + foundInstanceMethod = true; + } + } + boolean lookForPossibleStaticMethod = !methodName.equals("call"); + lookForPossibleStaticMethod &= !foundInstanceMethod; + lookForPossibleStaticMethod |= inSpecialConstructorCall; + lookForPossibleStaticMethod &= !inInnerClass; + if (!inClosure && lookForPossibleStaticMethod && + (hasPossibleStaticMethod(currentClass, methodName, args, true)) + || hasPossibleStaticProperty(currentClass, methodName)) { + StaticMethodCallExpression smce = new StaticMethodCallExpression(currentClass, methodName, args); + setSourcePosition(smce, mce); + return smce; + } + if (!inClosure && inInnerClass && inSpecialConstructorCall && mce.isImplicitThis() && !foundInstanceMethod) { + if (currentClass.getOuterClass().hasPossibleMethod(methodName, args)) { + object = new PropertyExpression(new ClassExpression(currentClass.getOuterClass()), new ConstantExpression("this")); + } else if (hasPossibleStaticMethod(currentClass.getOuterClass(), methodName, args, true) + || hasPossibleStaticProperty(currentClass.getOuterClass(), methodName)) { + StaticMethodCallExpression smce = new StaticMethodCallExpression(currentClass.getOuterClass(), methodName, args); + setSourcePosition(smce, mce); + return smce; + } + } + } + } + } + + MethodCallExpression result = new MethodCallExpression(object, method, args); + result.setSafe(mce.isSafe()); + result.setImplicitThis(mce.isImplicitThis()); + result.setSpreadSafe(mce.isSpreadSafe()); + result.setMethodTarget(mce.getMethodTarget()); + // GROOVY-6757 + result.setGenericsTypes(mce.getGenericsTypes()); + setSourcePosition(result, mce); + return result; + } + + protected Expression transformConstructorCallExpression(ConstructorCallExpression cce) { + inSpecialConstructorCall = cce.isSpecialCall(); + Expression expression = cce.getArguments(); + if (expression instanceof TupleExpression) { + TupleExpression tuple = (TupleExpression) expression; + if (tuple.getExpressions().size() == 1) { + expression = tuple.getExpression(0); + if (expression instanceof NamedArgumentListExpression) { + NamedArgumentListExpression namedArgs = (NamedArgumentListExpression) expression; + List<MapEntryExpression> entryExpressions = namedArgs.getMapEntryExpressions(); + for (int i = 0; i < entryExpressions.size(); i++) { + entryExpressions.set(i, (MapEntryExpression) transformMapEntryExpression(entryExpressions.get(i), cce.getType())); + } + } + } + } + Expression ret = cce.transformExpression(this); + inSpecialConstructorCall = false; + return ret; + } + + protected Expression transformClosureExpression(ClosureExpression ce) { + boolean oldInClosure = inClosure; + inClosure = true; + if (ce.getParameters() != null) { + for (Parameter p : ce.getParameters()) { + if (p.hasInitialExpression()) { + p.setInitialExpression(transform(p.getInitialExpression())); + } + } + } + Statement code = ce.getCode(); + if (code != null) code.visit(this); + inClosure = oldInClosure; + return ce; + } + + protected Expression transformPropertyExpression(PropertyExpression pe) { + if (currentMethod!=null && currentMethod.isStatic() + && pe.getObjectExpression() instanceof VariableExpression + && ((VariableExpression) pe.getObjectExpression()).isSuperExpression()) { + PropertyExpression pexp = new PropertyExpression( + new ClassExpression(currentClass.getSuperClass()), + transform(pe.getProperty()) + ); + pexp.setSourcePosition(pe); + return pexp; + } + boolean oldInPropertyExpression = inPropertyExpression; + Expression oldFoundArgs = foundArgs; + Expression oldFoundConstant = foundConstant; + inPropertyExpression = true; + foundArgs = null; + foundConstant = null; + Expression objectExpression = transform(pe.getObjectExpression()); + boolean candidate = false; + if (objectExpression instanceof MethodCallExpression) { + candidate = ((MethodCallExpression)objectExpression).isImplicitThis(); + } + + if (foundArgs != null && foundConstant != null && candidate) { + Expression result = findStaticMethodImportFromModule(foundConstant, foundArgs); + if (result != null) { + objectExpression = result; + objectExpression.setSourcePosition(pe); + } + } + inPropertyExpression = oldInPropertyExpression; + foundArgs = oldFoundArgs; + foundConstant = oldFoundConstant; + pe.setObjectExpression(objectExpression); + return pe; + } + + private Expression findStaticFieldOrPropAccessorImportFromModule(String name) { + ModuleNode module = currentClass.getModule(); + if (module == null) return null; + Map<String, ImportNode> importNodes = module.getStaticImports(); + Expression expression; + String accessorName = getAccessorName(name); + // look for one of these: + // import static MyClass.setProp [as setOtherProp] + // import static MyClass.getProp [as getOtherProp] + // when resolving prop reference + if (importNodes.containsKey(accessorName)) { + expression = findStaticProperty(importNodes, accessorName); + if (expression != null) return expression; + } + if (accessorName.startsWith("get")) { + accessorName = "is" + accessorName.substring(3); + if (importNodes.containsKey(accessorName)) { + expression = findStaticProperty(importNodes, accessorName); + if (expression != null) return expression; + } + } + + // look for one of these: + // import static MyClass.prop [as otherProp] + // when resolving prop or field reference + if (importNodes.containsKey(name)) { + ImportNode importNode = importNodes.get(name); + expression = findStaticPropertyAccessor(importNode.getType(), importNode.getFieldName()); + if (expression != null) return expression; + expression = findStaticField(importNode.getType(), importNode.getFieldName()); + if (expression != null) return expression; + } + // look for one of these: + // import static MyClass.* + // when resolving prop or field reference + for (ImportNode importNode : module.getStaticStarImports().values()) { + ClassNode node = importNode.getType(); + expression = findStaticPropertyAccessor(node, name); + if (expression != null) return expression; + expression = findStaticField(node, name); + if (expression != null) return expression; + } + return null; + } + + private Expression findStaticProperty(Map<String, ImportNode> importNodes, String accessorName) { + Expression result = null; + ImportNode importNode = importNodes.get(accessorName); + ClassNode importClass = importNode.getType(); + String importMember = importNode.getFieldName(); + result = findStaticPropertyAccessorByFullName(importClass, importMember); + if (result == null) { + result = findStaticPropertyAccessor(importClass, getPropNameForAccessor(importMember)); + } + return result; + } + + private Expression findStaticMethodImportFromModule(Expression method, Expression args) { + ModuleNode module = currentClass.getModule(); + if (module == null || !(method instanceof ConstantExpression)) return null; + Map<String, ImportNode> importNodes = module.getStaticImports(); + ConstantExpression ce = (ConstantExpression) method; + Expression expression; + Object value = ce.getValue(); + // skip non-Strings, e.g. Integer + if (!(value instanceof String)) return null; + final String name = (String) value; + // look for one of these: + // import static SomeClass.method [as otherName] + // when resolving methodCall() or getProp() or setProp() + if (importNodes.containsKey(name)) { + ImportNode importNode = importNodes.get(name); + expression = findStaticMethod(importNode.getType(), importNode.getFieldName(), args); + if (expression != null) return expression; + expression = findStaticPropertyAccessorGivenArgs(importNode.getType(), getPropNameForAccessor(importNode.getFieldName()), args); + if (expression != null) { + return new StaticMethodCallExpression(importNode.getType(), importNode.getFieldName(), args); + } + } + // look for one of these: + // import static SomeClass.someProp [as otherName] + // when resolving getProp() or setProp() + if (isValidAccessorName(name)) { + String propName = getPropNameForAccessor(name); + if (importNodes.containsKey(propName)) { + ImportNode importNode = importNodes.get(propName); + ClassNode importClass = importNode.getType(); + String importMember = importNode.getFieldName(); + expression = findStaticMethod(importClass, prefix(name) + capitalize(importMember), args); + if (expression != null) return expression; + expression = findStaticPropertyAccessorGivenArgs(importClass, importMember, args); + if (expression != null) { + return new StaticMethodCallExpression(importClass, prefix(name) + capitalize(importMember), args); + } + } + } + Map<String, ImportNode> starImports = module.getStaticStarImports(); + ClassNode starImportType; + if (currentClass.isEnum() && starImports.containsKey(currentClass.getName())) { + ImportNode importNode = starImports.get(currentClass.getName()); + starImportType = importNode == null ? null : importNode.getType(); + expression = findStaticMethod(starImportType, name, args); + if (expression != null) return expression; + } else { + for (ImportNode importNode : starImports.values()) { + starImportType = importNode == null ? null : importNode.getType(); + expression = findStaticMethod(starImportType, name, args); + if (expression != null) return expression; + expression = findStaticPropertyAccessorGivenArgs(starImportType, getPropNameForAccessor(name), args); + if (expression != null) { + return new StaticMethodCallExpression(starImportType, name, args); + } + } + } + return null; + } + + private static String prefix(String name) { + return name.startsWith("is") ? "is" : name.substring(0, 3); + } + + private String getAccessorName(String name) { + return (inLeftExpression ? "set" : "get") + capitalize(name); + } + + private Expression findStaticPropertyAccessorGivenArgs(ClassNode staticImportType, String propName, Expression args) { + // TODO validate args? + return findStaticPropertyAccessor(staticImportType, propName); + } + + private Expression findStaticPropertyAccessor(ClassNode staticImportType, String propName) { + String accessorName = getAccessorName(propName); + Expression accessor = findStaticPropertyAccessorByFullName(staticImportType, accessorName); + if (accessor == null && accessorName.startsWith("get")) { + accessor = findStaticPropertyAccessorByFullName(staticImportType, "is" + accessorName.substring(3)); + } + if (accessor == null && hasStaticProperty(staticImportType, propName)) { + // args will be replaced + if (inLeftExpression) + accessor = new StaticMethodCallExpression(staticImportType, accessorName, ArgumentListExpression.EMPTY_ARGUMENTS); + else + accessor = new PropertyExpression(new ClassExpression(staticImportType), propName); + } + return accessor; + } + + private Expression findStaticPropertyAccessorByFullName(ClassNode staticImportType, String accessorMethodName) { + // anything will do as we only check size == 1 + ArgumentListExpression dummyArgs = new ArgumentListExpression(); + dummyArgs.addExpression(new EmptyExpression()); + return findStaticMethod(staticImportType, accessorMethodName, (inLeftExpression ? dummyArgs : ArgumentListExpression.EMPTY_ARGUMENTS)); + } + + private static Expression findStaticField(ClassNode staticImportType, String fieldName) { + if (staticImportType.isPrimaryClassNode() || staticImportType.isResolved()) { + FieldNode field = getField(staticImportType, fieldName); + if (field != null && field.isStatic()) + return new PropertyExpression(new ClassExpression(staticImportType), fieldName); + } + return null; + } + + private static FieldNode getField(ClassNode classNode, String fieldName) { + ClassNode node = classNode; + Set<String> visited = new HashSet<String>(); + while (node != null) { + FieldNode fn = node.getDeclaredField(fieldName); + if (fn != null) return fn; + ClassNode[] interfaces = node.getInterfaces(); + for (ClassNode iNode : interfaces) { + if (visited.contains(iNode.getName())) continue; + FieldNode ifn = getField(iNode, fieldName); + visited.add(iNode.getName()); + if (ifn != null) return ifn; + } + node = node.getSuperClass(); + } + return null; + } + + private static Expression findStaticMethod(ClassNode staticImportType, String methodName, Expression args) { + if (staticImportType.isPrimaryClassNode() || staticImportType.isResolved()) { + if (staticImportType.hasPossibleStaticMethod(methodName, args)) { + return new StaticMethodCallExpression(staticImportType, methodName, args); + } + } + return null; + } + + protected SourceUnit getSourceUnit() { + return source; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/control/StaticVerifier.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/StaticVerifier.java b/src/main/java/org/codehaus/groovy/control/StaticVerifier.java new file mode 100644 index 0000000..53706f9 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/StaticVerifier.java @@ -0,0 +1,204 @@ +/* + * 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.control; + +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.DynamicVariable; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.groovy.ast.tools.ClassNodeUtils.isInnerClass; + +/** + * Verifier to check non-static access in static contexts + */ +public class StaticVerifier extends ClassCodeVisitorSupport { + private boolean inSpecialConstructorCall; + private boolean inPropertyExpression; // TODO use it or lose it + private boolean inClosure; + private MethodNode currentMethod; + private SourceUnit source; + + public void visitClass(ClassNode node, SourceUnit source) { + this.source = source; + super.visitClass(node); + } + + @Override + public void visitVariableExpression(VariableExpression ve) { + Variable v = ve.getAccessedVariable(); + if (v != null && v instanceof DynamicVariable) { + if (!inPropertyExpression || inSpecialConstructorCall) addStaticVariableError(ve); + } + } + + @Override + public void visitClosureExpression(ClosureExpression ce) { + boolean oldInClosure = inClosure; + inClosure = true; + super.visitClosureExpression(ce); + inClosure = oldInClosure; + } + + @Override + public void visitConstructorCallExpression(ConstructorCallExpression cce) { + boolean oldIsSpecialConstructorCall = inSpecialConstructorCall; + inSpecialConstructorCall = cce.isSpecialCall(); + super.visitConstructorCallExpression(cce); + inSpecialConstructorCall = oldIsSpecialConstructorCall; + } + + @Override + public void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { + MethodNode oldCurrentMethod = currentMethod; + currentMethod = node; + super.visitConstructorOrMethod(node, isConstructor); + if (isConstructor) { + final Set<String> exceptions = new HashSet<String>(); + for (final Parameter param : node.getParameters()) { + exceptions.add(param.getName()); + if (param.hasInitialExpression()) { + param.getInitialExpression().visit(new CodeVisitorSupport() { + @Override + public void visitVariableExpression(VariableExpression ve) { + if (exceptions.contains(ve.getName())) return; + Variable av = ve.getAccessedVariable(); + if (av instanceof DynamicVariable || !av.isInStaticContext()) { + addVariableError(ve); + } + } + + @Override + public void visitMethodCallExpression(MethodCallExpression call) { + Expression objectExpression = call.getObjectExpression(); + if (objectExpression instanceof VariableExpression) { + VariableExpression ve = (VariableExpression) objectExpression; + if (ve.isThisExpression()) { + addError("Can't access instance method '" + call.getMethodAsString() + "' for a constructor parameter default value", param); + return; + } + } + super.visitMethodCallExpression(call); + } + + @Override + public void visitClosureExpression(ClosureExpression expression) { + //skip contents, because of dynamic scope + } + }); + } + } + } + currentMethod = oldCurrentMethod; + } + + @Override + public void visitMethodCallExpression(MethodCallExpression mce) { + if (inSpecialConstructorCall && !isInnerClass(currentMethod.getDeclaringClass())) { + Expression objectExpression = mce.getObjectExpression(); + if (objectExpression instanceof VariableExpression) { + VariableExpression ve = (VariableExpression) objectExpression; + if (ve.isThisExpression()) { + addError("Can't access instance method '" + mce.getMethodAsString() + "' before the class is constructed", mce); + return; + } + } + } + super.visitMethodCallExpression(mce); + } + + @Override + public void visitPropertyExpression(PropertyExpression pe) { + if (!inSpecialConstructorCall) checkStaticScope(pe); + } + + @Override + protected SourceUnit getSourceUnit() { + return source; + } + + + private void checkStaticScope(PropertyExpression pe) { + if (inClosure) return; + for (Expression it = pe; it != null; it = ((PropertyExpression) it).getObjectExpression()) { + if (it instanceof PropertyExpression) continue; + if (it instanceof VariableExpression) { + addStaticVariableError((VariableExpression) it); + } + return; + } + } + + private void addStaticVariableError(VariableExpression ve) { + // closures are always dynamic + // propertyExpressions will handle the error a bit differently + if (!inSpecialConstructorCall && (inClosure || !ve.isInStaticContext())) return; + if (ve.isThisExpression() || ve.isSuperExpression()) return; + Variable v = ve.getAccessedVariable(); + if (currentMethod != null && currentMethod.isStatic()) { + FieldNode fieldNode = getDeclaredOrInheritedField(currentMethod.getDeclaringClass(), ve.getName()); + if (fieldNode != null && fieldNode.isStatic()) return; + } + if (v != null && !(v instanceof DynamicVariable) && v.isInStaticContext()) return; + addVariableError(ve); + } + + private void addVariableError(VariableExpression ve) { + addError("Apparent variable '" + ve.getName() + "' was found in a static scope but doesn't refer" + + " to a local variable, static field or class. Possible causes:\n" + + "You attempted to reference a variable in the binding or an instance variable from a static context.\n" + + "You misspelled a classname or statically imported field. Please check the spelling.\n" + + "You attempted to use a method '" + ve.getName() + + "' but left out brackets in a place not allowed by the grammar.", ve); + } + + private static FieldNode getDeclaredOrInheritedField(ClassNode cn, String fieldName) { + ClassNode node = cn; + while (node != null) { + FieldNode fn = node.getDeclaredField(fieldName); + if (fn != null) return fn; + List<ClassNode> interfacesToCheck = new ArrayList<ClassNode>(Arrays.asList(node.getInterfaces())); + while (!interfacesToCheck.isEmpty()) { + ClassNode nextInterface = interfacesToCheck.remove(0); + fn = nextInterface.getDeclaredField(fieldName); + if (fn != null) return fn; + interfacesToCheck.addAll(Arrays.asList(nextInterface.getInterfaces())); + } + node = node.getSuperClass(); + } + return null; + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/control/XStreamUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/XStreamUtils.java b/src/main/java/org/codehaus/groovy/control/XStreamUtils.java new file mode 100644 index 0000000..3f7ccd3 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/XStreamUtils.java @@ -0,0 +1,68 @@ +/* + * 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.control; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; + +import java.io.File; +import java.io.FileWriter; +import java.net.URI; + +public abstract class XStreamUtils { + + public static void serialize(final String name, final Object ast) { + if (name == null || name.length() == 0) return; + + XStream xstream = new XStream(new StaxDriver()); + FileWriter astFileWriter = null; + try { + File astFile = astFile(name); + if (astFile == null) { + System.out.println("File-name for writing " + name + " AST could not be determined!"); + return; + } + astFileWriter = new FileWriter(astFile, false); + xstream.toXML(ast, astFileWriter); + System.out.println("Written AST to " + name + ".xml"); + + } catch (Exception e) { + System.out.println("Couldn't write to " + name + ".xml"); + e.printStackTrace(); + } finally { + DefaultGroovyMethods.closeQuietly(astFileWriter); + } + } + + /** + * Takes the incoming file-name and checks whether this is a URI using the <tt>file:</tt> protocol or a non-URI and treats + * it accordingly. + * + * @return a file-name {@link java.io.File} representation or <tt>null</tt> if the file-name was in an invalid URI format + */ + private static File astFile(final String uriOrFileName) { + try { + final String astFileName = uriOrFileName + ".xml"; + return uriOrFileName.startsWith("file:") ? new File(URI.create(astFileName)) : new File(astFileName); + } catch (IllegalArgumentException e) { + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java b/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java new file mode 100644 index 0000000..8f9e81c --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/customizers/CompilationCustomizer.java @@ -0,0 +1,45 @@ +/* + * 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.control.customizers; + +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.CompilePhase; + +/** + * Users wanting to customize the configuration process such as adding imports, restricting the + * language features or apply AST transformations by default should implement this class, then + * call the {@link org.codehaus.groovy.control.CompilerConfiguration#addCompilationCustomizers(CompilationCustomizer...)} + * method. + * + * @author Cedric Champeau + * + * @since 1.8.0 + * + */ +public abstract class CompilationCustomizer extends CompilationUnit.PrimaryClassNodeOperation { + private final CompilePhase phase; + + public CompilationCustomizer(CompilePhase phase) { + this.phase = phase; + } + + public CompilePhase getPhase() { + return phase; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/control/customizers/DelegatingCustomizer.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/customizers/DelegatingCustomizer.java b/src/main/java/org/codehaus/groovy/control/customizers/DelegatingCustomizer.java new file mode 100644 index 0000000..0f6af6a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/customizers/DelegatingCustomizer.java @@ -0,0 +1,45 @@ +/* + * 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.control.customizers; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.SourceUnit; + +/** + * Base class for compilation customizers which delegate to another customizer. The phase this + * customizer runs at is retrieved from the phase of the delegate. + * + * @author Cedric Champeau + * @since 2.1.0 + */ +public abstract class DelegatingCustomizer extends CompilationCustomizer { + protected final CompilationCustomizer delegate; + + public DelegatingCustomizer(CompilationCustomizer delegate) { + super(delegate.getPhase()); + this.delegate = delegate; + } + + @Override + public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException { + delegate.call(source, context, classNode); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/control/customizers/ImportCustomizer.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/customizers/ImportCustomizer.java b/src/main/java/org/codehaus/groovy/control/customizers/ImportCustomizer.java new file mode 100644 index 0000000..780b554 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/customizers/ImportCustomizer.java @@ -0,0 +1,169 @@ +/* + * 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.control.customizers; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.classgen.GeneratorContext; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.control.SourceUnit; + +import java.util.LinkedList; +import java.util.List; + +/** + * This compilation customizer allows addiing various types of imports to the compilation unit. Supports adding : + * <ul> + * <li>standard imports thanks to {@link #addImport(String)}, {@link #addImport(String, String)} or {@link #addImports(String...)}</li> + * <li>star imports thanks to {@link #addStarImport(String)} or {@link #addStarImports(String...)}</li> + * <li>static imports thanks to {@link #addStaticImport(String, String)} or {@link #addStaticImport(String, String, String)}</li> + * <li>static star imports thanks to {@link #addStaticStar(String)} or {@link #addStaticStars(String...)}</li> + * </ul> + * + * @author Cedric Champeau + * + * @since 1.8.0 + * + */ +public class ImportCustomizer extends CompilationCustomizer { + + private final List<Import> imports = new LinkedList<Import>(); + + public ImportCustomizer() { + super(CompilePhase.CONVERSION); + } + + @Override + public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException { + final ModuleNode ast = source.getAST(); + for (Import anImport : imports) { + switch (anImport.type) { + case regular: + ast.addImport(anImport.alias, anImport.classNode); + break; + case staticImport: + ast.addStaticImport(anImport.classNode, anImport.field, anImport.alias); + break; + case staticStar: + ast.addStaticStarImport(anImport.alias, anImport.classNode); + break; + case star: + ast.addStarImport(anImport.star); + break; + } + } + } + + public ImportCustomizer addImport(final String alias, final String className) { + imports.add(new Import(ImportType.regular, alias, ClassHelper.make(className))); + return this; + } + + public ImportCustomizer addStaticImport(final String className, final String fieldName) { + final ClassNode node = ClassHelper.make(className); + imports.add(new Import(ImportType.staticImport, fieldName, node, fieldName)); + return this; + } + + public ImportCustomizer addStaticStars(final String... classNames) { + for (String className : classNames) { + addStaticStar(className); + } + return this; + } + + public ImportCustomizer addStaticImport(final String alias, final String className, final String fieldName) { + imports.add(new Import(ImportCustomizer.ImportType.staticImport, alias, ClassHelper.make(className), fieldName)); + return this; + } + + public ImportCustomizer addImports(final String... imports) { + for (String anImport : imports) { + addImport(anImport); + } + return this; + } + + public ImportCustomizer addStarImports(final String... packageNames) { + for (String packageName : packageNames) { + addStarImport(packageName); + } + return this; + } + + private void addImport(final String className) { + final ClassNode node = ClassHelper.make(className); + imports.add(new Import(ImportType.regular, node.getNameWithoutPackage(), node)); + } + + private void addStaticStar(final String className) { + imports.add(new Import(ImportCustomizer.ImportType.staticStar, className, ClassHelper.make(className))); + } + + private void addStarImport(final String packagename) { + final String packageNameEndingWithDot = packagename.endsWith(".")?packagename:packagename+'.'; + imports.add(new Import(ImportType.star,packageNameEndingWithDot)); + } + + // -------------------- Helper classes ------------------------- + + /** + * Represents imports which are possibly aliased. + */ + private static final class Import { + final ImportType type; + final ClassNode classNode; + final String alias; + final String field; + final String star; // only used for star imports + + private Import(final ImportType type, final String alias, final ClassNode classNode, final String field) { + this.alias = alias; + this.classNode = classNode; + this.field = field; + this.type = type; + this.star = null; + } + + private Import(final ImportType type, final String alias, final ClassNode classNode) { + this.alias = alias; + this.classNode = classNode; + this.type = type; + this.field = null; + this.star = null; + } + + private Import(final ImportType type, final String star) { + this.type = type; + this.star = star; + this.alias = null; + this.classNode = null; + this.field = null; + } + } + + private enum ImportType { + regular, + staticImport, + staticStar, + star + } +}
