The Zipper
class provides a convenient mechanism for writing zip and jar
files; it's a simplifying layer that sits on top of the existing Zip and
Jar classes provided by the JDK.
The Zipper
class provides a convenient mechanism for writing zip and jar
files; it's a simplifying layer that sits on top of the existing Zip and
Jar classes provided by the JDK. A Zipper
object behaves somewhat like an
immutable Scala collection, into which you can drop File
objects,
InputStream
objects, Reader
objects, Source
objects, URLs and
pathnames. When you call writeZip
or writeJar
, the objects in Zipper
are written to the actual underlying zip or jar file.
A Zipper
can either preserve pathnames or flatten the paths down to single
components. When preserving pathnames, a Zipper
object converts absolute
paths to relative paths by stripping any leading "file system mount points."
On Unix-like systems, this means stripping the leading "/"; on Windows, it
means stripping any leading drive letter and the leading "\". (See
java.io.File.listRoots() for more information.) For instance, if you're not
flattening pathnames, and you add C:\Temp\hello.txt
to a Zipper
on
Windows, the Zipper
will strip the C:\
, adding Temp/hello.txt
. to the
zip or jar file. If you're on a Unix-like system, including Mac OS X, and
you add /tmp/foo/bar.txt
, the Zipper
will add tmp/foo/bar.txt
to the
file.
You can explicitly add directory entries to a Zipper
, using
addZipDirectory()
. When you're not flattening entries, a Zipper
object will
also ensure that any intermediate directories in a pathname are created in
the zip file. For instance, if you add file /tmp/foo/bar/baz.txt
to a
Zipper
, without flattening it, the Zipper
will create the following
entries in the underlying zip file:
tmp
(directory)tmp/foo
(directory)tmp/foo/bar
(directory)tmp/foo/bar/baz.txt
(the entry)If you use the JDK's zip or jar classes directly, you have to create those
intermediate directory entries yourself. In addition, you have to be careful
not to create a directory more than once; doing so will cause an error.
Zipper
automatically creating unique intermediate directories for you.
The class constructor is private; use the companion object's apply()
functions to instantiate Zipper
objects.
The addFile()
methods all return Try
objects, and they do not modify
the original Zipper
object. On success, they return a Success
object
that contains a new Zipper
.
Because the addFile()
methods return Try
, they are unsuitable for use
in traditional "builder" patterns. For instance, the following will not
work:
// Will NOT work val zipper = Zipper() zipper.addFile("/tmp/foo/bar.txt").addFile("/tmp/baz.txt")
There are other patterns you can use, however. Since Try
is monadic, a
for
comprehension works nicely:
val zipper = Zipper() val newZipper = for { z1 <- zipper.addFile("/tmp/foo/bar.txt") z2 <- z1.addFile("/tmp/baz.txt") z3 <- z2.addFile("hello.txt") } yield z3 // newZipper is a Try[Zipper]
If you're trying to add a collection of objects, a for
comprehension
can be problematic. If you're not averse to using a local var
, you
can just use a traditional imperative loop:
val zipper = Zipper() var z = zipper val paths: List[String] = ... for (path <- paths) { val t = z.addFile(path) z = t.get // will throw an exception if the add failed }
You can also avoid a var
using foldLeft()
, though you still have to
contend with a thrown exception. (You can always wrap the code in a Try
.)
val zipper = Zipper() val paths: List[String] = ... paths.foldLeft(zipper) { case (z, path) => z.addFile(path).get // throws an exception if the add fails }
Finally, to avoid the exception and the var
, use tail-recursion:
import scala.annnotation.tailrec import scala.util.{Failure, Success, Try} @tailrec def addNext(paths: List[String], currentZipper: Zipper): Try[Zipper] = { paths match { case Nil => Success(currentZipper) case path :: rest => // Can't use currentZipper.addFile(path).map(), because the recursion // will then be invoked within the lambda, violating tail-recursion. currentZipper.addFile(path) match { case Failure(ex) => Failure(ex) case Success(z) => addNext(rest, z) } } } val paths: List[String] = ... val zipper = addNext(paths, Zipper())
A Zipper
is not a true Scala collection. It does not support extensively
querying its contents, looping over them, or transforming them. It is simply a
container to be filled and then written.
The Zipper
class currently provides no support for storing uncompressed
(i.e., fully inflated) entries. All data stored in the underlying zip is
compressed, even though the JDK-supplied zip classes support both compressed
and uncompressed entries. If necessary, the Zipper
class can be extended
to support storing uncompressed data.
Companion object to the Zipper
class.
Companion object to the Zipper
class. You can only instantiate Zipper
objects via this companion.
The
grizzled.zip
package contains classes and functions to make it easier to operate on zip and jar files.