I have this at the beginning of a class:
@Grab(group = \'org.ccil.cowan.tagsoup\', module = \'tagsoup\', version = \'1.2\')
class MyClass{...
I assume you've tried adding
@GrabConfig(systemClassLoader=true)
like so:
@Grapes([
@Grab(group = 'org.ccil.cowan.tagsoup', module = 'tagsoup', version = '1.2'),
@GrabConfig( systemClassLoader=true )
])
class MyClass{...
Looking at the source code, this exception is thrown whenever the supplied ClassLoader's name (or it's superclasses) is not groovy.lang.GroovyClassLoader
or org.codehaus.groovy.tools.RootLoader
. i.e. The target classloader must be an instance of the aforementioned classes (a bit restrictive IMHO).
Currently I don't know how to configure a specific classloader using @Grape
/@Grab
/@GrabConfig
annotations. The closest would be to use @GrabConfig(systemClassLoader=true)
, and ensure the System classloader is an instance of one of the above ClassLoader classes.
If anyone does know, please let me know (and I'll update this answer).
The following code will programmatically download your Grapes, and load them into the supplied GroovyClassLoader (admittedly, not quite what you want).
def loadGrapes(){
ClassLoader classLoader = new groovy.lang.GroovyClassLoader()
Map[] grapez = [[group : 'org.ccil.cowan.tagsoup', module : 'tagsoup', version : '1.2']]
Grape.grab(classLoader: classLoader, grapez)
println "Class: " + classLoader.loadClass('org.ccil.cowan.tagsoup.jaxp.SAXParserImpl')
}
You can use Groovy's metaprogramming to override the methods responsible for determining if the class loader is an instance of groovy.lang.GroovyClassLoader
or org.codehaus.groovy.tools.RootLoader
.
Because of this Groovy bug, you cannot override the private methods using metaprogramming, otherwise you could go ahead and change the isValidTargetClassLoaderClass
method by doing this:
GrapeIvy.metaClass.isValidTargetClassLoaderClass = { Class loaderClass ->
return (loaderClass != null)
}
However, isValidTargetClassLoaderClass
is called by isValidTargetClassLoader
(another private method), which is called by chooseClassLoader
, which is a public method, which can be overridden using metaprogramming:
GrapeIvy.metaClass.chooseClassLoader = { Map args ->
def loader = args.classLoader
if (loader?.class == null) {
loader = (args.refObject?.class
?: ReflectionUtils.getCallingClass(args.calleeDepth?:1)
)?.classLoader
while (loader && loader?.class == null) {
loader = loader.parent
}
if (loader?.class == null) {
throw new RuntimeException("No suitable ClassLoader found for grab")
}
}
return loader
}
All I did was replace any calls to !isValidTargetClassLoader
with loader?.class == null
.
Now, I am using Spock, so I put this code in my setupSpec
method in my test class. However if you are using JUnit, I would imagine it would want to go in the method annotated with @BeforeClass
.
Here is an example of it working with Spock (notice IntelliJ showing it about to return a class loader that would normally throw an exception:
Using @Grab
makes code untestable, at least as of 01/26/2011.
There's one more solution for testing a class with @Grab
annotation:
@Grab
annotation to this class. Then make this class a simple wrapper, which just passes all the messages to the original class.@Grab
, use the wrapper.If you are not using systemClassLoader=true then it seems your IDE is not rrunning the code with a groovy compiler, you can check that with a simple groovy class that outputs the class name of its classloader. I would guess it tries to compile the groovy classes and run them with a non-groovy classloader.
See also this answer to General error during conversion: No suitable ClassLoader found for grab. Also this blog post explains more about running pre-compiled groovy classes with the stock classloader.