Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 940DA200D88 for ; Wed, 20 Dec 2017 01:56:04 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 9251E160C1B; Wed, 20 Dec 2017 00:56:04 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 80FE2160C40 for ; Wed, 20 Dec 2017 01:56:01 +0100 (CET) Received: (qmail 15738 invoked by uid 500); 20 Dec 2017 00:56:00 -0000 Mailing-List: contact commits-help@groovy.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@groovy.apache.org Delivered-To: mailing list commits@groovy.apache.org Received: (qmail 15410 invoked by uid 99); 20 Dec 2017 00:56:00 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 20 Dec 2017 00:56:00 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id AF05BF17D8; Wed, 20 Dec 2017 00:55:59 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sunlan@apache.org To: commits@groovy.apache.org Date: Wed, 20 Dec 2017 00:56:40 -0000 Message-Id: <560cae738ccb4eaba6086e57eade8eb3@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [43/49] groovy git commit: Move source files to proper packages archived-at: Wed, 20 Dec 2017 00:56:04 -0000 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 extends HashMap { + 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 GRABEXCLUDE_REQUIRED = Arrays.asList("group", "module"); + private static final List GRABRESOLVER_REQUIRED = Arrays.asList("name", "root"); + private static final List GRAB_REQUIRED = Arrays.asList("group", "module", "version"); + private static final List GRAB_OPTIONAL = Arrays.asList("classifier", "transitive", "conf", "ext", "type", "changing", "force", "initClass"); + private static final List GRAB_BOOLEAN = Arrays.asList("transitive", "changing", "force", "initClass"); + private static final Collection 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 grabAliases; + List grabAnnotations; + + boolean allowShortGrabExcludes; + Set grabExcludeAliases; + List grabExcludeAnnotations; + + boolean allowShortGrabConfig; + Set grabConfigAliases; + List grabConfigAnnotations; + + boolean allowShortGrapes; + Set grapesAliases; + List grapesAnnotations; + + boolean allowShortGrabResolver; + Set grabResolverAliases; + List grabResolverAnnotations; + + CompilationUnit compilationUnit; + SourceUnit sourceUnit; + ClassLoader loader; + boolean initContextClassLoader; + Boolean autoDownload; + Boolean disableChecksums; + Map 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(); + grabExcludeAliases = new HashSet(); + grabConfigAliases = new HashSet(); + grapesAliases = new HashSet(); + grabResolverAliases = new HashSet(); + 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> grabMaps = new ArrayList>(); + List> grabMapsInit = new ArrayList>(); + List> grabExcludeMaps = new ArrayList>(); + + for (ClassNode classNode : sourceUnit.getAST().getClasses()) { + grabAnnotations = new ArrayList(); + grabExcludeAnnotations = new ArrayList(); + grabConfigAnnotations = new ArrayList(); + grapesAnnotations = new ArrayList(); + grabResolverAnnotations = new ArrayList(); + + visitClass(classNode); + + ClassNode grapeClassNode = ClassHelper.make(Grape.class); + + List grabResolverInitializers = new ArrayList(); + + 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 grabResolverMap = new HashMap(); + 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 grabExcludeMap = new HashMap(); + 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 grabMap = new HashMap(); + 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 basicArgs = new HashMap(); + 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> grabMapsInit, List> grabExcludeMaps) { + List grabInitializers = new ArrayList(); + 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 map : grabExcludeMaps) { + Set> entries = map.entrySet(); + MapExpression inner = new MapExpression(); + for (Map.Entry entry : entries) { + inner.addMapEntryExpression(constX(entry.getKey()), constX(entry.getValue())); + } + list.addExpression(inner); + } + basicArgs.addMapEntryExpression(constX("excludes"), list); + } + + List argList = new ArrayList(); + argList.add(basicArgs); + if (grabMapsInit.isEmpty()) return; + for (Map 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 grabResolverInitializers, Map grabResolverMap) { + if ((node.getMember("initClass") == null) + || (node.getMember("initClass") == ConstantExpression.TRUE)) + { + MapExpression resolverArgs = new MapExpression(); + for (Map.Entry 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(); + List 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 parts = GrapeUtil.getIvyParts(allstr); + for (Map.Entry 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. + *

+ * This applies to the grab and resolve calls. + *

+ * 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 + *

+ * 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 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. + *

+ * This applies to the grab and resolve calls. + *

+ * 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. + *

+ * 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 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 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>> enumerateGrapes() { + Map>> 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 args, Map... dependencies) { + return resolve(args, null, dependencies); + } + + public static URI[] resolve(Map 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 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>> enumerateGrapes(); + + URI[] resolve(Map args, Map... dependencies); + + URI[] resolve(Map args, List depsInfo, Map... dependencies); + + Map[] listDependencies(ClassLoader classLoader); + + void addResolver(Map 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 resolvedDependencies + Set downloadedArtifacts + // weak hash map so we don't leak loaders directly + Map> loadedDeps = new WeakHashMap>() + // set that stores the IvyGrabRecord(s) for all the dependencies in each grab() call + Set grabRecordsForCurrDependencies = new LinkedHashSet() + // 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 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 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> metaMethods = new HashMap>() + mcRegistry.registerExtensionModuleFromProperties(props, loader, metaMethods) + // add old methods to the map + metaMethods.each { CachedClass c, List 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 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 processMetaInfServices(ClassLoader loader, File f) { + List 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 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 + 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>> enumerateGrapes() { + Map>> bunches = [:] + Pattern ivyFilePattern = ~/ivy-(.*)\.xml/ //TODO get pattern from ivy conf + grapeCacheDir.eachDir {File groupDir -> + Map> 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 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 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 getLoadedDepsForLoader(ClassLoader loader) { + Set localDeps = loadedDeps.get(loader) + if (localDeps == null) { + // use a linked set to preserve initial insertion order + localDeps = new LinkedHashSet() + loadedDeps.put(loader, localDeps) + } + return localDeps + } + + public Map[] listDependencies (ClassLoader classLoader) { + if (loadedDeps.containsKey(classLoader)) { + List 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 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 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)) + } + +}