Using Maps for Constructors appropriately when initializing Groovy classes when Composition is in play

I naively tried setting up a couple of Groovy classes Ellipsoid, and TransverseMercatorProjection. The former was part of the structure of the latter. I.e in UML speak, composition was being used.

package jgf
class Ellipsoid {
  double a
  double b
  Ellipsoid(a, b) {
    this.a = a
    this.b = b
  }
  String toString() {
    return "a: $a, b: $b"
  }
}
class TransverseMercatorProjection {
  Ellipsoid e
  double    f0
  double    lat0deg
  double    long0deg
  int       e0
  int       n0

  TransverseMercatorProjection(a, b, f0, lat0deg, long0deg, e0, n0) {
   this.e = new Ellipsoid(a, b)
   this.f0 = f0
   this.lat0deg = lat0deg
   this.long0deg = long0deg
   this.e0 = e0
   this.n0 = n0
  }
  String toString() {
    return "$e, f0: $f0, lat0deg: $lat0deg, long0deg $long0deg, e0:$e0, n0 $n0"
  }
}
def airy1830  = [a: 6377563.396, b: 6356256.910,  f0:0.9996012717,   lat0deg:49,   long0deg:-2, e0:400000, n0:-100000]
def tmp = new TransverseMercatorProjection(airy1830)

I ended up with this:

Exception thrown: Could not find matching constructor for: jgf.TransverseMercatorProjection(java.util.LinkedHashMap)

groovy.lang.GroovyRuntimeException: Could not find matching constructor for: jgf.TransverseMercatorProjection(java.util.LinkedHashMap)...

Now I half expected to have some issues because the map ‘airy1830’ didn’t contain the property e, that would be a pre-requisite for a constructor.

I also contemplated using e.a & e.b in the airy1830 Map (which I subsequently reversed out), using this structure:

def airy1830  = ['e.a': 6377563.396, 'e.b': 6356256.910,  f0:0.9996012717,   lat0deg:49,   long0deg:-2, e0:400000, n0:-100000]
def tmp = new TransverseMercatorProjection(airy1830)

but ended up with this error:

Exception thrown: No such property: e.a for class: jgf.TransverseMercatorProjection
groovy.lang.MissingPropertyException: No such property: e.a for class: jgf.TransverseMercatorProjection..

I was most surprised that  when I commented out the def tmp line and replaced it with…

def e = new Ellipsoid(airy1830)

that this gave the same error and didn’t work either.
If the map you are using has properties your class does not contain, Groovy doesn’t ignore them, which I would have preferred as an option. Instead it throws exceptions!
I thought Groovy should have acted more intelligently in this case. So I tried commenting out the constructor for Ellipsoid.

Then I ended up with:

Exception thrown: No such property: f0 for class: jgf.Ellipsoid
groovy.lang.MissingPropertyException: No such property: f0 for class: jgf.Ellipsoid...

I then tried:

def e = new Ellipsoid()
e.properties = airy1830

and

def e = new Ellipsoid()
e.properties = airy1830.properties

each time ending up with:

Exception thrown: Cannot set readonly property: properties for class: jgf.Ellipsoid
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: properties for class: jgf.Ellipsoid...

I ended up coming up with a solution, that gave the desired result but figured there must be a more elegant way of doing this kind of thing.

package jgf
class Ellipsoid {
  double a
  double b
  String toString() {
    return "a: $a, b: $b"
  }
}
class TransverseMercatorProjection {
  Ellipsoid e
  double    f0
  double    lat0deg
  double    long0deg
  int       e0
  int       n0
  String toString() {
    return "$e, f0: $f0, lat0deg: $lat0deg, long0deg $long0deg, e0:$e0, n0 $n0"
  }
}
def airy1830  = [a: 6377563.396, b: 6356256.910,  f0:0.9996012717,   lat0deg:49,   long0deg:-2, e0:400000, n0:-100000]
def tmp = new TransverseMercatorProjection()
def e = new Ellipsoid()
tmp.e = e
airy1830.each{k, v ->
  if (k in tmp.properties.keySet()) {
    println "tmp: $k $v"
    tmp[k] = v
  } else if (k in e.properties.keySet()) {
    println "e: $k $v"
    e[k]   = v
  }
}
println e
println tmp
return null
e: a 6377563.396
e: b 6356256.910
tmp: f0 0.9996012717
tmp: lat0deg 49
tmp: long0deg -2
tmp: e0 400000
tmp: n0 -100000
a: 6377563.396, b: 6356256.91
a: 6377563.396, b: 6356256.91, f0: 0.9996012717, lat0deg: 49.0, long0deg -2.0, e0:400000, n0 -100000

Revision 1:

Having had a chance to reflect on this some more, I came up with a solution I was happy with:

package jgf
class Ellipsoid {
  double a
  double b
  Ellipsoid(props) {
    this.a = props.a
    this.b = props.b
  }
  String toString() {
    return "a: $a, b: $b"
  }
}
class TransverseMercatorProjection {
  Ellipsoid e
  double    f0
  double    lat0deg
  double    long0deg
  int       e0
  int       n0
  TransverseMercatorProjection(props) {
   this.e = new Ellipsoid(props)
   this.f0 = props.f0
   this.lat0deg = props.lat0deg
   this.long0deg = props.long0deg
   this.e0 = props.e0
   this.n0 = props.n0
  }
  String toString() {
    return "$e, f0: $f0, lat0deg: $lat0deg, long0deg $long0deg, e0:$e0, n0 $n0"
  }
}
def airy1830  = [a: 6377563.396, b: 6356256.910,  f0:0.9996012717,   lat0deg:49,   long0deg:-2, e0:400000, n0:-100000]
def tmp = new TransverseMercatorProjection(airy1830)
def e   = new Ellipsoid(airy1830)
println tmp
println e
println tmp.e
return null
a: 6377563.396, b: 6356256.91, f0: 0.9996012717, lat0deg: 49.0, long0deg -2.0, e0:400000, n0 -100000
a: 6377563.396, b: 6356256.91
a: 6377563.396, b: 6356256.91
Advertisements

About this entry