Package

grizzled

zip

Permalink

package zip

The grizzled.zip package contains classes and functions to make it easier to operate on zip and jar files.

Linear Supertypes
AnyRef, Any
Ordering
  1. Alphabetic
  2. By Inheritance
Inherited
  1. zip
  2. AnyRef
  3. Any
  1. Hide All
  2. Show All
Visibility
  1. Public
  2. All

Type Members

  1. class Zipper extends AnyRef

    Permalink

    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.

    Zipper: Write zip and jar files more easily

    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 addFile 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 addFile /tmp/foo/bar.txt, the Zipper will addFile tmp/foo/bar.txt to the file.

    Directories

    You can explicitly addFile 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 addFile 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.

    Constructing a Zipper object

    The class constructor is private; use the companion object's apply() functions to instantiate Zipper objects.

    Using a Zipper object

    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 addFile 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 addFile 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 addFile 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())

    Notes

    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.

Value Members

  1. object Zipper

    Permalink

    Companion object to the Zipper class.

    Companion object to the Zipper class. You can only instantiate Zipper objects via this companion.

Inherited from AnyRef

Inherited from Any

Ungrouped