http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/org/codehaus/groovy/ast/builder/AstSpecificationCompiler.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/ast/builder/AstSpecificationCompiler.groovy b/src/main/groovy/org/codehaus/groovy/ast/builder/AstSpecificationCompiler.groovy new file mode 100644 index 0000000..e033af8 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/ast/builder/AstSpecificationCompiler.groovy @@ -0,0 +1,1080 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.ast.builder + +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.ConstructorNode +import org.codehaus.groovy.ast.DynamicVariable +import org.codehaus.groovy.ast.FieldNode +import org.codehaus.groovy.ast.GenericsType +import org.codehaus.groovy.ast.ImportNode +import org.codehaus.groovy.ast.InnerClassNode +import org.codehaus.groovy.ast.MethodNode +import org.codehaus.groovy.ast.MixinNode +import org.codehaus.groovy.ast.Parameter +import org.codehaus.groovy.ast.PropertyNode +import org.codehaus.groovy.ast.VariableScope +import org.codehaus.groovy.ast.expr.AnnotationConstantExpression +import org.codehaus.groovy.ast.expr.ArgumentListExpression +import org.codehaus.groovy.ast.expr.ArrayExpression +import org.codehaus.groovy.ast.expr.AttributeExpression +import org.codehaus.groovy.ast.expr.BinaryExpression +import org.codehaus.groovy.ast.expr.BitwiseNegationExpression +import org.codehaus.groovy.ast.expr.BooleanExpression +import org.codehaus.groovy.ast.expr.CastExpression +import org.codehaus.groovy.ast.expr.ClassExpression +import org.codehaus.groovy.ast.expr.ClosureExpression +import org.codehaus.groovy.ast.expr.ClosureListExpression +import org.codehaus.groovy.ast.expr.ConstantExpression +import org.codehaus.groovy.ast.expr.ConstructorCallExpression +import org.codehaus.groovy.ast.expr.DeclarationExpression +import org.codehaus.groovy.ast.expr.ElvisOperatorExpression +import org.codehaus.groovy.ast.expr.Expression +import org.codehaus.groovy.ast.expr.FieldExpression +import org.codehaus.groovy.ast.expr.GStringExpression +import org.codehaus.groovy.ast.expr.ListExpression +import org.codehaus.groovy.ast.expr.MapEntryExpression +import org.codehaus.groovy.ast.expr.MapExpression +import org.codehaus.groovy.ast.expr.MethodCallExpression +import org.codehaus.groovy.ast.expr.MethodPointerExpression +import org.codehaus.groovy.ast.expr.NamedArgumentListExpression +import org.codehaus.groovy.ast.expr.NotExpression +import org.codehaus.groovy.ast.expr.PostfixExpression +import org.codehaus.groovy.ast.expr.PrefixExpression +import org.codehaus.groovy.ast.expr.PropertyExpression +import org.codehaus.groovy.ast.expr.RangeExpression +import org.codehaus.groovy.ast.expr.SpreadExpression +import org.codehaus.groovy.ast.expr.SpreadMapExpression +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression +import org.codehaus.groovy.ast.expr.TernaryExpression +import org.codehaus.groovy.ast.expr.TupleExpression +import org.codehaus.groovy.ast.expr.UnaryMinusExpression +import org.codehaus.groovy.ast.expr.UnaryPlusExpression +import org.codehaus.groovy.ast.expr.VariableExpression +import org.codehaus.groovy.ast.stmt.AssertStatement +import org.codehaus.groovy.ast.stmt.BlockStatement +import org.codehaus.groovy.ast.stmt.BreakStatement +import org.codehaus.groovy.ast.stmt.CaseStatement +import org.codehaus.groovy.ast.stmt.CatchStatement +import org.codehaus.groovy.ast.stmt.ContinueStatement +import org.codehaus.groovy.ast.stmt.EmptyStatement +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.Statement +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.TryCatchStatement +import org.codehaus.groovy.ast.stmt.WhileStatement +import org.codehaus.groovy.runtime.MethodClosure +import org.codehaus.groovy.syntax.Token +import org.codehaus.groovy.syntax.Types + +/** + * Handles parsing the properties from the closure into values that can be referenced. + * + * This object is very stateful and not threadsafe. It accumulates expressions in the + * 'expression' field as they are found and executed within the DSL. + * + * Note: this class consists of many one-line method calls. A better implementation + * might be to take a declarative approach and replace the one-liners with map entries. + * + * @author Hamlet D'Arcy + */ +class AstSpecificationCompiler implements GroovyInterceptable { + + private final List<ASTNode> expression = [] + + /** + * Creates the DSL compiler. + */ + AstSpecificationCompiler(@DelegatesTo(AstSpecificationCompiler) Closure spec) { + spec.delegate = this + spec() + } + + /** + * Gets the current generated expression. + */ + List<ASTNode> getExpression() { + return expression + } + + /** + * This method takes a List of Classes (a "spec"), and makes sure that the expression field + * contains those classes. It is a safety mechanism to enforce that the DSL is being called + * properly. + * + * @param methodName + * the name of the method within the DSL that is being invoked. Used in creating error messages. + * @param spec + * the list of Class objects that the method expects to have in the expression field when invoked. + * @return + * the portions of the expression field that adhere to the spec. + */ + private List<ASTNode> enforceConstraints(String methodName, List<Class> spec) { + + // enforce that the correct # arguments was passed + if (spec.size() != expression.size()) { + throw new IllegalArgumentException("$methodName could not be invoked. Expected to receive parameters $spec but found ${expression?.collect { it.class }}") + } + + // enforce types and collect result + (0..(spec.size() - 1)).collect { int it -> + def actualClass = expression[it].class + def expectedClass = spec[it] + if (!expectedClass.isAssignableFrom(actualClass)) { + throw new IllegalArgumentException("$methodName could not be invoked. Expected to receive parameters $spec but found ${expression?.collect { it.class }}") + } + expression[it] + } + } + + /** + * This method helps you take Closure parameters to a method and bundle them into + * constructor calls to a specific ASTNode subtype. + * @param name + * name of object being constructed, used to create helpful error message. + * @param argBlock + * the actual parameters being specified for the node + * @param constructorStatement + * the type specific construction code that will be run + */ + private void captureAndCreateNode(String name, @DelegatesTo(AstSpecificationCompiler) Closure argBlock, Closure constructorStatement) { + if (!argBlock) throw new IllegalArgumentException("nodes of type $name require arguments to be specified") + + def oldProps = new ArrayList(expression) + expression.clear() + new AstSpecificationCompiler(argBlock) + def result = constructorStatement(expression) // invoke custom constructor for node + expression.clear() + expression.addAll(oldProps) + expression.add(result) + } + + /** + * Helper method to convert a DSL invocation into an ASTNode instance. + * + * @param target + * the class you are going to create + * @param typeAlias + * the DSL keyword that was used to invoke this type + * @param ctorArgs + * a specification of what arguments the constructor expects + * @param argBlock + * the single closure argument used during invocation + */ + private void makeNode(Class target, String typeAlias, List<Class<? super ASTNode>> ctorArgs, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode(target.class.simpleName, argBlock) { + target.newInstance(*enforceConstraints(typeAlias, ctorArgs)) + } + } + + /** + * Helper method to convert a DSL invocation with a list of parameters specified + * in a Closure into an ASTNode instance. + * + * @param target + * the class you are going to create + * @param argBlock + * the single closure argument used during invocation + */ + private void makeNodeFromList(Class target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + //todo: add better error handling? + captureAndCreateNode(target.simpleName, argBlock) { + target.newInstance(new ArrayList(expression)) + } + } + + /** + * Helper method to convert a DSL invocation with a String parameter into a List of ASTNode instances. + * + * @param argBlock + * the single closure argument used during invocation + * @param input + * the single String argument used during invocation + */ + private void makeListOfNodes(@DelegatesTo(AstSpecificationCompiler) Closure argBlock, String input) { + captureAndCreateNode(input, argBlock) { + new ArrayList(expression) + } + } + + /** + * Helper method to convert a DSL invocation with a String parameter into an Array of ASTNode instances. + * + * @param argBlock + * the single closure argument used during invocation + * @param target + * the target type + */ + private void makeArrayOfNodes(Object target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode(target.class.simpleName, argBlock) { + expression.toArray(target) + } + } + + /** + * Helper method to convert a DSL invocation into an ASTNode instance when a Class parameter is specified. + * + * @param target + * the class you are going to create + * @param alias + * the DSL keyword that was used to invoke this type + * @param spec + * the list of Classes that you expect to be present as parameters + * @param argBlock + * the single closure argument used during invocation + * @param type + * a type parameter + */ + private void makeNodeWithClassParameter(Class target, String alias, List<Class> spec, @DelegatesTo(AstSpecificationCompiler) Closure argBlock, Class type) { + captureAndCreateNode(target.class.simpleName, argBlock) { + expression.add(0, ClassHelper.make(type)) + target.newInstance(*enforceConstraints(alias, spec)) + } + } + + private void makeNodeWithStringParameter(Class target, String alias, List<Class> spec, @DelegatesTo(AstSpecificationCompiler) Closure argBlock, String text) { + captureAndCreateNode(target.class.simpleName, argBlock) { + expression.add(0, text) + target.newInstance(*enforceConstraints(alias, spec)) + } + } + + /** + * Creates a CastExpression. + */ + void cast(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeWithClassParameter(CastExpression, 'cast', [ClassNode, Expression], argBlock, type) + } + + /** + * Creates an ConstructorCallExpression. + */ + void constructorCall(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeWithClassParameter(ConstructorCallExpression, 'constructorCall', [ClassNode, Expression], argBlock, type) + } + + /** + * Creates a MethodCallExpression. + */ + void methodCall(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(MethodCallExpression, 'methodCall', [Expression, Expression, Expression], argBlock) + } + + /** + * Creates an AnnotationConstantExpression. + */ + void annotationConstant(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(AnnotationConstantExpression, 'annotationConstant', [AnnotationNode], argBlock) + } + + /** + * Creates a PostfixExpression. + */ + void postfix(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(PostfixExpression, 'postfix', [Expression, Token], argBlock) + } + + /** + * Creates a FieldExpression. + */ + void field(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(FieldExpression, 'field', [FieldNode], argBlock) + } + + /** + * Creates a MapExpression. + */ + void map(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(MapExpression, argBlock) + } + + /** + * Creates a TupleExpression. + */ + void tuple(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(TupleExpression, argBlock) + } + + /** + * Creates a MapEntryExpression. + */ + void mapEntry(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(MapEntryExpression, 'mapEntry', [Expression, Expression], argBlock) + } + + /** + * Creates a gString. + */ + void gString(String verbatimText, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeWithStringParameter(GStringExpression, 'gString', [String, List, List], argBlock, verbatimText) + } + + + /** + * Creates a methodPointer. + */ + + void methodPointer(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(MethodPointerExpression, 'methodPointer', [Expression, Expression], argBlock) + } + + /** + * Creates a property. + */ + void property(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(PropertyExpression, 'property', [Expression, Expression], argBlock) + } + + /** + * Creates a RangeExpression. + */ + void range(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(RangeExpression, 'range', [Expression, Expression, Boolean], argBlock) + } + + /** + * Creates EmptyStatement. + */ + void empty() { + expression << EmptyStatement.INSTANCE + } + + /** + * Creates a label. + */ + void label(String label) { + expression << label + } + + /** + * Creates an ImportNode. + */ + void importNode(Class target, String alias = null) { + expression << new ImportNode(ClassHelper.make(target), alias) + } + + /** + * Creates a CatchStatement. + */ + void catchStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(CatchStatement, 'catchStatement', [Parameter, Statement], argBlock) + } + + /** + * Creates a ThrowStatement. + */ + void throwStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ThrowStatement, 'throwStatement', [Expression], argBlock) + } + + /** + * Creates a SynchronizedStatement. + */ + void synchronizedStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(SynchronizedStatement, 'synchronizedStatement', [Expression, Statement], argBlock) + } + + /** + * Creates a ReturnStatement. + */ + void returnStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ReturnStatement, 'returnStatement', [Expression], argBlock) + } + + /** + * Creates a TernaryExpression. + */ + + private void ternary(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(TernaryExpression, 'ternary', [BooleanExpression, Expression, Expression], argBlock) + } + + + /** + * Creates an ElvisOperatorExpression. + */ + void elvisOperator(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ElvisOperatorExpression, 'elvisOperator', [Expression, Expression], argBlock) + } + + /** + * Creates a BreakStatement. + */ + void breakStatement(String label = null) { + if (label) { + expression << new BreakStatement(label) + } else { + expression << new BreakStatement() + } + } + + /** + * Creates a ContinueStatement. + */ + void continueStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (!argBlock) { + expression << new ContinueStatement() + } else { + makeNode(ContinueStatement, 'continueStatement', [String], argBlock) + } + } + + /** + * Create a CaseStatement. + */ + void caseStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(CaseStatement, 'caseStatement', [Expression, Statement], argBlock) + } + + /** + * Creates a BlockStatement. + */ + void defaultCase(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + block(argBlock) // same as arg block + } + + /** + * Creates a PrefixExpression. + */ + void prefix(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(PrefixExpression, 'prefix', [Token, Expression], argBlock) + } + + /** + * Creates a NotExpression. + */ + void not(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(NotExpression, 'not', [Expression], argBlock) + } + + /** + * Creates a DynamicVariable. + */ + void dynamicVariable(String variable, boolean isStatic = false) { + expression << new DynamicVariable(variable, isStatic) + } + + /** + * Creates a ClassNode[]. + */ + void exceptions(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeArrayOfNodes([] as ClassNode[], argBlock) + } + + /** + * Designates a list of AnnotationNodes. + */ + void annotations(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<AnnotationNode>") + } + + + /** + * Designates a list of MethodNodes. + */ + void methods(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<MethodNode>") + } + + /** + * Designates a list of ConstructorNodes. + */ + void constructors(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<ConstructorNode>") + } + + /** + * Designates a list of {@code PropertyNode}s. + */ + void properties(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<PropertyNode>") + } + + /** + * Designates a list of {@code FieldNode}s. + */ + void fields(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<FieldNode>") + } + + /** + * Designates a list of ConstantExpressions. + */ + + void strings(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<ConstantExpression>") + } + + /** + * Designates a list of Expressions. + */ + + void values(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<Expression>") + } + + /** + * Creates a boolean value. + */ + void inclusive(boolean value) { + expression << value + } + + /** + * Creates a ConstantExpression. + */ + void constant(Object value) { + expression << new ConstantExpression(value) + } + + /** + * Creates an IfStatement. + */ + void ifStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(IfStatement, 'ifStatement', [BooleanExpression, Statement, Statement], argBlock) + } + + /** + * Creates a SpreadExpression. + */ + void spread(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(SpreadExpression, 'spread', [Expression], argBlock) + } + + /** + * Creates a SpreadMapExpression. + */ + void spreadMap(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(SpreadMapExpression, 'spreadMap', [Expression], argBlock) + } + + /** + * Creates a WhileStatement. + */ + void whileStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(WhileStatement, 'whileStatement', [BooleanExpression, Statement], argBlock) + } + + /** + * Create a ForStatement. + */ + void forStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ForStatement, 'forStatement', [Parameter, Expression, Statement], argBlock) + } + + /** + * Creates a ClosureListExpression. + */ + void closureList(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(ClosureListExpression, argBlock) + } + + /** + * Creates a DeclarationExpression. + */ + void declaration(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(DeclarationExpression, 'declaration', [Expression, Token, Expression], argBlock) + } + + /** + * Creates a ListExpression. + */ + void list(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(ListExpression, argBlock) + } + + /** + * Creates a BitwiseNegationExpression. + */ + void bitwiseNegation(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(BitwiseNegationExpression, 'bitwiseNegation', [Expression], argBlock) + } + + /** + * Creates a ClosureExpression. + */ + void closure(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ClosureExpression, 'closure', [Parameter[], Statement], argBlock) + } + + /** + * Creates a BooleanExpression. + */ + void booleanExpression(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(BooleanExpression, 'booleanExpression', [Expression], argBlock) + } + + /** + * Creates a BinaryExpression. + */ + void binary(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(BinaryExpression, 'binary', [Expression, Token, Expression], argBlock) + } + + /** + * Creates a UnaryPlusExpression. + */ + void unaryPlus(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(UnaryPlusExpression, 'unaryPlus', [Expression], argBlock) + } + + /** + * Creates a ClassExpression. + */ + void classExpression(Class type) { + expression << new ClassExpression(ClassHelper.make(type)) + } + + /** + * Creates a UnaryMinusExpression + */ + void unaryMinus(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(UnaryMinusExpression, 'unaryMinus', [Expression], argBlock) + } + + /** + * Creates an AttributeExpression. + */ + void attribute(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(AttributeExpression, 'attribute', [Expression, Expression], argBlock) + } + + /** + * Creates an ExpressionStatement. + */ + void expression(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ExpressionStatement, 'expression', [Expression], argBlock) + } + + /** + * Creates a NamedArgumentListExpression. + */ + void namedArgumentList(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(NamedArgumentListExpression, argBlock) + } + + /** + * Creates a ClassNode[]. + */ + void interfaces(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<ClassNode>") + } + + /** + * Creates a MixinNode[]. + */ + void mixins(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<MixinNode>") + } + + /** + * Creates a GenericsTypes[]. + */ + void genericsTypes(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<GenericsTypes>") + } + + /** + * Creates a ClassNode. + */ + void classNode(Class target) { + expression << ClassHelper.make(target, false) + } + + /** + * Creates a Parameter[]. + */ + void parameters(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeArrayOfNodes([] as Parameter[], argBlock) + } + + /** + * Creates a BlockStatement. + */ + void block(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("BlockStatement", argBlock) { + return new BlockStatement(new ArrayList(expression), new VariableScope()) + } + } + + /** + * Creates a Parameter. + */ + void parameter(Map<String, Class> args, @DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (!args) throw new IllegalArgumentException() + if (args.size() > 1) throw new IllegalArgumentException() + + //todo: add better error handling? + if (argBlock) { + args.each {name, type -> + captureAndCreateNode("Parameter", argBlock) { + new Parameter(ClassHelper.make(type), name, expression[0]) + } + } + } else { + args.each {name, type -> + expression << (new Parameter(ClassHelper.make(type), name)) + } + } + } + + /** + * Creates an ArrayExpression. + */ + void array(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("ArrayExpression", argBlock) { + new ArrayExpression(ClassHelper.make(type), new ArrayList(expression)) + } + } + + /** + * Creates a GenericsType. + */ + void genericsType(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (argBlock) { + captureAndCreateNode("GenericsType", argBlock) { + new GenericsType(ClassHelper.make(type), expression[0] as ClassNode[], expression[1]) + } + } else { + expression << new GenericsType(ClassHelper.make(type)) + } + } + + /** + * Creates a list of upperBound ClassNodes. + */ + void upperBound(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, 'List<ClassNode>') + } + + /** + * Create lowerBound ClassNode. + */ + void lowerBound(Class target) { + expression << ClassHelper.make(target) + } + + /** + * Creates a 2 element list of name and Annotation. Used with Annotation Members. + */ + void member(String name, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("Annotation Member", argBlock) { + [name, expression[0]] + } + } + + /** + * Creates an ArgumentListExpression. + */ + void argumentList(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + if (!argBlock) { + expression << new ArgumentListExpression() + } else { + makeNodeFromList(ArgumentListExpression, argBlock) + } + } + + /** + * Creates an AnnotationNode. + */ + void annotation(Class target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (argBlock) { + //todo: add better error handling + captureAndCreateNode("ArgumentListExpression", argBlock) { + def node = new AnnotationNode(ClassHelper.make(target)) + expression?.each { + node.addMember(it[0], it[1]) + } + node + } + } else { + expression << new AnnotationNode(ClassHelper.make(target)) + } + } + + /** + * Creates a MixinNode. + */ + void mixin(String name, int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("AttributeExpression", argBlock) { + if (expression.size() > 1) { + new MixinNode(name, modifiers, expression[0], new ArrayList(expression[1]) as ClassNode[]) + } else { + new MixinNode(name, modifiers, expression[0]) + } + } + } + + /** + * Creates a ClassNode + */ + void classNode(String name, int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("ClassNode", argBlock) { + def result = new ClassNode(name, modifiers, + expression[0], + new ArrayList(expression[1]) as ClassNode[], + new ArrayList(expression[2]) as MixinNode[] + ) + while (expression.size() > 3) { + if (!List.isAssignableFrom(expression[3].getClass())) { + throw new IllegalArgumentException("Expecting to find list of additional items instead found: " + expression[3].getClass()) + } + if (expression[3].size() > 0) { + def clazz = expression[3][0].getClass() + switch(clazz) { + case GenericsType: + result.setGenericsTypes(new ArrayList(expression[3]) as GenericsType[]) + break + case MethodNode: + expression[3].each{ result.addMethod(it) } + break + case ConstructorNode: + expression[3].each{ result.addConstructor(it) } + break + case PropertyNode: + expression[3].each{ + it.field.owner = result + result.addProperty(it) + } + break + case FieldNode: + expression[3].each{ + it.owner = result + result.addField(it) + } + break + case AnnotationNode: + result.addAnnotations(new ArrayList(expression[3])) + break + default: + throw new IllegalArgumentException("Unexpected item found in ClassNode spec. Expecting [Field|Method|Property|Constructor|Annotation|GenericsType] but found: $clazz.name") + } + } + expression.remove(3) + } + result + } + } + + /** + * Creates an AssertStatement. + */ + void assertStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("AssertStatement", argBlock) { + if (expression.size() < 2) { + new AssertStatement(*enforceConstraints('assertStatement', [BooleanExpression])) + } else { + new AssertStatement(*enforceConstraints('assertStatement', [BooleanExpression, Expression])) + } + } + } + + /** + * Creates a TryCatchStatement. + */ + void tryCatch(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("TryCatchStatement", argBlock) { + def result = new TryCatchStatement(expression[0], expression[1]) + def catchStatements = expression.tail().tail() + catchStatements.each {statement -> result.addCatch(statement) } + return result + } + } + + /** + * Creates a VariableExpression. + */ + void variable(String variable) { + expression << new VariableExpression(variable) + } + + /** + * Creates a MethodNode. + */ + void method(String name, int modifiers, Class returnType, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("MethodNode", argBlock) { + //todo: enforce contract + def result = new MethodNode(name, modifiers, ClassHelper.make(returnType), expression[0], expression[1], expression[2]) + if (expression[3]) { + result.addAnnotations(new ArrayList(expression[3])) + } + result + } + } + + /** + * Creates a token. + */ + void token(String value) { + if (value == null) throw new IllegalArgumentException("Null: value") + + def tokenID = Types.lookupKeyword(value) + if (tokenID == Types.UNKNOWN) { + tokenID = Types.lookupSymbol(value) + } + if (tokenID == Types.UNKNOWN) throw new IllegalArgumentException("could not find token for $value") + + expression << new Token(tokenID, value, -1, -1) + } + + /** + * Creates a RangeExpression. + */ + void range(Range range) { + if (range == null) throw new IllegalArgumentException('Null: range') + expression << new RangeExpression(new ConstantExpression(range.getFrom()), new ConstantExpression(range.getTo()), true) //default is inclusive + } + + /** + * Creates a SwitchStatement. + */ + void switchStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("SwitchStatement", argBlock) { + def switchExpression = expression.head() + def caseStatements = expression.tail().tail() + def defaultExpression = expression.tail().head() + new SwitchStatement(switchExpression, caseStatements, defaultExpression) + } + } + + /** + * Creates a mapEntry. + */ + void mapEntry(Map map) { + map.entrySet().each { + expression << new MapEntryExpression( + new ConstantExpression(it.key), + new ConstantExpression(it.value)) + } + } + + // + // todo: these methods can still be reduced smaller + // + + /** + * Creates a FieldNode. + */ + void fieldNode(String name, int modifiers, Class type, Class owner, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("FieldNode", argBlock) { + def annotations = null + if (expression.size() > 1) { + annotations = expression[1] + expression.remove(1) + } + expression.add(0, ClassHelper.make(owner)) + expression.add(0, ClassHelper.make(type)) + expression.add(0, modifiers) + expression.add(0, name) + def result = new FieldNode(*enforceConstraints('fieldNode', [String, Integer, ClassNode, ClassNode, Expression])) + if (annotations) { + result.addAnnotations(new ArrayList(annotations)) + } + result + } + } + + /** + * Creates an inner class. + */ + void innerClass(String name, int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("InnerClassNode", argBlock) { + //todo: enforce contract + new InnerClassNode( + expression[0], + name, + modifiers, + expression[1], + new ArrayList(expression[2]) as ClassNode[], + new ArrayList(expression[3]) as MixinNode[]) + } + } + + /** + * Creates a PropertyNode. + */ + void propertyNode(String name, int modifiers, Class type, Class owner, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + //todo: improve error handling? + captureAndCreateNode("PropertyNode", argBlock) { + def annotations = null + // check if the last expression looks like annotations + if (List.isAssignableFrom(expression[-1].getClass())) { + annotations = expression[-1] + expression.remove(expression.size() - 1) + } + def result = new PropertyNode(name, modifiers, ClassHelper.make(type), ClassHelper.make(owner), + expression[0], // initial value (possibly null) + expression[1], // getter block (possibly null) + expression[2]) // setter block (possibly null) + if (annotations) { + result.addAnnotations(new ArrayList(annotations)) + } + result + } + } + + /** + * Creates a StaticMethodCallExpression. + */ + void staticMethodCall(Class target, String name, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("StaticMethodCallExpression", argBlock) { + expression.add(0, name) + expression.add(0, ClassHelper.make(target)) + new StaticMethodCallExpression(*enforceConstraints('staticMethodCall', [ClassNode, String, Expression])) + } + } + + /** + * Creates a StaticMethodCallExpression. + */ + void staticMethodCall(MethodClosure target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("StaticMethodCallExpression", argBlock) { + expression.add(0, target.method) + expression.add(0, ClassHelper.makeWithoutCaching(target.owner.class, false)) + new StaticMethodCallExpression(*enforceConstraints('staticMethodCall', [ClassNode, String, Expression])) + } + } + + /** + * Creates a ConstructorNode. + */ + void constructor(int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("ConstructorNode", argBlock) { + def annotations = null + if (expression.size() > 3) { + annotations = expression[3] + expression.remove(3) + } + expression.add(0, modifiers) + def result = new ConstructorNode(*enforceConstraints('constructor', [Integer, Parameter[], ClassNode[], Statement])) + if (annotations) { + result.addAnnotations(new ArrayList(annotations)) + } + result + } + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/org/codehaus/groovy/ast/builder/AstStringCompiler.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/ast/builder/AstStringCompiler.groovy b/src/main/groovy/org/codehaus/groovy/ast/builder/AstStringCompiler.groovy new file mode 100644 index 0000000..1ea3970 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/ast/builder/AstStringCompiler.groovy @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.codehaus.groovy.ast.builder + +import groovy.transform.PackageScope +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.ModuleNode +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.control.CompilePhase +import org.codehaus.groovy.control.CompilerConfiguration + +/** + * This class handles converting Strings to ASTNode lists. + * + * @author Hamlet D'Arcy + */ +@PackageScope class AstStringCompiler { + + /** + * Performs the String source to {@link List} of {@link ASTNode}. + * + * @param script + * a Groovy script in String form + * @param compilePhase + * the int based CompilePhase to compile it to. + * @param statementsOnly + */ + List<ASTNode> compile(String script, CompilePhase compilePhase, boolean statementsOnly) { + def scriptClassName = "script" + System.currentTimeMillis() + GroovyClassLoader classLoader = new GroovyClassLoader() + GroovyCodeSource codeSource = new GroovyCodeSource(script, scriptClassName + ".groovy", "/groovy/script") + CompilationUnit cu = new CompilationUnit(CompilerConfiguration.DEFAULT, codeSource.codeSource, classLoader) + cu.addSource(codeSource.getName(), script); + cu.compile(compilePhase.getPhaseNumber()) + // collect all the ASTNodes into the result, possibly ignoring the script body if desired + return cu.ast.modules.inject([]) {List acc, ModuleNode node -> + if (node.statementBlock) acc.add(node.statementBlock) + node.classes?.each { + if (!(it.name == scriptClassName && statementsOnly)) { + acc << it + } + } + acc + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/org/codehaus/groovy/classgen/genArrayAccess.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/classgen/genArrayAccess.groovy b/src/main/groovy/org/codehaus/groovy/classgen/genArrayAccess.groovy new file mode 100644 index 0000000..08cb68a --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/classgen/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/10110145/src/main/groovy/org/codehaus/groovy/classgen/genArrays.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/classgen/genArrays.groovy b/src/main/groovy/org/codehaus/groovy/classgen/genArrays.groovy new file mode 100644 index 0000000..9bbe3cf --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/classgen/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/10110145/src/main/groovy/org/codehaus/groovy/classgen/genDgmMath.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/classgen/genDgmMath.groovy b/src/main/groovy/org/codehaus/groovy/classgen/genDgmMath.groovy new file mode 100644 index 0000000..71bdd5f --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/classgen/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/10110145/src/main/groovy/org/codehaus/groovy/classgen/genMathModification.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/classgen/genMathModification.groovy b/src/main/groovy/org/codehaus/groovy/classgen/genMathModification.groovy new file mode 100644 index 0000000..10cc7eb --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/classgen/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/10110145/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy b/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy new file mode 100644 index 0000000..7e84968 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy @@ -0,0 +1,301 @@ +/* + * 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 groovy.transform.CompilationUnitAware +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode +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.Expression +import org.codehaus.groovy.ast.expr.ListExpression +import org.codehaus.groovy.classgen.GeneratorContext +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.control.CompilePhase +import org.codehaus.groovy.control.SourceUnit +import org.codehaus.groovy.transform.ASTTransformation +import org.codehaus.groovy.transform.GroovyASTTransformation +import org.codehaus.groovy.transform.GroovyASTTransformationClass + +import java.lang.annotation.Annotation + +/** + * This customizer allows applying an AST transformation to a source unit with + * several strategies. + * + * Creating a customizer with the {@link ASTTransformationCustomizer#ASTTransformationCustomizer(Class) + * class constructor} will trigger an AST transformation for + * each class node of a source unit. However, you cannot pass parameters to the annotation so the default values + * will be used. Writing : + * <pre> + * def configuration = new CompilerConfiguration() + * configuration.addCompilationCustomizers(new ASTTransformationCustomizer(Log)) + * def shell = new GroovyShell(configuration) + * shell.evaluate(""" + * class MyClass { + * + * }""") + * </pre> + * + * is equivalent to : + * + * <pre> + * def shell = new GroovyShell() + * shell.evaluate(""" + * @Log + * class MyClass { + * + * }""") + * </pre> + * + * The class passed as a constructor parameter must be an AST transformation annotation. + * + * Alternatively, you can apply a global AST transformation by calling the + * {@link ASTTransformationCustomizer#ASTTransformationCustomizer(ASTTransformation) AST transformation + * constructor}. In that case, the transformation is applied once for the whole source unit. + * + * Unlike a global AST transformation declared in the META-INF/services/org.codehaus.groovy.transform.ASTTransformation + * file, which are applied if the file is in the classpath, using this customizer you'll have the choice to apply + * your transformation selectively. It can also be useful to debug global AST transformations without having to + * package your annotation in a jar file. + * + * @author Cedric Champeau + * + * @since 1.8.0 + * + */ +class ASTTransformationCustomizer extends CompilationCustomizer implements CompilationUnitAware { + private final AnnotationNode annotationNode; + final ASTTransformation transformation + + protected CompilationUnit compilationUnit; + private boolean applied = false; // used for global AST transformations + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * It's assumed that the annotation is not annotated with {@code GroovyASTTransformationClass} and so the + * second argument supplies the link to the ASTTransformation class that should be used. + * @param transformationAnnotation + * @param astTransformationClassName + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, astTransformationClassName, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + } + + /** + * Creates an AST transformation customizer using the specified annotation. It's assumed that the annotation + * is not annotated with {@code GroovyASTTransformationClass} and so the second argument supplies the link to + * the ASTTransformation class that should be used. + * @param transformationAnnotation + * @param astTransformationClassName + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) { + this(transformationAnnotation, astTransformationClassName, transformationAnnotation.classLoader) + } + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * Additionally, you can pass a map of parameters that will be used to parameterize the annotation. + * It's assumed that the annotation is not annotated with {@code GroovyASTTransformationClass} and so the + * second argument supplies the link to the ASTTransformation class that should be used. + * @param transformationAnnotation + * @param astTransformationClassName + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, astTransformationClassName, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + setAnnotationParameters(annotationParams) + } + + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) { + this(annotationParams, transformationAnnotation, transformationAnnotation.classLoader) + } + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * @param transformationAnnotation + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + } + + /** + * Creates an AST transformation customizer using the specified annotation. + * @param transformationAnnotation + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation) { + this(transformationAnnotation, transformationAnnotation.classLoader) + } + + /** + * Creates an AST transformation customizer using the specified transformation. + */ + ASTTransformationCustomizer(final ASTTransformation transformation) { + super(findPhase(transformation)) + this.transformation = transformation + this.annotationNode = null + } + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * Additionally, you can pass a map of parameters that will be used to parameterize the annotation. + * @param transformationAnnotation + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + setAnnotationParameters(annotationParams) + } + + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation) { + this(annotationParams, transformationAnnotation, transformationAnnotation.classLoader) + } + + ASTTransformationCustomizer(final Map annotationParams, final ASTTransformation transformation) { + this(transformation) + setAnnotationParameters(annotationParams) + } + + void setCompilationUnit(CompilationUnit unit) { + compilationUnit = unit + } + + private static Class<ASTTransformation> findASTTranformationClass(Class<? extends Annotation> anAnnotationClass, ClassLoader transformationClassLoader) { + final GroovyASTTransformationClass annotation = anAnnotationClass.getAnnotation(GroovyASTTransformationClass) + if (annotation==null) throw new IllegalArgumentException("Provided class doesn't look like an AST @interface") + + Class[] classes = annotation.classes() + String[] classesAsStrings = annotation.value() + if (classes.length+classesAsStrings.length>1) { + throw new IllegalArgumentException("AST transformation customizer doesn't support AST transforms with multiple classes") + } + return classes?classes[0]:Class.forName(classesAsStrings[0], true, transformationClassLoader?:anAnnotationClass.classLoader) + } + + private static Class<ASTTransformation> findASTTranformationClass(Class<? extends Annotation> anAnnotationClass, String astTransformationClassName, ClassLoader transformationClassLoader) { + return Class.forName(astTransformationClassName, true, transformationClassLoader?:anAnnotationClass.classLoader) as Class<ASTTransformation> + } + + private static CompilePhase findPhase(ASTTransformation transformation) { + if (transformation==null) throw new IllegalArgumentException("Provided transformation must not be null") + final Class<?> clazz = transformation.class + final GroovyASTTransformation annotation = clazz.getAnnotation(GroovyASTTransformation) + if (annotation==null) throw new IllegalArgumentException("Provided ast transformation is not annotated with "+GroovyASTTransformation.name) + + annotation.phase() + } + + private static CompilePhase findPhase(Class<? extends Annotation> annotationClass, ClassLoader transformationClassLoader) { + Class<ASTTransformation> clazz = findASTTranformationClass(annotationClass, transformationClassLoader); + + findPhase(clazz.newInstance()) + } + + private static CompilePhase findPhase(Class<? extends Annotation> annotationClass, String astTransformationClassName, ClassLoader transformationClassLoader) { + Class<ASTTransformation> clazz = findASTTranformationClass(annotationClass, astTransformationClassName, transformationClassLoader); + + findPhase(clazz.newInstance()) + } + + /** + * Specify annotation parameters. For example, if the annotation is : + * <pre>@Log(value='logger')</pre> + * You could create an AST transformation customizer and specify the "value" parameter thanks to this method: + * <pre>annotationParameters = [value: 'logger'] + * + * Note that you cannot specify annotation closure values directly. If the annotation you want to add takes + * a closure as an argument, you will have to set a {@link ClosureExpression} instead. This can be done by either + * creating a custom {@link ClosureExpression} from code, or using the {@link org.codehaus.groovy.ast.builder.AstBuilder}. + * + * Here is an example : + * <pre> + * // add @Contract({distance >= 0 }) + * customizer = new ASTTransformationCustomizer(Contract) + * final expression = new AstBuilder().buildFromCode(CompilePhase.CONVERSION) {-> + * distance >= 0 + * }.expression[0] + * customizer.annotationParameters = [value: expression]</pre> + * + * @param params the annotation parameters + * + * @since 1.8.1 + */ + public void setAnnotationParameters(Map<String,Object> params) { + if (params==null || annotationNode==null) return; + params.each { key, value -> + if (!annotationNode.classNode.getMethod(key)) { + throw new IllegalArgumentException("${annotationNode.classNode.name} does not accept any [$key] parameter") + } + if (value instanceof Closure) { + throw new IllegalArgumentException("Direct usage of closure is not supported by the AST " + + "compilation customizer. Please use ClosureExpression instead.") + } else if (value instanceof Expression) { + // avoid NPEs due to missing source code + value.setLineNumber(0) + value.setLastLineNumber(0) + annotationNode.addMember(key, value) + } else if (value instanceof Class) { + annotationNode.addMember(key, new ClassExpression(ClassHelper.make(value))) + } else if (value instanceof List) { + annotationNode.addMember(key, new ListExpression(value.collect { + it instanceof Class ? new ClassExpression(ClassHelper.make(it)) : new ConstantExpression(it) + })) + } else { + annotationNode.addMember(key, new ConstantExpression(value)) + } + } + } + + @Override + void call(SourceUnit source, GeneratorContext context, ClassNode classNode) { + if (transformation instanceof CompilationUnitAware) { + transformation.compilationUnit = compilationUnit + } + if (annotationNode!=null) { + // this is a local ast transformation which is applied on every class node + annotationNode.sourcePosition = classNode + transformation.visit([annotationNode, classNode] as ASTNode[], source) + } else { + // this is a global AST transformation + if (!applied) transformation.visit(null, source) + } + applied = true + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/org/codehaus/groovy/control/customizers/builder/ASTTransformationCustomizerFactory.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/control/customizers/builder/ASTTransformationCustomizerFactory.groovy b/src/main/groovy/org/codehaus/groovy/control/customizers/builder/ASTTransformationCustomizerFactory.groovy new file mode 100644 index 0000000..4e4f5be --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/control/customizers/builder/ASTTransformationCustomizerFactory.groovy @@ -0,0 +1,60 @@ +/* + * 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.builder + +import groovy.transform.CompileStatic +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer + +/** + * This factory generates an {@link ASTTransformationCustomizer ast transformation customizer}. + * <p> + * Simple syntax: + * <pre>builder.ast(ToString)</pre> + * With AST transformation options: + * <pre>builder.ast(includeNames:true, ToString)</pre> + * + * @author Cedric Champeau + * @since 2.1.0 + */ +class ASTTransformationCustomizerFactory extends AbstractFactory { + + @Override + @CompileStatic + public boolean isLeaf() { + true + } + + @Override + @CompileStatic + public boolean onHandleNodeAttributes(final FactoryBuilderSupport builder, final Object node, final Map attributes) { + false + } + + @Override + public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException { + ASTTransformationCustomizer customizer + if (attributes) { + customizer = new ASTTransformationCustomizer(attributes, value) + } else { + customizer = new ASTTransformationCustomizer(value) + } + customizer + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/org/codehaus/groovy/control/customizers/builder/CompilerCustomizationBuilder.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/org/codehaus/groovy/control/customizers/builder/CompilerCustomizationBuilder.groovy b/src/main/groovy/org/codehaus/groovy/control/customizers/builder/CompilerCustomizationBuilder.groovy new file mode 100644 index 0000000..59b8cc5 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/control/customizers/builder/CompilerCustomizationBuilder.groovy @@ -0,0 +1,64 @@ +/* + * 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.builder + +import groovy.transform.CompileStatic +import org.codehaus.groovy.control.CompilerConfiguration + +/** + * <p>A builder which allows easy configuration of compilation customizers. Instead of creating + * various compilation customizers by hand, you may use this builder instead, which provides a + * shorter syntax and removes most of the verbosity. + * + */ +@CompileStatic +class CompilerCustomizationBuilder extends FactoryBuilderSupport { + public CompilerCustomizationBuilder() { + registerFactories() + } + + public static CompilerConfiguration withConfig(CompilerConfiguration config, Closure code) { + CompilerCustomizationBuilder builder = new CompilerCustomizationBuilder() + config.invokeMethod('addCompilationCustomizers', builder.invokeMethod('customizers', code)) + + config + } + + @Override + protected Object postNodeCompletion(final Object parent, final Object node) { + Object value = super.postNodeCompletion(parent, node) + Object factory = getContextAttribute(CURRENT_FACTORY) + if (factory instanceof PostCompletionFactory) { + value = factory.postCompleteNode(this, parent, value) + setParent(parent, value) + } + + value + } + + private void registerFactories() { + registerFactory("ast", new ASTTransformationCustomizerFactory()) + registerFactory("customizers", new CustomizersFactory()) + registerFactory("imports", new ImportCustomizerFactory()) + registerFactory("inline", new InlinedASTCustomizerFactory()) + registerFactory("secureAst", new SecureASTCustomizerFactory()) + registerFactory("source", new SourceAwareCustomizerFactory()) + } +}
