http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/groovy/VariableAccessReplacer.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/VariableAccessReplacer.groovy b/src/main/groovy/VariableAccessReplacer.groovy new file mode 100644 index 0000000..d62dc46 --- /dev/null +++ b/src/main/groovy/VariableAccessReplacer.groovy @@ -0,0 +1,73 @@ +/* + * 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.transform.tailrec + +import groovy.transform.CompileStatic +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.expr.VariableExpression + +/** + * Replace all access to variables and args by new variables. + * The variable names to replace as well as their replacement name and type have to be configured + * in nameAndTypeMapping before calling replaceIn(). + * + * The VariableReplacedListener can be set if clients want to react to variable replacement. + * + * @author Johannes Link + */ +@CompileStatic +class VariableAccessReplacer { + + /** + * Nested map of variable accesses to replace + * e.g.: [ + * 'varToReplace': [name: 'newVar', type: TypeOfVar], + * 'varToReplace2': [name: 'newVar2', type: TypeOfVar2], + * ] + */ + Map<String, Map> nameAndTypeMapping = [:] + + VariableReplacedListener listener = VariableReplacedListener.NULL + + void replaceIn(ASTNode root) { + Closure<Boolean> whenParam = { VariableExpression expr -> + return nameAndTypeMapping.containsKey(expr.name) + } + Closure<VariableExpression> replaceWithLocalVariable = { VariableExpression expr -> + Map nameAndType = nameAndTypeMapping[expr.name] + VariableExpression newVar = AstHelper.createVariableReference(nameAndType) + listener.variableReplaced(expr, newVar) + return newVar + } + new VariableExpressionReplacer(when: whenParam, replaceWith: replaceWithLocalVariable).replaceIn(root) + } + +} + +@CompileStatic +interface VariableReplacedListener { + void variableReplaced(VariableExpression oldVar, VariableExpression newVar) + + static VariableReplacedListener NULL = new VariableReplacedListener() { + @Override + void variableReplaced(VariableExpression oldVar, VariableExpression newVar) { + //do nothing + } + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/groovy/VariableExpressionReplacer.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/VariableExpressionReplacer.groovy b/src/main/groovy/VariableExpressionReplacer.groovy new file mode 100644 index 0000000..1f14490 --- /dev/null +++ b/src/main/groovy/VariableExpressionReplacer.groovy @@ -0,0 +1,171 @@ +/* + * 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.transform.tailrec + +import groovy.transform.CompileStatic +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.CodeVisitorSupport +import org.codehaus.groovy.ast.expr.BinaryExpression +import org.codehaus.groovy.ast.expr.BooleanExpression +import org.codehaus.groovy.ast.expr.Expression +import org.codehaus.groovy.ast.expr.ExpressionTransformer +import org.codehaus.groovy.ast.expr.VariableExpression +import org.codehaus.groovy.ast.stmt.AssertStatement +import org.codehaus.groovy.ast.stmt.CaseStatement +import org.codehaus.groovy.ast.stmt.DoWhileStatement +import org.codehaus.groovy.ast.stmt.ExpressionStatement +import org.codehaus.groovy.ast.stmt.ForStatement +import org.codehaus.groovy.ast.stmt.IfStatement +import org.codehaus.groovy.ast.stmt.ReturnStatement +import org.codehaus.groovy.ast.stmt.SwitchStatement +import org.codehaus.groovy.ast.stmt.SynchronizedStatement +import org.codehaus.groovy.ast.stmt.ThrowStatement +import org.codehaus.groovy.ast.stmt.WhileStatement + +import java.lang.reflect.Method + +/** + * Tool for replacing VariableExpression instances in an AST by other VariableExpression instances. + * Regardless of a real change taking place in nested expressions, all considered expression (trees) will be replaced. + * This could be optimized to accelerate compilation. + * + * Within @TailRecursive it is used + * - to swap the access of method args with the access to iteration variables + * - to swap the access of iteration variables with the access of temp vars + * + * @author Johannes Link + */ +@CompileStatic +class VariableExpressionReplacer extends CodeVisitorSupport { + + Closure<Boolean> when = { VariableExpression node -> false } + Closure<VariableExpression> replaceWith = { VariableExpression variableExpression -> variableExpression } + + private ExpressionTransformer transformer + + synchronized void replaceIn(ASTNode root) { + transformer = new VariableExpressionTransformer(when: when, replaceWith: replaceWith) + root.visit(this) + } + + public void visitReturnStatement(ReturnStatement statement) { + replaceExpressionPropertyWhenNecessary(statement) + super.visitReturnStatement(statement); + } + + public void visitIfElse(IfStatement ifElse) { + replaceExpressionPropertyWhenNecessary(ifElse, 'booleanExpression', BooleanExpression) + super.visitIfElse(ifElse); + } + + public void visitForLoop(ForStatement forLoop) { + replaceExpressionPropertyWhenNecessary(forLoop, 'collectionExpression') + super.visitForLoop(forLoop); + } + + /** + * It's the only Expression type in which replacing is considered. + * That's an abuse of the class, but I couldn't think of a better way. + */ + public void visitBinaryExpression(BinaryExpression expression) { + //A hack: Only replace right expression b/c ReturnStatementToIterationConverter needs it that way :-/ + replaceExpressionPropertyWhenNecessary(expression, 'rightExpression') + expression.getRightExpression().visit(this); + super.visitBinaryExpression(expression) + } + + public void visitWhileLoop(WhileStatement loop) { + replaceExpressionPropertyWhenNecessary(loop, 'booleanExpression', BooleanExpression) + super.visitWhileLoop(loop); + } + + public void visitDoWhileLoop(DoWhileStatement loop) { + replaceExpressionPropertyWhenNecessary(loop, 'booleanExpression', BooleanExpression) + super.visitDoWhileLoop(loop); + } + + public void visitSwitch(SwitchStatement statement) { + replaceExpressionPropertyWhenNecessary(statement) + super.visitSwitch(statement) + } + + public void visitCaseStatement(CaseStatement statement) { + replaceExpressionPropertyWhenNecessary(statement) + super.visitCaseStatement(statement) + } + + public void visitExpressionStatement(ExpressionStatement statement) { + replaceExpressionPropertyWhenNecessary(statement) + super.visitExpressionStatement(statement); + } + + public void visitThrowStatement(ThrowStatement statement) { + replaceExpressionPropertyWhenNecessary(statement) + super.visitThrowStatement(statement) + } + + public void visitAssertStatement(AssertStatement statement) { + replaceExpressionPropertyWhenNecessary(statement, 'booleanExpression', BooleanExpression) + replaceExpressionPropertyWhenNecessary(statement, 'messageExpression') + super.visitAssertStatement(statement) + } + + public void visitSynchronizedStatement(SynchronizedStatement statement) { + replaceExpressionPropertyWhenNecessary(statement) + super.visitSynchronizedStatement(statement) + } + + private void replaceExpressionPropertyWhenNecessary(ASTNode node, String propName = "expression", Class propClass = Expression) { + Expression expr = getExpression(node, propName) + + if (expr instanceof VariableExpression) { + if (when(expr)) { + VariableExpression newExpr = replaceWith(expr) + replaceExpression(node, propName, propClass, expr, newExpr) + } + } else { + Expression newExpr = expr.transformExpression(transformer) + replaceExpression(node, propName, propClass, expr, newExpr) + } + } + + private void replaceExpression(ASTNode node, String propName, Class propClass, Expression oldExpr, Expression newExpr) { + //Use reflection to enable CompileStatic + String setterName = 'set' + capitalizeFirst(propName) + Method setExpressionMethod = node.class.getMethod(setterName, [propClass].toArray(new Class[1])) + newExpr.setSourcePosition(oldExpr); + newExpr.copyNodeMetaData(oldExpr); + setExpressionMethod.invoke(node, [newExpr].toArray()) + } + + private Expression getExpression(ASTNode node, String propName) { + //Use reflection to enable CompileStatic + String getterName = 'get' + capitalizeFirst(propName) + Method getExpressionMethod = node.class.getMethod(getterName, new Class[0]) + getExpressionMethod.invoke(node, new Object[0]) as Expression + } + + private String capitalizeFirst(String propName) { + propName[0].toUpperCase() + propName[1..-1] + } + + +} + + http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/groovy/VariableExpressionTransformer.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/VariableExpressionTransformer.groovy b/src/main/groovy/VariableExpressionTransformer.groovy new file mode 100644 index 0000000..106a2f1 --- /dev/null +++ b/src/main/groovy/VariableExpressionTransformer.groovy @@ -0,0 +1,47 @@ +/* + * 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.transform.tailrec + +import groovy.transform.CompileStatic +import org.codehaus.groovy.ast.expr.Expression +import org.codehaus.groovy.ast.expr.ExpressionTransformer +import org.codehaus.groovy.ast.expr.VariableExpression + +/** + * An expression transformer used in the process of replacing the access to variables + * + * @author Johannes Link + */ +@CompileStatic +class VariableExpressionTransformer implements ExpressionTransformer { + + Closure<Boolean> when + Closure<VariableExpression> replaceWith + + @Override + Expression transform(Expression expr) { + if ((expr instanceof VariableExpression) && when(expr)) { + VariableExpression newExpr = replaceWith(expr) + newExpr.setSourcePosition(expr); + newExpr.copyNodeMetaData(expr); + return newExpr + } + return expr.transformExpression(this) + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/groovy/genArrayAccess.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/genArrayAccess.groovy b/src/main/groovy/genArrayAccess.groovy new file mode 100644 index 0000000..08cb68a --- /dev/null +++ b/src/main/groovy/genArrayAccess.groovy @@ -0,0 +1,146 @@ +/* + * 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.classgen + +println """ +package org.codehaus.groovy.runtime.dgmimpl; + +import groovy.lang.MetaClassImpl; +import groovy.lang.MetaMethod; +import org.codehaus.groovy.runtime.callsite.CallSite; +import org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite; +import org.codehaus.groovy.reflection.CachedClass; +import org.codehaus.groovy.reflection.ReflectionCache; + +public class ArrayOperations { + ${genInners()} +} +""" + +def genInners () { + def res = "" + + final Map primitives = [ + "boolean": "Boolean", + "byte": "Byte", + "char": "Character", + "short": "Short", + "int": "Integer", + "long": "Long", + "float": "Float", + "double": "Double" + ] + + primitives.each {primName, clsName -> + res += """ + public static class ${clsName}ArrayGetAtMetaMethod extends ArrayGetAtMetaMethod { + private static final CachedClass ARR_CLASS = ReflectionCache.getCachedClass(${primName}[].class); + + public Class getReturnType() { + return ${clsName}.class; + } + + public final CachedClass getDeclaringClass() { + return ARR_CLASS; + } + + public Object invoke(Object object, Object[] args) { + final ${primName}[] objects = (${primName}[]) object; + return objects[normaliseIndex(((Integer) args[0]).intValue(), objects.length)]; + } + + public CallSite createPojoCallSite(CallSite site, MetaClassImpl metaClass, MetaMethod metaMethod, Class[] params, Object receiver, Object[] args) { + if (!(args [0] instanceof Integer)) + return PojoMetaMethodSite.createNonAwareCallSite(site, metaClass, metaMethod, params, args); + else + return new PojoMetaMethodSite(site, metaClass, metaMethod, params) { + public Object invoke(Object receiver, Object[] args) { + final ${primName}[] objects = (${primName}[]) receiver; + return objects[normaliseIndex(((Integer) args[0]).intValue(), objects.length)]; + } + + public Object callBinop(Object receiver, Object arg) { + if ((receiver instanceof ${primName}[] && arg instanceof Integer) + && checkMetaClass()) { + final ${primName}[] objects = (${primName}[]) receiver; + return objects[normaliseIndex(((Integer) arg).intValue(), objects.length)]; + } + else + return super.callBinop(receiver,arg); + } + + public Object invokeBinop(Object receiver, Object arg) { + final ${primName}[] objects = (${primName}[]) receiver; + return objects[normaliseIndex(((Integer) arg).intValue(), objects.length)]; + } + }; + } + } + + + public static class ${clsName}ArrayPutAtMetaMethod extends ArrayPutAtMetaMethod { + private static final CachedClass OBJECT_CLASS = ReflectionCache.OBJECT_CLASS; + private static final CachedClass ARR_CLASS = ReflectionCache.getCachedClass(${primName}[].class); + private static final CachedClass [] PARAM_CLASS_ARR = new CachedClass[] {INTEGER_CLASS, OBJECT_CLASS}; + + public ${clsName}ArrayPutAtMetaMethod() { + parameterTypes = PARAM_CLASS_ARR; + } + + public final CachedClass getDeclaringClass() { + return ARR_CLASS; + } + + public Object invoke(Object object, Object[] args) { + final ${primName}[] objects = (${primName}[]) object; + final int index = normaliseIndex(((Integer) args[0]).intValue(), objects.length); + Object newValue = args[1]; + if (!(newValue instanceof ${clsName})) { + Number n = (Number) newValue; + objects[index] = ((Number)newValue).${primName}Value(); + } + else + objects[index] = ((${clsName})args[1]).${primName}Value(); + return null; + } + + public CallSite createPojoCallSite(CallSite site, MetaClassImpl metaClass, MetaMethod metaMethod, Class[] params, Object receiver, Object[] args) { + if (!(args [0] instanceof Integer) || !(args [1] instanceof ${clsName})) + return PojoMetaMethodSite.createNonAwareCallSite(site, metaClass, metaMethod, params, args); + else + return new PojoMetaMethodSite(site, metaClass, metaMethod, params) { + public Object call(Object receiver, Object[] args) { + if ((receiver instanceof ${primName}[] && args[0] instanceof Integer && args[1] instanceof ${clsName} ) + && checkMetaClass()) { + final ${primName}[] objects = (${primName}[]) receiver; + objects[normaliseIndex(((Integer) args[0]).intValue(), objects.length)] = ((${clsName})args[1]).${primName}Value(); + return null; + } + else + return super.call(receiver,args); + } + }; + } + } + + """ + } + + res +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/groovy/genArrays.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/genArrays.groovy b/src/main/groovy/genArrays.groovy new file mode 100644 index 0000000..9bbe3cf --- /dev/null +++ b/src/main/groovy/genArrays.groovy @@ -0,0 +1,53 @@ +/* + * 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.classgen + +print """ + +public class ArrayUtil { + ${genMethods()} +} + +""" + +def genMethods () { + def res = "" + for (i in 1..250) + res += "\n\n" + genMethod (i) + res +} + +def genMethod (int paramNum) { + def res = "public static Object [] createArray (" + for (k in 0..<paramNum) { + res += "Object arg" + k + if (k != paramNum-1) + res += ", " + } + res += ") {\n" + res += "return new Object [] {\n" + for (k in 0..<paramNum) { + res += "arg" + k + if (k != paramNum-1) + res += ", " + } + res += "};\n" + res += "}" + res +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/groovy/genDgmMath.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/genDgmMath.groovy b/src/main/groovy/genDgmMath.groovy new file mode 100644 index 0000000..71bdd5f --- /dev/null +++ b/src/main/groovy/genDgmMath.groovy @@ -0,0 +1,87 @@ +/* + * 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.classgen + +def types = ["Integer", "Long", "Float", "Double"] + +def getMath (a,b) { + if (a == "Double" || b == "Double" || a == "Float" || b == "Float") + return "FloatingPointMath" + + if (a == "Long" || b == "Long") + return "LongMath" + + "IntegerMath" +} + +println """ +public CallSite createPojoCallSite(CallSite site, MetaClassImpl metaClass, MetaMethod metaMethod, Class[] params, Object receiver, Object[] args) { + NumberMath m = NumberMath.getMath((Number)receiver, (Number)args[0]); +""" + +types.each { + a -> + print """ + if (receiver instanceof $a) {""" + types.each { + b -> + print """ + if (args[0] instanceof $b) + return new NumberNumberCallSite (site, metaClass, metaMethod, params, (Number)receiver, (Number)args[0]){ + public final Object invoke(Object receiver, Object[] args) { + return ${getMath(a,b)}.INSTANCE.addImpl(($a)receiver,($b)args[0]); + } + + public final Object invokeBinop(Object receiver, Object arg) { + return ${getMath(a,b)}.INSTANCE.addImpl(($a)receiver,($b)arg); + } + }; + """ + } + println "}" +} + +println """ + return new NumberNumberCallSite (site, metaClass, metaMethod, params, (Number)receiver, (Number)args[0]){ + public final Object invoke(Object receiver, Object[] args) { + return math.addImpl((Number)receiver,(Number)args[0]); + } + + public final Object invokeBinop(Object receiver, Object arg) { + return math.addImpl((Number)receiver,(Number)arg); + } +} +""" + +for (i in 2..256) { + print "public Object invoke$i (Object receiver, " + for (j in 1..(i-1)) { + print "Object a$j, " + } + println "Object a$i) {" + + print " return invoke (receiver, new Object[] {" + + for (j in 1..(i-1)) { + print "a$j, " + } + println "a$i} );" + + println "}" +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/groovy/genMathModification.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/genMathModification.groovy b/src/main/groovy/genMathModification.groovy new file mode 100644 index 0000000..10cc7eb --- /dev/null +++ b/src/main/groovy/genMathModification.groovy @@ -0,0 +1,133 @@ +/* + * 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.classgen + +def ops = [ + "plus", + "minus", + "multiply", + "div", + "or", + "and", + "xor", + "intdiv", + "mod", + "leftShift", + "rightShift", + "rightShiftUnsigned" +] + +def numbers = ["Byte":"byte", "Short":"short", "Integer":"int", "Long":"long", "Float":"float", "Double":"double"] + +ops.each { op -> + numbers.each { wrappedType, type -> + println "public boolean ${type}_${op};"; + } +} + +ops.each { op -> + println "if (\"${op}\".equals(name)) {" + numbers.each { wrappedType, type -> + println """if (klazz==${wrappedType}.class) { + ${type}_${op} = true; + }""" + } + println "if (klazz==Object.class) {" + numbers.each { wrappedType, type -> + println "${type}_${op} = true;" + } + println "}" + println "}" +} + +ops.each { op -> + numbers.each { wrappedType1, type1 -> + numbers.each { wrappedType2, type2 -> + def math = getMath(wrappedType1, wrappedType2) + if (math [op]) { + println """public static ${math.resType} ${op}(${type1} op1, ${type2} op2) { + if (instance.${type1}_${op}) { + return ${op}Slow(op1, op2); + } + else { + return ${math.resType != type1 ? "((" + math.resType+ ")op1)" : "op1"} ${math[op]} ${math.resType != type2 ? "((" + math.resType+ ")op2)" : "op2"}; + } + }""" + println """private static ${math.resType} ${op}Slow(${type1} op1,${type2} op2) { + return ((Number)InvokerHelper.invokeMethod(op1, "${op}", op2)).${math.resType}Value(); + }""" + } + } + } +} + +def isFloatingPoint(number) { + return number == "Double" || number == "Float"; +} + +def isLong(number) { + return number == "Long"; +} + +def getMath (left, right) { + if (isFloatingPoint(left) || isFloatingPoint(right)) { + return [ + resType : "double", + + plus : "+", + minus : "-", + multiply : "*", + div : "/", + ]; + } + if (isLong(left) || isLong(right)){ + return [ + resType : "long", + + plus : "+", + minus : "-", + multiply : "*", + div : "/", + or : "|", + and : "&", + xor : "^", + intdiv : "/", + mod : "%", + leftShift : "<<", + rightShift : ">>", + rightShiftUnsigned : ">>>" + ] + } + return [ + resType : "int", + + plus : "+", + minus : "-", + multiply : "*", + div : "/", + or : "|", + and : "&", + xor : "^", + intdiv : "/", + mod : "%", + leftShift : "<<", + rightShift : ">>", + rightShiftUnsigned : ">>>" + ] +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java new file mode 100644 index 0000000..98e0fe2 --- /dev/null +++ b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java @@ -0,0 +1,273 @@ +/* + * 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.apache.groovy.ast.tools; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MapExpression; +import org.codehaus.groovy.ast.expr.SpreadExpression; +import org.codehaus.groovy.ast.expr.TupleExpression; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; + +/** + * Utility class for working with ClassNodes + */ +public class ClassNodeUtils { + /** + * Formats a type name into a human readable version. For arrays, appends "[]" to the formatted + * type name of the component. For unit class nodes, uses the class node name. + * + * @param cNode the type to format + * @return a human readable version of the type name (java.lang.String[] for example) + */ + public static String formatTypeName(ClassNode cNode) { + if (cNode.isArray()) { + ClassNode it = cNode; + int dim = 0; + while (it.isArray()) { + dim++; + it = it.getComponentType(); + } + StringBuilder sb = new StringBuilder(it.getName().length() + 2 * dim); + sb.append(it.getName()); + for (int i = 0; i < dim; i++) { + sb.append("[]"); + } + return sb.toString(); + } + return cNode.getName(); + } + + /** + * Add methods from the super class. + * + * @param cNode The ClassNode + * @return A map of methods + */ + public static Map<String, MethodNode> getDeclaredMethodsFromSuper(ClassNode cNode) { + ClassNode parent = cNode.getSuperClass(); + if (parent == null) { + return new HashMap<String, MethodNode>(); + } + return parent.getDeclaredMethodsMap(); + } + + /** + * Add in methods from all interfaces. Existing entries in the methods map take precedence. + * Methods from interfaces visited early take precedence over later ones. + * + * @param cNode The ClassNode + * @param methodsMap A map of existing methods to alter + */ + public static void addDeclaredMethodsFromInterfaces(ClassNode cNode, Map<String, MethodNode> methodsMap) { + // add in unimplemented abstract methods from the interfaces + for (ClassNode iface : cNode.getInterfaces()) { + Map<String, MethodNode> ifaceMethodsMap = iface.getDeclaredMethodsMap(); + for (Map.Entry<String, MethodNode> entry : ifaceMethodsMap.entrySet()) { + String methSig = entry.getKey(); + if (!methodsMap.containsKey(methSig)) { + methodsMap.put(methSig, entry.getValue()); + } + } + } + } + + /** + * Get methods from all interfaces. + * Methods from interfaces visited early will be overwritten by later ones. + * + * @param cNode The ClassNode + * @return A map of methods + */ + public static Map<String, MethodNode> getDeclaredMethodsFromInterfaces(ClassNode cNode) { + Map<String, MethodNode> result = new HashMap<String, MethodNode>(); + ClassNode[] interfaces = cNode.getInterfaces(); + for (ClassNode iface : interfaces) { + result.putAll(iface.getDeclaredMethodsMap()); + } + return result; + } + + /** + * Adds methods from interfaces and parent interfaces. Existing entries in the methods map take precedence. + * Methods from interfaces visited early take precedence over later ones. + * + * @param cNode The ClassNode + * @param methodsMap A map of existing methods to alter + */ + public static void addDeclaredMethodsFromAllInterfaces(ClassNode cNode, Map<String, MethodNode> methodsMap) { + List cnInterfaces = Arrays.asList(cNode.getInterfaces()); + ClassNode parent = cNode.getSuperClass(); + while (parent != null && !parent.equals(ClassHelper.OBJECT_TYPE)) { + ClassNode[] interfaces = parent.getInterfaces(); + for (ClassNode iface : interfaces) { + if (!cnInterfaces.contains(iface)) { + methodsMap.putAll(iface.getDeclaredMethodsMap()); + } + } + parent = parent.getSuperClass(); + } + } + + /** + * Returns true if the given method has a possibly matching static method with the given name and arguments. + * Handles default arguments and optionally spread expressions. + * + * @param cNode the ClassNode of interest + * @param name the name of the method of interest + * @param arguments the arguments to match against + * @param trySpread whether to try to account for SpreadExpressions within the arguments + * @return true if a matching method was found + */ + public static boolean hasPossibleStaticMethod(ClassNode cNode, String name, Expression arguments, boolean trySpread) { + int count = 0; + boolean foundSpread = false; + + if (arguments instanceof TupleExpression) { + TupleExpression tuple = (TupleExpression) arguments; + for (Expression arg : tuple.getExpressions()) { + if (arg instanceof SpreadExpression) { + foundSpread = true; + } else { + count++; + } + } + } else if (arguments instanceof MapExpression) { + count = 1; + } + + for (MethodNode method : cNode.getMethods(name)) { + if (method.isStatic()) { + Parameter[] parameters = method.getParameters(); + // do fuzzy match for spread case: count will be number of non-spread args + if (trySpread && foundSpread && parameters.length >= count) return true; + + if (parameters.length == count) return true; + + // handle varargs case + if (parameters.length > 0 && parameters[parameters.length - 1].getType().isArray()) { + if (count >= parameters.length - 1) return true; + // fuzzy match any spread to a varargs + if (trySpread && foundSpread) return true; + } + + // handle parameters with default values + int nonDefaultParameters = 0; + for (Parameter parameter : parameters) { + if (!parameter.hasInitialExpression()) { + nonDefaultParameters++; + } + } + + if (count < parameters.length && nonDefaultParameters <= count) { + return true; + } + // TODO handle spread with nonDefaultParams? + } + } + return false; + } + + /** + * Return true if we have a static accessor + */ + public static boolean hasPossibleStaticProperty(ClassNode cNode, String methodName) { + // assume explicit static method call checked first so we can assume a simple check here + if (!methodName.startsWith("get") && !methodName.startsWith("is")) { + return false; + } + String propName = getPropNameForAccessor(methodName); + PropertyNode pNode = getStaticProperty(cNode, propName); + return pNode != null && (methodName.startsWith("get") || boolean_TYPE.equals(pNode.getType())); + } + + /** + * Returns the property name, e.g. age, given an accessor name, e.g. getAge. + * Returns the original if a valid prefix cannot be removed. + * + * @param accessorName the accessor name of interest, e.g. getAge + * @return the property name, e.g. age, or original if not a valid property accessor name + */ + public static String getPropNameForAccessor(String accessorName) { + if (!isValidAccessorName(accessorName)) return accessorName; + int prefixLength = accessorName.startsWith("is") ? 2 : 3; + return String.valueOf(accessorName.charAt(prefixLength)).toLowerCase() + accessorName.substring(prefixLength + 1); + } + + /** + * Detect whether the given accessor name starts with "get", "set" or "is" followed by at least one character. + * + * @param accessorName the accessor name of interest, e.g. getAge + * @return true if a valid prefix is found + */ + public static boolean isValidAccessorName(String accessorName) { + if (accessorName.startsWith("get") || accessorName.startsWith("is") || accessorName.startsWith("set")) { + int prefixLength = accessorName.startsWith("is") ? 2 : 3; + return accessorName.length() > prefixLength; + }; + return false; + } + + public static boolean hasStaticProperty(ClassNode cNode, String propName) { + return getStaticProperty(cNode, propName) != null; + } + + /** + * Detect whether a static property with the given name is within the class + * or a super class. + * + * @param cNode the ClassNode of interest + * @param propName the property name + * @return the static property if found or else null + */ + public static PropertyNode getStaticProperty(ClassNode cNode, String propName) { + ClassNode classNode = cNode; + while (classNode != null) { + for (PropertyNode pn : classNode.getProperties()) { + if (pn.getName().equals(propName) && pn.isStatic()) return pn; + } + classNode = classNode.getSuperClass(); + } + return null; + } + + /** + * Detect whether a given ClassNode is a inner class (non-static). + * + * @param cNode the ClassNode of interest + * @return true if the given node is a (non-static) inner class, else false + */ + public static boolean isInnerClass(ClassNode cNode) { + return cNode.redirect().getOuterClass() != null + && !Modifier.isStatic(cNode.getModifiers()); + } + + private ClassNodeUtils() { } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/ast/tools/MethodNodeUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/ast/tools/MethodNodeUtils.java b/src/main/java/org/apache/groovy/ast/tools/MethodNodeUtils.java new file mode 100644 index 0000000..94427f0 --- /dev/null +++ b/src/main/java/org/apache/groovy/ast/tools/MethodNodeUtils.java @@ -0,0 +1,69 @@ +/* + * 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.apache.groovy.ast.tools; + +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; + +/** + * Utility class for working with MethodNodes + */ +public class MethodNodeUtils { + /** + * Return the method node's descriptor including its + * name and parameter types without generics. + * + * @param mNode the method node + * @return the method node's abbreviated descriptor excluding the return type + */ + public static String methodDescriptorWithoutReturnType(MethodNode mNode) { + StringBuilder sb = new StringBuilder(); + sb.append(mNode.getName()).append(':'); + for (Parameter p : mNode.getParameters()) { + sb.append(ClassNodeUtils.formatTypeName(p.getType())).append(','); + } + return sb.toString(); + } + + /** + * Return the method node's descriptor which includes its return type, + * name and parameter types without generics. + * + * @param mNode the method node + * @return the method node's descriptor + */ + public static String methodDescriptor(MethodNode mNode) { + StringBuilder sb = new StringBuilder(mNode.getName().length() + mNode.getParameters().length * 10); + sb.append(mNode.getReturnType().getName()); + sb.append(' '); + sb.append(mNode.getName()); + sb.append('('); + for (int i = 0; i < mNode.getParameters().length; i++) { + if (i > 0) { + sb.append(", "); + } + Parameter p = mNode.getParameters()[i]; + sb.append(ClassNodeUtils.formatTypeName(p.getType())); + } + sb.append(')'); + return sb.toString(); + } + + private MethodNodeUtils() { } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/internal/metaclass/MetaClassConstant.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/internal/metaclass/MetaClassConstant.java b/src/main/java/org/apache/groovy/internal/metaclass/MetaClassConstant.java new file mode 100644 index 0000000..df5a7ec --- /dev/null +++ b/src/main/java/org/apache/groovy/internal/metaclass/MetaClassConstant.java @@ -0,0 +1,50 @@ +/* + * 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.apache.groovy.internal.metaclass; + +import groovy.lang.MetaClassImpl; +import groovy.lang.MetaMethod; +import org.apache.groovy.lang.annotation.Incubating; + +import java.lang.invoke.SwitchPoint; + +/** + * The one and only implementation of a meta class. + * INTERNAL USE ONLY. + */ +@Incubating +public final class MetaClassConstant<T> { + private final SwitchPoint switchPoint = new SwitchPoint(); + //TODO Joche: replace with real implementation + private final MetaClassImpl impl; + + public MetaClassConstant(Class<T> clazz) { + impl = new MetaClassImpl(clazz); + } + + public SwitchPoint getSwitchPoint() { + return switchPoint; + } + + // TODO Jochen: replace with new MetaMethod + public MetaMethod getMethod(String name, Class[] parameters) { + return impl.pickMethod(name, parameters); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/internal/util/Function.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/internal/util/Function.java b/src/main/java/org/apache/groovy/internal/util/Function.java new file mode 100644 index 0000000..3a4fea5 --- /dev/null +++ b/src/main/java/org/apache/groovy/internal/util/Function.java @@ -0,0 +1,31 @@ +/* + * 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.apache.groovy.internal.util; + +import org.apache.groovy.lang.annotation.Incubating; + +/** + * Backport of Java8 Function. + * INTERNAL USE ONLY. + */ +@Incubating +public interface Function<T, R> { + R apply(T t); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/internal/util/ReevaluatingReference.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/internal/util/ReevaluatingReference.java b/src/main/java/org/apache/groovy/internal/util/ReevaluatingReference.java new file mode 100644 index 0000000..feeeea8 --- /dev/null +++ b/src/main/java/org/apache/groovy/internal/util/ReevaluatingReference.java @@ -0,0 +1,88 @@ +/* + * 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.apache.groovy.internal.util; + +import org.apache.groovy.lang.annotation.Incubating; +import org.codehaus.groovy.GroovyBugError; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.SwitchPoint; +import java.lang.ref.WeakReference; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +/** + * This class represents a reference to the most actual incarnation of a Metaclass. + * INTERNAL USE ONLY. + */ +@Incubating +public class ReevaluatingReference<T> { + private static final MethodHandle FALLBACK_HANDLE; + static { + try { + //TODO Jochen: move the findSpecial to a central place together with others to easy security configuration + FALLBACK_HANDLE = AccessController.doPrivileged(new PrivilegedExceptionAction<MethodHandle>() { + @Override + public MethodHandle run() throws Exception { + return MethodHandles.lookup().findSpecial( + ReevaluatingReference.class, "replacePayLoad", + MethodType.methodType(Object.class), + ReevaluatingReference.class); + } + }); + } catch (PrivilegedActionException e) { + throw new GroovyBugError(e); + } + } + + private final Supplier<T> valueSupplier; + private final Function<T, SwitchPoint> validationSupplier; + private final WeakReference<Class<T>> clazzRef; + private MethodHandle returnRef; + + + public ReevaluatingReference(Class clazz, Supplier<T> valueSupplier, Function<T, SwitchPoint> validationSupplier) { + this.valueSupplier = valueSupplier; + this.validationSupplier = validationSupplier; + clazzRef = new WeakReference<Class<T>>(clazz); + replacePayLoad(); + } + + private T replacePayLoad() { + T payload = valueSupplier.get(); + MethodHandle ref = MethodHandles.constant(clazzRef.get(), payload); + SwitchPoint sp = validationSupplier.apply(payload); + returnRef = sp.guardWithTest(ref, FALLBACK_HANDLE); + return payload; + } + + public T getPayload() { + T ref = null; + try { + ref = (T) returnRef.invokeExact(); + } catch (Throwable throwable) { + UncheckedThrow.rethrow(throwable); + } + return ref; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/internal/util/Supplier.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/internal/util/Supplier.java b/src/main/java/org/apache/groovy/internal/util/Supplier.java new file mode 100644 index 0000000..3a01785 --- /dev/null +++ b/src/main/java/org/apache/groovy/internal/util/Supplier.java @@ -0,0 +1,31 @@ +/* + * 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.apache.groovy.internal.util; + +import org.apache.groovy.lang.annotation.Incubating; + +/** + * Backport of Java8 Supplier. + * INTERNAL USE ONLY. + */ +@Incubating +public interface Supplier<T> { + T get(); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/internal/util/UncheckedThrow.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/internal/util/UncheckedThrow.java b/src/main/java/org/apache/groovy/internal/util/UncheckedThrow.java new file mode 100644 index 0000000..7f6cc8a --- /dev/null +++ b/src/main/java/org/apache/groovy/internal/util/UncheckedThrow.java @@ -0,0 +1,38 @@ +/* + * 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.apache.groovy.internal.util; + +import org.apache.groovy.lang.annotation.Incubating; + +/** + * Allows to throw a checked exception unchecked. + * INTERNAL USE ONLY. + */ +@Incubating +public class UncheckedThrow { + public static void rethrow( final Throwable checkedException ) { + UncheckedThrow.<RuntimeException>thrownInsteadOf( checkedException ); + } + @SuppressWarnings("unchecked") + private static <T extends Throwable> void thrownInsteadOf(Throwable t) throws T { + throw (T) t; + } +} + http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/lang/annotation/Incubating.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/lang/annotation/Incubating.java b/src/main/java/org/apache/groovy/lang/annotation/Incubating.java new file mode 100644 index 0000000..d6964f8 --- /dev/null +++ b/src/main/java/org/apache/groovy/lang/annotation/Incubating.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.apache.groovy.lang.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation to indicate experimental and still to be refined API, which may change at any time + */ +@Incubating +@Documented +@Retention(value=RUNTIME) +@Target(value={TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) +public @interface Incubating { +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/metaclass/MetaClass.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/metaclass/MetaClass.java b/src/main/java/org/apache/groovy/metaclass/MetaClass.java new file mode 100644 index 0000000..6ebe4fc --- /dev/null +++ b/src/main/java/org/apache/groovy/metaclass/MetaClass.java @@ -0,0 +1,41 @@ +/* + * 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.apache.groovy.metaclass; + +import groovy.lang.MetaMethod; +import org.apache.groovy.internal.metaclass.MetaClassConstant; +import org.apache.groovy.internal.util.ReevaluatingReference; +import org.apache.groovy.lang.annotation.Incubating; + +/** + * A MetaClass within Groovy defines the behaviour of any given Groovy or Java class + */ +@Incubating +public final class MetaClass<T> { + private final ReevaluatingReference<MetaClassConstant<T>> implRef; + + MetaClass(ReevaluatingReference<MetaClassConstant<T>> implRef) { + this.implRef = implRef; + } + + public MetaMethod getMethod(String name, Class[] parameters) { + return implRef.getPayload().getMethod(name, parameters); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/metaclass/Realm.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/metaclass/Realm.java b/src/main/java/org/apache/groovy/metaclass/Realm.java new file mode 100644 index 0000000..a652dab --- /dev/null +++ b/src/main/java/org/apache/groovy/metaclass/Realm.java @@ -0,0 +1,91 @@ +/* + * 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.apache.groovy.metaclass; + +import org.apache.groovy.internal.metaclass.MetaClassConstant; +import org.apache.groovy.internal.util.Function; +import org.apache.groovy.internal.util.ReevaluatingReference; +import org.apache.groovy.internal.util.Supplier; +import org.apache.groovy.lang.annotation.Incubating; + +import java.lang.invoke.SwitchPoint; +import java.util.Objects; + +/** + * A Realm is the representation of a metaclass layer in a tree of realm objects. + */ +@Incubating +public final class Realm { + private static final Realm ROOT = new Realm("ROOT", null); + + private final String name; + private final Realm parent; + private final ClassValue<MetaClassConstant<?>> cv = new ClassValue<MetaClassConstant<?>>() { + @Override + @SuppressWarnings("unchecked") + protected MetaClassConstant<?> computeValue(Class<?> type) { + return new MetaClassConstant(type); + } + }; + + private Realm(String name, Realm parent) { + this.name = name; + this.parent = parent; + } + + public static Realm newRealm(String name, Realm parent) { + Objects.requireNonNull(name, "missing realm name"); + if (parent == null) { + return new Realm(name, ROOT); + } else { + return new Realm(name, parent); + } + } + + @Override + public String toString() { + return "Realm{" + + "name='" + name + '\'' + + ", parent=" + parent + + '}'; + } + + public <T> MetaClass<T> getMetaClass(final Class<T> theClass) { + Supplier<MetaClassConstant<T>> valueSupplier = new Supplier<MetaClassConstant<T>>() { + @Override + @SuppressWarnings("unchecked") + public MetaClassConstant<T> get() { + return (MetaClassConstant<T>) cv.get(theClass); + } + }; + Function<MetaClassConstant<T>, SwitchPoint> validationSupplier = new Function<MetaClassConstant<T>, SwitchPoint>() { + @Override + public SwitchPoint apply(MetaClassConstant<T> metaClassImpl) { + return metaClassImpl.getSwitchPoint(); + } + }; + ReevaluatingReference<MetaClassConstant<T>> ref = new ReevaluatingReference<>( + MetaClassConstant.class, + valueSupplier, + validationSupplier + ); + return new MetaClass<>(ref); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/plugin/DefaultRunners.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/plugin/DefaultRunners.java b/src/main/java/org/apache/groovy/plugin/DefaultRunners.java new file mode 100644 index 0000000..5f570eb --- /dev/null +++ b/src/main/java/org/apache/groovy/plugin/DefaultRunners.java @@ -0,0 +1,218 @@ +/* + * 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.apache.groovy.plugin; + +import groovy.lang.GroovyClassLoader; +import groovy.lang.GroovyRuntimeException; +import org.codehaus.groovy.runtime.InvokerHelper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; + +/** + * Provides access to built-in {@link GroovyRunner} instances + * for the registry. These instances should be accessed via + * the registry and not used directly. + */ +final class DefaultRunners { + + /* + * These runners were originally included directly in GroovyShell. + * Since they are part of core they are added directly to the + * GroovyRunnerRegistry rather than via a provider configuration + * file in META-INF/services. If any of these runners are moved + * out to a submodule then they should be registered using the + * provider configuration file (see groovy-testng). + * + * These are internal classes and not meant to be referenced + * outside of the GroovyRunnerRegistry. + */ + + private static final GroovyRunner JUNIT3_TEST = new Junit3TestRunner(); + private static final GroovyRunner JUNIT3_SUITE = new Junit3SuiteRunner(); + private static final GroovyRunner JUNIT4_TEST = new Junit4TestRunner(); + + private DefaultRunners() { + } + + static GroovyRunner junit3TestRunner() { + return JUNIT3_TEST; + } + + static GroovyRunner junit3SuiteRunner() { + return JUNIT3_SUITE; + } + + static GroovyRunner junit4TestRunner() { + return JUNIT4_TEST; + } + + private static class Junit3TestRunner implements GroovyRunner { + /** + * Utility method to check through reflection if the class appears to be a + * JUnit 3.8.x test, i.e. checks if it extends JUnit 3.8.x's TestCase. + * + * @param scriptClass the class we want to check + * @param loader the class loader + * @return true if the class appears to be a test + */ + @Override + public boolean canRun(Class<?> scriptClass, GroovyClassLoader loader) { + try { + Class<?> testCaseClass = loader.loadClass("junit.framework.TestCase"); + return testCaseClass.isAssignableFrom(scriptClass); + } catch (Throwable e) { + return false; + } + } + + /** + * Run the specified class extending TestCase as a unit test. + * This is done through reflection, to avoid adding a dependency to the JUnit framework. + * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile + * groovy scripts and classes would have to add another dependency on their classpath. + * + * @param scriptClass the class to be run as a unit test + * @param loader the class loader + */ + @Override + public Object run(Class<?> scriptClass, GroovyClassLoader loader) { + try { + Object testSuite = InvokerHelper.invokeConstructorOf("junit.framework.TestSuite", new Object[]{scriptClass}); + return InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object[]{testSuite}); + } catch (ClassNotFoundException e) { + throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.", e); + } + } + } + + private static class Junit3SuiteRunner implements GroovyRunner { + /** + * Utility method to check through reflection if the class appears to be a + * JUnit 3.8.x test suite, i.e. checks if it extends JUnit 3.8.x's TestSuite. + * + * @param scriptClass the class we want to check + * @param loader the class loader + * @return true if the class appears to be a test + */ + @Override + public boolean canRun(Class<?> scriptClass, GroovyClassLoader loader) { + try { + Class<?> testSuiteClass = loader.loadClass("junit.framework.TestSuite"); + return testSuiteClass.isAssignableFrom(scriptClass); + } catch (Throwable e) { + return false; + } + } + + /** + * Run the specified class extending TestSuite as a unit test. + * This is done through reflection, to avoid adding a dependency to the JUnit framework. + * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile + * groovy scripts and classes would have to add another dependency on their classpath. + * + * @param scriptClass the class to be run as a unit test + * @param loader the class loader + */ + @Override + public Object run(Class<?> scriptClass, GroovyClassLoader loader) { + try { + Object testSuite = InvokerHelper.invokeStaticMethod(scriptClass, "suite", new Object[]{}); + return InvokerHelper.invokeStaticMethod("junit.textui.TestRunner", "run", new Object[]{testSuite}); + } catch (ClassNotFoundException e) { + throw new GroovyRuntimeException("Failed to run the unit test. JUnit is not on the Classpath.", e); + } + } + } + + private static class Junit4TestRunner implements GroovyRunner { + /** + * Utility method to check via reflection if the parsed class appears to be a JUnit4 + * test, i.e. checks whether it appears to be using the relevant JUnit 4 annotations. + * + * @param scriptClass the class we want to check + * @param loader the class loader + * @return true if the class appears to be a test + */ + @Override + public boolean canRun(Class<?> scriptClass, GroovyClassLoader loader) { + return hasRunWithAnnotation(scriptClass, loader) + || hasTestAnnotatedMethod(scriptClass, loader); + } + + /** + * Run the specified class extending TestCase as a unit test. + * This is done through reflection, to avoid adding a dependency to the JUnit framework. + * Otherwise, developers embedding Groovy and using GroovyShell to load/parse/compile + * groovy scripts and classes would have to add another dependency on their classpath. + * + * @param scriptClass the class to be run as a unit test + * @param loader the class loader + */ + @Override + public Object run(Class<?> scriptClass, GroovyClassLoader loader) { + try { + Class<?> junitCoreClass = loader.loadClass("org.junit.runner.JUnitCore"); + Object result = InvokerHelper.invokeStaticMethod(junitCoreClass, + "runClasses", new Object[]{scriptClass}); + System.out.print("JUnit 4 Runner, Tests: " + InvokerHelper.getProperty(result, "runCount")); + System.out.print(", Failures: " + InvokerHelper.getProperty(result, "failureCount")); + System.out.println(", Time: " + InvokerHelper.getProperty(result, "runTime")); + List<?> failures = (List<?>) InvokerHelper.getProperty(result, "failures"); + for (Object f : failures) { + System.out.println("Test Failure: " + InvokerHelper.getProperty(f, "description")); + System.out.println(InvokerHelper.getProperty(f, "trace")); + } + return result; + } catch (ClassNotFoundException e) { + throw new GroovyRuntimeException("Error running JUnit 4 test.", e); + } + } + + private static boolean hasRunWithAnnotation(Class<?> scriptClass, ClassLoader loader) { + try { + @SuppressWarnings("unchecked") + Class<? extends Annotation> runWithAnnotationClass = + (Class<? extends Annotation>)loader.loadClass("org.junit.runner.RunWith"); + return scriptClass.isAnnotationPresent(runWithAnnotationClass); + } catch (Throwable e) { + return false; + } + } + + private static boolean hasTestAnnotatedMethod(Class<?> scriptClass, ClassLoader loader) { + try { + @SuppressWarnings("unchecked") + Class<? extends Annotation> testAnnotationClass = + (Class<? extends Annotation>) loader.loadClass("org.junit.Test"); + Method[] methods = scriptClass.getMethods(); + for (Method method : methods) { + if (method.isAnnotationPresent(testAnnotationClass)) { + return true; + } + } + } catch (Throwable e) { + // fall through + } + return false; + } + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/apache/groovy/plugin/GroovyRunner.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/groovy/plugin/GroovyRunner.java b/src/main/java/org/apache/groovy/plugin/GroovyRunner.java new file mode 100644 index 0000000..283d092 --- /dev/null +++ b/src/main/java/org/apache/groovy/plugin/GroovyRunner.java @@ -0,0 +1,49 @@ +/* + * 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.apache.groovy.plugin; + +import groovy.lang.GroovyClassLoader; + +/** + * Classes which can run scripts should implement this interface. + * + * @since 2.5.0 + */ +public interface GroovyRunner { + + /** + * Returns {@code true} if this runner is able to + * run the given class. + * + * @param scriptClass class to run + * @param loader used to locate classes and resources + * @return true if given class can be run, else false + */ + boolean canRun(Class<?> scriptClass, GroovyClassLoader loader); + + /** + * Runs the given class. + * + * @param scriptClass class to run + * @param loader used to locate classes and resources + * @return result of running the class + */ + Object run(Class<?> scriptClass, GroovyClassLoader loader); + +}
