Unexpected behaviour when using @Immutable on a class with java.util.Map field

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Unexpected behaviour when using @Immutable on a class with java.util.Map field

Miro Bezjak
Hi all,

consider the following code:

-----------------------------
@groovy.transform.Immutable
class Foo {
    Map props
}

def foo = new Foo(props: [a:1, b:2])
println foo.props
println foo.props.a
println foo.props['a']
-----------------------------


Executed in groovy 2.1.6, the (unexpected) output is:
-----------------------------
[props:[a:1, b:2]]
null
null
-----------------------------


However, when `@Immutable` line is removed, the following (expected) output is
produced:
-----------------------------
[a:1, b:2]
1
1
-----------------------------


Should I file a JIRA issue for this?

Regards,
Miro
Reply | Threaded
Open this post in threaded view
|

Re: Unexpected behaviour when using @Immutable on a class with java.util.Map field

Zhaorui (Richard.Jor)
According to Immutable doc describes http://groovy.codehaus.org/gapi/groovy/transform/Immutable.html
"As outlined above, Collections and Maps are wrapped by immutable wrapper classes (but not deeply cloned!)."

'props' is wrapped by a immutable map, so that it cannot reassigned. foo.props just return the wrapper map, not the props itself. I don't know if it's expected.

@groovy.transform.Immutable   
class Foo {
  Map atts
}

def foo = new Foo(atts: ['a':1, 'b':2])
println foo.atts
println foo.atts.a
println foo.atts['a']
foo.atts['atts']['C'] = 3  // inner attrs can be updated still.
println foo.atts['atts']
foo.atts['d'] = 4         // throw UnsupportedOperationException




On 16 July 2013 20:24, Miro Bezjak <[hidden email]> wrote:
Hi all,

consider the following code:

-----------------------------
@groovy.transform.Immutable
class Foo {
    Map props
}

def foo = new Foo(props: [a:1, b:2])
println foo.props
println foo.props.a
println foo.props['a']
-----------------------------


Executed in groovy 2.1.6, the (unexpected) output is:
-----------------------------
[props:[a:1, b:2]]
null
null
-----------------------------


However, when `@Immutable` line is removed, the following (expected) output is
produced:
-----------------------------
[a:1, b:2]
1
1
-----------------------------


Should I file a JIRA issue for this?

Regards,
Miro



--
赵瑞-Richard
Reply | Threaded
Open this post in threaded view
|

Re: Unexpected behaviour when using @Immutable on a class with java.util.Map field

paulk_asert
Administrator

What you see here and in your original query are expected. I think we should copy the following line out of the @Canonical groovydoc into @Immutable to help clarify:

   * Limitations:
   * ...
   * Groovy's normal map-style naming conventions will not be available if the first property
   * has type LinkedHashMap or if there is a single Map, AbstractMap or HashMap property

To understand why, you need to know how the internals work. Basically if you have

   @groovy.transform.Immutable
   class Foo {
     String a, b
   }

It will cause two constructors  to be created.

   public Foo(String a, String b) { (HashMap)['a': a , 'b': b ] }
   public Foo(HashMap args) { this.a = args.a; this.b = args.b }

But for the cases outlined in the limitation above the two constructors would fold onto the one signature. Most real world classes will have more than one property or use a range of types, so this limitation isn't usually a problem. If you must have just one property, you can always roll your own immutable class. You won't have much work to do.

Cheers, Paul.

On 17/07/2013 11:17 AM, Zhaorui (Richard.Jor) wrote:

> According to Immutable doc describes http://groovy.codehaus.org/gapi/groovy/transform/Immutable.html
> "As outlined above, Collections and Maps are wrapped by immutable wrapper classes (but not deeply cloned!)."
>
> 'props' is wrapped by a immutable map, so that it cannot reassigned. foo.props just return the wrapper map, not the props itself. I don't know if it's expected.
>
> @groovy.transform.Immutable
> class Foo {
>    Map atts
> }
>
> def foo = new Foo(atts: ['a':1, 'b':2])
> println foo.atts
> println foo.atts.a
> println foo.atts['a']
> foo.atts['atts']['C'] = 3  // inner attrs can be updated still.
> println foo.atts['atts']
> foo.atts['d'] = 4         // throw UnsupportedOperationException
>
>
>
>
> On 16 July 2013 20:24, Miro Bezjak <[hidden email] <mailto:[hidden email]>> wrote:
>
>     Hi all,
>
>     consider the following code:
>
>     -----------------------------
>     @groovy.transform.Immutable
>     class Foo {
>          Map props
>     }
>
>     def foo = new Foo(props: [a:1, b:2])
>     println foo.props
>     println foo.props.a
>     println foo.props['a']
>     -----------------------------
>
>
>     Executed in groovy 2.1.6, the (unexpected) output is:
>     -----------------------------
>     [props:[a:1, b:2]]
>     null
>     null
>     -----------------------------
>
>
>     However, when `@Immutable` line is removed, the following (expected) output is
>     produced:
>     -----------------------------
>     [a:1, b:2]
>     1
>     1
>     -----------------------------
>
>
>     Should I file a JIRA issue for this?
>
>     Regards,
>     Miro
>
>
>
>
> --
> 赵瑞-Richard


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

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Unexpected behaviour when using @Immutable on a class with java.util.Map field

