-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathAbstractFile.scala
311 lines (261 loc) · 10.9 KB
/
AbstractFile.scala
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Martin Odersky
*/
package dotty.tools.io
import scala.language.unsafeNulls
import java.io.{
IOException, InputStream, OutputStream, BufferedOutputStream,
ByteArrayOutputStream
}
import java.net.URL
import java.nio.file.{FileAlreadyExistsException, Files, Paths}
/**
* 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 {
def getFile(path: String): AbstractFile = getFile(File(path))
def getDirectory(path: String): AbstractFile = getDirectory(Directory(path))
def getFile(path: JPath): AbstractFile = getFile(File(path))
def getDirectory(path: JPath): AbstractFile = getDirectory(Directory(path))
/**
* If the specified File exists and is a regular file, returns an
* abstract regular file backed by it. Otherwise, returns `null`.
*/
def getFile(path: Path): AbstractFile =
if (path.isFile) new PlainFile(path) else null
/**
* 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`.
*/
def getDirectory(path: Path): AbstractFile =
if (path.isDirectory) new PlainFile(path)
else if (path.isFile && Path.isExtensionJarOrZip(path.jpath)) ZipArchive fromFile path.toFile
else null
/**
* If the specified URL exists and is a regular file or a directory, returns an
* abstract regular file or an abstract directory, respectively, backed by it.
* Otherwise, returns `null`.
*/
def getURL(url: URL): AbstractFile =
if (url.getProtocol != "file") null
else new PlainFile(new Path(Paths.get(url.toURI)))
def getResources(url: URL): AbstractFile = ZipArchive fromManifestURL url
}
/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* The interface does <b>not</b> 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`.
* </p>
*
* ''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 absolute path of this abstract file. */
def absolutePath: String = path
/** Returns the path of this abstract file in a canonical form. */
def canonicalPath: String = if (jpath == null) path else jpath.normalize.toString
/** Checks extension case insensitively. */
@deprecated("prefer queries on ext")
def hasExtension(other: String): Boolean = ext.toLowerCase.equalsIgnoreCase(other)
/** Returns the extension of this abstract file. */
val ext: FileExtension = Path.fileExtension(name)
/** Returns the extension of this abstract file as a String. */
@deprecated("use ext instead.")
def extension: String = ext.toLowerCase
/** 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 = try {
if (jpath == null) null
else jpath.toFile
} catch {
case _: UnsupportedOperationException => null
}
/** Returns the underlying Path if any and null otherwise. */
def jpath: JPath
/** 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 = {
(jpath eq null) || Files.exists(jpath)
}
/** Does this abstract file represent something which can contain classfiles? */
def isClassContainer: Boolean = isDirectory || (jpath != null && ext.isJarOrZip)
/** 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
/** Does this abstract file correspond to something on-disk? */
def isVirtual: Boolean = false
/** 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 (jpath == null) null else jpath.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: Array[Char] = new String(toByteArray).toCharArray
/** Returns contents of file (if applicable) in a byte array.
*/
@throws(classOf[IOException])
def toByteArray: Array[Byte] = {
val in = input
sizeOption match {
case Some(size) =>
var rest = size
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
case None =>
val out = new ByteArrayOutputStream()
var c = in.read()
while(c != -1) {
out.write(c)
c = in.read()
}
in.close()
out.toByteArray()
}
}
/** Returns all abstract subfiles of this abstract directory. */
def iterator(): Iterator[AbstractFile]
/** Drill down through subdirs looking for the target, as in lookupName.
* Ths target name is the last of parts.
*/
final def lookupPath(parts: Seq[String], directory: Boolean): AbstractFile =
var file: AbstractFile = this
var i = 0
val n = parts.length - 1
while file != null && i < n do
file = file.lookupName(parts(i), directory = true)
i += 1
if file == null then null else file.lookupName(parts(i), directory = directory)
end lookupPath
/** 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
/** 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
}
/** Returns the sibling abstract file in the parent of this abstract file or directory.
* If there is no such file, returns `null`.
*/
final def resolveSibling(name: String): AbstractFile | Null =
container.lookupName(name, directory = false)
final def resolveSiblingWithExtension(extension: FileExtension): AbstractFile | Null =
resolveSibling(Path.fileName(name) + "." + extension)
private def fileOrSubdirectoryNamed(name: String, isDir: Boolean): AbstractFile =
lookupName(name, isDir) match {
case null =>
// the optional exception may be thrown for symlinks, notably /tmp on macOS.
// isDirectory tests for existing directory. The default behavior is hypothetical isDirectory(jpath, FOLLOW_LINKS).
try Files.createDirectories(jpath)
catch { case _: FileAlreadyExistsException if Files.isDirectory(jpath) => }
// a race condition in creating the entry after the failed lookup may throw
val path = jpath.resolve(name)
try
if (isDir) Files.createDirectory(path)
else Files.createFile(path)
catch case _: FileAlreadyExistsException => ()
new PlainFile(new File(path))
case lookup => lookup
}
/**
* 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, isDir = 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, isDir = 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(): String = path
}