Search code examples
javagroovycopymappingdozer

"Copy Only If Exists Pattern" in Groovy?


I have a situation where I need to copy the contents (attributes) of one POJO instance to another instance of the same type, but only for non-null attributes/member and non-empty collections. For instance:

// Groovy pseudo-code
class Widget {
    Fizz fizz
    List<Buzz> buzzes
    List<Foo> foos

    Widget(Fizz fizz, List<Buzz> buzzes, List<Foo> foos) {
        super()

        this.fizz = fizz
        this.buzz = buzz
        this.foos = foos
    }
}

class ExistentialCopier<T> {
    T copyExistentialContents(T toCopy) {
        // ???
    }
}

class Driver {
    static void main(String[] args) {
        ExistentialCopier<Widget> widgetCopier = new ExistentialCopier<Widget>()

        Fizz fizz = new Fizz()

        Buzz b1, b2, b3
        b1 = new Buzz()
        b2 = null            // <— note the NULL
        b3 = new Buzz()
        List<Buzz> buzzes = []
        buzzes << b1
        buzzes << b2
        buzzes << b3

        Widget w1 = new Widget(fizz, buzzes, null)
        Widget w2 = widgetCopier.copyExistentialContents(w1)

        // All of these return true.
        assert w2.fizz == w1.fizz
        assert w2.buzzes.get(0) == w1.buzzes.get(0)
        assert w2.buzzes.get(1) == w1.buzzes.get(2) // because b2 was null, it didn’t copy over
        assert w2.size() == 2 // again, b2 didn’t copy (it was null/nonexistent)
        assert w2.fizzes == null
    }
}

So what I’m looking for in ExistentialCopier is:

  • A way to be generic and handle any type, Widgets or otherwise (perhaps the T generic will need to implement some interface (?)
  • For non-collections, copy toCopy’s non-null attributes
  • For collections, if toCopy’s collection is null, don’t copy it (level the copy’s respective collection null as well)
  • For collections, if a particular element/member is null, don’t copy it, otherwise, copy it

I feel like there is a Groovy way of accomplishing this, but perhaps this type of “Copy Only If Exists” pattern is well known and already solved by some open source library. Perhaps a solution best suited for bean-to-bean mapping utilities like Dozer null exclusions perhaps (which I have no experience with)?


Solution

  • I don't know of pre-existing functionality or library, but you might be able to roll your own solution with metaprogramming.

    Consider these classes:

    class Fizz { def fizzName }
    class Buzz { def buzzName }
    class Foo { def fooName }
    
    class Widget {
       Fizz fizz
       List<Buzz> buzzes
       List<Foo> foos
    }
    

    This is crucial to the solution:

    // in this example 'delegate' is w1
    Object.metaClass.copyExistentialContents = { 
        def newObj = delegate.class.newInstance()
    
        delegate.properties.each { prop, val ->
            if (prop in ['metaClass','class']) return
            if (val == null) return
    
            def newVal = val
    
            if (val instanceof Collection) {
                newVal = val.findAll { it != null }
            } 
    
            newObj."${prop}" = newVal 
        }
    
        return newObj
    } 
    

    The following works for me with Groovy 2.4.3 (with all this code in the same file). Note the copyExistentialContents method (from above):

    // ---------------- main
    
    def fizz = new Fizz(fizzName: 'fizz')
    
    def buzzes = []
    buzzes << new Buzz(buzzName: 'buzz 1')
    buzzes << null
    buzzes << new Buzz(buzzName: 'buzz 3')
    
    def w1 = new Widget(fizz: fizz, buzzes: buzzes, foos: null)
    
    def w2 = w1.copyExistentialContents()
    
    assert w1.fizz.fizzName == w2.fizz.fizzName
    assert null == w2.foos
    assert 2 == w2.buzzes.size()
    assert 'buzz 1' == w2.buzzes[0].buzzName
    assert 'buzz 3' == w2.buzzes[1].buzzName