You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2017/12/20 00:56:40 UTC

[43/49] groovy git commit: Move source files to proper packages

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/cli/TypedOption.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/cli/TypedOption.java b/src/main/groovy/groovy/cli/TypedOption.java
new file mode 100644
index 0000000..e669324
--- /dev/null
+++ b/src/main/groovy/groovy/cli/TypedOption.java
@@ -0,0 +1,27 @@
+/*
+ *  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 groovy.cli;
+
+import java.util.HashMap;
+
+public class TypedOption<T> extends HashMap<String, T> {
+    public T defaultValue() {
+        return (T) super.get("defaultValue");
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/cli/Unparsed.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/cli/Unparsed.java b/src/main/groovy/groovy/cli/Unparsed.java
new file mode 100644
index 0000000..a741413
--- /dev/null
+++ b/src/main/groovy/groovy/cli/Unparsed.java
@@ -0,0 +1,39 @@
+/*
+ *  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 groovy.cli;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a method or property will contain the remaining arguments.
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD})
+public @interface Unparsed {
+    /**
+     * The description for the remaining non-option arguments
+     *
+     * @return the description for the remaining non-option arguments
+     */
+    String description() default "ARGUMENTS";
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/cli/UnparsedField.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/cli/UnparsedField.groovy b/src/main/groovy/groovy/cli/UnparsedField.groovy
new file mode 100644
index 0000000..b185431
--- /dev/null
+++ b/src/main/groovy/groovy/cli/UnparsedField.groovy
@@ -0,0 +1,27 @@
+/*
+ *  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 groovy.cli
+
+import groovy.transform.AnnotationCollector
+import groovy.transform.Field
+
+@Unparsed
+@Field
+@AnnotationCollector
+@interface UnparsedField { }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/grape/GrabAnnotationTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/grape/GrabAnnotationTransformation.java b/src/main/groovy/groovy/grape/GrabAnnotationTransformation.java
new file mode 100644
index 0000000..dd55ff9
--- /dev/null
+++ b/src/main/groovy/groovy/grape/GrabAnnotationTransformation.java
@@ -0,0 +1,639 @@
+/*
+ *  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 groovy.grape;
+
+import groovy.lang.Grab;
+import groovy.lang.GrabConfig;
+import groovy.lang.GrabExclude;
+import groovy.lang.GrabResolver;
+import groovy.lang.Grapes;
+import groovy.transform.CompilationUnitAware;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ImportNode;
+import org.codehaus.groovy.ast.ModuleNode;
+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.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.io.StringReaderSource;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.codehaus.groovy.tools.GrapeUtil;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.ASTTransformationVisitor;
+import org.codehaus.groovy.transform.AbstractASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+import static org.codehaus.groovy.transform.AbstractASTTransformation.getMemberStringValue;
+
+/**
+ * Transformation for declarative dependency management.
+ */
+@GroovyASTTransformation(phase=CompilePhase.CONVERSION)
+public class GrabAnnotationTransformation extends ClassCodeVisitorSupport implements ASTTransformation, CompilationUnitAware {
+    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 GRABEXCLUDE_CLASS_NAME = GrabExclude.class.getName();
+    private static final String GRABEXCLUDE_DOT_NAME = dotName(GRABEXCLUDE_CLASS_NAME);
+    private static final String GRABEXCLUDE_SHORT_NAME = shortName(GRABEXCLUDE_DOT_NAME);
+
+    private static final String GRABCONFIG_CLASS_NAME = GrabConfig.class.getName();
+    private static final String GRABCONFIG_DOT_NAME = dotName(GRABCONFIG_CLASS_NAME);
+    private static final String GRABCONFIG_SHORT_NAME = shortName(GRABCONFIG_DOT_NAME);
+
+    private static final String GRAPES_CLASS_NAME = Grapes.class.getName();
+    private static final String GRAPES_DOT_NAME = dotName(GRAPES_CLASS_NAME);
+    private static final String GRAPES_SHORT_NAME = shortName(GRAPES_DOT_NAME);
+
+    private static final String GRABRESOLVER_CLASS_NAME = GrabResolver.class.getName();
+    private static final String GRABRESOLVER_DOT_NAME = dotName(GRABRESOLVER_CLASS_NAME);
+    private static final String GRABRESOLVER_SHORT_NAME = shortName(GRABRESOLVER_DOT_NAME);
+
+    private static final ClassNode THREAD_CLASSNODE = ClassHelper.make(Thread.class);
+    private static final ClassNode SYSTEM_CLASSNODE = ClassHelper.make(System.class);
+
+    private static final List<String> GRABEXCLUDE_REQUIRED = Arrays.asList("group", "module");
+    private static final List<String> GRABRESOLVER_REQUIRED = Arrays.asList("name", "root");
+    private static final List<String> GRAB_REQUIRED = Arrays.asList("group", "module", "version");
+    private static final List<String> GRAB_OPTIONAL = Arrays.asList("classifier", "transitive", "conf", "ext", "type", "changing", "force", "initClass");
+    private static final List<String> GRAB_BOOLEAN = Arrays.asList("transitive", "changing", "force", "initClass");
+    private static final Collection<String> GRAB_ALL = DefaultGroovyMethods.plus(GRAB_REQUIRED, GRAB_OPTIONAL);
+    private static final Pattern IVY_PATTERN = Pattern.compile("([a-zA-Z0-9-/._+=]+)#([a-zA-Z0-9-/._+=]+)(;([a-zA-Z0-9-/.\\(\\)\\[\\]\\{\\}_+=,:@][a-zA-Z0-9-/.\\(\\)\\]\\{\\}_+=,:@]*))?(\\[([a-zA-Z0-9-/._+=,]*)\\])?");
+    private static final Pattern ATTRIBUTES_PATTERN = Pattern.compile("(.*;|^)([a-zA-Z0-9]+)=([a-zA-Z0-9.*\\[\\]\\-\\(\\),]*)$");
+
+    private static final String AUTO_DOWNLOAD_SETTING = Grape.AUTO_DOWNLOAD_SETTING;
+    private static final String DISABLE_CHECKSUMS_SETTING = Grape.DISABLE_CHECKSUMS_SETTING;
+    private static final String SYSTEM_PROPERTIES_SETTING = Grape.SYSTEM_PROPERTIES_SETTING;
+
+    private static String dotName(String className) {
+        return className.substring(className.lastIndexOf("."));
+    }
+
+    private static String shortName(String className) {
+        return className.substring(1);
+    }
+
+    boolean allowShortGrab;
+    Set<String> grabAliases;
+    List<AnnotationNode> grabAnnotations;
+
+    boolean allowShortGrabExcludes;
+    Set<String> grabExcludeAliases;
+    List<AnnotationNode> grabExcludeAnnotations;
+
+    boolean allowShortGrabConfig;
+    Set<String> grabConfigAliases;
+    List<AnnotationNode> grabConfigAnnotations;
+
+    boolean allowShortGrapes;
+    Set<String> grapesAliases;
+    List<AnnotationNode> grapesAnnotations;
+
+    boolean allowShortGrabResolver;
+    Set<String> grabResolverAliases;
+    List<AnnotationNode> grabResolverAnnotations;
+
+    CompilationUnit compilationUnit;
+    SourceUnit sourceUnit;
+    ClassLoader loader;
+    boolean initContextClassLoader;
+    Boolean autoDownload;
+    Boolean disableChecksums;
+    Map<String, String> systemProperties;
+
+    public SourceUnit getSourceUnit() {
+        return sourceUnit;
+    }
+
+    public void setCompilationUnit(final CompilationUnit compilationUnit) {
+        this.compilationUnit = compilationUnit;
+    }
+
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        sourceUnit = source;
+        loader = null;
+        initContextClassLoader = false;
+
+        ModuleNode mn = (ModuleNode) nodes[0];
+
+        allowShortGrab = true;
+        allowShortGrabExcludes = true;
+        allowShortGrabConfig = true;
+        allowShortGrapes = true;
+        allowShortGrabResolver = true;
+        grabAliases = new HashSet<String>();
+        grabExcludeAliases = new HashSet<String>();
+        grabConfigAliases = new HashSet<String>();
+        grapesAliases = new HashSet<String>();
+        grabResolverAliases = new HashSet<String>();
+        for (ImportNode im : 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());
+            }
+            if ((className.endsWith(GRABRESOLVER_DOT_NAME) && ((alias == null) || (alias.length() == 0)))
+                || (GRABRESOLVER_CLASS_NAME.equals(alias)))
+            {
+                allowShortGrabResolver = false;
+            } else if (GRABRESOLVER_CLASS_NAME.equals(className)) {
+                grabResolverAliases.add(im.getAlias());
+            }
+        }
+
+        List<Map<String,Object>> grabMaps = new ArrayList<Map<String,Object>>();
+        List<Map<String,Object>> grabMapsInit = new ArrayList<Map<String,Object>>();
+        List<Map<String,Object>> grabExcludeMaps = new ArrayList<Map<String,Object>>();
+
+        for (ClassNode classNode : sourceUnit.getAST().getClasses()) {
+            grabAnnotations = new ArrayList<AnnotationNode>();
+            grabExcludeAnnotations = new ArrayList<AnnotationNode>();
+            grabConfigAnnotations = new ArrayList<AnnotationNode>();
+            grapesAnnotations = new ArrayList<AnnotationNode>();
+            grabResolverAnnotations = new ArrayList<AnnotationNode>();
+
+            visitClass(classNode);
+
+            ClassNode grapeClassNode = ClassHelper.make(Grape.class);
+
+            List<Statement> grabResolverInitializers = new ArrayList<Statement>();
+
+            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 ConstantExpression) {
+                                extractGrab(init, (ConstantExpression) o);
+                            }
+                        }
+                    } else if (value instanceof ConstantExpression) {
+                        extractGrab(init, (ConstantExpression) value);
+                    }
+                    // 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 (!grabResolverAnnotations.isEmpty()) {
+                grabResolverAnnotationLoop:
+                for (AnnotationNode node : grabResolverAnnotations) {
+                    Map<String, Object> grabResolverMap = new HashMap<String, Object>();
+                    String sval = getMemberStringValue(node, "value");
+                    if (sval != null && sval.length() > 0) {
+                        for (String s : GRABRESOLVER_REQUIRED) {
+                            String mval = getMemberStringValue(node, s);
+                            if (mval != null && mval.isEmpty()) mval = null;
+                            if (mval != null) {
+                                addError("The attribute \"" + s + "\" conflicts with attribute 'value' in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                                continue grabResolverAnnotationLoop;
+                            }
+                        }
+                        grabResolverMap.put("name", sval);
+                        grabResolverMap.put("root", sval);
+                    } else {
+                        for (String s : GRABRESOLVER_REQUIRED) {
+                            String mval = getMemberStringValue(node, s);
+                            Expression member = node.getMember(s);
+                            if (member == null || (mval != null && mval.isEmpty())) {
+                                addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                                continue grabResolverAnnotationLoop;
+                            } else if (mval == null) {
+                                addError("Attribute \"" + s + "\" has value " + member.getText() + " but should be an inline constant String in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                                continue grabResolverAnnotationLoop;
+                            }
+                            grabResolverMap.put(s, mval);
+                        }
+                    }
+
+                    // If no scheme is specified for the repository root,
+                    // then turn it into a URI relative to that of the source file.
+                    String root = (String) grabResolverMap.get("root");
+                    if (root != null && !root.contains(":")) {
+                        URI sourceURI = null;
+                        // Since we use the data: scheme for StringReaderSources (which are fairly common)
+                        // and those are not hierarchical we can't use them for making an absolute URI.
+                        if (!(getSourceUnit().getSource() instanceof StringReaderSource)) {
+                            // Otherwise let's trust the source to know where it is from.
+                            // And actually InputStreamReaderSource doesn't know what to do and so returns null.
+                            sourceURI = getSourceUnit().getSource().getURI();
+                        }
+                        // If source doesn't know how to get a reference to itself,
+                        // then let's use the current working directory, since the repo can be relative to that.
+                        if (sourceURI == null) {
+                            sourceURI = new File(".").toURI();
+                        }
+                        try {
+                            URI rootURI = sourceURI.resolve(new URI(root));
+                            grabResolverMap.put("root", rootURI.toString());
+                        } catch (URISyntaxException e) {
+                            // We'll be silent here.
+                            // If the URI scheme is unknown or not hierarchical, then we just can't help them and shouldn't cause any trouble either.
+                            // addError("Attribute \"root\" has value '" + root + "' which can't be turned into a valid URI relative to it's source '" + getSourceUnit().getName() + "' @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                        }
+                    }
+
+                    Grape.addResolver(grabResolverMap);
+                    addGrabResolverAsStaticInitIfNeeded(grapeClassNode, node, grabResolverInitializers, grabResolverMap);
+                }
+            }
+
+            if (!grabConfigAnnotations.isEmpty()) {
+                for (AnnotationNode node : grabConfigAnnotations) {
+                    checkForClassLoader(node);
+                    checkForInitContextClassLoader(node);
+                    checkForAutoDownload(node);
+                    checkForSystemProperties(node);
+                    checkForDisableChecksums(node);
+                }
+                addInitContextClassLoaderIfNeeded(classNode);
+            }
+
+            if (!grabExcludeAnnotations.isEmpty()) {
+                grabExcludeAnnotationLoop:
+                for (AnnotationNode node : grabExcludeAnnotations) {
+                    Map<String, Object> grabExcludeMap = new HashMap<String, Object>();
+                    checkForConvenienceForm(node, true);
+                    for (String s : GRABEXCLUDE_REQUIRED) {
+                        Expression member = node.getMember(s);
+                        if (member == null) {
+                            addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                            continue grabExcludeAnnotationLoop;
+                        } else if (member != null && !(member instanceof ConstantExpression)) {
+                            addError("Attribute \"" + s + "\" has value " + member.getText() + " but should be an inline constant in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                            continue grabExcludeAnnotationLoop;
+                        }
+                        grabExcludeMap.put(s, ((ConstantExpression)member).getValue());
+                    }
+                    grabExcludeMaps.add(grabExcludeMap);
+                }
+            }
+
+            if (!grabAnnotations.isEmpty()) {
+                grabAnnotationLoop:
+                for (AnnotationNode node : grabAnnotations) {
+                    Map<String, Object> grabMap = new HashMap<String, Object>();
+                    checkForConvenienceForm(node, false);
+                    for (String s : GRAB_ALL) {
+                        Expression member = node.getMember(s);
+                        String mval = getMemberStringValue(node, s);
+                        if (mval != null && mval.isEmpty()) member = null;
+                        if (member == null && !GRAB_OPTIONAL.contains(s)) {
+                            addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                            continue grabAnnotationLoop;
+                        } else if (member != null && !(member instanceof ConstantExpression)) {
+                            addError("Attribute \"" + s + "\" has value " + member.getText() + " but should be an inline constant in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
+                            continue grabAnnotationLoop;
+                        }
+                        if (node.getMember(s) != null) {
+                            grabMap.put(s, ((ConstantExpression)member).getValue());
+                        }
+                    }
+                    grabMaps.add(grabMap);
+                    if ((node.getMember("initClass") == null) || (node.getMember("initClass") == ConstantExpression.TRUE)) {
+                        grabMapsInit.add(grabMap);
+                    }
+                }
+                callGrabAsStaticInitIfNeeded(classNode, grapeClassNode, grabMapsInit, grabExcludeMaps);
+            }
+
+            if (!grabResolverInitializers.isEmpty()) {
+                classNode.addStaticInitializerStatements(grabResolverInitializers, true);
+            }
+        }
+
+        if (!grabMaps.isEmpty()) {
+            Map<String, Object> basicArgs = new HashMap<String, Object>();
+            basicArgs.put("classLoader", loader != null ? loader : sourceUnit.getClassLoader());
+            if (!grabExcludeMaps.isEmpty()) basicArgs.put("excludes", grabExcludeMaps);
+            if (autoDownload != null) basicArgs.put(AUTO_DOWNLOAD_SETTING, autoDownload);
+            if (disableChecksums != null) basicArgs.put(DISABLE_CHECKSUMS_SETTING, disableChecksums);
+            if (systemProperties != null) basicArgs.put(SYSTEM_PROPERTIES_SETTING, systemProperties);
+
+            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
+                if (compilationUnit!=null) {
+                    ASTTransformationVisitor.addGlobalTransformsAfterGrab(compilationUnit.getASTTransformationsContext());
+                }
+            } 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 callGrabAsStaticInitIfNeeded(ClassNode classNode, ClassNode grapeClassNode, List<Map<String,Object>> grabMapsInit, List<Map<String, Object>> grabExcludeMaps) {
+        List<Statement> grabInitializers = new ArrayList<Statement>();
+        MapExpression basicArgs = new MapExpression();
+        if (autoDownload != null)  {
+            basicArgs.addMapEntryExpression(constX(AUTO_DOWNLOAD_SETTING), constX(autoDownload));
+        }
+
+        if (disableChecksums != null)  {
+            basicArgs.addMapEntryExpression(constX(DISABLE_CHECKSUMS_SETTING), constX(disableChecksums));
+        }
+
+        if (systemProperties != null && !systemProperties.isEmpty()) {
+            BlockStatement block = new BlockStatement();
+            for(Map.Entry e : systemProperties.entrySet()) {
+                block.addStatement(stmt(callX(SYSTEM_CLASSNODE, "setProperty", args(constX(e.getKey()), constX(e.getValue())))));
+            }
+            StaticMethodCallExpression enabled = callX(SYSTEM_CLASSNODE, "getProperty", args(constX("groovy.grape.enable"), constX("true")));
+            grabInitializers.add(ifS(eqX(enabled, constX("true")), block));
+        }
+
+        if (!grabExcludeMaps.isEmpty()) {
+            ListExpression list = new ListExpression();
+            for (Map<String, Object> map : grabExcludeMaps) {
+                Set<Map.Entry<String, Object>> entries = map.entrySet();
+                MapExpression inner = new MapExpression();
+                for (Map.Entry<String, Object> entry : entries) {
+                    inner.addMapEntryExpression(constX(entry.getKey()), constX(entry.getValue()));
+                }
+                list.addExpression(inner);
+            }
+            basicArgs.addMapEntryExpression(constX("excludes"), list);
+        }
+
+        List<Expression> argList = new ArrayList<Expression>();
+        argList.add(basicArgs);
+        if (grabMapsInit.isEmpty()) return;
+        for (Map<String, Object> grabMap : grabMapsInit) {
+            // add Grape.grab(excludeArgs, [group:group, module:module, version:version, classifier:classifier])
+            // or Grape.grab([group:group, module:module, version:version, classifier:classifier])
+            MapExpression dependencyArg = new MapExpression();
+            for (String s : GRAB_REQUIRED) {
+                dependencyArg.addMapEntryExpression(constX(s), constX(grabMap.get(s)));
+            }
+            for (String s : GRAB_OPTIONAL) {
+                if (grabMap.containsKey(s))
+                    dependencyArg.addMapEntryExpression(constX(s), constX(grabMap.get(s)));
+            }
+            argList.add(dependencyArg);
+        }
+        grabInitializers.add(stmt(callX(grapeClassNode, "grab", args(argList))));
+
+        // insert at beginning so we have the classloader set up before the class is called
+        classNode.addStaticInitializerStatements(grabInitializers, true);
+    }
+
+    private static void addGrabResolverAsStaticInitIfNeeded(ClassNode grapeClassNode, AnnotationNode node,
+                                                      List<Statement> grabResolverInitializers, Map<String, Object> grabResolverMap) {
+        if ((node.getMember("initClass") == null)
+            || (node.getMember("initClass") == ConstantExpression.TRUE))
+        {
+            MapExpression resolverArgs = new MapExpression();
+            for (Map.Entry<String, Object> next : grabResolverMap.entrySet()) {
+                resolverArgs.addMapEntryExpression(constX(next.getKey()), constX(next.getValue()));
+            }
+            grabResolverInitializers.add(stmt(callX(grapeClassNode, "addResolver", args(resolverArgs))));
+        }
+    }
+
+    private void addInitContextClassLoaderIfNeeded(ClassNode classNode) {
+        if (initContextClassLoader) {
+            Statement initStatement = stmt(callX(
+                            callX(THREAD_CLASSNODE, "currentThread"),
+                            "setContextClassLoader",
+                            callX(callThisX("getClass"), "getClassLoader")
+                    )
+            );
+            classNode.addObjectInitializerStatements(initStatement);
+        }
+    }
+
+    private void checkForClassLoader(AnnotationNode node) {
+        Object val = node.getMember("systemClassLoader");
+        if (val == null || !(val instanceof ConstantExpression)) return;
+        Object systemClassLoaderObject = ((ConstantExpression)val).getValue();
+        if (!(systemClassLoaderObject instanceof Boolean)) return;
+        Boolean systemClassLoader = (Boolean) systemClassLoaderObject;
+        if (systemClassLoader) loader = ClassLoader.getSystemClassLoader();
+    }
+
+    private void checkForInitContextClassLoader(AnnotationNode node) {
+        Object val = node.getMember("initContextClassLoader");
+        if (val == null || !(val instanceof ConstantExpression)) return;
+        Object initContextClassLoaderObject = ((ConstantExpression)val).getValue();
+        if (!(initContextClassLoaderObject instanceof Boolean)) return;
+        initContextClassLoader = (Boolean) initContextClassLoaderObject;
+    }
+
+    private void checkForAutoDownload(AnnotationNode node) {
+        Object val = node.getMember(AUTO_DOWNLOAD_SETTING);
+        if (val == null || !(val instanceof ConstantExpression)) return;
+        Object autoDownloadValue = ((ConstantExpression)val).getValue();
+        if (!(autoDownloadValue instanceof Boolean)) return;
+        autoDownload = (Boolean) autoDownloadValue;
+    }
+
+    private void checkForDisableChecksums(AnnotationNode node) {
+        Object val = node.getMember(DISABLE_CHECKSUMS_SETTING);
+        if (val == null || !(val instanceof ConstantExpression)) return;
+        Object disableChecksumsValue = ((ConstantExpression)val).getValue();
+        if (!(disableChecksumsValue instanceof Boolean)) return;
+        disableChecksums = (Boolean) disableChecksumsValue;
+    }
+
+    private void checkForSystemProperties(AnnotationNode node) {
+        systemProperties = new HashMap<String, String>();
+        List<String> nameValueList = AbstractASTTransformation.getMemberStringList(node, SYSTEM_PROPERTIES_SETTING);
+        if (nameValueList != null) {
+            for (String nameValue : nameValueList) {
+                int equalsDelim = nameValue.indexOf('=');
+                if (equalsDelim != -1) {
+                    systemProperties.put(nameValue.substring(0, equalsDelim), nameValue.substring(equalsDelim + 1));
+                }
+            }
+        }
+    }
+
+    private static void checkForConvenienceForm(AnnotationNode node, boolean exclude) {
+        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;
+
+        // strip off trailing attributes
+        boolean done = false;
+        while (!done) {
+            Matcher attrs = ATTRIBUTES_PATTERN.matcher(allstr);
+            if (attrs.find()) {
+                String attrName = attrs.group(2);
+                String attrValue = attrs.group(3);
+                if (attrName == null || attrValue == null) continue;
+                boolean isBool = GRAB_BOOLEAN.contains(attrName);
+                ConstantExpression value = constX(isBool ? Boolean.valueOf(attrValue) : attrValue);
+                value.setSourcePosition(node);
+                node.addMember(attrName, value);
+                int lastSemi = allstr.lastIndexOf(';');
+                if (lastSemi == -1) {
+                    allstr = "";
+                    break;
+                }
+                allstr = allstr.substring(0, lastSemi);
+            } else {
+                done = true;
+            }
+        }
+
+        if (allstr.contains("#")) {
+            // see: http://ant.apache.org/ivy/history/latest-milestone/textual.html
+            Matcher m = IVY_PATTERN.matcher(allstr);
+            if (!m.find()) return;
+            if (m.group(1) == null || m.group(2) == null) return;
+            node.addMember("module", constX(m.group(2)));
+            node.addMember("group", constX(m.group(1)));
+            if (m.group(6) != null) node.addMember("conf", constX(m.group(6)));
+            if (m.group(4) != null) node.addMember("version", constX(m.group(4)));
+            else if (!exclude && node.getMember("version") == null) node.addMember("version", constX("*"));
+            node.getMembers().remove("value");
+        } else if (allstr.contains(":")) {
+            // assume gradle syntax
+            // see: http://www.gradle.org/latest/docs/userguide/dependency_management.html#sec:how_to_declare_your_dependencies
+            Map<String, Object> parts = GrapeUtil.getIvyParts(allstr);
+            for (Map.Entry<String, Object> entry : parts.entrySet()) {
+                String key = entry.getKey();
+                String value = entry.getValue().toString();
+                if (!key.equals("version") || !value.equals("*") || !exclude) {
+                    node.addMember(key, constX(value));
+                }
+            }
+            node.getMembers().remove("value");
+        }
+    }
+
+    private void extractGrab(Expression init, ConstantExpression ce) {
+        if (ce.getValue() instanceof AnnotationNode) {
+            AnnotationNode annotation = (AnnotationNode) ce.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);
+            }
+            if ((GRABEXCLUDE_CLASS_NAME.equals(name))
+                    || (allowShortGrabExcludes && GRABEXCLUDE_SHORT_NAME.equals(name))
+                    || (grabExcludeAliases.contains(name))) {
+                grabExcludeAnnotations.add(annotation);
+            }
+            if ((GRABCONFIG_CLASS_NAME.equals(name))
+                    || (allowShortGrabConfig && GRABCONFIG_SHORT_NAME.equals(name))
+                    || (grabConfigAliases.contains(name))) {
+                grabConfigAnnotations.add(annotation);
+            }
+            if ((GRABRESOLVER_CLASS_NAME.equals(name))
+                    || (allowShortGrabResolver && GRABRESOLVER_SHORT_NAME.equals(name))
+                    || (grabResolverAliases.contains(name))) {
+                grabResolverAnnotations.add(annotation);
+            }
+        }
+    }
+
+    /**
+     * Adds the annotation to the internal target list if a match is found.
+     *
+     * @param node the AST node we are processing
+     */
+    public void visitAnnotations(AnnotatedNode node) {
+        super.visitAnnotations(node);
+        for (AnnotationNode an : 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 ((GRABEXCLUDE_CLASS_NAME.equals(name))
+                    || (allowShortGrabExcludes && GRABEXCLUDE_SHORT_NAME.equals(name))
+                    || (grabExcludeAliases.contains(name))) {
+                grabExcludeAnnotations.add(an);
+            }
+            if ((GRABCONFIG_CLASS_NAME.equals(name))
+                    || (allowShortGrabConfig && GRABCONFIG_SHORT_NAME.equals(name))
+                    || (grabConfigAliases.contains(name))) {
+                grabConfigAnnotations.add(an);
+            }
+            if ((GRAPES_CLASS_NAME.equals(name))
+                    || (allowShortGrapes && GRAPES_SHORT_NAME.equals(name))
+                    || (grapesAliases.contains(name))) {
+                grapesAnnotations.add(an);
+            }
+            if ((GRABRESOLVER_CLASS_NAME.equals(name))
+                    || (allowShortGrabResolver && GRABRESOLVER_SHORT_NAME.equals(name))
+                    || (grabResolverAliases.contains(name))) {
+                grabResolverAnnotations.add(an);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/grape/Grape.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/grape/Grape.java b/src/main/groovy/groovy/grape/Grape.java
new file mode 100644
index 0000000..ad89a20
--- /dev/null
+++ b/src/main/groovy/groovy/grape/Grape.java
@@ -0,0 +1,236 @@
+/*
+ *  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 groovy.grape;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Facade to GrapeEngine.
+ */
+public class Grape {
+
+    public static final String AUTO_DOWNLOAD_SETTING = "autoDownload";
+    public static final String DISABLE_CHECKSUMS_SETTING = "disableChecksums";
+    public static final String SYSTEM_PROPERTIES_SETTING = "systemProperties";
+
+    private static boolean enableGrapes = Boolean.valueOf(System.getProperty("groovy.grape.enable", "true"));
+    private static boolean enableAutoDownload = Boolean.valueOf(System.getProperty("groovy.grape.autoDownload", "true"));
+    private static boolean disableChecksums = Boolean.valueOf(System.getProperty("groovy.grape.disableChecksums", "false"));
+    protected static GrapeEngine instance;
+
+    /**
+     * This is a static access kill-switch.  All of the static shortcut
+     * methods in this class will not work if this property is set to false.
+     * By default it is set to true.
+     */
+    public static boolean getEnableGrapes() {
+        return enableGrapes;
+    }
+
+    /**
+     * This is a static access kill-switch.  All of the static shortcut
+     * methods in this class will not work if this property is set to false.
+     * By default it is set to true.
+     */
+    public static void setEnableGrapes(boolean enableGrapes) {
+        Grape.enableGrapes = enableGrapes;
+    }
+
+    /**
+     * This is a static access auto download enabler.  It will set the
+     * 'autoDownload' value to the passed in arguments map if not already set.
+     * If 'autoDownload' is set the value will not be adjusted.
+     * <p>
+     * This applies to the grab and resolve calls.
+     * <p>
+     * If it is set to false, only previously downloaded grapes
+     * will be used.  This may cause failure in the grape call
+     * if the library has not yet been downloaded
+     * <p>
+     * If it is set to true, then any jars not already downloaded will
+     * automatically be downloaded.  Also, any versions expressed as a range
+     * will be checked for new versions and downloaded (with dependencies)
+     * if found.
+     * <p>
+     * By default it is set to true.
+     */
+    public static boolean getEnableAutoDownload() {
+        return enableAutoDownload;
+    }
+
+    /**
+     * This is a static access auto download enabler.  It will set the
+     * 'autoDownload' value to the passed in arguments map if not already
+     * set.  If 'autoDownload' is set the value will not be adjusted.
+     * <p>
+     * This applies to the grab and resolve calls.
+     * <p>
+     * If it is set to false, only previously downloaded grapes
+     * will be used.  This may cause failure in the grape call
+     * if the library has not yet been downloaded.
+     * <p>
+     * If it is set to true, then any jars not already downloaded will
+     * automatically be downloaded.  Also, any versions expressed as a range
+     * will be checked for new versions and downloaded (with dependencies)
+     * if found. By default it is set to true.
+     */
+    public static void setEnableAutoDownload(boolean enableAutoDownload) {
+        Grape.enableAutoDownload = enableAutoDownload;
+    }
+
+    /**
+     * Global flag to ignore checksums.
+     * By default it is set to false.
+     */
+    public static boolean getDisableChecksums() {
+        return disableChecksums;
+    }
+
+    /**
+     * Set global flag to ignore checksums.
+     * By default it is set to false.
+     */
+    public static void setDisableChecksums(boolean disableChecksums) {
+        Grape.disableChecksums = disableChecksums;
+    }
+
+    public static synchronized GrapeEngine getInstance() {
+        if (instance == null) {
+            try {
+                // by default use GrapeIvy
+                //TODO META-INF/services resolver?
+                instance = (GrapeEngine) Class.forName("groovy.grape.GrapeIvy").newInstance();
+            } catch (InstantiationException e) {
+                //LOGME
+            } catch (IllegalAccessException e) {
+                //LOGME
+            } catch (ClassNotFoundException e) {
+                //LOGME
+            }
+        }
+        return instance;
+    }
+
+    public static void grab(String endorsed) {
+        if (enableGrapes) {
+            GrapeEngine instance = getInstance();
+            if (instance != null) {
+                instance.grab(endorsed);
+            }
+        }
+    }
+
+    public static void grab(Map<String, Object> dependency) {
+        if (enableGrapes) {
+            GrapeEngine instance = getInstance();
+            if (instance != null) {
+                if (!dependency.containsKey(AUTO_DOWNLOAD_SETTING)) {
+                    dependency.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload);
+                }
+                if (!dependency.containsKey(DISABLE_CHECKSUMS_SETTING)) {
+                    dependency.put(DISABLE_CHECKSUMS_SETTING, disableChecksums);
+                }
+                instance.grab(dependency);
+            }
+        }
+    }
+
+    public static void grab(Map<String, Object> args, Map... dependencies) {
+        if (enableGrapes) {
+            GrapeEngine instance = getInstance();
+            if (instance != null) {
+                if (!args.containsKey(AUTO_DOWNLOAD_SETTING)) {
+                    args.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload);
+                }
+                if (!args.containsKey(DISABLE_CHECKSUMS_SETTING)) {
+                    args.put(DISABLE_CHECKSUMS_SETTING, disableChecksums);
+                }
+                instance.grab(args, dependencies);
+            }
+        }
+    }
+
+    public static Map<String, Map<String, List<String>>> enumerateGrapes() {
+        Map<String, Map<String, List<String>>> grapes = null;
+        if (enableGrapes) {
+            GrapeEngine instance = getInstance();
+            if (instance != null) {
+                grapes = instance.enumerateGrapes();
+            }
+        }
+        if (grapes == null) {
+            return Collections.emptyMap();
+        } else {
+            return grapes;
+        }
+    }
+
+    public static URI[] resolve(Map<String, Object> args, Map... dependencies) {
+        return resolve(args, null, dependencies);
+    }
+    
+    public static URI[] resolve(Map<String, Object> args, List depsInfo, Map... dependencies) {
+        URI[] uris = null;
+        if (enableGrapes) {
+            GrapeEngine instance = getInstance();
+            if (instance != null) {
+                if (!args.containsKey(AUTO_DOWNLOAD_SETTING)) {
+                    args.put(AUTO_DOWNLOAD_SETTING, enableAutoDownload);
+                }
+                if (!args.containsKey(DISABLE_CHECKSUMS_SETTING)) {
+                    args.put(DISABLE_CHECKSUMS_SETTING, disableChecksums);
+                }
+                uris = instance.resolve(args, depsInfo, dependencies);
+            }
+        }
+        if (uris == null) {
+            return new URI[0];
+        } else {
+            return uris;
+        }
+    }
+
+    public static Map[] listDependencies(ClassLoader cl) {
+        Map[] maps = null;
+        if (enableGrapes) {
+            GrapeEngine instance = getInstance();
+            if (instance != null) {
+                maps = instance.listDependencies(cl);
+            }
+        }
+        if (maps == null) {
+            return new Map[0];
+        } else {
+            return maps;
+        }
+
+    }
+    
+    public static void addResolver(Map<String, Object> args) {
+        if (enableGrapes) {
+            GrapeEngine instance = getInstance();
+            if (instance != null) {
+                instance.addResolver(args);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/grape/GrapeEngine.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/grape/GrapeEngine.java b/src/main/groovy/groovy/grape/GrapeEngine.java
new file mode 100644
index 0000000..d98882f
--- /dev/null
+++ b/src/main/groovy/groovy/grape/GrapeEngine.java
@@ -0,0 +1,46 @@
+/*
+ *  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 groovy.grape;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Danno Ferrin
+ */
+public interface GrapeEngine {
+
+    Object grab(String endorsedModule);
+
+    Object grab(Map args);
+
+    Object grab(Map args, Map... dependencies);
+
+    Map<String, Map<String, List<String>>> enumerateGrapes();
+
+    URI[] resolve(Map args, Map... dependencies);
+
+    URI[] resolve(Map args, List depsInfo, Map... dependencies);
+
+    Map[] listDependencies(ClassLoader classLoader);
+
+    void addResolver(Map<String, Object> args);
+}
+

http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/grape/GrapeIvy.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/grape/GrapeIvy.groovy b/src/main/groovy/groovy/grape/GrapeIvy.groovy
new file mode 100644
index 0000000..a2c22a8
--- /dev/null
+++ b/src/main/groovy/groovy/grape/GrapeIvy.groovy
@@ -0,0 +1,729 @@
+/*
+ *  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 groovy.grape
+
+import org.apache.groovy.plugin.GroovyRunner
+import org.apache.groovy.plugin.GroovyRunnerRegistry
+import org.apache.ivy.Ivy
+import org.apache.ivy.core.cache.ResolutionCacheManager
+import org.apache.ivy.core.event.IvyListener
+import org.apache.ivy.core.event.download.PrepareDownloadEvent
+import org.apache.ivy.core.event.resolve.StartResolveEvent
+import org.apache.ivy.core.module.descriptor.Configuration
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.apache.ivy.core.module.id.ArtifactId
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.core.report.ArtifactDownloadReport
+import org.apache.ivy.core.report.ResolveReport
+import org.apache.ivy.core.resolve.ResolveOptions
+import org.apache.ivy.core.settings.IvySettings
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
+import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.apache.ivy.plugins.resolver.ChainResolver
+import org.apache.ivy.plugins.resolver.IBiblioResolver
+import org.apache.ivy.util.DefaultMessageLogger
+import org.apache.ivy.util.Message
+import org.codehaus.groovy.reflection.CachedClass
+import org.codehaus.groovy.reflection.ClassInfo
+import org.codehaus.groovy.reflection.ReflectionUtils
+import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl
+
+import javax.xml.parsers.DocumentBuilderFactory
+import java.util.jar.JarFile
+import java.util.regex.Pattern
+import java.util.zip.ZipEntry
+import java.util.zip.ZipException
+import java.util.zip.ZipFile
+
+/**
+ * @author Danno Ferrin
+ * @author Paul King
+ * @author Roshan Dawrani (roshandawrani)
+ */
+class GrapeIvy implements GrapeEngine {
+
+    static final int DEFAULT_DEPTH = 3
+
+    private static final String METAINF_PREFIX = 'META-INF/services/'
+    private static final String RUNNER_PROVIDER_CONFIG = GroovyRunner.class.getName()
+
+    private final exclusiveGrabArgs = [
+            ['group', 'groupId', 'organisation', 'organization', 'org'],
+            ['module', 'artifactId', 'artifact'],
+            ['version', 'revision', 'rev'],
+            ['conf', 'scope', 'configuration'],
+        ].inject([:], {m, g -> g.each {a -> m[a] = (g - a) as Set};  m})
+
+    boolean enableGrapes
+    Ivy ivyInstance
+    Set<String> resolvedDependencies
+    Set<String> downloadedArtifacts
+    // weak hash map so we don't leak loaders directly
+    Map<ClassLoader, Set<IvyGrabRecord>> loadedDeps = new WeakHashMap<ClassLoader, Set<IvyGrabRecord>>()
+    // set that stores the IvyGrabRecord(s) for all the dependencies in each grab() call
+    Set<IvyGrabRecord> grabRecordsForCurrDependencies = new LinkedHashSet<IvyGrabRecord>()
+    // we keep the settings so that addResolver can add to the resolver chain
+    IvySettings settings
+
+    public GrapeIvy() {
+        // if we are already initialized, quit
+        if (enableGrapes) return
+
+        // start ivy
+        Message.defaultLogger = new DefaultMessageLogger(System.getProperty("ivy.message.logger.level", "-1") as int)
+        settings = new IvySettings()
+
+        // configure settings
+        def grapeConfig = getLocalGrapeConfig()
+        if (!grapeConfig.exists()) {
+            grapeConfig = GrapeIvy.getResource("defaultGrapeConfig.xml")
+        }
+        try {
+            settings.load(grapeConfig) // exploit multi-methods for convenience
+        } catch (java.text.ParseException ex) {
+            def configLocation = grapeConfig instanceof File ? grapeConfig.canonicalPath : grapeConfig.toString()
+            System.err.println "Local Ivy config file '$configLocation' appears corrupt - ignoring it and using default config instead\nError was: " + ex.message
+            grapeConfig = GrapeIvy.getResource("defaultGrapeConfig.xml")
+            settings.load(grapeConfig)
+        }
+
+        // set up the cache dirs
+        settings.defaultCache = getGrapeCacheDir()
+
+        settings.setVariable("ivy.default.configuration.m2compatible", "true")
+        ivyInstance = Ivy.newInstance(settings)
+        org.apache.ivy.core.IvyContext.getContext().setIvy(ivyInstance);
+        resolvedDependencies = []
+        downloadedArtifacts = []
+
+        //TODO add grab to the DGM??
+
+        enableGrapes = true
+    }
+
+    public File getGroovyRoot() {
+        String root = System.getProperty("groovy.root")
+        def groovyRoot
+        if (root == null) {
+            groovyRoot = new File(System.getProperty("user.home"), ".groovy")
+        } else {
+            groovyRoot = new File(root)
+        }
+        try {
+            groovyRoot = groovyRoot.canonicalFile
+        } catch (IOException e) {
+            // skip canonicalization then, it may not exist yet
+        }
+        return groovyRoot
+    }
+
+    public File getLocalGrapeConfig() {
+        String grapeConfig = System.getProperty("grape.config")
+        if(grapeConfig) {
+            return new File(grapeConfig)
+        }
+        return new File(getGrapeDir(), 'grapeConfig.xml')
+    }
+
+    public File getGrapeDir() {
+        String root = System.getProperty("grape.root")
+        if(root == null) {
+            return getGroovyRoot()
+        }
+        File grapeRoot = new File(root)
+        try {
+            grapeRoot = grapeRoot.canonicalFile
+        } catch (IOException e) {
+            // skip canonicalization then, it may not exist yet
+        }
+        return grapeRoot
+    }
+
+    public File getGrapeCacheDir() {
+        File cache =  new File(getGrapeDir(), 'grapes')
+        if (!cache.exists()) {
+            cache.mkdirs()
+        } else if (!cache.isDirectory()) {
+            throw new RuntimeException("The grape cache dir $cache is not a directory")
+        }
+        return cache
+    }
+
+    public def chooseClassLoader(Map args) {
+        def loader = args.classLoader
+        if (!isValidTargetClassLoader(loader)) {
+            loader = (args.refObject?.class
+                        ?:ReflectionUtils.getCallingClass(args.calleeDepth?:1)
+                      )?.classLoader
+            while (loader && !isValidTargetClassLoader(loader)) {
+                loader = loader.parent
+            }
+            //if (!isValidTargetClassLoader(loader)) {
+            //    loader = Thread.currentThread().contextClassLoader
+            //}
+            //if (!isValidTargetClassLoader(loader)) {
+            //    loader = GrapeIvy.class.classLoader
+            //}
+            if (!isValidTargetClassLoader(loader)) {
+                throw new RuntimeException("No suitable ClassLoader found for grab")
+            }
+        }
+        return loader
+    }
+
+    private boolean isValidTargetClassLoader(loader) {
+        return isValidTargetClassLoaderClass(loader?.class)
+    }
+
+    private boolean isValidTargetClassLoaderClass(Class loaderClass) {
+        return (loaderClass != null) &&
+            (
+             (loaderClass.name == 'groovy.lang.GroovyClassLoader') ||
+             (loaderClass.name == 'org.codehaus.groovy.tools.RootLoader') ||
+             isValidTargetClassLoaderClass(loaderClass.superclass)
+            )
+    }
+
+    public IvyGrabRecord createGrabRecord(Map deps) {
+        // parse the actual dependency arguments
+        String module =  deps.module ?: deps.artifactId ?: deps.artifact
+        if (!module) {
+            throw new RuntimeException('grab requires at least a module: or artifactId: or artifact: argument')
+        }
+
+        String groupId = deps.group ?: deps.groupId ?: deps.organisation ?: deps.organization ?: deps.org ?: ''
+        String ext = deps.ext ?: deps.type ?: ''
+        String type = deps.type ?: ''
+
+        //TODO accept ranges and decode them?  except '1.0.0'..<'2.0.0' won't work in groovy
+        String version = deps.version ?: deps.revision ?: deps.rev ?: '*'
+        if ('*' == version) version = 'latest.default'
+
+        ModuleRevisionId mrid = ModuleRevisionId.newInstance(groupId, module, version)
+
+        boolean force      = deps.containsKey('force')      ? deps.force      : true
+        boolean changing   = deps.containsKey('changing')   ? deps.changing   : false
+        boolean transitive = deps.containsKey('transitive') ? deps.transitive : true
+        def conf = deps.conf ?: deps.scope ?: deps.configuration ?: ['default']
+        if (conf instanceof String) {
+            if (conf.startsWith("[") && conf.endsWith("]")) conf = conf[1..-2]
+            conf = conf.split(",").toList()
+        }
+        def classifier = deps.classifier ?: null
+
+        return new IvyGrabRecord(mrid:mrid, conf:conf, changing:changing, transitive:transitive, force:force, classifier:classifier, ext:ext, type:type)
+    }
+
+    public grab(String endorsedModule) {
+        return grab(group:'groovy.endorsed', module:endorsedModule, version:GroovySystem.version)
+    }
+
+    public grab(Map args) {
+        args.calleeDepth = args.calleeDepth?:DEFAULT_DEPTH + 1
+        return grab(args, args)
+    }
+
+    public grab(Map args, Map... dependencies) {
+        ClassLoader loader = null
+        grabRecordsForCurrDependencies.clear()
+
+        try {
+            // identify the target classloader early, so we fail before checking repositories
+            loader = chooseClassLoader(
+                classLoader:args.remove('classLoader'),
+                refObject:args.remove('refObject'),
+                calleeDepth:args.calleeDepth?:DEFAULT_DEPTH,
+            )
+
+            // check for non-fail null.
+            // If we were in fail mode we would have already thrown an exception
+            if (!loader) return
+
+            def uris = resolve(loader, args, dependencies)
+            for (URI uri in uris) {
+                loader.addURL(uri.toURL())
+            }
+            boolean runnerServicesFound = false
+            for (URI uri in uris) {
+                //TODO check artifact type, jar vs library, etc
+                File file = new File(uri)
+                processCategoryMethods(loader, file)
+                Collection<String> services = processMetaInfServices(loader, file)
+                if (!runnerServicesFound) {
+                    runnerServicesFound = services.contains(RUNNER_PROVIDER_CONFIG)
+                }
+            }
+            if (runnerServicesFound) {
+                GroovyRunnerRegistry.getInstance().load(loader)
+            }
+        } catch (Exception e) {
+            // clean-up the state first
+            Set<IvyGrabRecord> grabRecordsForCurrLoader = getLoadedDepsForLoader(loader)
+            grabRecordsForCurrLoader.removeAll(grabRecordsForCurrDependencies)
+            grabRecordsForCurrDependencies.clear()
+
+            if (args.noExceptions) {
+                return e
+            }
+            throw e
+        }
+        return null
+    }
+
+    private processCategoryMethods(ClassLoader loader, File file) {
+        // register extension methods if jar
+        if (file.name.toLowerCase().endsWith(".jar")) {
+            def mcRegistry = GroovySystem.metaClassRegistry
+            if (mcRegistry instanceof MetaClassRegistryImpl) {
+                try {
+                    JarFile jar = new JarFile(file)
+                    def entry = jar.getEntry(MetaClassRegistryImpl.MODULE_META_INF_FILE)
+                    if (entry) {
+                        Properties props = new Properties()
+                        props.load(jar.getInputStream(entry))
+                        Map<CachedClass, List<MetaMethod>> metaMethods = new HashMap<CachedClass, List<MetaMethod>>()
+                        mcRegistry.registerExtensionModuleFromProperties(props, loader, metaMethods)
+                        // add old methods to the map
+                        metaMethods.each { CachedClass c, List<MetaMethod> methods ->
+                            // GROOVY-5543: if a module was loaded using grab, there are chances that subclasses
+                            // have their own ClassInfo, and we must change them as well!
+                            Set<CachedClass> classesToBeUpdated = [c]
+                            ClassInfo.onAllClassInfo { ClassInfo info ->
+                                if (c.theClass.isAssignableFrom(info.cachedClass.theClass)) {
+                                    classesToBeUpdated << info.cachedClass
+                                }
+                            }
+                            classesToBeUpdated*.addNewMopMethods(methods)
+                        }
+                    }
+                }
+                catch(ZipException zipException) {
+                    throw new RuntimeException("Grape could not load jar '$file'", zipException)
+                }
+            }
+        }
+    }
+
+    void processOtherServices(ClassLoader loader, File f) {
+        processMetaInfServices(loader, f) // ignore result
+    }
+
+    /**
+     * Searches the given File for known service provider
+     * configuration files to process.
+     *
+     * @param loader used to locate service provider files
+     * @param f ZipFile in which to search for services
+     * @return a collection of service provider files that were found
+     */
+    private Collection<String> processMetaInfServices(ClassLoader loader, File f) {
+        List<String> services = new ArrayList<>()
+        try {
+            ZipFile zf = new ZipFile(f)
+            String providerConfig = 'org.codehaus.groovy.runtime.SerializedCategoryMethods'
+            ZipEntry serializedCategoryMethods = zf.getEntry(METAINF_PREFIX + providerConfig)
+            if (serializedCategoryMethods != null) {
+                services.add(providerConfig)
+                processSerializedCategoryMethods(zf.getInputStream(serializedCategoryMethods))
+            }
+            // TODO: remove in a future release (replaced by GroovyRunnerRegistry)
+            providerConfig = 'org.codehaus.groovy.plugins.Runners'
+            ZipEntry pluginRunners = zf.getEntry(METAINF_PREFIX + providerConfig)
+            if (pluginRunners != null) {
+                services.add(providerConfig)
+                processRunners(zf.getInputStream(pluginRunners), f.getName(), loader)
+            }
+            // GroovyRunners are loaded per ClassLoader using a ServiceLoader so here
+            // it only needs to be indicated that the service provider file was found
+            if (zf.getEntry(METAINF_PREFIX + RUNNER_PROVIDER_CONFIG) != null) {
+                services.add(RUNNER_PROVIDER_CONFIG)
+            }
+        } catch(ZipException ignore) {
+            // ignore files we can't process, e.g. non-jar/zip artifacts
+            // TODO log a warning
+        }
+        return services
+    }
+
+    void processSerializedCategoryMethods(InputStream is) {
+        is.text.readLines().each {
+            println it.trim() // TODO implement this or delete it
+        }
+    }
+
+    void processRunners(InputStream is, String name, ClassLoader loader) {
+        GroovyRunnerRegistry registry = GroovyRunnerRegistry.getInstance()
+        is.text.readLines()*.trim().findAll{ !it.isEmpty() && it[0] != '#' }.each {
+            try {
+                registry[name] = loader.loadClass(it).newInstance()
+            } catch (Exception ex) {
+                throw new IllegalStateException("Error registering runner class '" + it + "'", ex)
+            }
+        }
+    }
+
+    public ResolveReport getDependencies(Map args, IvyGrabRecord... grabRecords) {
+        ResolutionCacheManager cacheManager = ivyInstance.resolutionCacheManager
+
+        def millis = System.currentTimeMillis()
+        def md = new DefaultModuleDescriptor(ModuleRevisionId
+                .newInstance("caller", "all-caller", "working" + millis.toString()[-2..-1]), "integration", null, true)
+        md.addConfiguration(new Configuration('default'))
+        md.setLastModified(millis)
+
+        addExcludesIfNeeded(args, md)
+
+        for (IvyGrabRecord grabRecord : grabRecords) {
+            def conf = grabRecord.conf ?: ['*']
+            DefaultDependencyDescriptor dd = md.dependencies.find {it.dependencyRevisionId.equals(grabRecord.mrid)}
+            if (dd) {
+                createAndAddDependencyArtifactDescriptor(dd, grabRecord, conf)
+            } else {
+                dd = new DefaultDependencyDescriptor(md, grabRecord.mrid, grabRecord.force,
+                        grabRecord.changing, grabRecord.transitive)
+                conf.each {dd.addDependencyConfiguration('default', it)}
+                createAndAddDependencyArtifactDescriptor(dd, grabRecord, conf)
+                md.addDependency(dd)
+            }
+        }
+
+       // resolve grab and dependencies
+        ResolveOptions resolveOptions = new ResolveOptions()\
+            .setConfs(['default'] as String[])\
+            .setOutputReport(false)\
+            .setValidate(args.containsKey('validate') ? args.validate : false)
+
+        ivyInstance.settings.defaultResolver = args.autoDownload ? 'downloadGrapes' : 'cachedGrapes'
+        if (args.disableChecksums) {
+            ivyInstance.settings.setVariable('ivy.checksums', '')
+        }
+        boolean reportDownloads = System.getProperty('groovy.grape.report.downloads', 'false') == 'true'
+        if (reportDownloads) {
+            ivyInstance.eventManager.addIvyListener([progress:{ ivyEvent -> switch(ivyEvent) {
+                case StartResolveEvent:
+                    ivyEvent.moduleDescriptor.dependencies.each { it ->
+                        def name = it.toString()
+                        if (!resolvedDependencies.contains(name)) {
+                            resolvedDependencies << name
+                            System.err.println "Resolving " + name
+                        }
+                    }
+                    break
+                case PrepareDownloadEvent:
+                    ivyEvent.artifacts.each { it ->
+                        def name = it.toString()
+                        if (!downloadedArtifacts.contains(name)) {
+                            downloadedArtifacts << name
+                            System.err.println "Preparing to download artifact " + name
+                        }
+                    }
+                    break
+            } } ] as IvyListener)
+        }
+
+        ResolveReport report = null
+        int attempt = 8 // max of 8 times
+        while (true) {
+            try {
+                report = ivyInstance.resolve(md, resolveOptions)
+                break
+            } catch(IOException ioe) {
+                if (attempt--) {
+                    if (reportDownloads)
+                        System.err.println "Grab Error: retrying..."
+                    sleep attempt > 4 ? 350 : 1000
+                    continue
+                }
+                throw new RuntimeException("Error grabbing grapes -- $ioe.message")
+            }
+        }
+
+        if (report.hasError()) {
+            throw new RuntimeException("Error grabbing Grapes -- $report.allProblemMessages")
+        }
+        if (report.downloadSize && reportDownloads) {
+            System.err.println "Downloaded ${report.downloadSize >> 10} Kbytes in ${report.downloadTime}ms:\n  ${report.allArtifactsReports*.toString().join('\n  ')}"
+        }
+        md = report.moduleDescriptor
+
+        if (!args.preserveFiles) {
+            cacheManager.getResolvedIvyFileInCache(md.moduleRevisionId).delete()
+            cacheManager.getResolvedIvyPropertiesInCache(md.moduleRevisionId).delete()
+        }
+
+        return report
+    }
+
+    private void createAndAddDependencyArtifactDescriptor(DefaultDependencyDescriptor dd, IvyGrabRecord grabRecord, List<String> conf) {
+        // TODO: find out "unknown" reason and change comment below - also, confirm conf[0] check vs conf.contains('optional')
+        if (conf[0]!="optional" || grabRecord.classifier) {  // for some unknown reason optional dependencies should not have an artifactDescriptor
+            def dad = new DefaultDependencyArtifactDescriptor(dd,
+                    grabRecord.mrid.name, grabRecord.type ?: 'jar', grabRecord.ext ?: 'jar', null, grabRecord.classifier ? [classifier: grabRecord.classifier] : null)
+            conf.each { dad.addConfiguration(it) }
+            dd.addDependencyArtifact('default', dad)
+        }
+    }
+
+    public void uninstallArtifact(String group, String module, String rev) {
+        // TODO consider transitive uninstall as an option
+        Pattern ivyFilePattern = ~/ivy-(.*)\.xml/ //TODO get pattern from ivy conf
+        grapeCacheDir.eachDir { File groupDir ->
+            if (groupDir.name == group) groupDir.eachDir { File moduleDir ->
+                if (moduleDir.name == module) moduleDir.eachFileMatch(ivyFilePattern) { File ivyFile ->
+                    def m = ivyFilePattern.matcher(ivyFile.name)
+                    if (m.matches() && m.group(1) == rev) {
+                        // TODO handle other types? e.g. 'dlls'
+                        def jardir = new File(moduleDir, 'jars')
+                        if (!jardir.exists()) return
+                        def dbf = DocumentBuilderFactory.newInstance()
+                        def db = dbf.newDocumentBuilder()
+                        def root = db.parse(ivyFile).documentElement
+                        def publis = root.getElementsByTagName('publications')
+                        for (int i=0; i<publis.length;i++) {
+                            def artifacts = publis.item(i).getElementsByTagName('artifact')
+                            for (int j=0; j<artifacts.length; j++) {
+                                def artifact = artifacts.item(j)
+                                def attrs = artifact.attributes
+                                def name = attrs.getNamedItem('name').getTextContent() + "-$rev"
+                                def classifier = attrs.getNamedItemNS("m", "classifier")?.getTextContent()
+                                if (classifier) name += "-$classifier"
+                                name += ".${attrs.getNamedItem('ext').getTextContent()}"
+                                def jarfile = new File(jardir, name)
+                                if (jarfile.exists()) {
+                                    println "Deleting ${jarfile.name}"
+                                    jarfile.delete()
+                                }
+                            }
+                        }
+                        ivyFile.delete()
+                    }
+                }
+            }
+        }
+    }
+
+    private addExcludesIfNeeded(Map args, DefaultModuleDescriptor md) {
+        if (!args.containsKey('excludes')) return
+        args.excludes.each{ map ->
+            def excludeRule = new DefaultExcludeRule(new ArtifactId(
+                    new ModuleId(map.group, map.module), PatternMatcher.ANY_EXPRESSION,
+                    PatternMatcher.ANY_EXPRESSION,
+                    PatternMatcher.ANY_EXPRESSION),
+                    ExactPatternMatcher.INSTANCE, null)
+            excludeRule.addConfiguration('default')
+            md.addExcludeRule(excludeRule)
+        }
+    }
+
+    public Map<String, Map<String, List<String>>> enumerateGrapes() {
+        Map<String, Map<String, List<String>>> bunches = [:]
+        Pattern ivyFilePattern = ~/ivy-(.*)\.xml/ //TODO get pattern from ivy conf
+        grapeCacheDir.eachDir {File groupDir ->
+            Map<String, List<String>> grapes = [:]
+            bunches[groupDir.name] = grapes
+            groupDir.eachDir { File moduleDir ->
+                def versions = []
+                moduleDir.eachFileMatch(ivyFilePattern) {File ivyFile ->
+                    def m = ivyFilePattern.matcher(ivyFile.name)
+                    if (m.matches()) versions += m.group(1)
+                }
+                grapes[moduleDir.name] = versions
+            }
+        }
+        return bunches
+    }
+
+    public URI[] resolve(Map args, Map ... dependencies) {
+        resolve(args, null, dependencies)
+    }
+
+    public URI[] resolve(Map args, List depsInfo, Map ... dependencies) {
+        // identify the target classloader early, so we fail before checking repositories
+        def loader = chooseClassLoader(
+                classLoader: args.remove('classLoader'),
+                refObject: args.remove('refObject'),
+                calleeDepth: args.calleeDepth ?: DEFAULT_DEPTH,
+        )
+
+        // check for non-fail null.
+        // If we were in fail mode we would have already thrown an exception
+        if (!loader) return
+
+        resolve(loader, args, depsInfo, dependencies)
+    }
+
+    URI [] resolve(ClassLoader loader, Map args, Map... dependencies) {
+        return resolve(loader, args, null, dependencies)
+    }
+
+    URI [] resolve(ClassLoader loader, Map args, List depsInfo, Map... dependencies) {
+        // check for mutually exclusive arguments
+        Set keys = args.keySet()
+        keys.each {a ->
+            Set badArgs = exclusiveGrabArgs[a]
+            if (badArgs && !badArgs.disjoint(keys)) {
+                throw new RuntimeException("Mutually exclusive arguments passed into grab: ${keys.intersect(badArgs) + a}")
+            }
+        }
+
+        // check the kill switch
+        if (!enableGrapes) { return }
+
+        boolean populateDepsInfo = (depsInfo != null)
+
+        Set<IvyGrabRecord> localDeps = getLoadedDepsForLoader(loader)
+
+        dependencies.each {
+            IvyGrabRecord igr = createGrabRecord(it)
+            grabRecordsForCurrDependencies.add(igr)
+            localDeps.add(igr)
+        }
+        // the call to reverse ensures that the newest additions are in
+        // front causing existing dependencies to come last and thus
+        // claiming higher priority.  Thus when module versions clash we
+        // err on the side of using the class already loaded into the
+        // classloader rather than adding another jar of the same module
+        // with a different version
+        ResolveReport report = null
+        try {
+            report = getDependencies(args, *localDeps.asList().reverse())
+        } catch (Exception e) {
+            // clean-up the state first
+            localDeps.removeAll(grabRecordsForCurrDependencies)
+            grabRecordsForCurrDependencies.clear()
+            throw e
+        }
+
+        List<URI> results = []
+        for (ArtifactDownloadReport adl in report.allArtifactsReports) {
+            //TODO check artifact type, jar vs library, etc
+            if (adl.localFile) {
+                results += adl.localFile.toURI()
+            }
+        }
+
+        if (populateDepsInfo) {
+            def deps = report.dependencies
+            deps.each { depNode ->
+                def id = depNode.id
+                depsInfo << ['group' : id.organisation, 'module' : id.name, 'revision' : id.revision]
+            }
+        }
+
+        return results as URI[]
+    }
+
+    private Set<IvyGrabRecord> getLoadedDepsForLoader(ClassLoader loader) {
+        Set<IvyGrabRecord> localDeps = loadedDeps.get(loader)
+        if (localDeps == null) {
+            // use a linked set to preserve initial insertion order
+            localDeps = new LinkedHashSet<IvyGrabRecord>()
+            loadedDeps.put(loader, localDeps)
+        }
+        return localDeps
+    }
+
+    public Map[] listDependencies (ClassLoader classLoader) {
+        if (loadedDeps.containsKey(classLoader)) {
+            List<Map> results = []
+            loadedDeps[classLoader].each { IvyGrabRecord grabbed ->
+                def dep =  [
+                    group : grabbed.mrid.organisation,
+                    module : grabbed.mrid.name,
+                    version : grabbed.mrid.revision
+                ]
+                if (grabbed.conf != ['default']) {
+                    dep.conf = grabbed.conf
+                }
+                if (grabbed.changing) {
+                    dep.changing = grabbed.changing
+                }
+                if (!grabbed.transitive) {
+                    dep.transitive = grabbed.transitive
+                }
+                if (!grabbed.force) {
+                    dep.force = grabbed.force
+                }
+                if (grabbed.classifier) {
+                    dep.classifier = grabbed.classifier
+                }
+                if (grabbed.ext) {
+                    dep.ext = grabbed.ext
+                }
+                if (grabbed.type) {
+                    dep.type = grabbed.type
+                }
+                results << dep
+            }
+            return results
+        }
+        return null
+    }
+
+    public void addResolver(Map<String, Object> args) {
+        ChainResolver chainResolver = settings.getResolver("downloadGrapes")
+
+        IBiblioResolver resolver = new IBiblioResolver(name: args.name, root:args.root,
+              m2compatible:(args.m2Compatible ?: true), settings:settings)
+
+        chainResolver.add(resolver)
+
+        ivyInstance = Ivy.newInstance(settings)
+        resolvedDependencies = []
+        downloadedArtifacts = []
+    }
+}
+
+class IvyGrabRecord {
+    ModuleRevisionId mrid
+    List<String> conf
+    boolean changing
+    boolean transitive
+    boolean force
+    String classifier
+    String ext
+    String type
+
+    public int hashCode() {
+        return (mrid.hashCode() ^ conf.hashCode()
+            ^ (changing ? 0xaaaaaaaa : 0x55555555)
+            ^ (transitive ? 0xbbbbbbbb : 0x66666666)
+            ^ (force ? 0xcccccccc: 0x77777777)
+            ^ (classifier ? classifier.hashCode() : 0)
+            ^ (ext ? ext.hashCode() : 0)
+            ^ (type ? type.hashCode() : 0))
+    }
+
+    public boolean equals(Object o) {
+        return ((o.class == IvyGrabRecord)
+            && (changing == o.changing)
+            && (transitive == o.transitive)
+            && (force== o.force)
+            && (mrid == o.mrid)
+            && (conf == o.conf)
+            && (classifier == o.classifier)
+            && (ext == o.ext)
+            && (type == o.type))
+    }
+
+}