2009-09-15

Playing again with zip and Groovy MOP ( and spread operator)

After I posted the zip and unzip snippet, I came accross an interesting question on gr8forums.org.
Someone told me it would be great if ths code could be applied to an array of files.

That's a good use case, isn't it ? So, I thought about it and saw immediately which groovy 'magic item' could be useful : the spread operator aka star-dot operator, mister *.
Better than I could explain myself, here's what's said about this little thing in Groovy's doc :

The Spread Operator is used to invoke an action on all items of an aggregate object.


Unfortunately, ZipOutputStream in Java doesn't permit to append something to a zip file. So, I'll show you an awful (yes, unzip the existing zip-append file-rezip IS awful) implementation of this functionnality. It's just for fun, not for production, after all.

First, you'll need a deleteDir closure, as java.io.File.delete() doesn't delete non-empty directories :
def deleteDir
deleteDir = { File f ->
    if (f?.exists()){
      f.listFiles().each{ sf ->
        sf.isDirectory()?deleteDir(sf):sf.delete()
      }
    }
    f?.delete()
}

With this wonderful snippet, you can replace the File.metaClass.zip block in this snippet by :
File.metaClass.zip = { String destination ->
    //cache the delegate (the File Object) as it will be modified
    //in the withStream closure
    def input = delegate
    if (destination == null){
        destination = input.canonicalPath + ".zip"
    }
    def dest = new File(destination)
    def append = false
    def tmp
    if (dest.exists()){
        tmp = new File(System.properties['java.io.tmpdir'] + File.separator + 'grooz')           
        tmp.mkdir()
        dest.unzip(tmp.canonicalPath)
        append = true
    }
    def result = new ZipOutputStream(new FileOutputStream(destination))
    try{
    result.withStream {zipOutStream->
        //recursively zip files
        if (append) {
            
                tmp.eachFile{
                    zip(zipOutStream,it,"")
                }
            
        }
        zip(zipOutStream,input,"")
    }
    }
    catch(all){
      all.printStackTrace()
    }
    finally{                        
      deleteDir(tmp)
    }    
}


This version is very easy to use. If you want to zip a bunch of files, then list them and use the spread operator like this :
// to zip all files in a directory
new File('/path/to/dir').listFiles()*.zip('allmyfiles.zip') 
//if you've got files instances
[file1,file2,dir1,dir2]*.zip('stuff.zip')

This code is not performance-friendly nor beauty-frienly and is provided as is. Every enhancement you can think of is welcome (yes, I know, it'll be difficult to improve, but you can do it ;-) )

See you soon with a totally new subject : joy of Java Classoading !

1 comment:

Share your feelings about this post, or this blog