开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