The code is working, however, there are corner cases where it may break.
To fix this replace the property access properties
with the method call getProperties()
, which might be enough for your case. To cover all cases, you will need to write code for special cases (see bottom)
Working example for the original version
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.properties*.keySet()
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
def a = new Expando()
a.foo = "foo"
a.bar = "bar"
def b = new Expando()
b.baz = "baz"
b.bar = "old"
copyProperties(a, b)
println b
Example causing problems
If the parameters have a property called properties
I get the same exception you got (if the value is a List
):
def c = new Expando()
c.properties = []
c.bar = "bar"
def d = new Expando()
d.baz = "baz"
d.bar = "old"
copyProperties(c, d)
println d
What works in both cases:
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.getProperties()*.keySet()
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
Not that here I used an explicit call to getProperties
rather than just accessing the properties
property.
We can still break this
def e = new Object() {
// causes same Exception again
def getProperties() {
return []
}
def bar = "bar"
}
def f = new Expando()
f.baz = "baz"
f.bar = "old"
copyProperties(e, f)
You can fix the last example for e
by using the metaClass explicitly
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.getMetaClass()*.properties*.name
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
However, that will fail due to f
.
Handle special cases
def getProperties(Expando obj) {
return obj.getProperties().keySet()
}
def getProperties(Object obj) {
return obj.getMetaClass()*.properties*.name
}
def copyProperties(source, target) {
def (sProps, tProps) = [source, target].collect {getProperties(it)}
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
Here we give objects that need a special treatment what they need ;)
Note that this only works like this for groovy with @CompileDynamic
as the decision which getProperties
implementation is called will be made at runtime. The alternative is a check with instanceof
for all the cases.