[groovy-user] MockFor of inner classes has misc. problems

99 views
Skip to first unread message

Aschemann Gerd

unread,
Aug 20, 2014, 7:12:51 AM8/20/14
to us...@groovy.codehaus.org
Hi,

check out the following code:

import groovy.mock.interceptor.MockFor

interface Inner {}
class Tst {
  class InnerImpl implements Inner {
// Variant 1: Having multiple constructors prevents the "as" operator to work properly
    InnerImpl () {
      println "Inner: Real Inner Constructor called"
    }

    InnerImpl (boolean second) {
      println "Inner: Second Real Inner Constructor called"
    }
  }

  Tst () {
      println "Tst: Constructor called"
  }

  Tst (boolean second) {
      println "Tst: Second Constructor called"
  }

  void tst () {
    println "Constructing Inner: "
    Inner inner = new InnerImpl(true)
  }
}

Tst tst = new Tst()
tst.tst()

// This works: Coercing the array to Tst - though Tst has two constructors
def dummyTst = [:] as Tst
println "Successfully created the dummy Tst: $dummyTst"

// Now the problem start!
// Variant 1
def dummyInner = [toString:{"DummyInner1"}] as Tst.InnerImpl
// Variant 2 (also comment out the InnerImpl default constructor)
// def dummyInner = [toString:{"DummyInner2"}] as Inner
// Variant 3
// def dummyInner = new Tst.InnerImpl()
// Variant 4: Works! But it is very strange to have the additional argument
// def dummyInner = new Tst.InnerImpl(tst)
println "DummyInner is ${dummyInner}"

MockFor mockInner = new MockFor (Tst.InnerImpl, true)
mockInner.demand.with {
  InnerImpl {Tst ignore, boolean second->
    println "Mock Constructor called"
    println "Returning $dummyInner"
    dummyInner
  }
}

mockInner.use {
  Tst tstMocked = new Tst()
  tstMocked.tst()
}

Only the last version (Variant 4) works, though it is really strange to give the constructor of the inner class a first argument of the outer class to make Groovy properly dispatch to it.

The other three variants have the following errors:

V1:
Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Error casting map to Tst$InnerImpl, Reason: null
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Error casting map to Tst$InnerImpl, Reason: null
        at mockfor2.run(mockfor2.groovy:39)

V2:
Caught: java.lang.ClassCastException: com.sun.proxy.$Proxy5 cannot be cast to groovy.lang.GroovyObject
java.lang.ClassCastException: com.sun.proxy.$Proxy5 cannot be cast to groovy.lang.GroovyObject
        at Tst.tst(mockfor2.groovy:26)
        at Tst$tst.call(Unknown Source)
        at mockfor2$_run_closure3.doCall(mockfor2.groovy:59)
        at mockfor2$_run_closure3.doCall(mockfor2.groovy)
        at mockfor2.run(mockfor2.groovy:57)

V3:
Caught: org.codehaus.groovy.runtime.metaclass.MethodSelectionException: Could not find which method <init>() to invoke from this list:
  public Tst$InnerImpl#<init>(Tst)
  public Tst$InnerImpl#<init>(Tst, boolean)
org.codehaus.groovy.runtime.metaclass.MethodSelectionException: Could not find which method <init>() to invoke from this list:
  public Tst$InnerImpl#<init>(Tst)
  public Tst$InnerImpl#<init>(Tst, boolean)
        at mockfor2.run(mockfor2.groovy:43)

I would like Variant 1 to work! Is it a bug in Groovy? Coercing of maps to (first) classes with multiple constructors works obviously well. Why does it make problems with inner classes?

BTW: I only introduced the interface to make use of the as operator but as V2 shows this does not work either.

Thx,
--
Gerd Aschemann --- Veröffentlichen heißt Verändern (Carmen Thomas)
+49/173/3264070 -- ge...@aschemann.net -- http://www.aschemann.net

signature.asc

Dinko Srkoč

unread,
Aug 20, 2014, 8:57:46 AM8/20/14
to us...@groovy.codehaus.org
Inner classes cannot be constructed without their enclosing class
being constructed first. For that, one would need a static nested
class.

Cheers,
Dinko
---------------------------------------------------------------------
To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email


Aschemann Gerd