Miro Bezjak
Dear all,

thanks for the clarification. I agree, the limitation of @Canonical applies to
@Immutable and should probably be documented.

The funny thing is, in my attempt to produce a small enough code snippet, it
appears that I have completely missed the bug.

Here is another code snippet. Hopefully this time outlining the real bug.

-----------------------------
@groovy.transform.CompileStatic
@groovy.transform.Immutable
class Foo {
    String name
    Map<String, Integer> properties
}

def foo = new Foo(name: 'abc', properties: [a:1, b:2])
println foo.properties
println foo.properties.a
println foo.properties['a']
-----------------------------


The output is:
-----------------------------
[class:class java.util.LinkedHashMap, empty:false]
null
null
-----------------------------


I believe the combination of @Immutable + @CompileStatic + a field named
`properties' results in the above output. If you do any of the following:
- remove @Immutable
- remove @CompileStatic
- replace @CompileStatic with @TypeChecked
- rename the field to anything but `properties'

the output becomes:
-----------------------------
[a:1, b:2]
1
1
-----------------------------


Regards,
Miro


On Wed, Jul 17, 2013 at 10:32 AM, Paul King <[hidden email]> wrote:

What you see here and in your original query are expected. I think we should copy the following line out of the @Canonical groovydoc into @Immutable to help clarify:

  * Limitations:
  * ...
  * Groovy's normal map-style naming conventions will not be available if the first property
  * has type LinkedHashMap or if there is a single Map, AbstractMap or HashMap property

To understand why, you need to know how the internals work. Basically if you have

  @groovy.transform.Immutable
  class Foo {
    String a, b
  }

It will cause two constructors  to be created.

  public Foo(String a, String b) { (HashMap)['a': a , 'b': b ] }
  public Foo(HashMap args) { this.a = args.a; this.b = args.b }

But for the cases outlined in the limitation above the two constructors would fold onto the one signature. Most real world classes will have more than one property or use a range of types, so this limitation isn't usually a problem. If you must have just one property, you can always roll your own immutable class. You won't have much work to do.

Cheers, Paul.


On 17/07/2013 11:17 AM, Zhaorui (Richard.Jor) wrote:
According to Immutable doc describes http://groovy.codehaus.org/gapi/groovy/transform/Immutable.html
"As outlined above, Collections and Maps are wrapped by immutable wrapper classes (but not deeply cloned!)."

'props' is wrapped by a immutable map, so that it cannot reassigned. foo.props just return the wrapper map, not the props itself. I don't know if it's expected.

@groovy.transform.Immutable
class Foo {
   Map atts
}

def foo = new Foo(atts: ['a':1, 'b':2])
println foo.atts
println foo.atts.a
println foo.atts['a']
foo.atts['atts']['C'] = 3  // inner attrs can be updated still.
println foo.atts['atts']
foo.atts['d'] = 4         // throw UnsupportedOperationException




On 16 July 2013 20:24, Miro Bezjak <[hidden email] <mailto:[hidden email]>> wrote:

    Hi all,

    consider the following code:

    -----------------------------
    @groovy.transform.Immutable
    class Foo {
         Map props
    }

    def foo = new Foo(props: [a:1, b:2])
    println foo.props
    println foo.props.a
    println foo.props['a']
    -----------------------------


    Executed in groovy 2.1.6, the (unexpected) output is:
    -----------------------------
    [props:[a:1, b:2]]
    null
    null
    -----------------------------


    However, when `@Immutable` line is removed, the following (expected) output is
    produced:
    -----------------------------
    [a:1, b:2]
    1
    1
    -----------------------------


    Should I file a JIRA issue for this?

    Regards,
    Miro




--
赵瑞-Richard


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

   http://xircles.codehaus.org/manage_email