/* * Copyright 2008-2010 the original author or authors. * * Licensed 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; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.messages.SyntaxErrorMessage; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ForStatement; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.syntax.SyntaxException; import org.objectweb.asm.Opcodes; import groovy.lang.Reference; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * Handles generation of code for the @Category annotation. * <p> * Transformation logic is as follows: * <ul> * <li>all non-static methods converted to static ones with an additional 'self' parameter * <li>references to 'this' changed to the additional 'self' parameter * </ul> * * @author Alex Tkachman */ @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) public class CategoryASTTransformation implements ASTTransformation, Opcodes { private static final VariableExpression THIS_EXPRESSION; static { THIS_EXPRESSION = new VariableExpression("$this"); THIS_EXPRESSION.setClosureSharedVariable(true); } /** * Property invocations done on 'this' reference are transformed so that the invocations at runtime are * done on the additional parameter 'self' */ public void visit(ASTNode[] nodes, final SourceUnit source) { if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof ClassNode)) { throw new RuntimeException("Internal error: expecting [AnnotationNode, ClassNode] but got: " + Arrays.asList(nodes)); } AnnotationNode annotation = (AnnotationNode) nodes[0]; ClassNode parent = (ClassNode) nodes[1]; ClassNode targetClass = getTargetClass(source, annotation); final LinkedList<Set<String>> varStack = new LinkedList<Set<String>>(); Set<String> names = new HashSet<String>(); for (FieldNode field : parent.getFields()) { names.add(field.getName()); } varStack.add(names); final Reference parameter = new Reference(); final ClassCodeExpressionTransformer expressionTransformer = new ClassCodeExpressionTransformer() { protected SourceUnit getSourceUnit() { return source; } private void addVariablesToStack(Parameter[] params) { Set<String> names = new HashSet<String>(); names.addAll(varStack.getLast()); for (Parameter param : params) { names.add(param.getName()); } varStack.add(names); } @Override public void visitMethod(MethodNode node) { addVariablesToStack(node.getParameters()); super.visitMethod(node); varStack.removeLast(); } @Override public void visitBlockStatement(BlockStatement block) { Set<String> names = new HashSet<String>(); names.addAll(varStack.getLast()); varStack.add(names); super.visitBlockStatement(block); varStack.remove(names); } @Override public void visitClosureExpression(ClosureExpression ce) { addVariablesToStack(ce.getParameters()); super.visitClosureExpression(ce); varStack.removeLast(); } @Override public void visitDeclarationExpression(DeclarationExpression expression) { if (expression.isMultipleAssignmentDeclaration()) { TupleExpression te = expression.getTupleExpression(); List<Expression> list = te.getExpressions(); for (Expression arg : list) { VariableExpression ve = (VariableExpression) arg; varStack.getLast().add(ve.getName()); } } else { VariableExpression ve = expression.getVariableExpression(); varStack.getLast().add(ve.getName()); } super.visitDeclarationExpression(expression); } @Override public void visitForLoop(ForStatement forLoop) { Expression exp = forLoop.getCollectionExpression(); exp.visit(this); Parameter loopParam = forLoop.getVariable(); if (loopParam != null) { varStack.getLast().add(loopParam.getName()); } super.visitForLoop(forLoop); } @Override public void visitExpressionStatement(ExpressionStatement es) { // GROOVY-3543: visit the declaration expressions so that declaration variables get added on the varStack Expression exp = es.getExpression(); if (exp instanceof DeclarationExpression) { exp.visit(this); } super.visitExpressionStatement(es); } @Override public Expression transform(Expression exp) { if (exp instanceof VariableExpression) { VariableExpression ve = (VariableExpression) exp; if (ve.getName().equals("this")) return THIS_EXPRESSION; else { if (!varStack.getLast().contains(ve.getName())) { return new PropertyExpression(THIS_EXPRESSION, ve.getName()); } } } else if (exp instanceof PropertyExpression) { PropertyExpression pe = (PropertyExpression) exp; if (pe.getObjectExpression() instanceof VariableExpression) { VariableExpression vex = (VariableExpression) pe.getObjectExpression(); if (vex.isThisExpression()) { pe.setObjectExpression(THIS_EXPRESSION); return pe; } } } else if (exp instanceof ClosureExpression) { ClosureExpression ce = (ClosureExpression) exp; ce.getVariableScope().putReferencedLocalVariable((Parameter) parameter.get()); Parameter[] params = ce.getParameters(); if (params==null){ params = new Parameter[0]; } else if (params.length==0) { params = new Parameter[] { new Parameter(ClassHelper.OBJECT_TYPE, "it") }; } addVariablesToStack(params); ce.getCode().visit(this); varStack.removeLast(); } return super.transform(exp); } }; for (MethodNode method : parent.getMethods()) { if (!method.isStatic()) { method.setModifiers(method.getModifiers() | Opcodes.ACC_STATIC); final Parameter[] origParams = method.getParameters(); final Parameter[] newParams = new Parameter[origParams.length + 1]; Parameter p = new Parameter(targetClass, "$this"); p.setClosureSharedVariable(true); newParams[0] = p; parameter.set(p); System.arraycopy(origParams, 0, newParams, 1, origParams.length); method.setParameters(newParams); expressionTransformer.visitMethod(method); } } } private ClassNode getTargetClass(SourceUnit source, AnnotationNode annotation) { Expression value = annotation.getMember("value"); if (value == null || !(value instanceof ClassExpression)) { //noinspection ThrowableInstanceNeverThrown source.getErrorCollector().addErrorAndContinue( new SyntaxErrorMessage(new SyntaxException( "@groovy.lang.Category must define 'value' which is the class to apply this category to", annotation.getLineNumber(), annotation.getColumnNumber()), source)); return null; } else { ClassExpression ce = (ClassExpression) value; return ce.getType(); } } }