0

I try to dynamically change and add methods of a class defined in a groovy script from another groovy script but cannot figure out why it works if I use the classname directly in .metaClass. but not if I load the class using the GroovyClassLoader(which I need to do!).

In one file 'MyTest.groovy' I have

class MyTest {

   public void setUp() {
      println "SET UP"
   }
   public void tearDown() {
      println "TEAR DOWN"
   }
   public void testA() {
      println "testA"
   }
}

and another file 'suite.groovy' contains

#!/usr/bin/env groovy

public class MyTestSuite {
   // For debug
   public static printClassLoader(Class cls) {
      ClassLoader loader = cls.classLoader
      while (loader) {
         println loader.class
         println loader.URLs.join("\n")
         println "\n\n"
         loader = loader.parent
      }
   }

   public static void suite()  {
      // First method to define my class (change/addition of methods works)
      //Class testClass = MyTest

      // Second way to define my class (change/addition of methods doesn't work)
      ClassLoader parent = MyTestSuite.class.getClassLoader();
      GroovyClassLoader gcl = new GroovyClassLoader(parent);
      Class testClass = gcl.parseClass(new File("MyTest.groovy"));

      printClassLoader(testClass)

      testClass.metaClass.setUp = {-> println "I'm your new setUp()" ;}
      testClass.metaClass.newTest = {-> println "I'm your new newTest()" ; }
   }
}

MyTestSuite.suite()
MyTest aTest = new MyTest()
aTest.setUp()
aTest.newTest()

With the first method I get the expected result:

I'm your new setUp()
I'm your new newTest()

But with the second one I get

SET UP
Caught: groovy.lang.MissingMethodException: No signature of method: MyTest.newTest() is applicable for argument types: () values: []
Possible solutions: testA(), getAt(java.lang.String), wait(), inspect(), every()
    at suite.run(suite.groovy:34)

Interestingly modifying an existing method doesn't work but there is no exception but trying to add a new one throws one. I reckon it has to do something with the class loaders used but couldn't figure what exactly and what to change! For the first version here are the class loaders called:

class groovy.lang.GroovyClassLoader$InnerLoader
class groovy.lang.GroovyClassLoader
class org.codehaus.groovy.tools.RootLoader
class sun.misc.Launcher$AppClassLoader
class sun.misc.Launcher$ExtClassLoader

For the second version:

class groovy.lang.GroovyClassLoader$InnerLoader
class groovy.lang.GroovyClassLoader
class groovy.lang.GroovyClassLoader$InnerLoader
class groovy.lang.GroovyClassLoader
class org.codehaus.groovy.tools.RootLoader
class sun.misc.Launcher$AppClassLoader
class sun.misc.Launcher$ExtClassLoader

Any idea welcome!
Franck

Franck Valentin
  • 1,115
  • 8
  • 14

1 Answers1

0

The new GroovyClassLoader is not the Groovy classloader of the current thread.
It will be disposed after the suite() method finishes - along with its own Class and MetaClass associations.

However, this works:

Class testClass = gcl.parseClass(new File("MyTest.groovy"));
testClass = Class.forName(testClass.getName())
robbbert
  • 2,183
  • 15
  • 15
  • Hi Robbert, thanks for the insight. I'm not sure the new Groovy classloader will be disposed as it's got a reference to the parent. Anyway your solution doesn't work (Caught: java.lang.ClassNotFoundException: MyTest). I tried to figure out how the different class loaders are chained but unfortunately the groovy documentation is unclear and incomplete concerning this point: http://groovy.codehaus.org/Groovy+Internals. So I guess I'll try to use the first option in my code. – Franck Valentin Nov 29 '10 at 09:47
  • An object will be disposed when no *other* object has a reference to it (not the other way round). - As for the error, try using a fully-qualified path for a start. - I'd tested the code: It works, definitely. – robbbert Nov 29 '10 at 11:25