unread,
Aug 21, 2014, 1:32:28 AM8/21/14
to us...@groovy.codehaus.org
In "reality" my problem code is much more complex ... tried to reduce it to a small sample which shows the critical points

On 20.08.2014, at 14:56, Dinko Srkoč <dinko...@gmail.com> wrote:
> Inner classes cannot be constructed without their enclosing class
> being constructed first. For that, one would need a static nested
> class.

Thanks, Dinko, that at least explains why Variant 4 works, but V3 not: The call to the constructor of the inner class needs a reference to an object of the outer class (since in more advanced cases, the inner class might use attributes/methods of the outer class, or the respective object, to be more precise).

And it might also be a hint why V1 does not work: Does the coerced object also need a reference to the outer class? In that case I would like to have a better error message, not "Reason: null" ... Should I file a bug for groovy?

> On 20 August 2014 13:11, Aschemann Gerd <ge...@aschemann.net> wrote:
>>
>> // Now the problem start!
>> // Variant 1
>> def dummyInner = [toString:{"DummyInner1"}] as Tst.InnerImpl

>> ...

>> The other three variants have the following errors:
>>
>> V1:
>>
>> Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Error
>> casting map to Tst$InnerImpl, Reason: null
>> org.codehaus.groovy.runtime.typehandling.GroovyCastException: Error casting
>> map to Tst$InnerImpl, Reason: null
>> at mockfor2.run(mockfor2.groovy:39)

signature.asc

Dinko Srkoč

unread,
Aug 21, 2014, 3:43:33 AM8/21/14
to us...@groovy.codehaus.org
On 21 August 2014 07:31, Aschemann Gerd <ge...@aschemann.net> wrote:
> In "reality" my problem code is much more complex ... tried to reduce it to a small sample which shows the critical points
>
> On 20.08.2014, at 14:56, Dinko Srkoč <dinko...@gmail.com> wrote:
>> Inner classes cannot be constructed without their enclosing class
>> being constructed first. For that, one would need a static nested
>> class.
>
> Thanks, Dinko, that at least explains why Variant 4 works, but V3 not: The call to the constructor of the inner class needs a reference to an object of the outer class (since in more advanced cases, the inner class might use attributes/methods of the outer class, or the respective object, to be more precise).

Yes, and I believe this is where Groovy differs from Java - it adds a
reference to the enclosing class as a first parameter to its
constructors.

>
> And it might also be a hint why V1 does not work: Does the coerced object also need a reference to the outer class? In that case I would like to have a better error message, not "Reason: null" ... Should I file a bug for groovy?

To coerce a map to given object Groovy must somehow construct that
object, so yes, I would say that the coerced object also needs a
reference to the outer class during construction.

If `InnerImpl` was a static class, the coercion would probably work.

I cannot say whether this warrants a bug report or not, sorry. Perhaps
someone from the core team would be able to say more.

Cheers,
Dinko

>
>> On 20 August 2014 13:11, Aschemann Gerd <ge...@aschemann.net> wrote:
>>>
>>> // Now the problem start!
>>> // Variant 1
>>> def dummyInner = [toString:{"DummyInner1"}] as Tst.InnerImpl
>
>>> ...
>
>>> The other three variants have the following errors:
>>>
>>> V1:
>>>
>>> Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Error
>>> casting map to Tst$InnerImpl, Reason: null
>>> org.codehaus.groovy.runtime.typehandling.GroovyCastException: Error casting
>>> map to Tst$InnerImpl, Reason: null
>>> at mockfor2.run(mockfor2.groovy:39)
>
> --
> Gerd Aschemann --- Veröffentlichen heißt Verändern (Carmen Thomas)
> +49/173/3264070 -- ge...@aschemann.net -- http://www.aschemann.net
>

Aschemann Gerd

unread,
Aug 21, 2014, 4:33:02 AM8/21/14
to us...@groovy.codehaus.org

On 21.08.2014, at 09:42, Dinko Srkoč <dinko...@gmail.com> wrote:

On 21 August 2014 07:31, Aschemann Gerd <ge...@aschemann.net> wrote:
In "reality" my problem code is much more complex ... tried to reduce it to a small sample which shows the critical points

On 20.08.2014, at 14:56, Dinko Srkoč <dinko...@gmail.com> wrote:
Inner classes cannot be constructed without their enclosing class
being constructed first. For that, one would need a static nested
class.

