Groovy application classes and Java Unit tests
I've got an application being build by Maven that is a mixed Groovy and Java project.
Using the GMaven plugin(v1.3), I can easily run Groovy tests against Java and Groovy classes. And during builds of the application, my java classes get linked against the augmented stub files that declare methods from GroovyObject.
However, if I write a test in Java against the application Groovy code and try to call methods from GroovyObject, I get compile time failures.
Is there any workaround for this? Is there any configuration parameters to GMaven that will make this possible?
thanks.
here's the build.plugins stuff from my pom:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<includes>
<include>开发者_开发问答;target/generated-sources/groovy-stubs/main</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<goals>
<goal>generateStubs</goal>
<goal>generateTestStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<!-- providerSelection probably defaults to 1.7 now -->
<providerSelection>1.7</providerSelection>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Here's the java test class:
public class JavaGroovyTest extends TestCase {
@Test
public void testGroovyClasses(){
Model m = new Model(); //Model is an application class written in Groovy
assertNotNull(m);
assertEquals(4,m.getMetaClass().getProperties().size());
}
}
And here's the compiler output:
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Compilation failure
/Users/mkohout/Documents/trunk/src/test/java/JavaGroovyTest.java:[17,24] cannot find symbol
symbol : method getMetaClass()
location: class com.q.Model
Declare the groovy object as a GroovyObject. Example:
import groovy.lang.GroovyObject
public class JavaGroovyTest extends TestCase {
@Test
public void testGroovyClasses(){
GroovyObject m = new Model(); //Model is an application class written in Groovy
assertNotNull(m);
assertEquals(4,m.getMetaClass().getProperties().size());
}
}
Edit: a longer explanation
The groovy compiler adds a getMetaClass
method to classes, but it marks it as synthetic
. This is an internal JVM flag for methods and fields that are generated as "implementation details" and shouldn't be visible to code. You can verify this with javap:
$ javap -verbose Model | grep -A18 getMetaClass\(\)
public groovy.lang.MetaClass getMetaClass();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: getfield #42; //Field metaClass:Lgroovy/lang/MetaClass;
4: dup
5: ifnull 9
8: areturn
9: pop
10: aload_0
11: dup
12: invokevirtual #28; //Method $getStaticMetaClass:()Lgroovy/lang/MetaClass;
15: putfield #42; //Field metaClass:Lgroovy/lang/MetaClass;
18: aload_0
19: getfield #42; //Field metaClass:Lgroovy/lang/MetaClass;
22: areturn
23: nop
Synthetic: true
You can get around this though, by casting it to the groovy.lang.GroovyObject
interface, which declares the getMetaClass
method (this time not synthetic).
This may not be a great solution, but that said, poking around the groovy metaClass in java is probably ill-advised in the first place. If it's not possible to just use groovy in your tests, I'd look at exposing the metaClass information you need from the groovy classes with normal java accessible methods.
Short answer: You cannot access Groovy "meta methods" from Java.
Long answer:
DefaultGroovyMethods.getMetaClass(..)
is not a method that can be statically compiled into Java bytecode.
[Correction:
In the meantime, Don has posted an answer, suggesting to cast to GroovyObject
. This is correct, and that way you should be able to call Groovy meta methods like that:
List<Book> books = (List<Book>)
((GroovyObject) Book).invokeMethod(
"findAllByAuthorAndTitle", new String[] {"author", "title"})
(or similar). - Nevertheless, that's impractical for everyday programming. ]
Have a look at the DefaultGroovyMethods
ApiDocs. The first parameter of each method is the runtime type of the object. The rest of the corresponding method forms the method signature to be used in Groovy code. You'll be already familiar with many of those.
All of these "runtime meta methods" are not statically compiled into the GroovyObject
((almost) any Groovy object derives from that type), but, when called, dynamically dispatched at runtime - typically, by using the GroovyObject.invokeMethod(String, Object)
method.
So, your Java code calls a method that simply does not exist at compile time. - But, how could Groovy code refer to that method if it's not compiled to Java bytecode? - Groovy (Java) bytecode does not reference methods (constructors, properties, etc.), directly, but instead builds structures that are called at runtime for dynamic dispatch.
Your test class, for example, written in Groovy would compile to this (shortened for clarity):
public class JavaGroovyTest extends TestCase
implements GroovyObject
{
public JavaGroovyTest()
{
JavaGroovyTest this;
CallSite[] arrayOfCallSite = $getCallSiteArray();
MetaClass tmp12_9 = $getStaticMetaClass(); this.metaClass = ((MetaClass)ScriptBytecodeAdapter.castToType(tmp12_9, $get$$class$groovy$lang$MetaClass())); tmp12_9;
}
public void testGroovyClasses() { CallSite[] arrayOfCallSite = $getCallSiteArray(); Model m = arrayOfCallSite[0].callConstructor($get$$class$Model());
arrayOfCallSite[1].callStatic($get$$class$JavaGroovyTest(), m);
arrayOfCallSite[2].callStatic($get$$class$JavaGroovyTest(), $const$0, arrayOfCallSite[3].call(arrayOfCallSite[4].call(arrayOfCallSite[5].call(m)))); return;
}
}
When this code is executed, the Groovy runtime will perform hundreds, thousand and sometimes even billions of lookups ;-) in order to, finally, not call the method, directly, but by a reflection-like invokeMethod(..)
invocation.
Most portions of the Groovy programming language (including builders and other libraries) heavily rely on this concept of meta programming, which can be implemented at compile-time or runtime.
Unfortunately, Groovy prefers the latter, although all dynamically added features are not compiled to Java bytecode, and can not be accessed by Java code, directly.
精彩评论