I am trying to use call operator () overloading, but it does not work on class fields. What's wrong?
class Foo {
void call(int x){
println("x="+x)
}
}
class MyCallable {
Foo foo = new Foo()
}
Foo foo = new Foo()
foo(5) //works
MyCallable mc = new MyCallable()
mc.foo(2) //not works
But program terminated with exception:
Exception in thread "main" groovy.lang.MissingMethodException: No
signature of method: mpctests.MyCallable.foo() is applicable for
argument types: (java.lang.Integer) values: [2]
You get MissingMethodException when you call mc.foo(5), because Groovy's invoke object method mechanism gets triggered. There is one thing worth explaining to get a better understanding about this situation. Your MyCallable class:
class MyCallable {
Foo foo = new Foo()
}
gets compiled to something like this:
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class MyCallable implements GroovyObject {
private Foo foo;
public MyCallable() {
CallSite[] var1 = $getCallSiteArray();
Object var2 = var1[0].callConstructor(Foo.class);
this.foo = (Foo)ScriptBytecodeAdapter.castToType(var2, Foo.class);
MetaClass var3 = this.$getStaticMetaClass();
this.metaClass = var3;
}
public Foo getFoo() {
return this.foo;
}
public void setFoo(Foo var1) {
this.foo = var1;
}
}
Groovy also compiles every field access like mc.foo to a getter method call mc.getFoo(). So when you call mc.foo(5) it is clear for Groovy runtime that you expect to call a foo(5) method on mc object. And this method does not exist and MissingMethodException gets thrown.
However, it works if you create object def foo = new Foo() and then you call foo(5), because foo is an object and foo(5) is a strict instruction to invoke call(5) method on foo object (foo(5) is a shorthand version of foo.call(5)). The same situation would take place if you call mc() - Groovy would try to invoke mc.call() method. But when you say mc.foo(5) it's clear that you are trying to invoke foo(5) method.
If you want to use call operator on mc.foo field there are two options:
1. Use direct field access operator #
mc.#foo(5)
In this case you refer directly to foo field and you can use shorthand call operator.
2. Use with {} method
mc.with {
foo(5)
}
In this case it is also a straightforward for Groovy runtime that you are accessing foo field and you can use call operator on it.
Alternatives
Using getter method:
mc.getFoo()(5)
Using method call() directly:
mc.foo.call(5) // equivalent of mc.getFoo().call(5)
Related
I'm running the following code (in Jenkins script console):
def sayHello() {
println "Hello"
}
class MyClass {
MyClass() {
sayHello()
}
}
def a = new MyClass()
In all the good faith, I expect the constructor code to call the function that will print Hello.
Instead I get
groovy.lang.MissingMethodException: No signature of method: MyClass.sayHello() is applicable for argument types: () values: []
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58)
What is going on here? I can't call the function from inside the class?
You get the error because you cannot access methods of one class from another class, unless you access an instance of that class.
In your case, the code is embedded automatically into a run() method inside the Main class derived from groovy.lang.Script. The class MyClass is an inner class of the Main class. See here Scripts versus classes.
Solution: to access the method sayHello() of the Main class, you must pass an instance of it, using this keyword:
def sayHello() {
println "Hello"
}
class MyClass {
MyClass(Script host) {
host.sayHello()
}
}
def a = new MyClass(this)
Not sure what and why you are trying to do, but the simplest option to call a "function" from a constructor inside a Script is to put it in another class:
class A {
static sayHello() {
println "Hello"
}
}
class MyClass {
MyClass() {
A.sayHello()
}
}
def a = new MyClass()
I am experimenting with some dynamic variable creation with GroovyShell and encountered an issue. First, the working code:
static def defVar(def glob) {
glob.setVariable('test', new Test())
}
class MyBinding extends Binding {
}
class Test {
def call() {
println("--- hello ---")
}
}
Binding glob = new MyBinding()
GroovyShell shell = new GroovyShell(glob)
defVar(glob)
shell.parse('test()').run()
This gives me the expected output:
--- hello ---
However, I want to call setVariable() dynamically when getVariable() is called, something like this:
static def defVar(def glob) {
glob.setVariable('test', new Test())
}
class MyBinding extends Binding {
def getVariable(String name) {
if (! hasVariable('test')) {
BindingTest.defVar(this)
}
return super.getVariable(name)
}
}
class Test {
def call() {
println("--- hello ---")
}
}
Binding glob = new MyBinding()
GroovyShell shell = new GroovyShell(glob)
//defVar(glob)
shell.parse('test()').run()
But this fails with the below error:
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
Possible solutions: getAt(java.lang.String), use([Ljava.lang.Object;), use(java.lang.Class, groovy.lang.Closure), use(java.util.List, groovy.lang.Closure), wait(), wait(long)
groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
Possible solutions: getAt(java.lang.String), use([Ljava.lang.Object;), use(java.lang.Class, groovy.lang.Closure), use(java.util.List, groovy.lang.Closure), wait(), wait(long)
at Script1.run(Script1.groovy:1)
at Script1$run.call(Unknown Source)
at BindingTest.run(BindingTest.groovy:23)
When I added tracing code like this:
class MyBinding extends Binding {
def getVariable(String name) {
if (! hasVariable(name)) {
BindingTest.defVar(this)
}
println("getVariable: ${name}: ${super.getVariable(name).getClass().getName()}")
return super.getVariable(name)
}
void setVariable (String name, def val) {
println("setVariable: ${name}: ${val.getClass().getName()}")
super.setVariable(name, val)
}
def getProperty(String name) {
println("getProperty: ${name}: ${super.getProperty(name)}")
return super.getProperty(name)
}
void setProperty (String name, def val) {
println("setProperty: ${name}: ${val.getClass().getName()}")
super.setProperty(name, val)
}
}
In the working case, I get the below output:
setVariable: test: Test
--- hello ---
In the non-working case, I get this output:
setVariable: test: Test
getVariable: test: Test
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.test() is applicable for argument types: () values: []
...
Two questions:
In the working scenario, why is there no getVariable?
In the non-working scenario, why is the Test object returned by getVariable getting rejected?
Note that this issue is specific to callable values. If I set a simple value such as a string, to test, then both approaches work fine. E.g., with this sort of a change:
...
static def defVar(def glob) {
glob.setVariable('test', '--- hello ---')
}
...
shell.parse('println(test)').run()
I get the below identical output with both approaches:
setVariable: test: java.lang.String
getVariable: test: java.lang.String
setVariable: test: java.lang.String
--- hello ---
Though, I am not sure why setVariable gets called twice. I couldn't find any documentation explaining these puzzling behaviors. Could anybody here shed some light on them?
Please note, all the code snippets have been simplified for the ease of demonstrating the problem rather than for their intended purpose
When you use a property as a callable fallback, the Binding.getVariable() method does not get involved. This behavior is controlled by the metaclass, and in your case, it all drives to the execution of the MetaClassImpl.invokePropertyOrMissing() method. This method determines if
test()
should invoke test.call() (in case of an existing property), or should it fallback to the missingMethod() method. Here is what this method implementation looks like:
private Object invokePropertyOrMissing(Object object, String methodName, Object[] originalArguments, boolean fromInsideClass, boolean isCallToSuper) {
// if no method was found, try to find a closure defined as a field of the class and run it
Object value = null;
final MetaProperty metaProperty = this.getMetaProperty(methodName, false);
if (metaProperty != null)
value = metaProperty.getProperty(object);
else {
if (object instanceof Map)
value = ((Map)object).get(methodName);
}
if (value instanceof Closure) { // This test ensures that value != this If you ever change this ensure that value != this
Closure closure = (Closure) value;
MetaClass delegateMetaClass = closure.getMetaClass();
return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass);
}
if (object instanceof Script) {
Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
if (bindingVar != null) {
MetaClass bindingVarMC = ((MetaClassRegistryImpl) registry).getMetaClass(bindingVar);
return bindingVarMC.invokeMethod(bindingVar, CLOSURE_CALL_METHOD, originalArguments);
}
}
return invokeMissingMethod(object, methodName, originalArguments, null, isCallToSuper);
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_5_X/src/main/groovy/groovy/lang/MetaClassImpl.java#L1262-L1287
Now, pay attention to the branch if (object instanceof Script) and how the binding variable gets retrieved. It tries to retrieve test variable from binding object using:
Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
Your code would work if it was:
Object bindingVar = ((Script) object).getBinding().getVariable(methodName);
instead. But it's not.
You can make your second case working if you override getVariables() method instead of getVariable(String name), for instance:
class MyBinding extends Binding {
#Override
Map getVariables() {
return super.getVariables() + [
test: new Test()
]
}
}
Of course, your final implementation might be much more sophisticated. (E.g. you could get super.getVariables() map first, check which variables are missing and add a default variable only if the initial map was missing given variable.) But this is up to you.
Alternatively, consider using methodMissing instead of the binding variable fallback. It could make your code much easier to read and reason about.
The following Groovy trait implements the GroovyInterceptable interface to allow execution of code before and after method calls.
trait Bar implements GroovyInterceptable {
def bar = "bar"
#Override
invokeMethod(String name, Object args) {
System.out.println(bar)
metaClass.getMetaMethod(name, args).invoke(this, args)
}
def doSomething() {
}
}
The following class implements the trait Bar.
class Foo implements Bar {
}
Have a look at the following code.
def foo = new Foo()
foo.doSomething()
The call to doSomething() is being intercepted by invokeMethod(). A java.lang.StackOverflowError occurs because accessing the property bar inside invokeMethod() implicitly makes a call to the getter of bar which in turn is intercepted by invokeMethod() just trying to access bar again.
How can I access a class property inside invokeMethod without calling this property's getter or setter?
In combination with the trait using this.#bar to access the property does not work.
The code metaClass.getMetaMethod(name, args).invoke(this, args) to invoke the intercepted method could be incorrect although it works when using the trait logic directly inside a class.
Edit for Solution:
The accepted answer contributed by user Opal works like a charm in a script environment. Since the trait is part of a larger project and defined in its own file I made it work like this:
package com.example.project
trait Bar implements GroovyInterceptable {
def bar = "bar"
#Override
invokeMethod(String name, Object args) {
System.out.println(this.com_example_project_Bar__bar)
metaClass.getMetaMethod(name, args).invoke(this, args)
}
def doSomething() {
}
}
It turns out that there's no need to use # for direct field access:
trait Bar implements GroovyInterceptable {
def bar = "bar"
#Override
invokeMethod(String name, Object args) {
System.out.println(Bar__bar)
metaClass.getMetaMethod(name, args).invoke(this, args)
}
def doSomething() {
}
}
class Foo implements Bar {
}
def foo = new Foo()
foo.doSomething()
I am using groovy as an extension language in my application. The constructor of the class which a script extend accepts variable arguments. When I try to instantiate the groovy class, I get an java.lang.ArrayIndexOutOfBoundsException from the super() call in the constructor. The issue can easily be reproduced in a standalone groovy script:
// problem.groovy
class A {
A(float ... more) {}
}
class B extends A {
B() {
super();
}
}
new B();
when run, this produces:
$ groovy problem.groovy
Caught: java.lang.ArrayIndexOutOfBoundsException: 0
java.lang.ArrayIndexOutOfBoundsException: 0
at B.<init>(problem.groovy:7)
at problem.run(problem.groovy:11)
line 7 is the super() call in class B.
Is this a bug in the language itself? I couldn't find any other mention of it online. I'm new to Groovy and I may well not be understanding some subtlety of the language. At the very least, it seems like this should throw a compiler error when loading the script.
You can use #InheritConstructors AST to avoid this boilerplate code.
class A {
A(float ... more) {}
}
#groovy.transform.InheritConstructors
class B extends A {}
new B()
Moreover, in your example wouldn't you provide a default constructor in A (since it is being overloaded) and then use super() in B's constructor. Or initialize the overloaded constructors args to null.
class A {
A(){println 'default'}
//Or use A(float... more = null) {println 'varargs'}
//instead of default constructor
A(float... more) {println 'varargs'}
}
class B extends A {
B(){
super()
}
}
new B()
//Uses default constructor A() if overloaded
//constructor is not initialized to null
It seems to be calling a nonexistent constructor with super() - call it even with a null or put a no arg constructor in A and it works.
It appears that the variable arg constructor on the superclass isn't matching. Fun.
I have this very simple example as follows:
public class Foo {
public int foo() {
return foo1();
}
public int foo1() {
return 1;
}
}
public class FooTest {
#Test public void testFoo() {
Foo f = mock(Foo.class);
doReturn(1000).when(f).foo1();
assertThat(f.foo(), equalTo(1000));
}
}
I'm getting a java.lang.AssertionError: Expected: is(1000) got: <0>, and I don't understand why. Obviously I must be doing something wrong, as this is very basic mocking, so I can't imagine this doesn't work.
Note that you recorded the expectation on foo1(), but then called foo() later... So of course, it returns 0, not 1000.
What you need is a spy instead of a mock.
http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#13
Try this
Foo foo = new Foo();
Foo f = spy(foo);
By the time CGLIB has gotten hold of your Foo class it doesn't care about what clever implementation you have underlying. Your return statement is not considered. Mockito sees (1) a mocked class called Foo, (2) an instruction when foo1() is invoked and (3) no instructions when foo() is invoked.
On a stylistic note isn't when preferred over doReturn now? So;
when(f.foo1()).thenReturn(1000);