From c6dde6a1e884b71ae8bf8e0136287e434594e92b Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Tue, 18 Sep 2012 10:35:26 -0400 Subject: 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. --- src/compiler/scala/tools/nsc/io/package.scala | 17 ++ src/reflect/scala/reflect/api/Symbols.scala | 2 +- src/reflect/scala/reflect/internal/Symbols.scala | 2 +- .../scala/reflect/internal/util/SourceFile.scala | 2 +- src/reflect/scala/reflect/io/AbstractFile.scala | 264 +++++++++++++++++++ src/reflect/scala/reflect/io/Directory.scala | 79 ++++++ src/reflect/scala/reflect/io/File.scala | 197 ++++++++++++++ .../scala/reflect/io/FileOperationException.scala | 13 + src/reflect/scala/reflect/io/NoAbstractFile.scala | 33 +++ src/reflect/scala/reflect/io/Path.scala | 289 +++++++++++++++++++++ src/reflect/scala/reflect/io/PlainFile.scala | 104 ++++++++ src/reflect/scala/reflect/io/Streamable.scala | 127 +++++++++ .../scala/reflect/io/VirtualDirectory.scala | 72 +++++ src/reflect/scala/reflect/io/VirtualFile.scala | 103 ++++++++ src/reflect/scala/reflect/io/ZipArchive.scala | 234 +++++++++++++++++ .../scala/reflect/runtime/ReflectionUtils.scala | 2 +- .../reflect/runtime/SynchronizedSymbols.scala | 2 +- src/reflect/scala/tools/nsc/io/AbstractFile.scala | 258 ------------------ src/reflect/scala/tools/nsc/io/Directory.scala | 75 ------ src/reflect/scala/tools/nsc/io/File.scala | 193 -------------- .../tools/nsc/io/FileOperationException.scala | 13 - .../scala/tools/nsc/io/NoAbstractFile.scala | 31 --- src/reflect/scala/tools/nsc/io/Path.scala | 286 -------------------- src/reflect/scala/tools/nsc/io/PlainFile.scala | 102 -------- src/reflect/scala/tools/nsc/io/Streamable.scala | 122 --------- .../scala/tools/nsc/io/VirtualDirectory.scala | 70 ----- src/reflect/scala/tools/nsc/io/VirtualFile.scala | 101 ------- src/reflect/scala/tools/nsc/io/ZipArchive.scala | 231 ---------------- 28 files changed, 1537 insertions(+), 1487 deletions(-) create mode 100644 src/reflect/scala/reflect/io/AbstractFile.scala create mode 100644 src/reflect/scala/reflect/io/Directory.scala create mode 100644 src/reflect/scala/reflect/io/File.scala create mode 100644 src/reflect/scala/reflect/io/FileOperationException.scala create mode 100644 src/reflect/scala/reflect/io/NoAbstractFile.scala create mode 100644 src/reflect/scala/reflect/io/Path.scala create mode 100644 src/reflect/scala/reflect/io/PlainFile.scala create mode 100644 src/reflect/scala/reflect/io/Streamable.scala create mode 100644 src/reflect/scala/reflect/io/VirtualDirectory.scala create mode 100644 src/reflect/scala/reflect/io/VirtualFile.scala create mode 100644 src/reflect/scala/reflect/io/ZipArchive.scala delete mode 100644 src/reflect/scala/tools/nsc/io/AbstractFile.scala delete mode 100644 src/reflect/scala/tools/nsc/io/Directory.scala delete mode 100644 src/reflect/scala/tools/nsc/io/File.scala delete mode 100644 src/reflect/scala/tools/nsc/io/FileOperationException.scala delete mode 100644 src/reflect/scala/tools/nsc/io/NoAbstractFile.scala delete mode 100644 src/reflect/scala/tools/nsc/io/Path.scala delete mode 100644 src/reflect/scala/tools/nsc/io/PlainFile.scala delete mode 100644 src/reflect/scala/tools/nsc/io/Streamable.scala delete mode 100644 src/reflect/scala/tools/nsc/io/VirtualDirectory.scala delete mode 100644 src/reflect/scala/tools/nsc/io/VirtualFile.scala delete mode 100644 src/reflect/scala/tools/nsc/io/ZipArchive.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/io/package.scala b/src/compiler/scala/tools/nsc/io/package.scala index 775ad6bde0..ae83a7728b 100644 --- a/src/compiler/scala/tools/nsc/io/package.scala +++ b/src/compiler/scala/tools/nsc/io/package.scala @@ -11,6 +11,23 @@ import java.util.jar.{ Attributes } import scala.language.implicitConversions package object io { + // Forwarders from scala.reflect.io + type AbstractFile = scala.reflect.io.AbstractFile + val AbstractFile = scala.reflect.io.AbstractFile + type Directory = scala.reflect.io.Directory + val Directory = scala.reflect.io.Directory + type File = scala.reflect.io.File + val File = scala.reflect.io.File + type Path = scala.reflect.io.Path + val Path = scala.reflect.io.Path + type PlainFile = scala.reflect.io.PlainFile + val PlainFile = scala.reflect.io.PlainFile + val Streamable = scala.reflect.io.Streamable + type VirtualDirectory = scala.reflect.io.VirtualDirectory + type VirtualFile = scala.reflect.io.VirtualFile + val ZipArchive = scala.reflect.io.ZipArchive + type ZipArchive = scala.reflect.io.ZipArchive + implicit def postfixOps = scala.language.postfixOps // make all postfix ops in this package compile without warning type JManifest = java.util.jar.Manifest diff --git a/src/reflect/scala/reflect/api/Symbols.scala b/src/reflect/scala/reflect/api/Symbols.scala index 0c4be4f7e1..40d2179cfc 100644 --- a/src/reflect/scala/reflect/api/Symbols.scala +++ b/src/reflect/scala/reflect/api/Symbols.scala @@ -18,7 +18,7 @@ trait Symbols extends base.Symbols { self: Universe => /** Source file if this symbol is created during this compilation run, * or a class file if this symbol is loaded from a *.class or *.jar. */ - def associatedFile: scala.tools.nsc.io.AbstractFile + def associatedFile: scala.reflect.io.AbstractFile /** A list of annotations attached to this Symbol. */ diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 74f4769fec..0a61e261a3 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -12,7 +12,7 @@ import util.Statistics import Flags._ import base.Attachments import scala.annotation.tailrec -import scala.tools.nsc.io.AbstractFile +import scala.reflect.io.AbstractFile trait Symbols extends api.Symbols { self: SymbolTable => import definitions._ diff --git a/src/reflect/scala/reflect/internal/util/SourceFile.scala b/src/reflect/scala/reflect/internal/util/SourceFile.scala index 9a71e02e08..788c7532d1 100644 --- a/src/reflect/scala/reflect/internal/util/SourceFile.scala +++ b/src/reflect/scala/reflect/internal/util/SourceFile.scala @@ -6,7 +6,7 @@ package scala.reflect.internal.util -import scala.tools.nsc.io.{ AbstractFile, VirtualFile } +import scala.reflect.io.{ AbstractFile, VirtualFile } import scala.collection.mutable.ArrayBuffer import scala.annotation.tailrec import java.util.regex.Pattern 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 null. + */ + 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 null. + * + * @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 + * null. + * + * @param file ... + * @return ... + */ + def getURL(url: URL): AbstractFile = { + if (url == null || !Path.isExtensionJarOrZip(url.getPath)) null + else ZipArchive fromURL url + } +} + +/** + *

+ * 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. + *

+ *

+ * 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. + *

+ *

+ * The interface does not allow to access the content. + * The class symtab.classfile.AbstractFileReader accesses + * bytes, knowing that the character set of classfiles is UTF-8. For + * all other cases, the class SourceFile is used, which honors + * global.settings.encoding.value. + *

+ * + * ''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 Global.getSourceFile() 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 null. The argument + * directory 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 + * directory 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 + } +} diff --git a/src/reflect/scala/reflect/runtime/ReflectionUtils.scala b/src/reflect/scala/reflect/runtime/ReflectionUtils.scala index eaf7d8326f..44d9d94a46 100644 --- a/src/reflect/scala/reflect/runtime/ReflectionUtils.scala +++ b/src/reflect/scala/reflect/runtime/ReflectionUtils.scala @@ -49,7 +49,7 @@ object ReflectionUtils { case cl: java.net.URLClassLoader => (cl.getURLs mkString ",") case cl if cl != null && isAbstractFileClassLoader(cl.getClass) => - cl.asInstanceOf[{val root: scala.tools.nsc.io.AbstractFile}].root.canonicalPath + cl.asInstanceOf[{val root: scala.reflect.io.AbstractFile}].root.canonicalPath case null => inferBootClasspath case _ => diff --git a/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala b/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala index 7705610efb..3c2885a9f4 100644 --- a/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala +++ b/src/reflect/scala/reflect/runtime/SynchronizedSymbols.scala @@ -1,7 +1,7 @@ package scala.reflect package runtime -import scala.tools.nsc.io.AbstractFile +import scala.reflect.io.AbstractFile trait SynchronizedSymbols extends internal.Symbols { self: SymbolTable => diff --git a/src/reflect/scala/tools/nsc/io/AbstractFile.scala b/src/reflect/scala/tools/nsc/io/AbstractFile.scala deleted file mode 100644 index 018a017c6d..0000000000 --- a/src/reflect/scala/tools/nsc/io/AbstractFile.scala +++ /dev/null @@ -1,258 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Martin Odersky - */ - - -package scala.tools.nsc -package io - -import java.io.{ FileOutputStream, IOException, InputStream, OutputStream, BufferedOutputStream } -import java.io.{ File => JFile } -import java.net.URL -import scala.collection.mutable.ArrayBuffer - -/** - * @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 null. - */ - 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 null. - * - * @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 - * null. - * - * @param file ... - * @return ... - */ - def getURL(url: URL): AbstractFile = { - if (url == null || !Path.isExtensionJarOrZip(url.getPath)) null - else ZipArchive fromURL url - } -} - -/** - *

- * 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. - *

- *

- * 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. - *

- *

- * The interface does not allow to access the content. - * The class symtab.classfile.AbstractFileReader accesses - * bytes, knowing that the character set of classfiles is UTF-8. For - * all other cases, the class SourceFile is used, which honors - * global.settings.encoding.value. - *

- */ -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 Global.getSourceFile() 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 null. The argument - * directory 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 - * directory 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/tools/nsc/io/Directory.scala b/src/reflect/scala/tools/nsc/io/Directory.scala deleted file mode 100644 index ebd6edc8d8..0000000000 --- a/src/reflect/scala/tools/nsc/io/Directory.scala +++ /dev/null @@ -1,75 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.tools.nsc -package io - -import java.io.{ File => JFile } - -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 - */ -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/tools/nsc/io/File.scala b/src/reflect/scala/tools/nsc/io/File.scala deleted file mode 100644 index fce0e339e0..0000000000 --- a/src/reflect/scala/tools/nsc/io/File.scala +++ /dev/null @@ -1,193 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - - -package scala.tools.nsc -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} - -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 - */ -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/tools/nsc/io/FileOperationException.scala b/src/reflect/scala/tools/nsc/io/FileOperationException.scala deleted file mode 100644 index f23658efbc..0000000000 --- a/src/reflect/scala/tools/nsc/io/FileOperationException.scala +++ /dev/null @@ -1,13 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - - -package scala.tools.nsc -package io - -case class FileOperationException(msg: String) extends RuntimeException(msg) diff --git a/src/reflect/scala/tools/nsc/io/NoAbstractFile.scala b/src/reflect/scala/tools/nsc/io/NoAbstractFile.scala deleted file mode 100644 index 2af933c27b..0000000000 --- a/src/reflect/scala/tools/nsc/io/NoAbstractFile.scala +++ /dev/null @@ -1,31 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package io - -import java.io.InputStream -import java.io.{ File => JFile } - -/** A distinguished object so you can avoid both null - * and Option. - */ -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/tools/nsc/io/Path.scala b/src/reflect/scala/tools/nsc/io/Path.scala deleted file mode 100644 index 0a27e49686..0000000000 --- a/src/reflect/scala/tools/nsc/io/Path.scala +++ /dev/null @@ -1,286 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -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 - */ - -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. - */ -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/tools/nsc/io/PlainFile.scala b/src/reflect/scala/tools/nsc/io/PlainFile.scala deleted file mode 100644 index a4f378ad5e..0000000000 --- a/src/reflect/scala/tools/nsc/io/PlainFile.scala +++ /dev/null @@ -1,102 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Martin Odersky - */ - - -package scala.tools.nsc -package io - -import java.io.{ FileInputStream, FileOutputStream, IOException } -import PartialFunction._ - -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 -} - -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. - */ -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/tools/nsc/io/Streamable.scala b/src/reflect/scala/tools/nsc/io/Streamable.scala deleted file mode 100644 index 625429bdb3..0000000000 --- a/src/reflect/scala/tools/nsc/io/Streamable.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -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 - */ - -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. - */ - 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. - */ - 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/tools/nsc/io/VirtualDirectory.scala b/src/reflect/scala/tools/nsc/io/VirtualDirectory.scala deleted file mode 100644 index fa016f86f4..0000000000 --- a/src/reflect/scala/tools/nsc/io/VirtualDirectory.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - */ - -package scala.tools.nsc -package io - -import scala.collection.mutable - -/** - * An in-memory directory. - * - * @author Lex Spoon - */ -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/tools/nsc/io/VirtualFile.scala b/src/reflect/scala/tools/nsc/io/VirtualFile.scala deleted file mode 100644 index 8a5114bfe7..0000000000 --- a/src/reflect/scala/tools/nsc/io/VirtualFile.scala +++ /dev/null @@ -1,101 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Martin Odersky - */ - - -package scala.tools.nsc -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 - */ -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/tools/nsc/io/ZipArchive.scala b/src/reflect/scala/tools/nsc/io/ZipArchive.scala deleted file mode 100644 index 49d2200895..0000000000 --- a/src/reflect/scala/tools/nsc/io/ZipArchive.scala +++ /dev/null @@ -1,231 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2012 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -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, - */ -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._ - -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) - - 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 + ")" - } - 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) - } -} - -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 - } -} - -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 - } -} -- cgit v1.2.3 From d7acf92be65b6005aaab33e071b4bff1d7aa9911 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Mon, 17 Sep 2012 13:51:35 -0400 Subject: Adds OSGi support / tests to Scala. Fixes SI-5822. * Adds BND manifest generation to the build. * Adds OSGi pax-exam testing infrastructure * Adds simple OSGi verification test for bundle resolution. * Modifies distribution to use bundles. --- build.xml | 190 +++++++++++++++++++++++++++++-- src/build/bnd/continuations.bnd | 5 + src/build/bnd/scala-actors-migration.bnd | 5 + src/build/bnd/scala-actors.bnd | 5 + src/build/bnd/scala-compiler.bnd | 8 ++ src/build/bnd/scala-library.bnd | 6 + src/build/bnd/scala-reflect.bnd | 6 + src/build/bnd/scala-swing.bnd | 5 + test/osgi/src/BasicTest.scala | 33 ++++++ test/osgi/src/ScalaOsgiHelper.scala | 18 +++ 10 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 src/build/bnd/continuations.bnd create mode 100644 src/build/bnd/scala-actors-migration.bnd create mode 100644 src/build/bnd/scala-actors.bnd create mode 100644 src/build/bnd/scala-compiler.bnd create mode 100644 src/build/bnd/scala-library.bnd create mode 100644 src/build/bnd/scala-reflect.bnd create mode 100644 src/build/bnd/scala-swing.bnd create mode 100644 test/osgi/src/BasicTest.scala create mode 100644 test/osgi/src/ScalaOsgiHelper.scala (limited to 'src') diff --git a/build.xml b/build.xml index 99b68ced84..ac25e1e757 100644 --- a/build.xml +++ b/build.xml @@ -22,7 +22,7 @@ END-USER TARGETS - + @@ -279,6 +280,28 @@ INITIALISATION + + + + + + + + + + + + + + + + + + + + + + @@ -400,7 +423,7 @@ INITIALISATION - + @@ -1672,6 +1695,139 @@ PACKED QUICK BUILD (PACK) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2553,9 +2709,24 @@ BOOTRAPING TEST AND TEST SUITE DISTRIBUTION ============================================================================ --> - + + + + + + + + + + + + + + @@ -2564,6 +2735,13 @@ DISTRIBUTION + + + + + + + @@ -2573,9 +2751,7 @@ DISTRIBUTION - - - + @@ -2663,7 +2839,7 @@ TEST AND DISTRIBUTION BUNDLE (ALL) - +