All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ammonite.compiler.internal.CustomURLZipArchive.scala Maven / Gradle / Ivy

The newest version!
package ammonite.compiler.internal

import java.io.{ByteArrayInputStream, InputStream}
import java.util.zip.{ZipEntry, ZipFile, ZipInputStream}

import scala.reflect.io.{AbstractFile, Streamable, VirtualFile}

// Adapted from scala.reflect.io.URLZipArchive.
// The only relevant change here is that it doesn't rely on
// java.util.zip.ZipEntry.getSize to get the actual size of
// a zip entry, as it is usually -1 (size unknown).
// Plus allDirsByDottedName was added for scala 2.12, and dirs is memo-ized via a lazy val.
final class CustomURLZipArchive(val url: java.net.URL) extends AbstractFile with Equals { self =>

  override def toURL = url

  def file: java.io.File = null

  private def dirPath(path: String) = path.split('/').toSeq.filter(_.nonEmpty)
  private def dirName(path: String) = splitPath(path, front = true)
  private def baseName(path: String) = splitPath(path, front = false)
  private def splitPath(path0: String, front: Boolean): String = {
    val isDir = path0.charAt(path0.length - 1) == '/'
    val path = if (isDir) path0.substring(0, path0.length - 1) else path0
    val idx = path.lastIndexOf('/')

    if (idx < 0)
      if (front) "/"
      else path
    else if (front) path.substring(0, idx + 1)
    else path.substring(idx + 1)
  }
  override def underlyingSource = Some(this)
  def isDirectory = true
  def lookupName(name: String, directory: Boolean) = unsupported()
  def lookupNameUnchecked(name: String, directory: Boolean) = unsupported()
  def create() = unsupported()
  def delete() = unsupported()
  def output = unsupported()
  def container = unsupported()
  def absolute = unsupported()

  abstract class Entry(path: String) extends VirtualFile(baseName(path), path) {
    // have to keep this name for compat with sbt's compiler-interface
    def getArchive: ZipFile = null
    override def underlyingSource = Some(self)
    override def toString = self.path + "(" + path + ")"
  }

  final class DirEntry(path: String) extends Entry(path) {
    val entries = collection.mutable.HashMap[String, Entry]()

    override def isDirectory = true
    override def iterator: Iterator[Entry] = entries.valuesIterator
    override def lookupName(name: String, directory: Boolean): Entry = {
      if (directory) entries(name + "/")
      else entries(name)
    }
  }

  private object Entry {

    final class EmptyFile(name: String) extends Entry(name) {
      override def toByteArray: Array[Byte] = null
      override def sizeOption = Some(0)
    }

    final class File(name: String, content: Array[Byte]) extends Entry(name) {
      override val toByteArray: Array[Byte] = content
      private def initContent(): Unit = {
        // this initializes VirtualFile.content (private field),
        // which is required in 2.12.11, for the newly added AbstractFile.unsafeToByteArray
        val os = output
        os.write(content)
        os.close()
      }
      if (CustomURLZipArchive.is2_12_11)
        initContent()
      override def sizeOption = Some(toByteArray.length)
      // Seems we have to provide that one in 2.13
      override def input: InputStream =
        new ByteArrayInputStream(content)
    }
  }

  private def ensureDir(
      dirs: collection.mutable.Map[Seq[String], DirEntry],
      path: String,
      zipEntry: ZipEntry
  ): DirEntry =
    dirs.get(dirPath(path)) match {
      case Some(v) => v
      case None =>
        val parent = ensureDir(dirs, dirName(path), null)
        val dir = new DirEntry(path)
        parent.entries(baseName(path)) = dir
        dirs(dirPath(path)) = dir
        dir
    }

  private def getDir(
      dirs: collection.mutable.Map[Seq[String], DirEntry],
      entry: ZipEntry
  ): DirEntry = {
    if (entry.isDirectory) ensureDir(dirs, entry.getName, entry)
    else ensureDir(dirs, dirName(entry.getName), null)
  }

  private lazy val dirs: Map[Seq[String], DirEntry] = {
    val root = new DirEntry("/")
    val dirs = collection.mutable.HashMap[Seq[String], DirEntry](Nil -> root)
    val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input)))

    @annotation.tailrec
    def loop() {
      val zipEntry = in.getNextEntry

      if (zipEntry != null) {
        val dir = getDir(dirs, zipEntry)
        if (!zipEntry.isDirectory) {
          val f =
            if (zipEntry.getSize == 0)
              new Entry.EmptyFile(zipEntry.getName)
            else {
              val content = {
                val baos = new java.io.ByteArrayOutputStream
                val b = Array.ofDim[Byte](16 * 1024)

                def loop() {
                  val read = in.read(b, 0, b.length)
                  if (read >= 0) {
                    baos.write(b, 0, read)
                    loop()
                  }
                }
                loop()

                baos.toByteArray
              }
              new Entry.File(zipEntry.getName, content)
            }
          dir.entries(f.name) = f
        }
        in.closeEntry()
        loop()
      }
    }

    loop()
    dirs.toMap
  }

  def iterator: Iterator[AbstractFile] =
    dirs(Nil).iterator

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

  override def canEqual(other: Any) = other.isInstanceOf[CustomURLZipArchive]
  override def hashCode() = url.hashCode
  override def equals(that: Any) = that match {
    case x: CustomURLZipArchive => url == x.url
    case _ => false
  }

  def allDirsByDottedName: collection.Map[String, DirEntry] = {
    dirs.map {
      case (k, v) =>
        k.mkString(".") -> v
    }
  }

}

object CustomURLZipArchive {
  def closeZipFile = false
  private val is2_12_11 = scala.util.Properties.versionNumberString == "2.12.11"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy