summaryrefslogtreecommitdiff
path: root/src/reflect/scala/reflect/io
diff options
context:
space:
mode:
authorJosh Suereth <joshua.suereth@gmail.com>2012-09-18 10:35:26 -0400
committerJosh Suereth <joshua.suereth@gmail.com>2012-09-18 15:36:13 -0400
commitc6dde6a1e884b71ae8bf8e0136287e434594e92b (patch)
tree261fb3b1693806bbcb334e500767304ef67382fc /src/reflect/scala/reflect/io
parenta9f95dc29f366d935604f15a4a99cbfd1a1bd275 (diff)
downloadscala-c6dde6a1e884b71ae8bf8e0136287e434594e92b.tar.gz
scala-c6dde6a1e884b71ae8bf8e0136287e434594e92b.tar.bz2
scala-c6dde6a1e884b71ae8bf8e0136287e434594e92b.zip
Fixes SI-6305 scala.tools.nsc.io split between jars.
* migrates scala.tools.nsc.io portions into scala.reflect.io * marks all classes in scala.reflect.io experimental/internal * rewires src/reflect to use new io locations * creates forwarders in scala.tools.nsci.io package object.
Diffstat (limited to 'src/reflect/scala/reflect/io')
-rw-r--r--src/reflect/scala/reflect/io/AbstractFile.scala264
-rw-r--r--src/reflect/scala/reflect/io/Directory.scala79
-rw-r--r--src/reflect/scala/reflect/io/File.scala197
-rw-r--r--src/reflect/scala/reflect/io/FileOperationException.scala13
-rw-r--r--src/reflect/scala/reflect/io/NoAbstractFile.scala33
-rw-r--r--src/reflect/scala/reflect/io/Path.scala289
-rw-r--r--src/reflect/scala/reflect/io/PlainFile.scala104
-rw-r--r--src/reflect/scala/reflect/io/Streamable.scala127
-rw-r--r--src/reflect/scala/reflect/io/VirtualDirectory.scala72
-rw-r--r--src/reflect/scala/reflect/io/VirtualFile.scala103
-rw-r--r--src/reflect/scala/reflect/io/ZipArchive.scala234
11 files changed, 1515 insertions, 0 deletions
diff --git a/src/reflect/scala/reflect/io/AbstractFile.scala b/src/reflect/scala/reflect/io/AbstractFile.scala
new file mode 100644
index 0000000000..e32207c58c
--- /dev/null
+++ b/src/reflect/scala/reflect/io/AbstractFile.scala
@@ -0,0 +1,264 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+
+package scala.reflect
+package io
+
+import java.io.{ FileOutputStream, IOException, InputStream, OutputStream, BufferedOutputStream }
+import java.io.{ File => JFile }
+import java.net.URL
+import scala.collection.mutable.ArrayBuffer
+
+/**
+ * An abstraction over files for use in the reflection/compiler libraries.
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ *
+ * @author Philippe Altherr
+ * @version 1.0, 23/03/2004
+ */
+object AbstractFile {
+ /** Returns "getFile(new File(path))". */
+ def getFile(path: String): AbstractFile = getFile(File(path))
+ def getFile(path: Path): AbstractFile = getFile(path.toFile)
+
+ /**
+ * If the specified File exists and is a regular file, returns an
+ * abstract regular file backed by it. Otherwise, returns <code>null</code>.
+ */
+ def getFile(file: File): AbstractFile =
+ if (file.isFile) new PlainFile(file) else null
+
+ /** Returns "getDirectory(new File(path))". */
+ def getDirectory(path: Path): AbstractFile = getDirectory(path.toFile)
+
+ /**
+ * If the specified File exists and is either a directory or a
+ * readable zip or jar archive, returns an abstract directory
+ * backed by it. Otherwise, returns <code>null</code>.
+ *
+ * @param file ...
+ * @return ...
+ */
+ def getDirectory(file: File): AbstractFile =
+ if (file.isDirectory) new PlainFile(file)
+ else if (file.isFile && Path.isExtensionJarOrZip(file.jfile)) ZipArchive fromFile file
+ else null
+
+ /**
+ * If the specified URL exists and is a readable zip or jar archive,
+ * returns an abstract directory backed by it. Otherwise, returns
+ * <code>null</code>.
+ *
+ * @param file ...
+ * @return ...
+ */
+ def getURL(url: URL): AbstractFile = {
+ if (url == null || !Path.isExtensionJarOrZip(url.getPath)) null
+ else ZipArchive fromURL url
+ }
+}
+
+/**
+ * <p>
+ * This class and its children serve to unify handling of files and
+ * directories. These files and directories may or may not have some
+ * real counter part within the file system. For example, some file
+ * handles reference files within a zip archive or virtual ones
+ * that exist only in memory.
+ * </p>
+ * <p>
+ * Every abstract file has a path (i.e. a full name) and a name
+ * (i.e. a short name) and may be backed by some real File. There are
+ * two different kinds of abstract files: regular files and
+ * directories. Regular files may be read and have a last modification
+ * time. Directories may list their content and look for subfiles with
+ * a specified name or path and of a specified kind.
+ * </p>
+ * <p>
+ * The interface does <b>not</b> allow to access the content.
+ * The class <code>symtab.classfile.AbstractFileReader</code> accesses
+ * bytes, knowing that the character set of classfiles is UTF-8. For
+ * all other cases, the class <code>SourceFile</code> is used, which honors
+ * <code>global.settings.encoding.value</code>.
+ * </p>
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+abstract class AbstractFile extends Iterable[AbstractFile] {
+
+ /** Returns the name of this abstract file. */
+ def name: String
+
+ /** Returns the path of this abstract file. */
+ def path: String
+
+ /** Returns the path of this abstract file in a canonical form. */
+ def canonicalPath: String = if (file == null) path else file.getCanonicalPath
+
+ /** Checks extension case insensitively. */
+ def hasExtension(other: String) = extension == other.toLowerCase
+ private lazy val extension: String = Path.extension(name)
+
+ /** The absolute file, if this is a relative file. */
+ def absolute: AbstractFile
+
+ /** Returns the containing directory of this abstract file */
+ def container : AbstractFile
+
+ /** Returns the underlying File if any and null otherwise. */
+ def file: JFile
+
+ /** An underlying source, if known. Mostly, a zip/jar file. */
+ def underlyingSource: Option[AbstractFile] = None
+
+ /** Does this abstract file denote an existing file? */
+ def exists: Boolean = (file eq null) || file.exists
+
+ /** Does this abstract file represent something which can contain classfiles? */
+ def isClassContainer = isDirectory || (file != null && (extension == "jar" || extension == "zip"))
+
+ /** Create a file on disk, if one does not exist already. */
+ def create(): Unit
+
+ /** Delete the underlying file or directory (recursively). */
+ def delete(): Unit
+
+ /** Is this abstract file a directory? */
+ def isDirectory: Boolean
+
+ /** Returns the time that this abstract file was last modified. */
+ def lastModified: Long
+
+ /** returns an input stream so the file can be read */
+ def input: InputStream
+
+ /** Returns an output stream for writing the file */
+ def output: OutputStream
+
+ /** Returns a buffered output stream for writing the file - defaults to out */
+ def bufferedOutput: BufferedOutputStream = new BufferedOutputStream(output)
+
+ /** size of this file if it is a concrete file. */
+ def sizeOption: Option[Int] = None
+
+ def toURL: URL = if (file == null) null else file.toURI.toURL
+
+ /** Returns contents of file (if applicable) in a Char array.
+ * warning: use <code>Global.getSourceFile()</code> to use the proper
+ * encoding when converting to the char array.
+ */
+ @throws(classOf[IOException])
+ def toCharArray = new String(toByteArray).toCharArray
+
+ /** Returns contents of file (if applicable) in a byte array.
+ */
+ @throws(classOf[IOException])
+ def toByteArray: Array[Byte] = {
+ val in = input
+ var rest = sizeOption.getOrElse(0)
+ val arr = new Array[Byte](rest)
+ while (rest > 0) {
+ val res = in.read(arr, arr.length - rest, rest)
+ if (res == -1)
+ throw new IOException("read error")
+ rest -= res
+ }
+ in.close()
+ arr
+ }
+
+ /** Returns all abstract subfiles of this abstract directory. */
+ def iterator: Iterator[AbstractFile]
+
+ /** Returns the abstract file in this abstract directory with the specified
+ * name. If there is no such file, returns <code>null</code>. The argument
+ * <code>directory</code> tells whether to look for a directory or
+ * a regular file.
+ */
+ def lookupName(name: String, directory: Boolean): AbstractFile
+
+ /** Returns an abstract file with the given name. It does not
+ * check that it exists.
+ */
+ def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile
+
+ /** Returns the abstract file in this abstract directory with the specified
+ * path relative to it, If there is no such file, returns null. The argument
+ * <code>directory</code> tells whether to look for a directory or a regular
+ * file.
+ *
+ * @param path ...
+ * @param directory ...
+ * @return ...
+ */
+ def lookupPath(path: String, directory: Boolean): AbstractFile = {
+ lookup((f, p, dir) => f.lookupName(p, dir), path, directory)
+ }
+
+ /** Return an abstract file that does not check that `path` denotes
+ * an existing file.
+ */
+ def lookupPathUnchecked(path: String, directory: Boolean): AbstractFile = {
+ lookup((f, p, dir) => f.lookupNameUnchecked(p, dir), path, directory)
+ }
+
+ private def lookup(getFile: (AbstractFile, String, Boolean) => AbstractFile,
+ path0: String,
+ directory: Boolean): AbstractFile = {
+ val separator = java.io.File.separatorChar
+ // trim trailing '/'s
+ val path: String = if (path0.last == separator) path0 dropRight 1 else path0
+ val length = path.length()
+ assert(length > 0 && !(path.last == separator), path)
+ var file = this
+ var start = 0
+ while (true) {
+ val index = path.indexOf(separator, start)
+ assert(index < 0 || start < index, ((path, directory, start, index)))
+ val name = path.substring(start, if (index < 0) length else index)
+ file = getFile(file, name, if (index < 0) directory else true)
+ if ((file eq null) || index < 0) return file
+ start = index + 1
+ }
+ file
+ }
+
+ private def fileOrSubdirectoryNamed(name: String, isDir: Boolean): AbstractFile = {
+ val lookup = lookupName(name, isDir)
+ if (lookup != null) lookup
+ else {
+ val jfile = new JFile(file, name)
+ if (isDir) jfile.mkdirs() else jfile.createNewFile()
+ new PlainFile(jfile)
+ }
+ }
+
+ /**
+ * Get the file in this directory with the given name,
+ * creating an empty file if it does not already existing.
+ */
+ def fileNamed(name: String): AbstractFile = {
+ assert(isDirectory, "Tried to find '%s' in '%s' but it is not a directory".format(name, path))
+ fileOrSubdirectoryNamed(name, false)
+ }
+
+ /**
+ * Get the subdirectory with a given name, creating it if it
+ * does not already exist.
+ */
+ def subdirectoryNamed(name: String): AbstractFile = {
+ assert (isDirectory, "Tried to find '%s' in '%s' but it is not a directory".format(name, path))
+ fileOrSubdirectoryNamed(name, true)
+ }
+
+ protected def unsupported(): Nothing = unsupported(null)
+ protected def unsupported(msg: String): Nothing = throw new UnsupportedOperationException(msg)
+
+ /** Returns the path of this abstract file. */
+ override def toString() = path
+
+}
diff --git a/src/reflect/scala/reflect/io/Directory.scala b/src/reflect/scala/reflect/io/Directory.scala
new file mode 100644
index 0000000000..a24534137d
--- /dev/null
+++ b/src/reflect/scala/reflect/io/Directory.scala
@@ -0,0 +1,79 @@
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+package scala.reflect
+package io
+
+import java.io.{ File => JFile }
+/**
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+object Directory {
+ import scala.util.Properties.{ tmpDir, userHome, userDir }
+
+ private def normalizePath(s: String) = Some(apply(Path(s).normalize))
+ def Current: Option[Directory] = if (userDir == "") None else normalizePath(userDir)
+ def Home: Option[Directory] = if (userHome == "") None else normalizePath(userHome)
+ def TmpDir: Option[Directory] = if (tmpDir == "") None else normalizePath(tmpDir)
+
+ def apply(path: Path): Directory = path.toDirectory
+
+ // Like File.makeTemp but creates a directory instead
+ def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null): Directory = {
+ val path = File.makeTemp(prefix, suffix, dir)
+ path.delete()
+ path.createDirectory()
+ }
+}
+import Path._
+
+/** An abstraction for directories.
+ *
+ * @author Paul Phillips
+ * @since 2.8
+ *
+ * ''Note: This is library is considered experimental and should not be used unless you know what you are doing.''
+ */
+class Directory(jfile: JFile) extends Path(jfile) {
+ override def toAbsolute: Directory = if (isAbsolute) this else super.toAbsolute.toDirectory
+ override def toDirectory: Directory = this
+ override def toFile: File = new File(jfile)
+ override def isValid = jfile.isDirectory() || !jfile.exists()
+ override def normalize: Directory = super.normalize.toDirectory
+
+ /** An iterator over the contents of this directory.
+ */
+ def list: Iterator[Path] =
+ jfile.listFiles match {
+ case null => Iterator.empty
+ case xs => xs.iterator map Path.apply
+ }
+
+ def dirs: Iterator[Directory] = list collect { case x: Directory => x }
+ def files: Iterator[File] = list collect { case x: File => x }
+
+ override def walkFilter(cond: Path => Boolean): Iterator[Path] =
+ list filter cond flatMap (_ walkFilter cond)
+
+ def deepDirs: Iterator[Directory] = Path.onlyDirs(deepList())
+ def deepFiles: Iterator[File] = Path.onlyFiles(deepList())
+
+ /** If optional depth argument is not given, will recurse
+ * until it runs out of contents.
+ */
+ def deepList(depth: Int = -1): Iterator[Path] =
+ if (depth < 0) list ++ (dirs flatMap (_ deepList (depth)))
+ else if (depth == 0) Iterator.empty
+ else list ++ (dirs flatMap (_ deepList (depth - 1)))
+
+ /** An iterator over the directories underneath this directory,
+ * to the (optionally) given depth.
+ */
+ def subdirs(depth: Int = 1): Iterator[Directory] =
+ deepList(depth) collect { case x: Directory => x }
+}
diff --git a/src/reflect/scala/reflect/io/File.scala b/src/reflect/scala/reflect/io/File.scala
new file mode 100644
index 0000000000..9e306371f7
--- /dev/null
+++ b/src/reflect/scala/reflect/io/File.scala
@@ -0,0 +1,197 @@
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+
+package scala.reflect
+package io
+
+import java.io.{
+ FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter,
+ BufferedInputStream, BufferedOutputStream, IOException, PrintStream, PrintWriter, Closeable => JCloseable }
+import java.io.{ File => JFile }
+import java.nio.channels.{ Channel, FileChannel }
+import scala.io.Codec
+import scala.language.{reflectiveCalls, implicitConversions}
+/**
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+object File {
+ def pathSeparator = java.io.File.pathSeparator
+ def separator = java.io.File.separator
+
+ def apply(path: Path)(implicit codec: Codec) = new File(path.jfile)(codec)
+
+ // Create a temporary file, which will be deleted upon jvm exit.
+ def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null) = {
+ val jfile = java.io.File.createTempFile(prefix, suffix, dir)
+ jfile.deleteOnExit()
+ apply(jfile)
+ }
+
+ type HasClose = { def close(): Unit }
+
+ def closeQuietly(target: HasClose) {
+ try target.close() catch { case e: IOException => }
+ }
+ def closeQuietly(target: JCloseable) {
+ try target.close() catch { case e: IOException => }
+ }
+
+ // this is a workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6503430
+ // we are using a static initializer to statically initialize a java class so we don't
+ // trigger java.lang.InternalErrors later when using it concurrently. We ignore all
+ // the exceptions so as not to cause spurious failures when no write access is available,
+ // e.g. google app engine.
+ //
+ // XXX need to put this behind a setting.
+ //
+ // try {
+ // import Streamable.closing
+ // val tmp = java.io.File.createTempFile("bug6503430", null, null)
+ // try closing(new FileInputStream(tmp)) { in =>
+ // val inc = in.getChannel()
+ // closing(new FileOutputStream(tmp, true)) { out =>
+ // out.getChannel().transferFrom(inc, 0, 0)
+ // }
+ // }
+ // finally tmp.delete()
+ // }
+ // catch {
+ // case _: IllegalArgumentException | _: IllegalStateException | _: IOException | _: SecurityException => ()
+ // }
+}
+import File._
+import Path._
+
+/** An abstraction for files. For character data, a Codec
+ * can be supplied at either creation time or when a method
+ * involving character data is called (with the latter taking
+ * precedence if supplied.) If neither is available, the value
+ * of scala.io.Codec.default is used.
+ *
+ * @author Paul Phillips
+ * @since 2.8
+ *
+ * ''Note: This is library is considered experimental and should not be used unless you know what you are doing.''
+ */
+class File(jfile: JFile)(implicit constructorCodec: Codec) extends Path(jfile) with Streamable.Chars {
+ override val creationCodec = constructorCodec
+ def withCodec(codec: Codec): File = new File(jfile)(codec)
+
+ override def addExtension(ext: String): File = super.addExtension(ext).toFile
+ override def toAbsolute: File = if (isAbsolute) this else super.toAbsolute.toFile
+ override def toDirectory: Directory = new Directory(jfile)
+ override def toFile: File = this
+ override def normalize: File = super.normalize.toFile
+ override def isValid = jfile.isFile() || !jfile.exists()
+ override def length = super[Path].length
+ override def walkFilter(cond: Path => Boolean): Iterator[Path] =
+ if (cond(this)) Iterator.single(this) else Iterator.empty
+
+ /** Obtains an InputStream. */
+ def inputStream() = new FileInputStream(jfile)
+
+ /** Obtains a OutputStream. */
+ def outputStream(append: Boolean = false) = new FileOutputStream(jfile, append)
+ def bufferedOutput(append: Boolean = false) = new BufferedOutputStream(outputStream(append))
+ def printStream(append: Boolean = false) = new PrintStream(outputStream(append), true)
+
+ /** Obtains an OutputStreamWriter wrapped around a FileOutputStream.
+ * This should behave like a less broken version of java.io.FileWriter,
+ * in that unlike the java version you can specify the encoding.
+ */
+ def writer(): OutputStreamWriter = writer(false)
+ def writer(append: Boolean): OutputStreamWriter = writer(append, creationCodec)
+ def writer(append: Boolean, codec: Codec): OutputStreamWriter =
+ new OutputStreamWriter(outputStream(append), codec.charSet)
+
+ /** Wraps a BufferedWriter around the result of writer().
+ */
+ def bufferedWriter(): BufferedWriter = bufferedWriter(false)
+ def bufferedWriter(append: Boolean): BufferedWriter = bufferedWriter(append, creationCodec)
+ def bufferedWriter(append: Boolean, codec: Codec): BufferedWriter =
+ new BufferedWriter(writer(append, codec))
+
+ def printWriter(): PrintWriter = new PrintWriter(bufferedWriter(), true)
+ def printWriter(append: Boolean): PrintWriter = new PrintWriter(bufferedWriter(append), true)
+
+ /** Creates a new file and writes all the Strings to it. */
+ def writeAll(strings: String*): Unit = {
+ val out = bufferedWriter()
+ try strings foreach (out write _)
+ finally out.close()
+ }
+
+ def writeBytes(bytes: Array[Byte]): Unit = {
+ val out = bufferedOutput()
+ try out write bytes
+ finally out.close()
+ }
+
+ def appendAll(strings: String*): Unit = {
+ val out = bufferedWriter(append = true)
+ try strings foreach (out write _)
+ finally out.close()
+ }
+
+ /** Calls println on each string (so it adds a newline in the PrintWriter fashion.) */
+ def printlnAll(strings: String*): Unit = {
+ val out = printWriter()
+ try strings foreach (out println _)
+ finally out.close()
+ }
+
+ def safeSlurp(): Option[String] =
+ try Some(slurp())
+ catch { case _: IOException => None }
+
+ def copyTo(destPath: Path, preserveFileDate: Boolean = false): Boolean = {
+ val CHUNK = 1024 * 1024 * 16 // 16 MB
+ val dest = destPath.toFile
+ if (!isValid) fail("Source %s is not a valid file." format name)
+ if (this.normalize == dest.normalize) fail("Source and destination are the same.")
+ if (!dest.parent.exists) fail("Destination cannot be created.")
+ if (dest.exists && !dest.canWrite) fail("Destination exists but is not writable.")
+ if (dest.isDirectory) fail("Destination exists but is a directory.")
+
+ lazy val in_s = inputStream()
+ lazy val out_s = dest.outputStream()
+ lazy val in = in_s.getChannel()
+ lazy val out = out_s.getChannel()
+
+ try {
+ val size = in.size()
+ var pos, count = 0L
+ while (pos < size) {
+ count = (size - pos) min CHUNK
+ pos += out.transferFrom(in, pos, count)
+ }
+ }
+ finally List[HasClose](out, out_s, in, in_s) foreach closeQuietly
+
+ if (this.length != dest.length)
+ fail("Failed to completely copy %s to %s".format(name, dest.name))
+
+ if (preserveFileDate)
+ dest.lastModified = this.lastModified
+
+ true
+ }
+
+ /** Reflection since we're into the java 6+ API.
+ */
+ def setExecutable(executable: Boolean, ownerOnly: Boolean = true): Boolean = {
+ type JBoolean = java.lang.Boolean
+ val method =
+ try classOf[JFile].getMethod("setExecutable", classOf[Boolean], classOf[Boolean])
+ catch { case _: NoSuchMethodException => return false }
+
+ try method.invoke(jfile, executable: JBoolean, ownerOnly: JBoolean).asInstanceOf[JBoolean].booleanValue
+ catch { case _: Exception => false }
+ }
+}
diff --git a/src/reflect/scala/reflect/io/FileOperationException.scala b/src/reflect/scala/reflect/io/FileOperationException.scala
new file mode 100644
index 0000000000..6bce799cea
--- /dev/null
+++ b/src/reflect/scala/reflect/io/FileOperationException.scala
@@ -0,0 +1,13 @@
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+
+package scala.reflect
+package io
+/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+case class FileOperationException(msg: String) extends RuntimeException(msg)
diff --git a/src/reflect/scala/reflect/io/NoAbstractFile.scala b/src/reflect/scala/reflect/io/NoAbstractFile.scala
new file mode 100644
index 0000000000..d503328a37
--- /dev/null
+++ b/src/reflect/scala/reflect/io/NoAbstractFile.scala
@@ -0,0 +1,33 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.reflect
+package io
+
+import java.io.InputStream
+import java.io.{ File => JFile }
+
+/** A distinguished object so you can avoid both null
+ * and Option.
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+object NoAbstractFile extends AbstractFile {
+ def absolute: AbstractFile = this
+ def container: AbstractFile = this
+ def create(): Unit = ???
+ def delete(): Unit = ???
+ def file: JFile = null
+ def input: InputStream = null
+ def isDirectory: Boolean = false
+ def iterator: Iterator[AbstractFile] = Iterator.empty
+ def lastModified: Long = 0L
+ def lookupName(name: String, directory: Boolean): AbstractFile = null
+ def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = null
+ def name: String = ""
+ def output: java.io.OutputStream = null
+ def path: String = ""
+ override def toByteArray = Array[Byte]()
+}
diff --git a/src/reflect/scala/reflect/io/Path.scala b/src/reflect/scala/reflect/io/Path.scala
new file mode 100644
index 0000000000..9a1ff395a3
--- /dev/null
+++ b/src/reflect/scala/reflect/io/Path.scala
@@ -0,0 +1,289 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.reflect
+package io
+
+import java.io.{
+ FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter,
+ BufferedInputStream, BufferedOutputStream, RandomAccessFile }
+import java.io.{ File => JFile }
+import java.net.{ URI, URL }
+import scala.util.Random.alphanumeric
+import scala.language.implicitConversions
+
+/** An abstraction for filesystem paths. The differences between
+ * Path, File, and Directory are primarily to communicate intent.
+ * Since the filesystem can change at any time, there is no way to
+ * reliably associate Files only with files and so on. Any Path
+ * can be converted to a File or Directory (and thus gain access to
+ * the additional entity specific methods) by calling toFile or
+ * toDirectory, which has no effect on the filesystem.
+ *
+ * Also available are createFile and createDirectory, which attempt
+ * to create the path in question.
+ *
+ * @author Paul Phillips
+ * @since 2.8
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+object Path {
+ def isExtensionJarOrZip(jfile: JFile): Boolean = isExtensionJarOrZip(jfile.getName)
+ def isExtensionJarOrZip(name: String): Boolean = {
+ val ext = extension(name)
+ ext == "jar" || ext == "zip"
+ }
+ def extension(name: String): String = {
+ var i = name.length - 1
+ while (i >= 0 && name.charAt(i) != '.')
+ i -= 1
+
+ if (i < 0) ""
+ else name.substring(i + 1).toLowerCase
+ }
+
+ // not certain these won't be problematic, but looks good so far
+ implicit def string2path(s: String): Path = apply(s)
+ implicit def jfile2path(jfile: JFile): Path = apply(jfile)
+
+ // java 7 style, we don't use it yet
+ // object AccessMode extends Enumeration {
+ // val EXECUTE, READ, WRITE = Value
+ // }
+ // def checkAccess(modes: AccessMode*): Boolean = {
+ // modes foreach {
+ // case EXECUTE => throw new Exception("Unsupported") // can't check in java 5
+ // case READ => if (!jfile.canRead()) return false
+ // case WRITE => if (!jfile.canWrite()) return false
+ // }
+ // true
+ // }
+
+ def onlyDirs(xs: Iterator[Path]): Iterator[Directory] = xs filter (_.isDirectory) map (_.toDirectory)
+ def onlyDirs(xs: List[Path]): List[Directory] = xs filter (_.isDirectory) map (_.toDirectory)
+ def onlyFiles(xs: Iterator[Path]): Iterator[File] = xs filter (_.isFile) map (_.toFile)
+ def onlyFiles(xs: List[Path]): List[File] = xs filter (_.isFile) map (_.toFile)
+
+ def roots: List[Path] = java.io.File.listRoots().toList map Path.apply
+
+ def apply(segments: Seq[String]): Path = apply(segments mkString java.io.File.separator)
+ def apply(path: String): Path = apply(new JFile(path))
+ def apply(jfile: JFile): Path =
+ if (jfile.isFile) new File(jfile)
+ else if (jfile.isDirectory) new Directory(jfile)
+ else new Path(jfile)
+
+ /** Avoiding any shell/path issues by only using alphanumerics. */
+ private[io] def randomPrefix = alphanumeric take 6 mkString ""
+ private[io] def fail(msg: String) = throw FileOperationException(msg)
+}
+import Path._
+
+/** The Path constructor is private so we can enforce some
+ * semantics regarding how a Path might relate to the world.
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+class Path private[io] (val jfile: JFile) {
+ val separator = java.io.File.separatorChar
+ val separatorStr = java.io.File.separator
+
+ // Validation: this verifies that the type of this object and the
+ // contents of the filesystem are in agreement. All objects are
+ // valid except File objects whose path points to a directory and
+ // Directory objects whose path points to a file.
+ def isValid: Boolean = true
+
+ // conversions
+ def toFile: File = new File(jfile)
+ def toDirectory: Directory = new Directory(jfile)
+ def toAbsolute: Path = if (isAbsolute) this else Path(jfile.getAbsolutePath())
+ def toCanonical: Path = Path(jfile.getCanonicalPath())
+ def toURI: URI = jfile.toURI()
+ def toURL: URL = toURI.toURL()
+ /** If this path is absolute, returns it: otherwise, returns an absolute
+ * path made up of root / this.
+ */
+ def toAbsoluteWithRoot(root: Path) = if (isAbsolute) this else root.toAbsolute / this
+
+ /** Creates a new Path with the specified path appended. Assumes
+ * the type of the new component implies the type of the result.
+ */
+ def /(child: Path): Path = if (isEmpty) child else new Path(new JFile(jfile, child.path))
+ def /(child: Directory): Directory = /(child: Path).toDirectory
+ def /(child: File): File = /(child: Path).toFile
+
+ /** If this path is a container, recursively iterate over its contents.
+ * The supplied condition is a filter which is applied to each element,
+ * with that branch of the tree being closed off if it is true. So for
+ * example if the condition is true for some subdirectory, nothing
+ * under that directory will be in the Iterator; but otherwise each
+ * file and subdirectory underneath it will appear.
+ */
+ def walkFilter(cond: Path => Boolean): Iterator[Path] =
+ if (isFile) toFile walkFilter cond
+ else if (isDirectory) toDirectory walkFilter cond
+ else Iterator.empty
+
+ /** Equivalent to walkFilter(_ => false).
+ */
+ def walk: Iterator[Path] = walkFilter(_ => true)
+
+ // identity
+ def name: String = jfile.getName()
+ def path: String = jfile.getPath()
+ def normalize: Path = Path(jfile.getAbsolutePath())
+ def isRootPath: Boolean = roots exists (_ isSame this)
+
+ def resolve(other: Path) = if (other.isAbsolute || isEmpty) other else /(other)
+ def relativize(other: Path) = {
+ assert(isAbsolute == other.isAbsolute, "Paths not of same type: "+this+", "+other)
+
+ def createRelativePath(baseSegs: List[String], otherSegs: List[String]) : String = {
+ (baseSegs, otherSegs) match {
+ case (b :: bs, o :: os) if b == o => createRelativePath(bs, os)
+ case (bs, os) => ((".."+separator)*bs.length)+os.mkString(separatorStr)
+ }
+ }
+
+ Path(createRelativePath(segments, other.segments))
+ }
+
+ // derived from identity
+ def root: Option[Path] = roots find (this startsWith _)
+ def segments: List[String] = (path split separator).toList filterNot (_.length == 0)
+ /**
+ * @return The path of the parent directory, or root if path is already root
+ */
+ def parent: Directory = path match {
+ case "" | "." => Directory("..")
+ case _ =>
+ // the only solution <-- a comment which could have used elaboration
+ if (segments.nonEmpty && segments.last == "..")
+ (path / "..").toDirectory
+ else jfile.getParent match {
+ case null =>
+ if (isAbsolute) toDirectory // it should be a root. BTW, don't need to worry about relative pathed root
+ else Directory(".") // a dir under pwd
+ case x =>
+ Directory(x)
+ }
+ }
+ def parents: List[Directory] = {
+ val p = parent
+ if (p isSame this) Nil else p :: p.parents
+ }
+ // if name ends with an extension (e.g. "foo.jpg") returns the extension ("jpg"), otherwise ""
+ def extension: String = {
+ var i = name.length - 1
+ while (i >= 0 && name.charAt(i) != '.')
+ i -= 1
+
+ if (i < 0) ""
+ else name.substring(i + 1)
+ }
+ // def extension: String = (name lastIndexOf '.') match {
+ // case -1 => ""
+ // case idx => name drop (idx + 1)
+ // }
+ // compares against extensions in a CASE INSENSITIVE way.
+ def hasExtension(ext: String, exts: String*) = {
+ val lower = extension.toLowerCase
+ ext.toLowerCase == lower || exts.exists(_.toLowerCase == lower)
+ }
+ // returns the filename without the extension.
+ def stripExtension: String = name stripSuffix ("." + extension)
+ // returns the Path with the extension.
+ def addExtension(ext: String): Path = Path(path + "." + ext)
+ // changes the existing extension out for a new one, or adds it
+ // if the current path has none.
+ def changeExtension(ext: String): Path = (
+ if (extension == "") addExtension(ext)
+ else Path(path.stripSuffix(extension) + ext)
+ )
+
+ // conditionally execute
+ def ifFile[T](f: File => T): Option[T] = if (isFile) Some(f(toFile)) else None
+ def ifDirectory[T](f: Directory => T): Option[T] = if (isDirectory) Some(f(toDirectory)) else None
+
+ // Boolean tests
+ def canRead = jfile.canRead()
+ def canWrite = jfile.canWrite()
+ def exists = jfile.exists()
+ def notExists = try !jfile.exists() catch { case ex: SecurityException => false }
+
+ def isFile = jfile.isFile()
+ def isDirectory = jfile.isDirectory()
+ def isAbsolute = jfile.isAbsolute()
+ def isHidden = jfile.isHidden()
+ def isEmpty = path.length == 0
+
+ // Information
+ def lastModified = jfile.lastModified()
+ def lastModified_=(time: Long) = jfile setLastModified time // should use setXXX function?
+ def length = jfile.length()
+
+ // Boolean path comparisons
+ def endsWith(other: Path) = segments endsWith other.segments
+ def startsWith(other: Path) = segments startsWith other.segments
+ def isSame(other: Path) = toCanonical == other.toCanonical
+ def isFresher(other: Path) = lastModified > other.lastModified
+
+ // creations
+ def createDirectory(force: Boolean = true, failIfExists: Boolean = false): Directory = {
+ val res = if (force) jfile.mkdirs() else jfile.mkdir()
+ if (!res && failIfExists && exists) fail("Directory '%s' already exists." format name)
+ else if (isDirectory) toDirectory
+ else new Directory(jfile)
+ }
+ def createFile(failIfExists: Boolean = false): File = {
+ val res = jfile.createNewFile()
+ if (!res && failIfExists && exists) fail("File '%s' already exists." format name)
+ else if (isFile) toFile
+ else new File(jfile)
+ }
+
+ // deletions
+ def delete() = jfile.delete()
+ def deleteIfExists() = if (jfile.exists()) delete() else false
+
+ /** Deletes the path recursively. Returns false on failure.
+ * Use with caution!
+ */
+ def deleteRecursively(): Boolean = deleteRecursively(jfile)
+ private def deleteRecursively(f: JFile): Boolean = {
+ if (f.isDirectory) f.listFiles match {
+ case null =>
+ case xs => xs foreach deleteRecursively
+ }
+ f.delete()
+ }
+
+ def truncate() =
+ isFile && {
+ val raf = new RandomAccessFile(jfile, "rw")
+ raf setLength 0
+ raf.close()
+ length == 0
+ }
+
+ def touch(modTime: Long = System.currentTimeMillis) = {
+ createFile()
+ if (isFile)
+ lastModified = modTime
+ }
+
+ // todo
+ // def copyTo(target: Path, options ...): Boolean
+ // def moveTo(target: Path, options ...): Boolean
+
+ override def toString() = path
+ override def equals(other: Any) = other match {
+ case x: Path => path == x.path
+ case _ => false
+ }
+ override def hashCode() = path.hashCode()
+}
diff --git a/src/reflect/scala/reflect/io/PlainFile.scala b/src/reflect/scala/reflect/io/PlainFile.scala
new file mode 100644
index 0000000000..14cb09317c
--- /dev/null
+++ b/src/reflect/scala/reflect/io/PlainFile.scala
@@ -0,0 +1,104 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+
+package scala.reflect
+package io
+
+import java.io.{ FileInputStream, FileOutputStream, IOException }
+import PartialFunction._
+/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+object PlainFile {
+ /**
+ * If the specified File exists, returns an abstract file backed
+ * by it. Otherwise, returns null.
+ */
+ def fromPath(file: Path): PlainFile =
+ if (file.isDirectory) new PlainDirectory(file.toDirectory)
+ else if (file.isFile) new PlainFile(file)
+ else null
+}
+/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+class PlainDirectory(givenPath: Directory) extends PlainFile(givenPath) {
+ override def isDirectory = true
+ override def iterator = givenPath.list filter (_.exists) map (x => new PlainFile(x))
+ override def delete(): Unit = givenPath.deleteRecursively()
+}
+
+/** This class implements an abstract file backed by a File.
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+class PlainFile(val givenPath: Path) extends AbstractFile {
+ assert(path ne null)
+
+ val file = givenPath.jfile
+ override def underlyingSource = Some(this)
+
+ private val fpath = givenPath.toAbsolute
+
+ /** Returns the name of this abstract file. */
+ def name = givenPath.name
+
+ /** Returns the path of this abstract file. */
+ def path = givenPath.path
+
+ /** The absolute file. */
+ def absolute = new PlainFile(givenPath.toAbsolute)
+
+ override def container: AbstractFile = new PlainFile(givenPath.parent)
+ override def input = givenPath.toFile.inputStream()
+ override def output = givenPath.toFile.outputStream()
+ override def sizeOption = Some(givenPath.length.toInt)
+
+ override def toString = path
+ override def hashCode(): Int = fpath.hashCode
+ override def equals(that: Any): Boolean = that match {
+ case x: PlainFile => fpath == x.fpath
+ case _ => false
+ }
+
+ /** Is this abstract file a directory? */
+ def isDirectory: Boolean = givenPath.isDirectory
+
+ /** Returns the time that this abstract file was last modified. */
+ def lastModified: Long = givenPath.lastModified
+
+ /** Returns all abstract subfiles of this abstract directory. */
+ def iterator: Iterator[AbstractFile] = {
+ if (!isDirectory) Iterator.empty
+ else givenPath.toDirectory.list filter (_.exists) map (new PlainFile(_))
+ }
+
+ /**
+ * 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.
+ *
+ * @param name ...
+ * @param directory ...
+ * @return ...
+ */
+ def lookupName(name: String, directory: Boolean): AbstractFile = {
+ val child = givenPath / name
+ if ((child.isDirectory && directory) || (child.isFile && !directory)) new PlainFile(child)
+ else null
+ }
+
+ /** Does this abstract file denote an existing file? */
+ def create(): Unit = if (!exists) givenPath.createFile()
+
+ /** Delete the underlying file or directory (recursively). */
+ def delete(): Unit =
+ if (givenPath.isFile) givenPath.delete()
+ else if (givenPath.isDirectory) givenPath.toDirectory.deleteRecursively()
+
+ /** Returns a plain file with the given name. It does not
+ * check that it exists.
+ */
+ def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile =
+ new PlainFile(givenPath / name)
+}
diff --git a/src/reflect/scala/reflect/io/Streamable.scala b/src/reflect/scala/reflect/io/Streamable.scala
new file mode 100644
index 0000000000..a083890e09
--- /dev/null
+++ b/src/reflect/scala/reflect/io/Streamable.scala
@@ -0,0 +1,127 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.reflect
+package io
+
+import java.net.{ URI, URL }
+import java.io.{ BufferedInputStream, InputStream, PrintStream }
+import java.io.{ BufferedReader, InputStreamReader, Closeable => JCloseable }
+import scala.io.{ Codec, BufferedSource, Source }
+import scala.collection.mutable.ArrayBuffer
+import Path.fail
+
+/** Traits for objects which can be represented as Streams.
+ *
+ * @author Paul Phillips
+ * @since 2.8
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+object Streamable {
+ /** Traits which can be viewed as a sequence of bytes. Source types
+ * which know their length should override def length: Long for more
+ * efficient method implementations.
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+ trait Bytes {
+ def inputStream(): InputStream
+ def length: Long = -1
+
+ def bufferedInput() = new BufferedInputStream(inputStream())
+ def bytes(): Iterator[Byte] = bytesAsInts() map (_.toByte)
+ def bytesAsInts(): Iterator[Int] = {
+ val in = bufferedInput()
+ Iterator continually in.read() takeWhile (_ != -1)
+ }
+
+ /** This method aspires to be the fastest way to read
+ * a stream of known length into memory.
+ */
+ def toByteArray(): Array[Byte] = {
+ // if we don't know the length, fall back on relative inefficiency
+ if (length == -1L)
+ return (new ArrayBuffer[Byte]() ++= bytes()).toArray
+
+ val arr = new Array[Byte](length.toInt)
+ val len = arr.length
+ lazy val in = bufferedInput()
+ var offset = 0
+
+ def loop() {
+ if (offset < len) {
+ val read = in.read(arr, offset, len - offset)
+ if (read >= 0) {
+ offset += read
+ loop()
+ }
+ }
+ }
+ try loop()
+ finally in.close()
+
+ if (offset == arr.length) arr
+ else fail("Could not read entire source (%d of %d bytes)".format(offset, len))
+ }
+ }
+
+ /** For objects which can be viewed as Chars.
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+ trait Chars extends Bytes {
+ /** Calls to methods requiring byte<->char transformations should be offered
+ * in a form which allows specifying the codec. When it is not specified,
+ * the one discovered at creation time will be used, which will always find the
+ * one in scala.io.Codec if no other is available. This can be overridden
+ * to use a different default.
+ */
+ def creationCodec: Codec = implicitly[Codec]
+
+ def chars(): BufferedSource = chars(creationCodec)
+ def chars(codec: Codec): BufferedSource = Source.fromInputStream(inputStream())(codec)
+
+ def lines(): Iterator[String] = lines(creationCodec)
+ def lines(codec: Codec): Iterator[String] = chars(codec).getLines()
+
+ /** Obtains an InputStreamReader wrapped around a FileInputStream.
+ */
+ def reader(): InputStreamReader = reader(creationCodec)
+ def reader(codec: Codec): InputStreamReader = new InputStreamReader(inputStream, codec.charSet)
+
+ /** Wraps a BufferedReader around the result of reader().
+ */
+ def bufferedReader(): BufferedReader = bufferedReader(creationCodec)
+ def bufferedReader(codec: Codec) = new BufferedReader(reader(codec))
+
+ /** Creates a BufferedReader and applies the closure, automatically closing it on completion.
+ */
+ def applyReader[T](f: BufferedReader => T): T = {
+ val in = bufferedReader()
+ try f(in)
+ finally in.close()
+ }
+
+ /** Convenience function to import entire file into a String.
+ */
+ def slurp(): String = slurp(creationCodec)
+ def slurp(codec: Codec) = chars(codec).mkString
+ }
+
+ /** Call a function on something Closeable, finally closing it. */
+ def closing[T <: JCloseable, U](stream: T)(f: T => U): U =
+ try f(stream)
+ finally stream.close()
+
+ def bytes(is: => InputStream): Array[Byte] =
+ (new Bytes { def inputStream() = is }).toByteArray
+
+ def slurp(is: => InputStream)(implicit codec: Codec): String =
+ new Chars { def inputStream() = is } slurp codec
+
+ def slurp(url: URL)(implicit codec: Codec): String =
+ slurp(url.openStream())
+}
diff --git a/src/reflect/scala/reflect/io/VirtualDirectory.scala b/src/reflect/scala/reflect/io/VirtualDirectory.scala
new file mode 100644
index 0000000000..e71c5cbb6b
--- /dev/null
+++ b/src/reflect/scala/reflect/io/VirtualDirectory.scala
@@ -0,0 +1,72 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ */
+
+package scala.reflect
+package io
+
+import scala.collection.mutable
+
+/**
+ * An in-memory directory.
+ *
+ * @author Lex Spoon
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+class VirtualDirectory(val name: String, maybeContainer: Option[VirtualDirectory])
+extends AbstractFile {
+ def path: String =
+ maybeContainer match {
+ case None => name
+ case Some(parent) => parent.path+'/'+ name
+ }
+
+ def absolute = this
+
+ def container = maybeContainer.get
+ def isDirectory = true
+ var lastModified: Long = System.currentTimeMillis
+
+ override def file = null
+ override def input = sys.error("directories cannot be read")
+ override def output = sys.error("directories cannot be written")
+
+ /** Does this abstract file denote an existing file? */
+ def create() { unsupported }
+
+ /** Delete the underlying file or directory (recursively). */
+ def delete() { unsupported }
+
+ /** Returns an abstract file with the given name. It does not
+ * check that it exists.
+ */
+ def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = unsupported
+
+ private val files = mutable.Map.empty[String, AbstractFile]
+
+ // the toList is so that the directory may continue to be
+ // modified while its elements are iterated
+ def iterator = files.values.toList.iterator
+
+ override def lookupName(name: String, directory: Boolean): AbstractFile =
+ (files get name filter (_.isDirectory == directory)).orNull
+
+ override def fileNamed(name: String): AbstractFile =
+ Option(lookupName(name, false)) getOrElse {
+ val newFile = new VirtualFile(name, path+'/'+name)
+ files(name) = newFile
+ newFile
+ }
+
+ override def subdirectoryNamed(name: String): AbstractFile =
+ Option(lookupName(name, true)) getOrElse {
+ val dir = new VirtualDirectory(name, Some(this))
+ files(name) = dir
+ dir
+ }
+
+ def clear() {
+ files.clear();
+ }
+}
diff --git a/src/reflect/scala/reflect/io/VirtualFile.scala b/src/reflect/scala/reflect/io/VirtualFile.scala
new file mode 100644
index 0000000000..4884561f4e
--- /dev/null
+++ b/src/reflect/scala/reflect/io/VirtualFile.scala
@@ -0,0 +1,103 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ * @author Martin Odersky
+ */
+
+
+package scala.reflect
+package io
+
+import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, InputStream, OutputStream }
+import java.io.{ File => JFile }
+
+/** This class implements an in-memory file.
+ *
+ * @author Philippe Altherr
+ * @version 1.0, 23/03/2004
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+class VirtualFile(val name: String, override val path: String) extends AbstractFile {
+ /**
+ * Initializes this instance with the specified name and an
+ * identical path.
+ *
+ * @param name the name of the virtual file to be created
+ * @return the created virtual file
+ */
+ def this(name: String) = this(name, name)
+
+ override def hashCode = path.hashCode
+ override def equals(that: Any) = that match {
+ case x: VirtualFile => x.path == path
+ case _ => false
+ }
+
+ //########################################################################
+ // Private data
+ private var content = Array.emptyByteArray
+
+ //########################################################################
+ // Public Methods
+ def absolute = this
+
+ /** Returns null. */
+ final def file: JFile = null
+
+ override def sizeOption: Option[Int] = Some(content.size)
+
+ def input : InputStream = new ByteArrayInputStream(content);
+
+ override def output: OutputStream = {
+ new ByteArrayOutputStream() {
+ override def close() {
+ super.close()
+ content = toByteArray()
+ }
+ }
+ }
+
+ def container: AbstractFile = NoAbstractFile
+
+ /** Is this abstract file a directory? */
+ def isDirectory: Boolean = false
+
+ /** Returns the time that this abstract file was last modified. */
+ private var _lastModified: Long = 0
+ def lastModified: Long = _lastModified
+ def lastModified_=(x: Long) = _lastModified = x
+
+ /** Returns all abstract subfiles of this abstract directory. */
+ def iterator: Iterator[AbstractFile] = {
+ assert(isDirectory, "not a directory '" + this + "'")
+ Iterator.empty
+ }
+
+ /** Does this abstract file denote an existing file? */
+ def create() { unsupported }
+
+ /** Delete the underlying file or directory (recursively). */
+ def delete() { unsupported }
+
+ /**
+ * 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.
+ *
+ * @param name ...
+ * @param directory ...
+ * @return ...
+ */
+ def lookupName(name: String, directory: Boolean): AbstractFile = {
+ assert(isDirectory, "not a directory '" + this + "'")
+ null
+ }
+
+ /** Returns an abstract file with the given name. It does not
+ * check that it exists.
+ */
+ def lookupNameUnchecked(name: String, directory: Boolean) = unsupported
+
+ //########################################################################
+}
diff --git a/src/reflect/scala/reflect/io/ZipArchive.scala b/src/reflect/scala/reflect/io/ZipArchive.scala
new file mode 100644
index 0000000000..2512c4d92f
--- /dev/null
+++ b/src/reflect/scala/reflect/io/ZipArchive.scala
@@ -0,0 +1,234 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2012 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.reflect
+package io
+
+import java.net.URL
+import java.io.{ IOException, InputStream, ByteArrayInputStream }
+import java.io.{ File => JFile }
+import java.util.zip.{ ZipEntry, ZipFile, ZipInputStream }
+import scala.collection.{ immutable, mutable }
+import scala.annotation.tailrec
+
+/** 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,
+ *
+ * ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
+ */
+object ZipArchive {
+ def fromPath(path: String): FileZipArchive = fromFile(new JFile(path))
+ def fromPath(path: Path): FileZipArchive = fromFile(path.toFile)
+
+ /**
+ * @param file a File
+ * @return A ZipArchive if `file` is a readable zip file, otherwise null.
+ */
+ def fromFile(file: File): FileZipArchive = fromFile(file.jfile)
+ def fromFile(file: JFile): FileZipArchive =
+ try { new FileZipArchive(file) }
+ catch { case _: IOException => null }
+
+ /**
+ * @param url the url of a zip file
+ * @return A ZipArchive backed by the given url.
+ */
+ def fromURL(url: URL): URLZipArchive = new URLZipArchive(url)
+ def fromURL(url: String): URLZipArchive = fromURL(new URL(url))
+
+ 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 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)
+ }
+}
+import ZipArchive._
+/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+abstract class ZipArchive(override val file: JFile) extends AbstractFile with Equals {
+ self =>
+
+ 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
+
+ private def walkIterator(its: Iterator[AbstractFile]): Iterator[AbstractFile] = {
+ its flatMap { f =>
+ if (f.isDirectory) walkIterator(f.iterator)
+ else Iterator(f)
+ }
+ }
+ def deepIterator = walkIterator(iterator)
+ /** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+ sealed 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 + ")"
+ }
+ /** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+ class DirEntry(path: String) extends Entry(path) {
+ val entries = 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 def ensureDir(dirs: mutable.Map[String, DirEntry], path: String, zipEntry: ZipEntry): DirEntry =
+ //OPT inlined from getOrElseUpdate; saves ~50K closures on test run.
+ // was:
+ // dirs.getOrElseUpdate(path, {
+ // val parent = ensureDir(dirs, dirName(path), null)
+ // val dir = new DirEntry(path)
+ // parent.entries(baseName(path)) = dir
+ // dir
+ // })
+ dirs get 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(path) = dir
+ dir
+ }
+
+ protected def getDir(dirs: mutable.Map[String, DirEntry], entry: ZipEntry): DirEntry = {
+ if (entry.isDirectory) ensureDir(dirs, entry.getName, entry)
+ else ensureDir(dirs, dirName(entry.getName), null)
+ }
+}
+/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+final class FileZipArchive(file: JFile) extends ZipArchive(file) {
+ def iterator: Iterator[Entry] = {
+ val zipFile = new ZipFile(file)
+ val root = new DirEntry("/")
+ val dirs = mutable.HashMap[String, DirEntry]("/" -> root)
+ val enum = zipFile.entries()
+
+ while (enum.hasMoreElements) {
+ val zipEntry = enum.nextElement
+ val dir = getDir(dirs, zipEntry)
+ if (zipEntry.isDirectory) dir
+ else {
+ class FileEntry() extends Entry(zipEntry.getName) {
+ override def getArchive = zipFile
+ override def lastModified = zipEntry.getTime()
+ override def input = getArchive getInputStream zipEntry
+ override def sizeOption = Some(zipEntry.getSize().toInt)
+ }
+ val f = new FileEntry()
+ dir.entries(f.name) = f
+ }
+ }
+
+ try root.iterator
+ finally dirs.clear()
+ }
+
+ def name = file.getName
+ def path = file.getPath
+ def input = File(file).inputStream()
+ def lastModified = file.lastModified
+
+ override def sizeOption = Some(file.length.toInt)
+ override def canEqual(other: Any) = other.isInstanceOf[FileZipArchive]
+ override def hashCode() = file.hashCode
+ override def equals(that: Any) = that match {
+ case x: FileZipArchive => file.getAbsoluteFile == x.file.getAbsoluteFile
+ case _ => false
+ }
+}
+/** ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */
+final class URLZipArchive(val url: URL) extends ZipArchive(null) {
+ def iterator: Iterator[Entry] = {
+ val root = new DirEntry("/")
+ val dirs = mutable.HashMap[String, DirEntry]("/" -> root)
+ val in = new ZipInputStream(new ByteArrayInputStream(Streamable.bytes(input)))
+
+ @tailrec def loop() {
+ val zipEntry = in.getNextEntry()
+ class EmptyFileEntry() extends Entry(zipEntry.getName) {
+ override def toByteArray: Array[Byte] = null
+ override def sizeOption = Some(0)
+ }
+ class FileEntry() extends Entry(zipEntry.getName) {
+ override val toByteArray: Array[Byte] = {
+ val len = zipEntry.getSize().toInt
+ val arr = if (len == 0) Array.emptyByteArray else new Array[Byte](len)
+ var offset = 0
+
+ def loop() {
+ if (offset < len) {
+ val read = in.read(arr, offset, len - offset)
+ if (read >= 0) {
+ offset += read
+ loop()
+ }
+ }
+ }
+ loop()
+
+ if (offset == arr.length) arr
+ else throw new IOException("Input stream truncated: read %d of %d bytes".format(offset, len))
+ }
+ override def sizeOption = Some(zipEntry.getSize().toInt)
+ }
+
+ if (zipEntry != null) {
+ val dir = getDir(dirs, zipEntry)
+ if (zipEntry.isDirectory)
+ dir
+ else {
+ val f = if (zipEntry.getSize() == 0) new EmptyFileEntry() else new FileEntry()
+ dir.entries(f.name) = f
+ }
+ in.closeEntry()
+ loop()
+ }
+ }
+
+ loop()
+ try root.iterator
+ finally dirs.clear()
+ }
+
+ def name = url.getFile()
+ def path = url.getPath()
+ def input = url.openStream()
+ def lastModified =
+ try url.openConnection().getLastModified()
+ catch { case _: IOException => 0 }
+
+ override def canEqual(other: Any) = other.isInstanceOf[URLZipArchive]
+ override def hashCode() = url.hashCode
+ override def equals(that: Any) = that match {
+ case x: URLZipArchive => url == x.url
+ case _ => false
+ }
+}