1
/* NSC -- new Scala compiler
2
* Copyright 2005-2013 LAMP/EPFL
3
* @author Paul Phillips
10
FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter,
11
BufferedInputStream, BufferedOutputStream, RandomAccessFile }
12
import java.io.{ File => JFile }
13
import java.net.{ URI, URL }
14
import scala.util.Random.alphanumeric
15
import scala.language.implicitConversions
17
/** An abstraction for filesystem paths. The differences between
18
* Path, File, and Directory are primarily to communicate intent.
19
* Since the filesystem can change at any time, there is no way to
20
* reliably associate Files only with files and so on. Any Path
21
* can be converted to a File or Directory (and thus gain access to
22
* the additional entity specific methods) by calling toFile or
23
* toDirectory, which has no effect on the filesystem.
25
* Also available are createFile and createDirectory, which attempt
26
* to create the path in question.
28
* @author Paul Phillips
31
* ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
34
def isExtensionJarOrZip(jfile: JFile): Boolean = isExtensionJarOrZip(jfile.getName)
35
def isExtensionJarOrZip(name: String): Boolean = {
36
val ext = extension(name)
37
ext == "jar" || ext == "zip"
39
def extension(name: String): String = {
40
var i = name.length - 1
41
while (i >= 0 && name.charAt(i) != '.')
45
else name.substring(i + 1).toLowerCase
48
// not certain these won't be problematic, but looks good so far
49
implicit def string2path(s: String): Path = apply(s)
50
implicit def jfile2path(jfile: JFile): Path = apply(jfile)
52
// java 7 style, we don't use it yet
53
// object AccessMode extends Enumeration {
54
// val EXECUTE, READ, WRITE = Value
56
// def checkAccess(modes: AccessMode*): Boolean = {
58
// case EXECUTE => throw new Exception("Unsupported") // can't check in java 5
59
// case READ => if (!jfile.canRead()) return false
60
// case WRITE => if (!jfile.canWrite()) return false
65
def onlyDirs(xs: Iterator[Path]): Iterator[Directory] = xs filter (_.isDirectory) map (_.toDirectory)
66
def onlyDirs(xs: List[Path]): List[Directory] = xs filter (_.isDirectory) map (_.toDirectory)
67
def onlyFiles(xs: Iterator[Path]): Iterator[File] = xs filter (_.isFile) map (_.toFile)
68
def onlyFiles(xs: List[Path]): List[File] = xs filter (_.isFile) map (_.toFile)
70
def roots: List[Path] = java.io.File.listRoots().toList map Path.apply
72
def apply(segments: Seq[String]): Path = apply(segments mkString java.io.File.separator)
73
def apply(path: String): Path = apply(new JFile(path))
74
def apply(jfile: JFile): Path =
75
if (jfile.isFile) new File(jfile)
76
else if (jfile.isDirectory) new Directory(jfile)
79
/** Avoiding any shell/path issues by only using alphanumerics. */
80
private[io] def randomPrefix = alphanumeric take 6 mkString ""
81
private[io] def fail(msg: String) = throw FileOperationException(msg)
85
/** The Path constructor is private so we can enforce some
86
* semantics regarding how a Path might relate to the world.
88
* ''Note: This library is considered experimental and should not be used unless you know what you are doing.''
90
class Path private[io] (val jfile: JFile) {
91
val separator = java.io.File.separatorChar
92
val separatorStr = java.io.File.separator
94
// Validation: this verifies that the type of this object and the
95
// contents of the filesystem are in agreement. All objects are
96
// valid except File objects whose path points to a directory and
97
// Directory objects whose path points to a file.
98
def isValid: Boolean = true
101
def toFile: File = new File(jfile)
102
def toDirectory: Directory = new Directory(jfile)
103
def toAbsolute: Path = if (isAbsolute) this else Path(jfile.getAbsolutePath())
104
def toCanonical: Path = Path(jfile.getCanonicalPath())
105
def toURI: URI = jfile.toURI()
106
def toURL: URL = toURI.toURL()
107
/** If this path is absolute, returns it: otherwise, returns an absolute
108
* path made up of root / this.
110
def toAbsoluteWithRoot(root: Path) = if (isAbsolute) this else root.toAbsolute / this
112
/** Creates a new Path with the specified path appended. Assumes
113
* the type of the new component implies the type of the result.
115
def /(child: Path): Path = if (isEmpty) child else new Path(new JFile(jfile, child.path))
116
def /(child: Directory): Directory = /(child: Path).toDirectory
117
def /(child: File): File = /(child: Path).toFile
119
/** If this path is a container, recursively iterate over its contents.
120
* The supplied condition is a filter which is applied to each element,
121
* with that branch of the tree being closed off if it is true. So for
122
* example if the condition is true for some subdirectory, nothing
123
* under that directory will be in the Iterator; but otherwise each
124
* file and subdirectory underneath it will appear.
126
def walkFilter(cond: Path => Boolean): Iterator[Path] =
127
if (isFile) toFile walkFilter cond
128
else if (isDirectory) toDirectory walkFilter cond
131
/** Equivalent to walkFilter(_ => false).
133
def walk: Iterator[Path] = walkFilter(_ => true)
136
def name: String = jfile.getName()
137
def path: String = jfile.getPath()
138
def normalize: Path = Path(jfile.getAbsolutePath())
139
def isRootPath: Boolean = roots exists (_ isSame this)
141
def resolve(other: Path) = if (other.isAbsolute || isEmpty) other else /(other)
142
def relativize(other: Path) = {
143
assert(isAbsolute == other.isAbsolute, "Paths not of same type: "+this+", "+other)
145
def createRelativePath(baseSegs: List[String], otherSegs: List[String]) : String = {
146
(baseSegs, otherSegs) match {
147
case (b :: bs, o :: os) if b == o => createRelativePath(bs, os)
148
case (bs, os) => ((".."+separator)*bs.length)+os.mkString(separatorStr)
152
Path(createRelativePath(segments, other.segments))
155
// derived from identity
156
def root: Option[Path] = roots find (this startsWith _)
157
def segments: List[String] = (path split separator).toList filterNot (_.length == 0)
159
* @return The path of the parent directory, or root if path is already root
161
def parent: Directory = path match {
162
case "" | "." => Directory("..")
164
// the only solution <-- a comment which could have used elaboration
165
if (segments.nonEmpty && segments.last == "..")
166
(path / "..").toDirectory
167
else jfile.getParent match {
169
if (isAbsolute) toDirectory // it should be a root. BTW, don't need to worry about relative pathed root
170
else Directory(".") // a dir under pwd
175
def parents: List[Directory] = {
177
if (p isSame this) Nil else p :: p.parents
179
// if name ends with an extension (e.g. "foo.jpg") returns the extension ("jpg"), otherwise ""
180
def extension: String = {
181
var i = name.length - 1
182
while (i >= 0 && name.charAt(i) != '.')
186
else name.substring(i + 1)
188
// def extension: String = (name lastIndexOf '.') match {
190
// case idx => name drop (idx + 1)
192
// compares against extensions in a CASE INSENSITIVE way.
193
def hasExtension(ext: String, exts: String*) = {
194
val lower = extension.toLowerCase
195
ext.toLowerCase == lower || exts.exists(_.toLowerCase == lower)
197
// returns the filename without the extension.
198
def stripExtension: String = name stripSuffix ("." + extension)
199
// returns the Path with the extension.
200
def addExtension(ext: String): Path = Path(path + "." + ext)
201
// changes the existing extension out for a new one, or adds it
202
// if the current path has none.
203
def changeExtension(ext: String): Path = (
204
if (extension == "") addExtension(ext)
205
else Path(path.stripSuffix(extension) + ext)
208
// conditionally execute
209
def ifFile[T](f: File => T): Option[T] = if (isFile) Some(f(toFile)) else None
210
def ifDirectory[T](f: Directory => T): Option[T] = if (isDirectory) Some(f(toDirectory)) else None
213
def canRead = jfile.canRead()
214
def canWrite = jfile.canWrite()
215
def exists = jfile.exists()
216
def notExists = try !jfile.exists() catch { case ex: SecurityException => false }
218
def isFile = jfile.isFile()
219
def isDirectory = jfile.isDirectory()
220
def isAbsolute = jfile.isAbsolute()
221
def isHidden = jfile.isHidden()
222
def isEmpty = path.length == 0
225
def lastModified = jfile.lastModified()
226
def lastModified_=(time: Long) = jfile setLastModified time // should use setXXX function?
227
def length = jfile.length()
229
// Boolean path comparisons
230
def endsWith(other: Path) = segments endsWith other.segments
231
def startsWith(other: Path) = segments startsWith other.segments
232
def isSame(other: Path) = toCanonical == other.toCanonical
233
def isFresher(other: Path) = lastModified > other.lastModified
236
def createDirectory(force: Boolean = true, failIfExists: Boolean = false): Directory = {
237
val res = if (force) jfile.mkdirs() else jfile.mkdir()
238
if (!res && failIfExists && exists) fail("Directory '%s' already exists." format name)
239
else if (isDirectory) toDirectory
240
else new Directory(jfile)
242
def createFile(failIfExists: Boolean = false): File = {
243
val res = jfile.createNewFile()
244
if (!res && failIfExists && exists) fail("File '%s' already exists." format name)
245
else if (isFile) toFile
250
def delete() = jfile.delete()
251
def deleteIfExists() = if (jfile.exists()) delete() else false
253
/** Deletes the path recursively. Returns false on failure.
256
def deleteRecursively(): Boolean = deleteRecursively(jfile)
257
private def deleteRecursively(f: JFile): Boolean = {
258
if (f.isDirectory) f.listFiles match {
260
case xs => xs foreach deleteRecursively
267
val raf = new RandomAccessFile(jfile, "rw")
273
def touch(modTime: Long = System.currentTimeMillis) = {
276
lastModified = modTime
280
// def copyTo(target: Path, options ...): Boolean
281
// def moveTo(target: Path, options ...): Boolean
283
override def toString() = path
284
override def equals(other: Any) = other match {
285
case x: Path => path == x.path
288
override def hashCode() = path.hashCode()