[ 
https://issues.apache.org/jira/browse/GROOVY-11871?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18065049#comment-18065049
 ] 

ASF GitHub Bot commented on GROOVY-11871:
-----------------------------------------

Copilot commented on code in PR #2140:
URL: https://github.com/apache/groovy/pull/2140#discussion_r2917767202


##########
src/main/groovy/groovy/grape/GrapeUtil.groovy:
##########
@@ -0,0 +1,174 @@
+/*
+ *  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.transform.CompileStatic
+import org.apache.groovy.plugin.GroovyRunner
+import org.apache.groovy.plugin.GroovyRunnerRegistry
+import org.codehaus.groovy.reflection.CachedClass
+import org.codehaus.groovy.reflection.ClassInfo
+import org.codehaus.groovy.runtime.m12n.ExtensionModuleScanner
+import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl
+
+import java.util.jar.JarFile
+import java.util.zip.ZipEntry
+import java.util.zip.ZipException
+import java.util.zip.ZipFile
+
+/**
+ * Utility methods shared between GrapeIvy and GrapeMaven implementations.
+ */
+@CompileStatic
+class GrapeUtil {
+
+    private static final String METAINF_PREFIX = 'META-INF/services/'
+    private static final String RUNNER_PROVIDER_CONFIG = GroovyRunner.name
+
+    /**
+     * Adds a URI to a classloader's classpath via reflection.
+     */
+    static void addURL(ClassLoader loader, URI uri) {
+        // Dynamic invocation needed as addURL is not part of ClassLoader 
interface
+        loader.metaClass.invokeMethod(loader, 'addURL', uri.toURL())
+    }
+
+    /**
+     * Processes and registers category methods (extension modules) from a JAR 
file.
+     *
+     * @param loader the classloader to register methods with
+     * @param file the JAR file to process
+     */
+    static void processCategoryMethods(ClassLoader loader, File file) {
+        // register extension methods if jar
+        if (file.getName().toLowerCase().endsWith('.jar')) {
+            def mcRegistry = GroovySystem.metaClassRegistry
+            if (mcRegistry instanceof MetaClassRegistryImpl) {
+                try (JarFile jar = new JarFile(file)) {
+                    ZipEntry entry = 
jar.getEntry(ExtensionModuleScanner.MODULE_META_INF_FILE)
+                    if (!entry) {
+                        entry = 
jar.getEntry(ExtensionModuleScanner.LEGACY_MODULE_META_INF_FILE)
+                    }
+                    if (entry) {
+                        Properties props = new Properties()
+
+                        try (InputStream is = jar.getInputStream(entry)) {
+                            props.load(is)
+                        }
+
+                        Map<CachedClass, List<MetaMethod>> metaMethods = [:]
+                        
mcRegistry.registerExtensionModuleFromProperties(props, loader, metaMethods)
+                        // add old methods to the map
+                        metaMethods.each { CachedClass c, List<MetaMethod> 
methods ->
+                            // GROOVY-5543: if a module was loaded using grab, 
there are chances that subclasses
+                            // have their own ClassInfo, and we must change 
them as well!
+                            Set<CachedClass> classesToBeUpdated = [c].toSet()
+                            ClassInfo.onAllClassInfo { ClassInfo info ->
+                                if 
(c.getTheClass().isAssignableFrom(info.getCachedClass().getTheClass())) {
+                                    classesToBeUpdated << info.getCachedClass()
+                                }
+                            }
+                            classesToBeUpdated*.addNewMopMethods(methods)
+                        }
+                    }
+                } catch (ZipException e) {
+                    throw new RuntimeException("Grape could not load jar 
'$file'", e)
+                }
+            }
+        }
+    }
+
+    /**
+     * 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
+     */
+    static Collection<String> processMetaInfServices(ClassLoader loader, File 
f) {
+        List<String> services = []
+        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)
+
+                try (InputStream is = 
zf.getInputStream(serializedCategoryMethods)) {
+                    processSerializedCategoryMethods(is)
+                }
+            }
+            // 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)
+
+                try (InputStream is = zf.getInputStream(pluginRunners)) {
+                    processRunners(is, 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
+        }
+        services
+    }
+
+    /**
+     * Processes serialized category methods from an input stream.
+     *
+     * @param is the input stream containing category method definitions
+     */
+    static void processSerializedCategoryMethods(InputStream is) {
+        is.getText().readLines().each {
+            System.err.println(it.trim()) // TODO: implement this or delete it
+        }
+    }
+
+    /**
+     * Processes and registers Groovy runner implementations from a service 
provider file.
+     *
+     * @param is the input stream containing runner class names
+     * @param name the name to register the runners under
+     * @param loader the classloader to load runner classes from
+     */
+    static void processRunners(InputStream is, String name, ClassLoader 
loader) {
+        GroovyRunnerRegistry registry = GroovyRunnerRegistry.instance
+        is.getText().readLines()*.trim().each { String line ->
+            if (!line.isEmpty() && line[0] != '#')
+            try {
+                registry[name] = (GroovyRunner) 
loader.loadClass(line).getDeclaredConstructor().newInstance()
+            } catch (Exception e) {
+                throw new IllegalStateException("Error registering runner 
class '$line'", e)

Review Comment:
   `processRunners` has an `if (!line.isEmpty() && line[0] != '#')` with no 
braces/body, so the subsequent `try { ... }` runs for *every* line, including 
empty/comment lines. This will attempt to load invalid class names and can 
break runner discovery. Wrap the `try` block inside the `if` (or add `continue` 
when the condition fails).
   ```suggestion
               if (!line.isEmpty() && line[0] != '#') {
                   try {
                       registry[name] = (GroovyRunner) 
loader.loadClass(line).getDeclaredConstructor().newInstance()
                   } catch (Exception e) {
                       throw new IllegalStateException("Error registering 
runner class '$line'", e)
                   }
   ```



##########
subprojects/groovy-xml/src/main/groovy/groovy/xml/StaxBuilder.groovy:
##########
@@ -31,7 +31,10 @@ package groovy.xml
  *     elem2('hello2')
  *     elem3(x:7)
  * }
- * assert writer.toString() == '<?xml version="1.0" ?><root1 a="5" 
b="7"><elem1>hello1</elem1><elem2>hello2</elem2><elem3 x="7"></elem3></root1>'
+ * def pretty= writer.toString()
+ *     .replaceAll(/<\?xml[^>]*>/, '') // remove XML declaration

Review Comment:
   The doc example removes the XML declaration using 
`.replaceAll(/<\?xml[^>]*>/, '')` but (as shown in the old asserted output) 
implementations may emit a trailing space after the declaration. That leaves a 
leading space before `<root1...>` and makes the sample assertion brittle. 
Consider stripping the declaration plus any following whitespace (or trimming) 
in the example as well.
   ```suggestion
    *     .replaceAll(/<\?xml[^>]*>\s*/, '') // remove XML declaration and any 
following whitespace
   ```



##########
subprojects/groovy-console/src/main/groovy/groovy/console/ui/Console.groovy:
##########
@@ -1242,12 +1249,7 @@ class Console implements CaretListener, 
HyperlinkListener, ComponentListener, Fo
 
     // actually run the script
     void runScript(EventObject evt = null, SourceType st = new 
GroovySourceType()) {

Review Comment:
   `runScript` no longer calls `saveInputAreaContentHash()`. 
`runSelectedScript` still does, so this looks like an unintended regression 
from the previous implementation, and may break dirty-state tracking or other 
logic relying on the hash being updated whenever a script is run. Keep the 
ternary if desired, but preserve the `saveInputAreaContentHash()` call at the 
start of `runScript`.
   ```suggestion
       void runScript(EventObject evt = null, SourceType st = new 
GroovySourceType()) {
           saveInputAreaContentHash()
   ```



##########
subprojects/groovy-xml/src/spec/test/StaxBuilderTest.groovy:
##########
@@ -32,7 +32,9 @@ class StaxBuilderTest extends GroovyTestCase {
             elem2('world')
         }
 
-        assert writer.toString() == '<?xml version="1.0" ?><root 
attribute="1"><elem1>hello</elem1><elem2>world</elem2></root>'
+        def pretty= writer.toString()
+            .replaceAll(/<\?xml[^>]*>/, '') // remove XML declaration

Review Comment:
   `writer.toString()` previously included a space after the XML declaration 
(`<?xml ... ?> <root...>`). After `.replaceAll(/<\?xml[^>]*>/, '')`, that 
leading space will remain, so `pretty` is likely `' <root...` and this 
assertion will fail depending on the StAX writer implementation. Consider 
removing the declaration *and* any adjacent whitespace (e.g., include `\s*` in 
the regex or trim the result) before comparing.
   ```suggestion
               .replaceAll(/<\?xml[^>]*>\s*/, '') // remove XML declaration and 
any following whitespace
   ```



##########
subprojects/groovy-grape-maven/src/test/groovy/groovy/grape/maven/GrapeMavenTest.groovy:
##########
@@ -0,0 +1,165 @@
+/*
+ *  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.maven
+
+import groovy.grape.Grape
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
+
+import java.nio.file.Files
+import java.util.jar.JarOutputStream
+
+final class GrapeMavenTest {
+    private static File writeEmptyJar(File jarFile) {
+        jarFile.parentFile.mkdirs()
+        jarFile.withOutputStream { os ->
+            new JarOutputStream(os).close()
+        }
+        jarFile
+    }
+
+    private static void deleteCachedGroup(String groupId) {
+        File cachedGroupDir = new File(GrapeMaven.grapeCacheDir, 
groupId.replace('.' as char, File.separatorChar))
+        if (cachedGroupDir.exists()) {
+            cachedGroupDir.deleteDir()
+        }
+    }
+
+    private static File writePom(File pomFile, String groupId, String 
artifactId, String version, List<Map<String, Object>> deps = []) {
+        pomFile.parentFile.mkdirs()
+        String depsXml = deps.collect { Map<String, Object> dep ->
+            String optionalXml = dep.optional ? '\n      
<optional>true</optional>' : ''
+            """\
+    <dependency>
+      <groupId>${dep.groupId}</groupId>
+      <artifactId>${dep.artifactId}</artifactId>
+      <version>${dep.version}</version>
+      <scope>compile</scope>${optionalXml}
+    </dependency>""".stripIndent()
+        }.join('\n')
+
+        pomFile.text = (
+"""<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>${groupId}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <version>${version}</version>
+  <packaging>jar</packaging>
+  <dependencies>
+${depsXml}
+  </dependencies>
+</project>
+"""
+        )
+        pomFile
+    }
+
+    private static void publishArtifact(File repoDir, String groupId, String 
artifactId, String version, List<Map<String, Object>> deps = []) {
+        String relPath = groupId.replace('.', '/') + 
"/${artifactId}/${version}"
+        File artifactDir = new File(repoDir, relPath)
+        writePom(new File(artifactDir, "${artifactId}-${version}.pom"), 
groupId, artifactId, version, deps)
+        writeEmptyJar(new File(artifactDir, "${artifactId}-${version}.jar"))
+    }
+
+    private static Set<String> jarNames(GroovyClassLoader loader) {
+        loader.URLs.collect { url -> url.path.split('/')[-1] } as Set
+    }
+
+    @BeforeAll
+    static void setUpClass() {
+        System.setProperty('groovy.grape.impl', 
'groovy.grape.maven.GrapeMaven')
+        Grape.reset()
+    }
+
+    @AfterAll
+    static void cleanup() {
+        Grape.reset()
+    }

Review Comment:
   This test sets the global `groovy.grape.impl` system property in 
`@BeforeAll` but `@AfterAll` only calls `Grape.reset()` and never 
restores/clears the property. That can leak into subsequent tests and change 
which engine they run against. Capture the previous property value in 
`setUpClass()` and restore it (or clear it) in `cleanup()`.



##########
subprojects/groovy-grape-maven/src/main/groovy/groovy/grape/maven/GrapeMaven.groovy:
##########
@@ -0,0 +1,747 @@
+/*
+ *  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.maven
+
+import groovy.grape.GrapeEngine
+import groovy.grape.GrapeUtil
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import groovy.transform.NamedParam
+import groovy.transform.NamedParams
+import org.codehaus.groovy.reflection.ReflectionUtils
+import org.eclipse.aether.AbstractRepositoryListener
+import org.eclipse.aether.RepositoryEvent
+import org.eclipse.aether.RepositorySystem
+import org.eclipse.aether.RepositorySystemSession
+import org.eclipse.aether.artifact.Artifact
+import org.eclipse.aether.artifact.DefaultArtifact
+import org.eclipse.aether.collection.CollectRequest
+import org.eclipse.aether.graph.Dependency
+import org.eclipse.aether.graph.DependencyFilter
+import org.eclipse.aether.graph.DependencyNode
+import org.eclipse.aether.internal.impl.scope.OptionalDependencySelector
+import org.eclipse.aether.internal.impl.scope.ScopeDependencySelector
+import org.eclipse.aether.repository.LocalRepository
+import org.eclipse.aether.repository.RemoteRepository
+import org.eclipse.aether.repository.RepositoryPolicy
+import org.eclipse.aether.resolution.ArtifactRequest
+import org.eclipse.aether.resolution.ArtifactResult
+import org.eclipse.aether.resolution.DependencyRequest
+import org.eclipse.aether.resolution.DependencyResolutionException
+import org.eclipse.aether.supplier.RepositorySystemSupplier
+import org.eclipse.aether.transfer.AbstractTransferListener
+import org.eclipse.aether.transfer.TransferEvent
+import org.eclipse.aether.util.graph.selector.AndDependencySelector
+import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy
+
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * Implementation supporting {@code @Grape} and {@code @Grab} annotations 
based on Maven.
+ */
+@AutoFinal
+@CompileStatic
+class GrapeMaven implements GrapeEngine {
+    private static final List<String> DEFAULT_CONF = 
Collections.singletonList('default')
+    private static final Map<String, Set<String>> MUTUALLY_EXCLUSIVE_KEYS = 
processGrabArgs([
+            ['group', 'groupId', 'organisation', 'organization', 'org'],
+            ['module', 'artifactId', 'artifact'],
+            ['version', 'revision', 'rev'],
+            ['conf', 'scope', 'configuration'],
+    ])
+    private static final boolean DEBUG_GRAB = 
Boolean.getBoolean('groovy.grape.debug')
+
+    @CompileDynamic // maps a->[b,c], b->[a,c] and c->[a,b] given [a,b,c]
+    private static Map<String, Set<String>> processGrabArgs(List<List<String>> 
grabArgs) {
+        grabArgs.inject([:]) { Map m, List g -> g.each { a -> m[a] = (g - a) 
as Set }; m }
+    }
+
+    // weak hash map so we don't leak loaders directly
+    final Map<ClassLoader, Set<MavenGrabRecord>> loadedDeps = [] as WeakHashMap
+    /** Stores the MavenGrabRecord(s) for all dependencies in each grab() 
call. */
+    final Set<MavenGrabRecord> grabRecordsForCurrDependencies = [] as 
LinkedHashSet
+    boolean enableGrapes = true
+    final List<Closure> progressListeners = new CopyOnWriteArrayList<>()
+    final List<RemoteRepository> repos = [
+        new RemoteRepository.Builder("central", "default", 
"https://repo.maven.apache.org/maven2/";).build()
+    ]
+
+    @CompileDynamic
+    void addProgressListener(Closure listener) {
+        if (listener != null) {
+            progressListeners.add(listener)
+        }
+    }
+
+    void removeProgressListener(Closure listener) {
+        progressListeners.remove(listener)
+    }
+
+    private boolean hasProgressListeners() {
+        !progressListeners.isEmpty()
+    }
+
+    @CompileDynamic
+    private void fireProgressEvent(String type, String name) {
+        if (!name) return
+        Map<String, String> event = [type: type, name: name]
+        progressListeners.each { Closure listener ->
+            listener.call(event)
+        }
+    }
+
+    private AbstractRepositoryListener createRepositoryListener() {
+        new AbstractRepositoryListener() {
+            @Override
+            void artifactResolving(RepositoryEvent event) {
+                Artifact artifact = event?.artifact
+                if (artifact != null && artifact.extension != 'pom') {
+                    fireProgressEvent('resolving', artifact.toString())
+                }
+            }
+        }
+    }
+
+    private AbstractTransferListener createTransferListener() {
+        new AbstractTransferListener() {
+            @Override
+            void transferInitiated(TransferEvent event) {
+                String resourceName = event?.resource?.resourceName
+                String displayName = displayDownloadName(resourceName)
+                if (displayName != null) {
+                    fireProgressEvent('downloading', displayName)
+                }
+            }
+        }
+    }
+
+    private static String displayDownloadName(String resourceName) {
+        if (!resourceName) return null
+        String fileName = resourceName.tokenize('/').last()
+        if (!fileName
+            || fileName.startsWith('maven-metadata')
+            || fileName.endsWith('.pom')
+            || fileName.endsWith('.sha1')
+            || fileName.endsWith('.md5')
+            || fileName.endsWith('.asc')) {
+            return null
+        }
+        fileName
+    }
+
+    /**
+     * Grab the endorsed module for the current Groovy version.
+     */
+    @Override
+    grab(String endorsedModule) {
+        grab(group: 'groovy.endorsed', module: endorsedModule, version: 
GroovySystem.getVersion())
+    }
+
+    @Override
+    grab(Map args) {
+        args.calleeDepth = args.calleeDepth ?: DEFAULT_CALLEE_DEPTH + 1
+        grab(args, args)
+    }
+
+    @Override
+    grab(Map args, Map... dependencies) {
+        ClassLoader loader = null
+        grabRecordsForCurrDependencies.clear()
+
+        try {
+            // identify the target classloader early, so we fail before 
checking repositories
+            loader = chooseClassLoader(
+                    refObject: args.remove('refObject'),
+                    classLoader: args.remove('classLoader'),
+                    calleeDepth: args.calleeDepth ?: DEFAULT_CALLEE_DEPTH,
+            )
+
+            // check for non-fail null
+            // if we were in fail mode we would have already thrown an 
exception
+            if (!loader) return
+
+            URI[] uris = resolve(loader, args, dependencies)
+            for (URI uri : uris) {
+                GrapeUtil.addURL(loader, uri)
+            }
+            boolean runnerServicesFound = false
+            for (URI uri : uris) {
+                // TODO: check artifact type, jar vs library, etc.
+                File file = new File(uri)
+                GrapeUtil.processCategoryMethods(loader, file)
+                Collection<String> services = 
GrapeUtil.processMetaInfServices(loader, file)
+                if (!runnerServicesFound) {
+                    runnerServicesFound = GrapeUtil.checkForRunner(services)
+                }
+            }
+            if (runnerServicesFound) {
+                GrapeUtil.registryLoad(loader)
+            }
+        } catch (Exception e) {
+            // clean-up the state first
+            Set<MavenGrabRecord> grabRecordsForCurrLoader = 
getLoadedDepsForLoader(loader)
+            grabRecordsForCurrLoader.removeAll(grabRecordsForCurrDependencies)
+            grabRecordsForCurrDependencies.clear()
+
+            if (args.noExceptions) {
+                return e
+            }
+            throw asRuntimeGrabError(e)
+        }
+        null
+    }
+
+
+    @Override
+    @CompileDynamic
+    Map<String, Map<String, List<String>>> enumerateGrapes() {
+        Map<String, Map<String, List<String>>> bunches = [:]
+        File cacheRoot = grapeCacheDir.canonicalFile
+
+        cacheRoot.eachFileRecurse { File f ->
+            if (!f.isFile()) return
+            String name = f.name
+            if (name.endsWith('.sha1') || name.endsWith('.md5') || 
name.endsWith('.asc')) return
+
+            File versionDir = f.parentFile
+            File moduleDir = versionDir?.parentFile
+            File groupDir = moduleDir?.parentFile
+            if (!versionDir || !moduleDir || !groupDir) return
+
+            String version = versionDir.name
+            String module = moduleDir.name
+            String expectedPrefix = module + '-' + version
+            if (!name.startsWith(expectedPrefix)) return
+
+            String groupPath = 
cacheRoot.toPath().relativize(groupDir.toPath()).toString()
+            if (!groupPath) return
+            String groupKey = groupPath.replace(File.separatorChar, '.' as 
char)
+
+            Map<String, List<String>> modules = 
bunches.computeIfAbsent(groupKey) { [:] }
+            List<String> versions = modules.computeIfAbsent(module) { [] }
+            if (!versions.contains(version)) {
+                versions << version
+            }
+        }
+
+        bunches.values()*.values()*.each { List<String> versions -> 
versions.sort() }
+        bunches
+    }
+
+    void uninstallArtifact(String group, String module, String rev) {
+        String groupPath = group.replace('.' as char, File.separatorChar)
+        def artifactDir = new File(grapeCacheDir, groupPath + File.separator + 
module + File.separator + rev)
+        if (artifactDir.exists()) {
+            artifactDir.deleteDir()
+        }
+    }
+
+    @Override
+    URI[] resolve(Map args, Map... dependencies) {
+        resolve(args, null, dependencies)
+    }
+
+    @Override
+    URI[] resolve(Map args, List depsInfo, Map... dependencies) {
+        // identify the target classloader early, so we fail before checking 
repositories
+        ClassLoader loader = chooseClassLoader(
+            refObject: args.remove('refObject'),
+            classLoader: args.remove('classLoader'),
+            calleeDepth: args.calleeDepth ?: DEFAULT_CALLEE_DEPTH,
+        )
+
+        // check for non-fail null
+        // if we were in fail mode we would have already thrown an exception
+        if (!loader) {
+            return new URI[0]
+        }
+
+        resolve(loader, args, depsInfo, dependencies)
+    }
+
+    private Set<MavenGrabRecord> getLoadedDepsForLoader(ClassLoader loader) {
+        // use a LinkedHashSet to preserve the initial insertion order
+        loadedDeps.computeIfAbsent(loader, k -> [] as LinkedHashSet)
+    }
+
+    URI[] resolve(ClassLoader loader, Map args, Map... dependencies) {
+        resolve(loader, args, null, dependencies)
+    }
+
+    URI[] resolve(ClassLoader loader, Map args, List depsInfo, Map... 
dependencies) {
+        if (!enableGrapes) {
+            return new URI[0]
+        }
+
+        boolean populateDepsInfo = (depsInfo != null)
+        Set<MavenGrabRecord> localDeps = getLoadedDepsForLoader(loader)
+        List<MavenGrabRecord> grabRecords = []
+        for (Map dep : dependencies) {
+             MavenGrabRecord mgr = createGrabRecord(dep)
+             grabRecordsForCurrDependencies.add(mgr)
+             localDeps.add(mgr)
+             grabRecords.add(mgr)
+        }
+
+        try {
+            URI[] results = getDependencies(args, populateDepsInfo ? depsInfo 
: null, grabRecords as MavenGrabRecord[])
+            return results
+        } catch (Exception e) {
+            localDeps.removeAll(grabRecordsForCurrDependencies)
+            grabRecordsForCurrDependencies.clear()
+            throw asRuntimeGrabError(e)
+        }
+    }
+
+    URI[] getDependencies(Map args, List depsInfo, MavenGrabRecord... 
grabRecords) {
+        try (RepositorySystem system = new RepositorySystemSupplier().get()) {
+            def localRepo = new LocalRepository(grapeCacheDir.toPath())
+            String checksumPolicy = args.disableChecksums ?

Review Comment:
   `try (RepositorySystem system = new RepositorySystemSupplier().get())` uses 
try-with-resources on `org.eclipse.aether.RepositorySystem`, which (in Maven 
Resolver/Aether) is not an `AutoCloseable` resource. Under `@CompileStatic` 
this is very likely to fail compilation. Use a plain local variable for the 
repository system (and keep the session as the closable resource), or otherwise 
manage the actual closeable object returned by the supplier if one exists.





> Support Maven Resolver based version of Grapes
> ----------------------------------------------
>
>                 Key: GROOVY-11871
>                 URL: https://issues.apache.org/jira/browse/GROOVY-11871
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Priority: Major
>




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to