summaryrefslogtreecommitdiff
path: root/src/library/scala/io/Path.scala
blob: 41ecfceee54ff16da608f34c9494945520a17fc2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2003-2009, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scala.io

import java.io.{
  FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter,
  BufferedInputStream, BufferedOutputStream, File => JFile }
import java.net.{ URI, URL }
import collection.{ Sequence, Traversable }
import collection.immutable.{ StringVector => SV }
import PartialFunction._
import util.Random.nextASCIIString

/** 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
{
  // not sure whether this will be problematic
  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("AccessMode") {
  //   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 roots: List[Path] = JFile.listRoots().toList map Path.apply

  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)
  }
  private[io] def randomPrefix = nextASCIIString(6)
  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 = JFile.separatorChar

  // 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 toURI: URI = jfile.toURI()
  def toURL: URL = toURI.toURL()

  /** 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 = new Path(new JFile(jfile, child.path))
  def /(child: Directory): Directory = /(child: Path).toDirectory
  def /(child: File): File = /(child: Path).toFile

  // identity
  def name: String = jfile.getName()
  def path: String = jfile.getPath()
  def normalize: Path = Path(jfile.getCanonicalPath())
  // todo -
  // def resolve(other: Path): Path
  // def relativize(other: Path): Path

  // derived from identity
  def root: Option[Path] = roots find (this startsWith _)
  def segments: List[String] = (path split separator).toList filterNot (_.isEmpty)
  def parent: Option[Path] = Option(jfile.getParent()) map Path.apply
  def parents: List[Path] = parent match {
    case None     => Nil
    case Some(p)  => p :: p.parents
  }
  // if name ends with an extension (e.g. "foo.jpg") returns the extension ("jpg")
  def extension: Option[String] =
    condOpt(SV.lastIndexWhere(name, _ == '.')) {
      case idx if idx != -1 => SV.drop(name, idx + 1)
    }
  // Alternative approach:
  // (Option fromReturnValue SV.lastIndexWhere(name, _ == '.') map (x => SV.drop(name, x + 1))

  // 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()

  // Information
  def lastModified = jfile.lastModified()
  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) = toAbsolute == other.toAbsolute
  def isFresher(other: Path) = lastModified > other.lastModified

  // creations
  def create(): Boolean = true
  def createDirectory(): Directory =
    if (jfile.mkdirs()) new Directory(jfile)
    else fail("Failed to create new directory.")
  def createFile(): File =
    if (jfile.createNewFile()) new File(jfile)
    else fail("Failed to create new file.")

  // deletions
  def delete() = jfile.delete()
  def deleteIfExists() = if (jfile.exists()) delete() else false

  // todo
  // def copyTo(target: Path, options ...): Boolean
  // def moveTo(target: Path, options ...): Boolean

  override def toString() = "Path(%s)".format(path)
  override def equals(other: Any) = other match {
    case x: Path  => path == x.path
    case _        => false
  }
  override def hashCode() = path.hashCode()
}