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

                       
 
                   
                                                                 
                                                          
                                              
 






                                                                               
   
                   
                                                              

     

                                                                            
     
                                        
                                                         


                                         

                                                                               
     

                                                        

     

                                                   
     
                                                            







                                                                
 
                   
 

                                       
 

                                                                              
 




                                                                                              
 
                                  



                                           

                                      
 
                                                                                                                         
                                                                            
                                                

                                                           
   



                                                                                    
                                     
 
                                   



                                                                        

     

                                                                                           

                                                              

   





                                                                                    
 








                                           
   
 


                                                                               
 


                                                                            
     


                                                
                                       
                                                          


                           

   


                                                    
                  


                                          
                                               
 

                                              
   
 
 

                                                                                                               

                                                    
   

 


                                                                                  
 











                                     
 
           
   


                                  
                                           
                    

                                              
 
                                                                             




                             
                             
 
/* NSC -- new Scala compiler
 * Copyright 2005-2011 LAMP/EPFL
 * @author  Paul Phillips
 */

package scala.tools.nsc
package io

import java.net.URL
import java.io.{ IOException, InputStream, ByteArrayInputStream }
import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream }
import scala.collection.{ immutable, mutable }

/** An abstraction for zip files and streams.  Everything is written the way
 *  it is for performance: we come through here a lot on every run.  Be careful
 *  about changing it.
 *
 *  @author  Philippe Altherr (original version)
 *  @author  Paul Phillips (this one)
 *  @version 2.0,
 */
object ZipArchive {
  def fromPath(path: Path): ZipArchive = fromFile(path.toFile)

  /**
   * @param   file  a File
   * @return  A ZipArchive if `file` is a readable zip file, otherwise null.
   */
  def fromFile(file: File): ZipArchive =
    try new FileZipArchive(file, new ZipFile(file.jfile))
    catch { case _: IOException => null }

  /**
   * @param   zipFile  a ZipFile
   * @return  A ZipArchive if `zipFile` is a readable zip file, otherwise null.
   */
  def fromArchive(zipFile: ZipFile): ZipArchive =
    new FileZipArchive(File(zipFile.getName()), zipFile)

  /**
   * @param   url  the url of a zip file
   * @return  A ZipArchive backed by the given url.
   */
  def fromURL(url: URL): ZipArchive = new URLZipArchive(url)

  class ZipFileIterable(z: ZipFile) extends Iterable[ZipEntry] {
    def iterator = new Iterator[ZipEntry] {
      val enum    = z.entries()
      def hasNext = enum.hasMoreElements
      def next    = enum.nextElement
    }
  }
}
import ZipArchive._

trait ZipArchive extends AbstractFile {
  self =>

  // The root of this archive, populated lazily.  Implemented by File and URL.
  def root: DirEntry

  // AbstractFile members with common implementations in File and URL
  override def lookupName(name: String, directory: Boolean) = root.lookupName(name, directory)
  override def lookupNameUnchecked(name: String, directory: Boolean) = unsupported
  override def iterator = root.iterator
  override def isDirectory = true

  // Collected Paths -> DirEntries
  private val dirs = newHashMap[DirEntry]()
  dirs("/") = new RootEntry
  protected def rootDir = dirs("/")

  // Override if there's one available
  def zipFile: ZipFile = null

  sealed abstract class Entry(override val container: DirEntry, path: String) extends VirtualFile(baseName(path), path) {
    // have to keep this apparently for compat with sbt's compiler-interface
    final def getArchive: ZipFile = self.zipFile
    override def underlyingSource = Some(self)
    final override def path = self + "(" + super.path + ")"
  }
  class RootEntry extends DirEntry(rootDir, "/") { }
  class DirEntry(container: DirEntry, path: String) extends Entry(container, path) {
    def this(path: String) = this(getDir(dirName(path)), path)

    val entries = newHashMap[Entry]()

    override def isDirectory = true
    override def iterator = entries.valuesIterator
    override def lookupName(name: String, directory: Boolean): Entry = {
      if (directory) entries(name + "/")
      else entries(name)
    }
  }
  class FileEntry(path: String, val zipEntry: ZipEntry) extends Entry(getDir(path), path) {
    override def input        = getArchive getInputStream zipEntry
    override def lastModified = zipEntry.getTime()
    override def sizeOption   = Some(zipEntry.getSize().toInt)
  }

  private def dirName(path: String)  = splitPath(path, true)
  private def baseName(path: String) = splitPath(path, false)
  private def splitPath(path0: String, front: Boolean): String = {
    val start = path0.length - 1
    val path  = if (path0.charAt(start) == '/') path0.substring(0, start) else path0
    var i     = path.length - 1

    while (i >= 0 && path.charAt(i) != '/')
      i -= 1

    if (i < 0)
      if (front) "/"
      else path
    else
      if (front) path.substring(0, i + 1)
      else path.substring(i + 1)
  }

  // Not using withDefault to be certain of performance.
  private def newHashMap[T >: Null]() =
    new mutable.HashMap[String, T] { override def default(key: String) = null }

  /** If the given path entry has already been created, return the DirEntry.
   *  Otherwise create it and update both dirs and the parent's dirEntries.
   *  Not using getOrElseUpdate to be sure of performance.
   */
  private def getDir(path: String): DirEntry = {
    if (dirs contains path) dirs(path)
    else {
      val newEntry = new DirEntry(path)
      newEntry.container.entries(newEntry.name) = newEntry
      dirs(path) = newEntry
      newEntry
    }
  }

  protected final def addZipEntry(entry: ZipEntry) {
    val path = entry.getName
    if (entry.isDirectory) {
      getDir(path)
    }
    else {
      val parent   = getDir(dirName(path))
      val newEntry = new FileEntry(path, entry)

      parent.entries(newEntry.name) = newEntry
    }
  }
}

final class FileZipArchive(file: File, override val zipFile: ZipFile) extends PlainFile(file) with ZipArchive {
  lazy val root: DirEntry = {
    new ZipFileIterable(zipFile) foreach addZipEntry
    rootDir
  }
}

final class URLZipArchive(url: URL) extends AbstractFile with ZipArchive {
  lazy val root: DirEntry = {
    val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input)))

    @annotation.tailrec def loop() {
      if (in.available != 0) {
        val entry = in.getNextEntry()
        if (entry != null) {
          addZipEntry(entry)
          in.closeEntry()
          loop()
        }
      }
    }
    try loop()
    finally in.close()

    rootDir
  }

  def name: String = url.getFile()
  def path: String = url.getPath()
  def input: InputStream = url.openStream()
  def lastModified =
    try url.openConnection().getLastModified()
    catch { case _: IOException => 0 }

  /** Methods we don't support but have to implement because of the design */
  def absolute  = this
  def file      = null
  def create()  = unsupported
  def delete()  = unsupported
  def output    = unsupported
  def container = unsupported
}