/* * Copyright 2003-2009 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 groovy.grape; import groovy.lang.Grab; import groovy.lang.Grapes; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.expr.*; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.transform.ASTTransformation; import org.codehaus.groovy.transform.ASTTransformationVisitor; import org.codehaus.groovy.transform.GroovyASTTransformation; import java.util.*; /** * Created by IntelliJ IDEA. * User: Danno * Date: Jan 18, 2008 * Time: 9:48:57 PM */ @GroovyASTTransformation(phase=CompilePhase.CONVERSION) public class GrabAnnotationTransformation extends ClassCodeVisitorSupport implements ASTTransformation { private static final String GRAB_CLASS_NAME = Grab.class.getName(); private static final String GRAB_DOT_NAME = GRAB_CLASS_NAME.substring(GRAB_CLASS_NAME.lastIndexOf(".")); private static final String GRAB_SHORT_NAME = GRAB_DOT_NAME.substring(1); private static final String GRAPES_CLASS_NAME = Grapes.class.getName(); private static final String GRAPES_DOT_NAME = GRAPES_CLASS_NAME.substring(GRAPES_CLASS_NAME.lastIndexOf(".")); private static final String GRAPES_SHORT_NAME = GRAPES_DOT_NAME.substring(1); boolean allowShortGrab; Set<String> grabAliases; List<AnnotationNode> grabAnnotations; boolean allowShortGrapes; Set<String> grapesAliases; List<AnnotationNode> grapesAnnotations; SourceUnit sourceUnit; public SourceUnit getSourceUnit() { return sourceUnit; } public void visit(ASTNode[] nodes, SourceUnit source) { sourceUnit = source; ModuleNode mn = (ModuleNode) nodes[0]; if (mn==null) { // FIXASC (groovychange) able to stop this being called in the first place? // assert: code has other errors == true return; } allowShortGrab = true; allowShortGrapes = true; grabAliases = new HashSet(); grapesAliases = new HashSet(); for (ImportNode im : (Collection<ImportNode>) mn.getImports()) { String alias = im.getAlias(); String className = im.getClassName(); if ((className.endsWith(GRAB_DOT_NAME) && ((alias == null) || (alias.length() == 0))) || (GRAB_CLASS_NAME.equals(alias))) { allowShortGrab = false; } else if (GRAB_CLASS_NAME.equals(className)) { grabAliases.add(im.getAlias()); } if ((className.endsWith(GRAPES_DOT_NAME) && ((alias == null) || (alias.length() == 0))) || (GRAPES_CLASS_NAME.equals(alias))) { allowShortGrapes = false; } else if (GRAPES_CLASS_NAME.equals(className)) { grapesAliases.add(im.getAlias()); } } List<Map<String,Object>> grabMaps = new ArrayList(); for (ClassNode classNode : (Collection<ClassNode>) sourceUnit.getAST().getClasses()) { grabAnnotations = new ArrayList<AnnotationNode>(); grapesAnnotations = new ArrayList<AnnotationNode>(); visitClass(classNode); ClassNode grapeClassNode = new ClassNode(Grape.class); if (!grapesAnnotations.isEmpty()) { for (AnnotationNode node : grapesAnnotations) { Expression init = node.getMember("initClass"); Expression value = node.getMember("value"); if (value instanceof ListExpression) { for (Object o : ((ListExpression)value).getExpressions()) { if (o instanceof AnnotationConstantExpression) { if (((AnnotationConstantExpression)o).getValue() instanceof AnnotationNode) { AnnotationNode annotation = (AnnotationNode) ((AnnotationConstantExpression)o).getValue(); if ((init != null) && (annotation.getMember("initClass") != null)) { annotation.setMember("initClass", init); } String name = annotation.getClassNode().getName(); if ((GRAB_CLASS_NAME.equals(name)) || (allowShortGrab && GRAB_SHORT_NAME.equals(name)) || (grabAliases.contains(name))) { grabAnnotations.add(annotation); } } } } } // don't worry if it's not a ListExpression, or AnnotationConstant, etc. // the rest of GroovyC will flag it as a syntax error later, so we don't // need to raise the error ourselves } } if (!grabAnnotations.isEmpty()) { grabAnnotationLoop: for (AnnotationNode node : grabAnnotations) { Map<String, Object> grabMap = new HashMap(); checkForConvenienceForm(node); for (String s : new String[]{"group", "module", "version"}) { if (node.getMember(s) == null) { addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node); continue grabAnnotationLoop; } } grabMap.put("group", ((ConstantExpression)node.getMember("group")).getValue()); grabMap.put("module", ((ConstantExpression)node.getMember("module")).getValue()); grabMap.put("version", ((ConstantExpression)node.getMember("version")).getValue()); if (node.getMember("classifier") != null) grabMap.put("classifier", ((ConstantExpression)node.getMember("classifier")).getValue()); grabMaps.add(grabMap); if ((node.getMember("initClass") == null) || (node.getMember("initClass") == ConstantExpression.TRUE)) { List grabInitializers = new ArrayList(); // add Grape.grab([group:group, module:module, version:version, classifier:classifier]) MapExpression me = new MapExpression(); me.addMapEntryExpression(new ConstantExpression("group"),node.getMember("group")); me.addMapEntryExpression(new ConstantExpression("module"),node.getMember("module")); me.addMapEntryExpression(new ConstantExpression("version"),node.getMember("version")); if (node.getMember("classifier") != null) me.addMapEntryExpression(new ConstantExpression("classifier"),node.getMember("classifier")); grabInitializers.add(new ExpressionStatement( new StaticMethodCallExpression( grapeClassNode, "grab", new ArgumentListExpression(me)))); // insert at beginning so we have the classloader set up before the class is called classNode.addStaticInitializerStatements(grabInitializers, true); } } } } if (!grabMaps.isEmpty()) { Map basicArgs = new HashMap(); basicArgs.put("classLoader", sourceUnit.getClassLoader()); try { Grape.grab(basicArgs, grabMaps.toArray(new Map[grabMaps.size()])); // grab may have added more transformations through new URLs added to classpath, so do one more scan ASTTransformationVisitor.addGlobalTransformsAfterGrab(); } catch (RuntimeException re) { // Decided against syntax exception since this is not a syntax error. // The down side is we lose line number information for the offending // @Grab annotation. source.addException(re); } } } private void checkForConvenienceForm(AnnotationNode node) { Object val = node.getMember("value"); if (val == null || !(val instanceof ConstantExpression)) return; Object allParts = ((ConstantExpression)val).getValue(); if (!(allParts instanceof String)) return; String allstr = (String) allParts; if (allstr.contains(":")) { String[] parts = allstr.split(":"); if (parts.length > 4) return; if (parts.length > 3) node.addMember("classifier", new ConstantExpression(parts[3])); if (parts.length > 2) node.addMember("version", new ConstantExpression(parts[2])); else node.addMember("version", new ConstantExpression("*")); // TODO '*' default in @Grab not working? node.addMember("module", new ConstantExpression(parts[1])); node.addMember("group", new ConstantExpression(parts[0])); } } protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { super.visitConstructorOrMethod(node, isConstructor); // this should be pushed into the super class... for (Parameter param : node.getParameters()) { visitAnnotations(param); } } /** * Adds the annotation to the internal target list if a match is found * @param node */ public void visitAnnotations(AnnotatedNode node) { super.visitAnnotations(node); for (AnnotationNode an : (Collection<AnnotationNode>) node.getAnnotations()) { String name = an.getClassNode().getName(); if ((GRAB_CLASS_NAME.equals(name)) || (allowShortGrab && GRAB_SHORT_NAME.equals(name)) || (grabAliases.contains(name))) { grabAnnotations.add(an); } if ((GRAPES_CLASS_NAME.equals(name)) || (allowShortGrapes && GRAPES_SHORT_NAME.equals(name)) || (grapesAliases.contains(name))) { grapesAnnotations.add(an); } } } }