scala.reflect.io.Path.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-reflect Show documentation
Show all versions of scala-reflect Show documentation
Reflection Library for the Scala Programming Language
The newest version!
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala
package reflect
package io
import scala.language.implicitConversions
import java.io.{RandomAccessFile, File => JFile}
import java.net.{URI, URL}
import scala.annotation.tailrec
import scala.util.Random.alphanumeric
/** 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.
*
* ''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 =
name.lastIndexOf('.') match {
case i if i >= 0 =>
val xt = name.substring(i + 1)
xt.equalsIgnoreCase("jar") || xt.equalsIgnoreCase("zip")
case _ => false
}
/** Lower case "extension", following the last dot. */
def extension(name: String): String = {
val i = name.lastIndexOf('.')
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)
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 roots: List[Path] = java.io.File.listRoots().toList map Path.apply
def apply(path: String): Path = apply(new JFile(path))
def apply(jfile: JFile): Path = try {
def isFile = {
//if (settings.areStatisticsEnabled) Statistics.incCounter(IOStats.fileIsFileCount)
jfile.isFile
}
def isDirectory = {
//if (settings.areStatisticsEnabled) Statistics.incCounter(IOStats.fileIsDirectoryCount)
jfile.isDirectory
}
if (isFile) new File(jfile)
else if (isDirectory) new Directory(jfile)
else new Path(jfile)
} catch { case ex: SecurityException => 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
// 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 directory, 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 false.
* So for example if the condition is false for some subdirectory, nothing
* under that directory will be in the Iterator. If it's true, all files for
* which the condition holds and are directly in that subdirectory are in the
* Iterator, and all sub-subdirectories are recursively evaluated
*/
def walkFilter(cond: Path => Boolean): Iterator[Path] =
if (isFile) toFile walkFilter cond
else if (isDirectory) toDirectory walkFilter cond
else Iterator.empty
/** Equivalent to walkFilter(_ => true).
*/
def walk: Iterator[Path] = walkFilter(_ => true)
// identity
def name: String = jfile.getName()
def path: String = jfile.getPath()
def normalize: Path = Path(jfile.getAbsolutePath())
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)
@tailrec
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))
}
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)
}
// 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 = {
//if (settings.areStatisticsEnabled) Statistics.incCounter(IOStats.fileExistsCount)
try jfile.exists() catch { case ex: SecurityException => false }
}
def isFile = {
//if (settings.areStatisticsEnabled) Statistics.incCounter(IOStats.fileIsFileCount)
try jfile.isFile() catch { case ex: SecurityException => false }
}
def isDirectory = {
//if (settings.areStatisticsEnabled) Statistics.incCounter(IOStats.fileIsDirectoryCount)
try jfile.isDirectory() catch { case ex: SecurityException => jfile.getPath == "." }
}
def isAbsolute = jfile.isAbsolute()
def isEmpty = path.length == 0
// Information
def lastModified = jfile.lastModified()
def length = jfile.length()
// Boolean path comparisons
def endsWith(other: Path) = segments endsWith 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(s"Directory '$name' already exists.")
else if (isDirectory) toDirectory
else new Directory(jfile)
}
def createFile(failIfExists: Boolean = false): File = {
val res = jfile.createNewFile()
if (!res && failIfExists && exists) fail(s"File '$name' already exists.")
else if (isFile) toFile
else new File(jfile)
}
// deletions
def delete() = jfile.delete()
/** 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
}
override def toString() = path
override def equals(other: Any) = other match {
case x: Path => path == x.path
case _ => false
}
override def hashCode() = path.hashCode()
}