summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/io/ZipArchive.scala
blob: 7951f7802d5ed75bf0461be4d8f067af759758ba (plain) (tree)
1
2
3
4
5
6
7
8
9
                            
                                

                          


       

                       
 
                    
                   
                            


                                                                               
 


                                                        
 



                            



                                                                            





                     
                                                                 

     





                                                                     

                                          
                                                         



                                                                   


                       

                                                   
                                                    








                                                                   

 





















                                                                             

                                                              




                                                                            
   

                                                                                                   
                         
 


                                                
 



















                                                                             

                      
                                 

                                                                  
                                                               






                                                                  
                                                                           
                                    
 





                                                                                    





                                                                  








                                                                           




                                                                            






                                                                             
                                                  
                                  
 
                                                               



                                                 





                                                                            




                                        
   
                                                   
                           
 
                                   
                                                                   

                                     
                                                                
 
                                                                          
 


                                                                               
     

   
                                     






                                        
                                         
                                                     
                                                      
                                                         
   








                                                                    
                                                                             

                     

                                          
 

                                                   

                    
                                       
                                                 












                                                                              
                                     
                                                                         
       
                      
     
        

   




























                                                                           














                                                                           

                                                                                    


                                  
                                                                                      



                                                   
                                                                   



                                                                
                                                                          






                                                                             
 




                                                                 
                                                         
   
 
/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id$


package scala.tools.nsc
package io

import scala.io.File
import java.net.URL
import java.util.Enumeration
import java.io.{ File => JFile, IOException, InputStream, BufferedInputStream }
import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream }
import PartialFunction._

import scala.collection.mutable.{ Map, HashMap }
import scala.collection.immutable.{ StringVector => SV }
import scala.collection.JavaConversions.asIterator

/**
 * @author  Philippe Altherr
 * @version 1.0, 23/03/2004
 */
object ZipArchive {

  //########################################################################

  /**
   * ...
   *
   * @param path ...
   *  @return     ...
   */
  def fromPath(path: String): AbstractFile = fromFile(File(path))

  /**
   * If the specified file <code>file</code> exists and is a readable
   * zip archive, returns an abstract file backed by it. Otherwise,
   * returns <code>null</code>.
   *
   * @param file ...
   * @return     ...
   */
  def fromFile(file: File): AbstractFile =
    try { new ZipArchive(file, new ZipFile(file.jfile)) }
    catch { case _: IOException => null }

  /**
   * Returns an abstract directory backed by the specified archive.
   *
   * @param archive ...
   * @return        ...
   */
  def fromArchive(archive: ZipFile): AbstractFile =
    new ZipArchive(File(archive.getName()), archive)

  /**
   * Returns an abstract directory backed by the specified archive.
   *
   * @param url ...
   * @return    ...
   */
  def fromURL(url: URL): AbstractFile =
    new URLZipArchive(url)
}

private[io] trait ZipFileCommon
{
  import SV.{ splitAt }

  protected def splitPath(path: String): (String, String) = {
    (path lastIndexOf '/') match {
      case -1   => ("/", path)
      case idx  => splitAt(path, idx + 1)
    }
  }

  protected def byteInputStream(in: InputStream): InputStream = {
    val buf = new BufferedInputStream(in)
    val bytes = Iterator continually in.read().toByte takeWhile (_ != -1)
    new java.io.ByteArrayInputStream(bytes.toSequence.toArray)
  }

  protected def zipEntryIterator(zis: ZipInputStream): Iterator[ZipEntry] = {
    Iterator continually zis.getNextEntry() takeWhile (_ != null)
  }
}

/**
 * This class implements an abstract directory backed by a zip
 * archive. We let the encoding be <code>null</code>, because we behave like
 * a directory.
 *
 * @author  Philippe Altherr
 * @version 1.0, 23/03/2004
 */
final class ZipArchive(file: File, val archive: ZipFile) extends PlainFile(file) with ZipFileCommon
{
  assert(archive ne null)

  /** The root directory  */
  private lazy val root = {
    val root = new DirEntry(this, "<root>", "/")

    // A path to DirEntry map
    val dirs: Map[String, DirEntry] = new HashMap()
    dirs("/") = root
    val entries = asIterator(archive.entries())

    entries foreach { case entry: ZipEntry =>
      val path = entry.getName
      if (entry.isDirectory) {
        val dir: DirEntry = getDir(dirs, path)
        if (dir.entry == null) dir.entry = entry
      }
      else {
        val (home, name) = splitPath(path)
        val parent: DirEntry = getDir(dirs, home)
        parent.entries.update(name, new FileEntry(parent, name, path, entry))
      }
    }

    root
  }

  /** Returns true. */
  override def isDirectory = true

  /** Returns all abstract subfiles of this abstract directory. */
  override def iterator: Iterator[AbstractFile] = root.iterator

  /**
   * Returns the abstract file in this abstract directory with the
   * specified name. If there is no such file, returns null. The
   * argument "directory" tells whether to look for a directory or
   * or a regular file.
   */
  override def lookupName(name: String, directory: Boolean): AbstractFile =
    root.lookupName(name, directory)

  /** Returns an abstract file with the given name. It does not
   *  check that it exists.
   */
  override def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile =
    throw new UnsupportedOperationException()

  /**
   * Lookups the specified table for a DirEntry with the specified
   * path. If successful, returns the found DirEntry. Otherwise
   * creates a new DirEntry, enters it into the table and in the
   * table of its parent ZipDir and returns it.
   */
  private def getDir(dirs: Map[String, DirEntry], path: String): DirEntry =
    (dirs get path) getOrElse {
      val (home, name) = splitPath(SV.dropRight(path, 1))
      val parent: DirEntry = getDir(dirs, home)
      val dir = new DirEntry(parent, name, path)
      parent.entries.update(name + SV.last(path), dir)
      dirs.update(path, dir)
      dir
    }

