/* * Copyright 2014 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.gradle.model.dsl.internal.transform; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.EmptyStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.control.SourceUnit; import org.gradle.api.Nullable; import org.gradle.groovy.scripts.internal.AstUtils; import org.gradle.groovy.scripts.internal.RestrictiveCodeVisitor; import org.gradle.internal.Pair; import org.gradle.model.internal.core.ModelPath; import java.util.List; public class RulesVisitor extends RestrictiveCodeVisitor { private static final String AST_NODE_METADATA_KEY = RulesVisitor.class.getName(); private static final ClassNode ANNOTATION_CLASS_NODE = new ClassNode(RulesBlock.class); // TODO - have to do much better here public static final String INVALID_STATEMENT = "illegal rule"; public static final String INVALID_RULE_SIGNATURE = "Rule must follow the pattern '«name»(«type») {}' for a registration, and '«name» {}' for an action"; private final RuleVisitor ruleVisitor; public RulesVisitor(SourceUnit sourceUnit, RuleVisitor ruleVisitor) { super(sourceUnit, INVALID_STATEMENT); this.ruleVisitor = ruleVisitor; } public static void visitGeneratedClosure(ClassNode node) { MethodNode method = AstUtils.getGeneratedClosureImplMethod(node); Boolean isRulesBlock = method.getCode().getNodeMetaData(AST_NODE_METADATA_KEY); if (isRulesBlock != null) { AnnotationNode markerAnnotation = new AnnotationNode(ANNOTATION_CLASS_NODE); node.addAnnotation(markerAnnotation); } } @Override public void visitBlockStatement(BlockStatement block) { block.setNodeMetaData(AST_NODE_METADATA_KEY, true); for (Statement statement : block.getStatements()) { statement.visit(this); } } @Override public void visitExpressionStatement(ExpressionStatement statement) { statement.getExpression().visit(this); } @Override public void visitMethodCallExpression(MethodCallExpression call) { ClosureExpression closureExpression = AstUtils.getSingleClosureArg(call); if (closureExpression != null) { // path { ... } rewriteAction(call, extractModelPathFromMethodTarget(call), closureExpression, RuleVisitor.displayName(call)); return; } Pair<ClassExpression, ClosureExpression> args = AstUtils.getClassAndClosureArgs(call); if (args != null) { // path(Type) { ... } rewriteCreator(call, extractModelPathFromMethodTarget(call), args.getRight(), args.getLeft(), RuleVisitor.displayName(call)); return; } ClassExpression classArg = AstUtils.getClassArg(call); if (classArg != null) { // path(Type) String displayName = RuleVisitor.displayName(call); List<Statement> statements = Lists.newLinkedList(); statements.add(new EmptyStatement()); BlockStatement block = new BlockStatement(statements, new VariableScope()); closureExpression = new ClosureExpression(Parameter.EMPTY_ARRAY, block); closureExpression.setVariableScope(block.getVariableScope()); String modelPath = extractModelPathFromMethodTarget(call); rewriteCreator(call, modelPath, closureExpression, classArg, displayName); return; } restrict(call, INVALID_RULE_SIGNATURE); } public void rewriteCreator(MethodCallExpression call, String modelPath, ClosureExpression closureExpression, ClassExpression typeExpression, String displayName) { // Rewrite the method call to match TransformedModelDslBacking#create(String, Closure), which is what the delegate will be ConstantExpression modelPathArgument = new ConstantExpression(modelPath); ArgumentListExpression replacedArgumentList = new ArgumentListExpression(modelPathArgument, typeExpression, closureExpression); call.setMethod(new ConstantExpression("create")); call.setArguments(replacedArgumentList); // Call directly on the delegate to avoid some dynamic dispatch call.setImplicitThis(true); call.setObjectExpression(new MethodCallExpression(VariableExpression.THIS_EXPRESSION, "getDelegate", ArgumentListExpression.EMPTY_ARGUMENTS)); ruleVisitor.visitRuleClosure(closureExpression, call, displayName); } public void rewriteAction(MethodCallExpression call, String modelPath, ClosureExpression closureExpression, String displayName) { // Rewrite the method call to match TransformedModelDslBacking#configure(String, Closure), which is what the delegate will be ConstantExpression modelPathArgument = new ConstantExpression(modelPath); ArgumentListExpression replacedArgumentList = new ArgumentListExpression(modelPathArgument, closureExpression); call.setMethod(new ConstantExpression("configure")); call.setArguments(replacedArgumentList); // Call directly on the delegate to avoid some dynamic dispatch call.setImplicitThis(true); call.setObjectExpression(new MethodCallExpression(VariableExpression.THIS_EXPRESSION, "getDelegate", ArgumentListExpression.EMPTY_ARGUMENTS)); ruleVisitor.visitRuleClosure(closureExpression, call, displayName); } @Nullable // if the target was invalid private String extractModelPathFromMethodTarget(MethodCallExpression call) { Expression target = call.getMethod(); List<String> names = Lists.newLinkedList(); while (true) { if (target instanceof ConstantExpression) { if (target.getType().equals(ClassHelper.STRING_TYPE)) { String name = target.getText(); names.add(0, name); if (call.isImplicitThis()) { break; } else { target = call.getObjectExpression(); continue; } } } else if (target instanceof PropertyExpression) { PropertyExpression propertyExpression = (PropertyExpression) target; Expression property = propertyExpression.getProperty(); if (property instanceof ConstantExpression) { ConstantExpression constantProperty = (ConstantExpression) property; if (constantProperty.getType().equals(ClassHelper.STRING_TYPE)) { String name = constantProperty.getText(); names.add(0, name); target = propertyExpression.getObjectExpression(); continue; } } } else if (target instanceof VariableExpression) { // This will be the left most property names.add(0, ((VariableExpression) target).getName()); break; } // Invalid paths fall through to here restrict(call); return null; } // TODO - validate that it's a valid model path return ModelPath.pathString(Iterables.toArray(names, String.class)); } }