<commit_before>/*
 * Copyright 2003-2013 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.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.ArgumentListExpression;
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.MethodCallExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
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.GroovyASTTransformation;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 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 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 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;

    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>();
                    Expression value = node.getMember("value");
                    ConstantExpression ce = null;
                    if (value != null && value instanceof ConstantExpression) {
                        ce = (ConstantExpression) value;
                    }
                    String sval = null;
                    if (ce != null && ce.getValue() instanceof String) {
                        sval = (String) ce.getValue();
                    }
                    if (sval != null && sval.length() > 0) {
                        for (String s : GRABRESOLVER_REQUIRED) {
                            Expression member = node.getMember(s);
                            if (member != 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) {
                            Expression member = node.getMember(s);
                            if (member == null) {
                                addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
                                continue grabResolverAnnotationLoop;
                            } 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 grabResolverAnnotationLoop;
                            }
                            grabResolverMap.put(s, ((ConstantExpression) member).getValue());
                        }
                    }

                    // 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);
                    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);
                        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);

            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(new ConstantExpression(AUTO_DOWNLOAD_SETTING), new ConstantExpression(autoDownload));
        }

        if (disableChecksums != null)  {
            basicArgs.addMapEntryExpression(new ConstantExpression(DISABLE_CHECKSUMS_SETTING), new ConstantExpression(disableChecksums));
        }
        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(new ConstantExpression(entry.getKey()), new ConstantExpression(entry.getValue()));
                }
                list.addExpression(inner);
            }
            basicArgs.addMapEntryExpression(new ConstantExpression("excludes"), list);
        }

        List<Expression> argList = new ArrayList<Expression>();
        argList.add(basicArgs);
        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(new ConstantExpression(s), new ConstantExpression(grabMap.get(s)));
            }
            for (String s : GRAB_OPTIONAL) {
                if (grabMap.containsKey(s))
                    dependencyArg.addMapEntryExpression(new ConstantExpression(s), new ConstantExpression(grabMap.get(s)));
            }
            argList.add(dependencyArg);
        }
        ArgumentListExpression grabArgs = new ArgumentListExpression(argList);
        grabInitializers.add(new ExpressionStatement(new StaticMethodCallExpression(grapeClassNode, "grab", grabArgs)));

        // insert at beginning so we have the classloader set up before the class is called
        classNode.addStaticInitializerStatements(grabInitializers, true);
    }<commit_msg>GROOVY-6648: @Grab not respecting initClass=false in all cases<commit_after>/*
 * Copyright 2003-2013 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.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.ArgumentListExpression;
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.MethodCallExpression;
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
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.GroovyASTTransformation;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 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 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 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;

    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>();
                    Expression value = node.getMember("value");
                    ConstantExpression ce = null;
                    if (value != null && value instanceof ConstantExpression) {
                        ce = (ConstantExpression) value;
                    }
                    String sval = null;
                    if (ce != null && ce.getValue() instanceof String) {
                        sval = (String) ce.getValue();
                    }
                    if (sval != null && sval.length() > 0) {
                        for (String s : GRABRESOLVER_REQUIRED) {
                            Expression member = node.getMember(s);
                            if (member != 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) {
                            Expression member = node.getMember(s);
                            if (member == null) {
                                addError("The missing attribute \"" + s + "\" is required in @" + node.getClassNode().getNameWithoutPackage() + " annotations", node);
                                continue grabResolverAnnotationLoop;
                            } 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 grabResolverAnnotationLoop;
                            }
                            grabResolverMap.put(s, ((ConstantExpression) member).getValue());
                        }
                    }

                    // 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);
                    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);
                        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);

            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(new ConstantExpression(AUTO_DOWNLOAD_SETTING), new ConstantExpression(autoDownload));
        }

        if (disableChecksums != null)  {
            basicArgs.addMapEntryExpression(new ConstantExpression(DISABLE_CHECKSUMS_SETTING), new ConstantExpression(disableChecksums));
        }
        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(new ConstantExpression(entry.getKey()), new ConstantExpression(entry.getValue()));
                }
                list.addExpression(inner);
            }
            basicArgs.addMapEntryExpression(new ConstantExpression("excludes"), list);
        }

        List<Expression> argList = new ArrayList<Expression>();
        argList.add(basicArgs);
        if (grabMapsInit.size() == 0) 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(new ConstantExpression(s), new ConstantExpression(grabMap.get(s)));
            }
            for (String s : GRAB_OPTIONAL) {
                if (grabMap.containsKey(s))
                    dependencyArg.addMapEntryExpression(new ConstantExpression(s), new ConstantExpression(grabMap.get(s)));
            }
            argList.add(dependencyArg);
        }
        ArgumentListExpression grabArgs = new ArgumentListExpression(argList);
        grabInitializers.add(new ExpressionStatement(new StaticMethodCallExpression(grapeClassNode, "grab", grabArgs)));

        // insert at beginning so we have the classloader set up before the class is called
        classNode.addStaticInitializerStatements(grabInitializers, true);
    }
{'fraction_non_alphanumeric': 0.09988495635108614, 'fraction_numerical': 0.0016015880535065756, 'mean_word_length': 2.2168928234525795, 'pattern_counts': {'":': 2, '<': 125, '<?xml version=': 0, '>': 127, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7586238', 'n_tokens_mistral': 11535, 'n_tokens_neox': 11055, 'n_words': 2589}