  //########################################################################
  // Private Class - Entry

  /** Superclass of archive entries */
  abstract class Entry(
    override val container: AbstractFile,
    name: String,
    path: String
  ) extends VirtualFile(name, path)
  {
    final override def path = "%s(%s)".format(ZipArchive.this, pathInArchive)
    final def getArchive = ZipArchive.this.archive
    def pathInArchive = super.path

    override def hashCode = super.hashCode + container.hashCode
    override def equals(that : Any) =
      super.equals(that) && (cond(that) {
        case e: Entry => container == e.container
      })
  }

  //########################################################################
  // Private Class - DirEntry

  /** A directory archive entry */
  private final class DirEntry(
    container: AbstractFile,
    name: String,
    path: String
  ) extends Entry(container, name, path)
  {
    val entries: Map[String, Entry] = new HashMap()
    var entry: ZipEntry = _

    override def isDirectory = true
    override def input = throw new Error("cannot read directories")

    override def lastModified: Long =
      if (entry ne null) entry.getTime() else super.lastModified

    override def iterator: Iterator[AbstractFile] = entries.valuesIterator

    override def lookupName(name: String, directory: Boolean): AbstractFile = {
      def slashName = if (directory) name + "/" else name
      entries.getOrElse(slashName, null)
    }
  }

  /** A regular file archive entry */
  final class FileEntry(
    container: AbstractFile,
    name: String,
    path: String,
    val entry: ZipEntry
  ) extends Entry(container, name, path)
  {
    def archive = ZipArchive.this.archive
    override def lastModified: Long = entry.getTime()
    override def input = archive.getInputStream(entry)
    override def sizeOption = Some(entry.getSize().toInt)
  }
}

/**
 * This class implements an abstract directory backed by a specified
 * zip archive.
 *
 * @author  Stephane Micheloud
 * @version 1.0, 29/05/2007
 */
final class URLZipArchive(url: URL) extends AbstractFile with ZipFileCommon {
  assert(url ne null)

  private lazy val root: DirEntry = {
    val root = new DirEntry("<root>", "/")

    // A path to DirEntry map
    val dirs: Map[String, DirEntry] = new HashMap()
    dirs("/") = root

    val zis = new ZipInputStream(input)
    zipEntryIterator(zis) foreach { case entry =>
      val path = entry.getName()
      assert(entry.isDirectory() == path.endsWith("/"),
             this.toString() + " - " + path);
      if (entry.isDirectory()) {
        val dir: DirEntry = getDir(dirs, path)
        assert(dir.entry eq null, this.toString() + " - " + path)
        dir.entry = entry
      } else {
        val index = path.lastIndexOf('/')
        val name = if (index < 0) path else path.substring(index + 1)
        val home = if (index < 0) "/"  else path.substring(0, index + 1)
        val parent: DirEntry = getDir(dirs, home)
        assert(!parent.entries.contains(path), this.toString() + " - " + path)
        val in = byteInputStream(zis)
        parent.entries.update(name, new FileEntry(name, path, entry, in))
      }
      zis.closeEntry()
    }
    root
  }

  def container = throw new Error("unsupported")

  def name: String = url.getFile()
  def path: String = url.getPath()
  def file: JFile = null
  def absolute: AbstractFile = this
  def isDirectory: Boolean = true

  def lastModified: Long =
    try url.openConnection().getLastModified()
    catch { case _: IOException => 0 }

  def create: Unit = throw new UnsupportedOperationException
  def delete: Unit = throw new UnsupportedOperationException

  def input: InputStream = url.openStream()
  def output = throw new Error("unsupported")

  override def iterator: Iterator[AbstractFile] = root.iterator

  override def lookupName(name: String, directory: Boolean): AbstractFile =
    root.lookupName(name, directory)

  /** Returns an abstract file with the given name. It does not
   *  check that it exists.
   */
  def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile =
    throw new UnsupportedOperationException()

  private def getDir(dirs: Map[String, DirEntry], path: String): DirEntry =
    dirs.get(path) match {
      case Some(dir) => dir
      case None =>
        val index = path.lastIndexOf('/', path.length() - 2)
        val name = if (index < 0) path else path.substring(index + 1)
        val home = if (index < 0) "/"  else path.substring(0, index + 1)
        val parent: DirEntry = getDir(dirs, home)
        val dir = new DirEntry(name.substring(0, name.length() - 1), path)
        parent.entries.update(name, dir)
        dirs.update(path, dir)
        dir
    }

  /** Superclass of archive entries */
  abstract class Entry(name: String, path: String) extends VirtualFile(name, path) {
    final override def path = "%s(%s)".format(URLZipArchive.this, super.path)
  }

  /** A directory archive entry */
  private final class DirEntry(name: String, path: String) extends Entry(name, path) {
    val entries: Map[String, Entry] = new HashMap()
    var entry: ZipEntry = _

    override def isDirectory = true
    override def input = throw new Error("cannot read directories")

    override def lastModified: Long =
      if (entry ne null) entry.getTime() else super.lastModified

    override def iterator: Iterator[AbstractFile] = entries.valuesIterator

    override def lookupName(name: String, directory: Boolean): AbstractFile =
      entries.get(if (directory) name + "/" else name) match {
        case Some(dir) => dir
        case None => null
      }
  }

  final class FileEntry(name: String, path: String,
                        val entry: ZipEntry, val in: InputStream)
        extends Entry(name, path) {
    override def lastModified: Long = entry.getTime()
    override def input = in
    override def sizeOption = Some(entry.getSize().toInt)
  }
}