Thanks, Dinko, that at least explains why Variant 4 works, but V3 not: The call to the constructor of the inner class needs a reference to an object of the outer class (since in more advanced cases, the inner class might use attributes/methods of the outer class, or the respective object, to be more precise).

Yes, and I believe this is where Groovy differs from Java - it adds a
reference to the enclosing class as a first parameter to its
constructors.


And it might also be a hint why V1 does not work: Does the coerced object also need a reference to the outer class? In that case I would like to have a better error message, not "Reason: null" ... Should I file a bug for groovy?

To coerce a map to given object Groovy must somehow construct that
object, so yes, I would say that the coerced object also needs a
reference to the outer class during construction.

If `InnerImpl` was a static class, the coercion would probably work.

Yes, making the inner class static:

...
class Tst {
  static class InnerImpl implements Inner {
...

And then dropping the implicit first argument from the mock constructor:

...
MockFor mockInner = new MockFor (Tst.InnerImpl, true)
mockInner.demand.with {
  InnerImpl {boolean second->
    println "Mock Constructor called"
    println "Returning $dummyInner"
    dummyInner
  }
}
...

makes Variant 1 work!
signature.asc

Jochen Theodorou

unread,
Aug 21, 2014, 6:26:36 AM8/21/14
to us...@groovy.codehaus.org
Am 21.08.2014 09:42, schrieb Dinko Srkoč:
> On 21 August 2014 07:31, Aschemann Gerd <ge...@aschemann.net> wrote:
>> In "reality" my problem code is much more complex ... tried to reduce it to a small sample which shows the critical points
>>
>> On 20.08.2014, at 14:56, Dinko Srkoč <dinko...@gmail.com> wrote:
>>> Inner classes cannot be constructed without their enclosing class
>>> being constructed first. For that, one would need a static nested
>>> class.
>>
>> Thanks, Dinko, that at least explains why Variant 4 works, but V3 not: The call to the constructor of the inner class needs a reference to an object of the outer class (since in more advanced cases, the inner class might use attributes/methods of the outer class, or the respective object, to be more precise).
>
> Yes, and I believe this is where Groovy differs from Java - it adds a
> reference to the enclosing class as a first parameter to its
> constructors.

unless I missed an important point here Java should behave mostly the
same. I think there are situations in which this is not needed in Java
and in which the compiler then does not use it, but I am not sure. For
static inner classes of course it is not needed in Java. We need it
still for some dispatch routines.

>> And it might also be a hint why V1 does not work: Does the coerced object also need a reference to the outer class? In that case I would like to have a better error message, not "Reason: null" ... Should I file a bug for groovy?
>
> To coerce a map to given object Groovy must somehow construct that
> object, so yes, I would say that the coerced object also needs a
> reference to the outer class during construction.
>
> If `InnerImpl` was a static class, the coercion would probably work.
>
> I cannot say whether this warrants a bug report or not, sorry. Perhaps
> someone from the core team would be able to say more.

we cannot magically make that instance... So imho no bug

bye blackdrag


--
Jochen "blackdrag" Theodorou - Groovy Project Tech Lead
blog: http://blackdragsview.blogspot.com/
german groovy discussion newsgroup: de.comp.lang.misc
For Groovy programming sources visit http://groovy-lang.org

Dinko Srkoč

unread,
Aug 21, 2014, 6:44:44 AM8/21/14
to us...@groovy.codehaus.org
On 21 August 2014 12:25, Jochen Theodorou <blac...@gmx.org> wrote:
> Am 21.08.2014 09:42, schrieb Dinko Srkoč:
>
>> On 21 August 2014 07:31, Aschemann Gerd <ge...@aschemann.net> wrote:
>>>
>>> In "reality" my problem code is much more complex ... tried to reduce it
>>> to a small sample which shows the critical points
>>>
>>> On 20.08.2014, at 14:56, Dinko Srkoč <dinko...@gmail.com> wrote:
>>>>
>>>> Inner classes cannot be constructed without their enclosing class
>>>> being constructed first. For that, one would need a static nested
>>>> class.
>>>
>>>
>>> Thanks, Dinko, that at least explains why Variant 4 works, but V3 not:
>>> The call to the constructor of the inner class needs a reference to an
>>> object of the outer class (since in more advanced cases, the inner class
>>> might use attributes/methods of the outer class, or the respective object,
>>> to be more precise).
>>
>>
>> Yes, and I believe this is where Groovy differs from Java - it adds a
>> reference to the enclosing class as a first parameter to its
>> constructors.
>
>
> unless I missed an important point here Java should behave mostly the same.
> I think there are situations in which this is not needed in Java and in
> which the compiler then does not use it, but I am not sure. For static inner
> classes of course it is not needed in Java. We need it still for some
> dispatch routines.

I meant the difference in the mechanism of providing the reference to
the instance of the outer class - Groovy changing the inner class'
constructors, Java not (or I may remember it wrongly).

>
>
>>> And it might also be a hint why V1 does not work: Does the coerced object
>>> also need a reference to the outer class? In that case I would like to have
>>> a better error message, not "Reason: null" ... Should I file a bug for
>>> groovy?
>>
>>
>> To coerce a map to given object Groovy must somehow construct that
>> object, so yes, I would say that the coerced object also needs a
>> reference to the outer class during construction.
>>
>> If `InnerImpl` was a static class, the coercion would probably work.
>>
>> I cannot say whether this warrants a bug report or not, sorry. Perhaps
>> someone from the core team would be able to say more.
>
>
> we cannot magically make that instance... So imho no bug

I believe the bug was meant to be about providing a more descriptive
error message in this case (so not actual bug, just enhancement).

Cheers,
Dinko

Jochen Theodorou

unread,
Aug 21, 2014, 7:01:35 AM8/21/14
to us...@groovy.codehaus.org
what I did mean was that if you have

class A {
class B {}
}

you will get this constructor for B in Java:
> <init>(LA;)V
> L0
> LINENUMBER 2 L0
> ALOAD 0
> ALOAD 1
> PUTFIELD A$B.this$0 : LA;
> ALOAD 0
> INVOKESPECIAL java/lang/Object.<init> ()V
> RETURN

java is hiding t fact that you need an A in the constructor, but using
bytecode viewing tools or reflection you can see it

Dinko Srkoč

unread,
Aug 21, 2014, 7:09:02 AM8/21/14
to us...@groovy.codehaus.org
On 21 August 2014 13:00, Jochen Theodorou <blac...@gmx.org> wrote:
> Am 21.08.2014 12:43, schrieb Dinko Srkoč:
>> On 21 August 2014 12:25, Jochen Theodorou <blac...@gmx.org> wrote:
>>> Am 21.08.2014 09:42, schrieb Dinko Srkoč:
>>>> On 21 August 2014 07:31, Aschemann Gerd <ge...@aschemann.net> wrote:
>>>>> [...]
Right, thanks for the explanation.

- Dinko

Aschemann Gerd

unread,
Aug 21, 2014, 8:46:54 AM8/21/14
to us...@groovy.codehaus.org

On 21.08.2014, at 12:43, Dinko Srkoč <dinko...@gmail.com> wrote:
>>>> And it might also be a hint why V1 does not work: Does the coerced object
>>>> also need a reference to the outer class? In that case I would like to have
>>>> a better error message, not "Reason: null" ... Should I file a bug for
>>>> groovy?
>>>
>>>
>>> To coerce a map to given object Groovy must somehow construct that
>>> object, so yes, I would say that the coerced object also needs a
>>> reference to the outer class during construction.
>>>
>>> If `InnerImpl` was a static class, the coercion would probably work.
>>>
>>> I cannot say whether this warrants a bug report or not, sorry. Perhaps
>>> someone from the core team would be able to say more.
>>
>>
>> we cannot magically make that instance... So imho no bug
>
> I believe the bug was meant to be about providing a more descriptive
> error message in this case (so not actual bug, just enhancement).

Yes, I wanted to get a more descriptive message ...
signature.asc

Jochen Theodorou

unread,
Aug 21, 2014, 10:43:36 AM8/21/14
to us...@groovy.codehaus.org
Am 21.08.2014 14:45, schrieb Aschemann Gerd:
[...]
>> I believe the bug was meant to be about providing a more descriptive
>> error message in this case (so not actual bug, just enhancement).
>
> Yes, I wanted to get a more descriptive message ...

sure, for that you can file an issue. Best is to give an example of what
is failing, the error message, and what you would like to have mentioned
in the improved one, if not the improved message itself.
Reply all
Reply to author
Forward
0 new messages