Closure with conditional logging

2019-07-20 19:19发布

问题:

I have a function like this:

private downloadAllFiles() {
    sftpRetriever.listFiles().findAll {
        filter.isResponse(it) || filter.isResponseTurned(it)
    }.each { String fileName ->
        log.info 'Downloading file: {}', fileName
        sftpRetriever.downloadFile(fileName)
        log.info 'File downloaded'
        removeRemoteFile(fileName)
    }
}

I am looking for a simple way of modyfing this closure inside of that function so if the size() of findAll is 0 it will simply log 'No more files to download' and .each won't be executed. Is there any simple way to make it in single closure? It is really simply task if I divide it in several parts, but trying to learn closures here and improve my expressiveness :) Thank you in advance for your help.

回答1:

Take a look at creature below :) It works due the fact that each returns the collection on which it's invoked (+ elvis operator and quite nice Groovy's truth evaluation):

def printContents(Collection collection) {
    collection.each {
        println it
    } ?: println('Collection is empty')
}

printContents([1,2,'hello'])
printContents([])

I don't like this syntax but it's the shorter version which came to my mind.

You can also use metaprogramming to add the method provided by Steinar. It must be added to metaClass before first use but you'll avoid an effort to make extension module:

Collection.metaClass.doIfEmpty { Closure ifEmptyClosure ->
        if (delegate.empty) {
            ifEmptyClosure()
        }
        return delegate
}

def printContents(Collection collection) {
    collection.doIfEmpty {
        println "Collection is empty"
    }.each {
        println it
    }
}

printContents([1,2,'hello'])
printContents([])


回答2:

One rather generic and reusable option is to extend Collection using an extension module. This is surprisingly easy to do and is even recognized in IDE's (at least in IntelliJ) so you get code completion, etc.

For example, write an the extension class for collections which will perform the closure if the collection is empty. In addtion, it should always return the collection to allow further chaining:

package stackoverflow

class CollectionExtension {
    static <T extends Collection> T doIfEmpty(T self, Closure closure) {
        if (self.empty) {
            closure()
        }
        return self
    }
}

You will also need to tell groovy that this file is an extension module. Add a property file as a resource on the classpath: META-INF/services/org.codehaus.groovy.runtime.ExtensionModule (note: this name and location is mandatory for extension modules, i.e. you cannot change it).

moduleName=stackoverflow-module
moduleVersion=1.0
extensionClasses=stackoverflow.CollectionExtension

Finally a simple test script to show how this can be used:

def printContents(Collection collection) {
    collection.doIfEmpty {
        println "Collection is empty"
    }.each {
        println it
    }
}

printContents([1,2,'hello'])
printContents([])

Output:

1
2
hello
Collection is empty


回答3:

You may try the following piece of code:

def l1 = [1,2,3,4]
def l2 = [5,6,7,8]

def m(list) {
    list.findAll { it < 5}.with { l ->
        size > 0 ? 
            l.each { e ->
                println e  
            }
        : 
            println('Zero elements found')
    }
}

m(l1)
m(l2)

No better idea at the moment.