scala/ScalaBook/chapter-09/src/main/scala/scalabook/path/Path.scala
package scalabook.path
trait Path {
def name: String
def fullName: String
def relative: Path
def isEmpty: Boolean
def isRoot: Boolean
def isUNC: Boolean
def isAbsolute: Boolean = !isRelative
def isRelative: Boolean = !isAbsolute
def /(that: String): Path
def /(that: Path): Path
def parent: Path
def parts: List[String]
def pathParts = parts.map(Path(_))
def compare(that: Path) = this.fullName compare that.fullName
override def hashCode = fullName.hashCode
override def toString = fullName
override def equals(any: Any) =
any.isInstanceOf[Path] &&
any.asInstanceOf[Path].fullName == this.fullName
def splitName = {
val dotPos = name.indexOf('.')
if(-1 == dotPos || 0 == dotPos) // no extension for .special files :)
(name, "")
else
(name.substring(0, dotPos), name.substring(dotPos + 1))
}
def extension = splitName._2
def bareName = splitName._1
def isChildOf(that: Path) =
// So that Path("/").isChildOf("") returns false
fullName.length > that.fullName.length &&
that.fullName.length > 0 &&
0 == this.parent.compare(that)
}
object EmptyPath extends Path {
def name = ""
def fullName = ""
def relative = this
def isEmpty = true
def isRoot = false
def isUNC = false
override def isAbsolute = false
override def isRelative = false
def parts = Nil
def parent = this
def /(that: String) = Path(that)
def /(that: Path) = that
override def isChildOf(that: Path) = false
}
object Path {
type GenericPath = UnixPath
implicit def string2PathWrapper(path: String) = new PathWrapper(path)
implicit def symbol2PathWrapper(path: Symbol) = new PathWrapper(path.name)
val isWindows = System.getProperty("os.name").toLowerCase.contains("windows")
def isBackSlash(ch: Char) = '\\' == ch
def isSlash(ch: Char) = '/' == ch
def isAnySlash(ch: Char) = isSlash(ch) || isBackSlash(ch)
def getSlashF(anySlash: Boolean) =
if(anySlash) isAnySlash _ else isSlash _
def countPrefixSlashes(path: String, startIndex: Int) = {
var index = startIndex
while(index < path.length && isSlash(path.charAt(index)))
index += 1
index - startIndex
}
def firstNonSlashIndex(path: String, startIndex: Int) = {
var index = startIndex
while(index < path.length && !isSlash(path.charAt(index)))
index += 1
index
}
def previousSlashIndex(path: String, endIndex: Int) = {
var index = endIndex
while(index > 0 && !isSlash(path.charAt(index)))
index -= 1
index
}
def lastNonSlashIndex(path: String, anySlash: Boolean) = {
val isSlashF = getSlashF(anySlash)
var index = path.length - 1
while(index > 0 && isSlashF(path.charAt(index)))
index -= 1
index
}
def lastNonSlashIndex2(path: String, anySlash: Boolean) = {
val isSlashF = getSlashF(anySlash)
def discoverIndex(index: Int): Int =
if(index <= 0)
-1
else if(isSlashF(path.charAt(index)))
discoverIndex(index - 1)
else
index
discoverIndex(path.length - 1)
}
def removeRedundantSlashes(path: String, startIndex: Int, endIndex: Int, anySlash: Boolean) = {
val sb = new StringBuilder
var index = startIndex
var previousWasSlash = false
val isSlashF = getSlashF(anySlash)
while(startIndex <= index && index <= endIndex) {
val ch = path.charAt(index)
if(isSlashF(ch)) {
if(!previousWasSlash) {
sb.append('/')
previousWasSlash = true
}
} else {
sb.append(ch)
previousWasSlash = false
}
index += 1
}
sb.toString
}
def isDriveLetter(ch: Char) =
'a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z'
def parseSimplePath(path: String, anySlash: Boolean) =
new UnixPath(removeRedundantSlashes(path, 0, lastNonSlashIndex(path, anySlash), anySlash))
def parseUnixPath(path: String): UnixPath =
parseSimplePath(path, false)
def parseWinPath(path: String) = {
val len = path.length
val ch0 = path.charAt(0)
len match {
case 1 =>
parseSimplePath(path, true)
case _ =>
val ch1 = path.charAt(1)
if(isAnySlash(ch0) && isAnySlash(ch1))
new UNCPath(
"//" +
removeRedundantSlashes(
path, 2, lastNonSlashIndex(path, true), true))
else if(len > 2 &&
isDriveLetter(ch0) &&
':' == ch1 &&
isAnySlash(path.charAt(2))) {
val prefix = path.substring(0, 2) // C:
val rest = path.substring(2)
val suffix = removeRedundantSlashes(
rest, 0, lastNonSlashIndex(rest, true), true)
new DriveAbsPath(prefix + suffix)
} else
parseSimplePath(path, true)
}
}
def apply(path: String): Path =
if("" equals path)
EmptyPath
else if(isWindows)
parseWinPath(path)
else
parseUnixPath(path)
def combine(a: Path, b: String): Path = combine(a, Path(b))
def combine(a: Path, b: Path) = {
(a.isAbsolute, b.isAbsolute) match {
case (_, true) => error("Cannot concatenate path <%s> with absolute path <%s>".format(a, b))
case (_, false) => Path(a.fullName + "/" + b.fullName)
}
}
object UnixPath {
def apply(path: String): Path =
if("" equals path)
EmptyPath
else
parseUnixPath(path)
}
}