OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library engine.source.io; |
| 6 |
| 7 import 'dart:collection'; |
| 8 |
| 9 import 'engine.dart'; |
| 10 import 'java_core.dart'; |
| 11 import 'java_engine.dart'; |
| 12 import 'java_io.dart'; |
| 13 import 'source.dart'; |
| 14 |
| 15 export 'source.dart'; |
| 16 |
| 17 /** |
| 18 * Instances of the class [DirectoryBasedSourceContainer] represent a source con
tainer that |
| 19 * contains all sources within a given directory. |
| 20 */ |
| 21 class DirectoryBasedSourceContainer implements SourceContainer { |
| 22 /** |
| 23 * The container's path (not `null`). |
| 24 */ |
| 25 String _path; |
| 26 |
| 27 /** |
| 28 * Construct a container representing the specified directory and containing a
ny sources whose |
| 29 * [Source.fullName] starts with the directory's path. This is a convenience m
ethod, |
| 30 * fully equivalent to [DirectoryBasedSourceContainer.con2]. |
| 31 * |
| 32 * @param directory the directory (not `null`) |
| 33 */ |
| 34 DirectoryBasedSourceContainer.con1(JavaFile directory) |
| 35 : this.con2(directory.getPath()); |
| 36 |
| 37 /** |
| 38 * Construct a container representing the specified path and containing any so
urces whose |
| 39 * [Source.fullName] starts with the specified path. |
| 40 * |
| 41 * @param path the path (not `null` and not empty) |
| 42 */ |
| 43 DirectoryBasedSourceContainer.con2(String path) { |
| 44 this._path = _appendFileSeparator(path); |
| 45 } |
| 46 |
| 47 @override |
| 48 int get hashCode => _path.hashCode; |
| 49 |
| 50 /** |
| 51 * Answer the receiver's path, used to determine if a source is contained in t
he receiver. |
| 52 * |
| 53 * @return the path (not `null`, not empty) |
| 54 */ |
| 55 String get path => _path; |
| 56 |
| 57 @override |
| 58 bool operator ==(Object obj) => |
| 59 (obj is DirectoryBasedSourceContainer) && obj.path == path; |
| 60 |
| 61 @override |
| 62 bool contains(Source source) => source.fullName.startsWith(_path); |
| 63 |
| 64 @override |
| 65 String toString() => "SourceContainer[$_path]"; |
| 66 |
| 67 /** |
| 68 * Append the system file separator to the given path unless the path already
ends with a |
| 69 * separator. |
| 70 * |
| 71 * @param path the path to which the file separator is to be added |
| 72 * @return a path that ends with the system file separator |
| 73 */ |
| 74 static String _appendFileSeparator(String path) { |
| 75 if (path == null || |
| 76 path.length <= 0 || |
| 77 path.codeUnitAt(path.length - 1) == JavaFile.separatorChar) { |
| 78 return path; |
| 79 } |
| 80 return "$path${JavaFile.separator}"; |
| 81 } |
| 82 } |
| 83 |
| 84 /** |
| 85 * Instances of the class `FileBasedSource` implement a source that represents a
file. |
| 86 */ |
| 87 class FileBasedSource extends Source { |
| 88 /** |
| 89 * A function that changes the way that files are read off of disk. |
| 90 */ |
| 91 static Function fileReadMode = (String s) => s; |
| 92 |
| 93 /** |
| 94 * Map from encoded URI/filepath pair to a unique integer identifier. This |
| 95 * identifier is used for equality tests and hash codes. |
| 96 * |
| 97 * The URI and filepath are joined into a pair by separating them with an '@' |
| 98 * character. |
| 99 */ |
| 100 static final Map<String, int> _idTable = new HashMap<String, int>(); |
| 101 |
| 102 /** |
| 103 * The URI from which this source was originally derived. |
| 104 */ |
| 105 final Uri uri; |
| 106 |
| 107 /** |
| 108 * The unique ID associated with this [FileBasedSource]. |
| 109 */ |
| 110 final int id; |
| 111 |
| 112 /** |
| 113 * The file represented by this source. |
| 114 */ |
| 115 final JavaFile file; |
| 116 |
| 117 /** |
| 118 * The cached absolute path of this source. |
| 119 */ |
| 120 String _absolutePath; |
| 121 |
| 122 /** |
| 123 * The cached encoding for this source. |
| 124 */ |
| 125 String _encoding; |
| 126 |
| 127 /** |
| 128 * Initialize a newly created source object to represent the given [file]. If |
| 129 * a [uri] is given, then it will be used as the URI from which the source was |
| 130 * derived, otherwise a `file:` URI will be created based on the [file]. |
| 131 */ |
| 132 FileBasedSource(JavaFile file, [Uri uri]) |
| 133 : this.uri = (uri == null ? file.toURI() : uri), |
| 134 this.file = file, |
| 135 id = _idTable.putIfAbsent( |
| 136 '${uri == null ? file.toURI() : uri}@${file.getPath()}', |
| 137 () => _idTable.length); |
| 138 |
| 139 /** |
| 140 * Initialize a newly created source object. |
| 141 * |
| 142 * @param file the file represented by this source |
| 143 */ |
| 144 @deprecated // Use new FileBasedSource(file) |
| 145 FileBasedSource.con1(JavaFile file) : this(file); |
| 146 |
| 147 /** |
| 148 * Initialize a newly created source object. |
| 149 * |
| 150 * @param file the file represented by this source |
| 151 * @param uri the URI from which this source was originally derived |
| 152 */ |
| 153 @deprecated // Use new FileBasedSource(file, uri) |
| 154 FileBasedSource.con2(Uri uri, JavaFile file) |
| 155 : uri = uri, |
| 156 file = file, |
| 157 id = _idTable.putIfAbsent( |
| 158 '$uri@${file.getPath()}', () => _idTable.length); |
| 159 |
| 160 @override |
| 161 TimestampedData<String> get contents { |
| 162 return PerformanceStatistics.io.makeCurrentWhile(() { |
| 163 return contentsFromFile; |
| 164 }); |
| 165 } |
| 166 |
| 167 /** |
| 168 * Get the contents and timestamp of the underlying file. |
| 169 * |
| 170 * Clients should consider using the the method [AnalysisContext.getContents] |
| 171 * because contexts can have local overrides of the content of a source that t
he source is not |
| 172 * aware of. |
| 173 * |
| 174 * @return the contents of the source paired with the modification stamp of th
e source |
| 175 * @throws Exception if the contents of this source could not be accessed |
| 176 * See [contents]. |
| 177 */ |
| 178 TimestampedData<String> get contentsFromFile { |
| 179 return new TimestampedData<String>( |
| 180 file.lastModified(), fileReadMode(file.readAsStringSync())); |
| 181 } |
| 182 |
| 183 @override |
| 184 String get encoding { |
| 185 if (_encoding == null) { |
| 186 _encoding = uri.toString(); |
| 187 } |
| 188 return _encoding; |
| 189 } |
| 190 |
| 191 @override |
| 192 String get fullName { |
| 193 if (_absolutePath == null) { |
| 194 _absolutePath = file.getAbsolutePath(); |
| 195 } |
| 196 return _absolutePath; |
| 197 } |
| 198 |
| 199 @override |
| 200 int get hashCode => id; |
| 201 |
| 202 @override |
| 203 bool get isInSystemLibrary => uri.scheme == DartUriResolver.DART_SCHEME; |
| 204 |
| 205 @override |
| 206 int get modificationStamp => file.lastModified(); |
| 207 |
| 208 @override |
| 209 String get shortName => file.getName(); |
| 210 |
| 211 @override |
| 212 UriKind get uriKind { |
| 213 String scheme = uri.scheme; |
| 214 if (scheme == PackageUriResolver.PACKAGE_SCHEME) { |
| 215 return UriKind.PACKAGE_URI; |
| 216 } else if (scheme == DartUriResolver.DART_SCHEME) { |
| 217 return UriKind.DART_URI; |
| 218 } else if (scheme == FileUriResolver.FILE_SCHEME) { |
| 219 return UriKind.FILE_URI; |
| 220 } |
| 221 return UriKind.FILE_URI; |
| 222 } |
| 223 |
| 224 @override |
| 225 bool operator ==(Object object) => |
| 226 object is FileBasedSource && id == object.id; |
| 227 |
| 228 @override |
| 229 bool exists() => file.isFile(); |
| 230 |
| 231 @override |
| 232 Uri resolveRelativeUri(Uri containedUri) { |
| 233 try { |
| 234 Uri baseUri = uri; |
| 235 bool isOpaque = uri.isAbsolute && !uri.path.startsWith('/'); |
| 236 if (isOpaque) { |
| 237 String scheme = uri.scheme; |
| 238 String part = uri.path; |
| 239 if (scheme == DartUriResolver.DART_SCHEME && part.indexOf('/') < 0) { |
| 240 part = "$part/$part.dart"; |
| 241 } |
| 242 baseUri = parseUriWithException("$scheme:/$part"); |
| 243 } |
| 244 Uri result = baseUri.resolveUri(containedUri); |
| 245 if (isOpaque) { |
| 246 result = parseUriWithException( |
| 247 "${result.scheme}:${result.path.substring(1)}"); |
| 248 } |
| 249 return result; |
| 250 } catch (exception, stackTrace) { |
| 251 throw new AnalysisException( |
| 252 "Could not resolve URI ($containedUri) relative to source ($uri)", |
| 253 new CaughtException(exception, stackTrace)); |
| 254 } |
| 255 } |
| 256 |
| 257 @override |
| 258 String toString() { |
| 259 if (file == null) { |
| 260 return "<unknown source>"; |
| 261 } |
| 262 return file.getAbsolutePath(); |
| 263 } |
| 264 } |
| 265 |
| 266 /** |
| 267 * Instances of the class `FileUriResolver` resolve `file` URI's. |
| 268 */ |
| 269 class FileUriResolver extends UriResolver { |
| 270 /** |
| 271 * The name of the `file` scheme. |
| 272 */ |
| 273 static String FILE_SCHEME = "file"; |
| 274 |
| 275 @override |
| 276 Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| 277 if (!isFileUri(uri)) { |
| 278 return null; |
| 279 } |
| 280 return new FileBasedSource( |
| 281 new JavaFile.fromUri(uri), actualUri != null ? actualUri : uri); |
| 282 } |
| 283 |
| 284 @override |
| 285 Uri restoreAbsolute(Source source) { |
| 286 return new Uri.file(source.fullName); |
| 287 } |
| 288 |
| 289 /** |
| 290 * Return `true` if the given URI is a `file` URI. |
| 291 * |
| 292 * @param uri the URI being tested |
| 293 * @return `true` if the given URI is a `file` URI |
| 294 */ |
| 295 static bool isFileUri(Uri uri) => uri.scheme == FILE_SCHEME; |
| 296 } |
| 297 |
| 298 /** |
| 299 * Instances of interface `LocalSourcePredicate` are used to determine if the gi
ven |
| 300 * [Source] is "local" in some sense, so can be updated. |
| 301 */ |
| 302 abstract class LocalSourcePredicate { |
| 303 /** |
| 304 * Instance of [LocalSourcePredicate] that always returns `false`. |
| 305 */ |
| 306 static final LocalSourcePredicate FALSE = new LocalSourcePredicate_FALSE(); |
| 307 |
| 308 /** |
| 309 * Instance of [LocalSourcePredicate] that always returns `true`. |
| 310 */ |
| 311 static final LocalSourcePredicate TRUE = new LocalSourcePredicate_TRUE(); |
| 312 |
| 313 /** |
| 314 * Instance of [LocalSourcePredicate] that returns `true` for all [Source]s |
| 315 * except of SDK. |
| 316 */ |
| 317 static final LocalSourcePredicate NOT_SDK = |
| 318 new LocalSourcePredicate_NOT_SDK(); |
| 319 |
| 320 /** |
| 321 * Determines if the given [Source] is local. |
| 322 * |
| 323 * @param source the [Source] to analyze |
| 324 * @return `true` if the given [Source] is local |
| 325 */ |
| 326 bool isLocal(Source source); |
| 327 } |
| 328 |
| 329 class LocalSourcePredicate_FALSE implements LocalSourcePredicate { |
| 330 @override |
| 331 bool isLocal(Source source) => false; |
| 332 } |
| 333 |
| 334 class LocalSourcePredicate_NOT_SDK implements LocalSourcePredicate { |
| 335 @override |
| 336 bool isLocal(Source source) => source.uriKind != UriKind.DART_URI; |
| 337 } |
| 338 |
| 339 class LocalSourcePredicate_TRUE implements LocalSourcePredicate { |
| 340 @override |
| 341 bool isLocal(Source source) => true; |
| 342 } |
| 343 |
| 344 /** |
| 345 * Instances of the class `PackageUriResolver` resolve `package` URI's in the co
ntext of |
| 346 * an application. |
| 347 * |
| 348 * For the purposes of sharing analysis, the path to each package under the "pac
kages" directory |
| 349 * should be canonicalized, but to preserve relative links within a package, the
remainder of the |
| 350 * path from the package directory to the leaf should not. |
| 351 */ |
| 352 class PackageUriResolver extends UriResolver { |
| 353 /** |
| 354 * The name of the `package` scheme. |
| 355 */ |
| 356 static String PACKAGE_SCHEME = "package"; |
| 357 |
| 358 /** |
| 359 * Log exceptions thrown with the message "Required key not available" only on
ce. |
| 360 */ |
| 361 static bool _CanLogRequiredKeyIoException = true; |
| 362 |
| 363 /** |
| 364 * The package directories that `package` URI's are assumed to be relative to. |
| 365 */ |
| 366 final List<JavaFile> _packagesDirectories; |
| 367 |
| 368 /** |
| 369 * Initialize a newly created resolver to resolve `package` URI's relative to
the given |
| 370 * package directories. |
| 371 * |
| 372 * @param packagesDirectories the package directories that `package` URI's are
assumed to be |
| 373 * relative to |
| 374 */ |
| 375 PackageUriResolver(this._packagesDirectories) { |
| 376 if (_packagesDirectories.length < 1) { |
| 377 throw new IllegalArgumentException( |
| 378 "At least one package directory must be provided"); |
| 379 } |
| 380 } |
| 381 |
| 382 /** |
| 383 * If the list of package directories contains one element, return it. |
| 384 * Otherwise raise an exception. Intended for testing. |
| 385 */ |
| 386 String get packagesDirectory_forTesting { |
| 387 int length = _packagesDirectories.length; |
| 388 if (length != 1) { |
| 389 throw new Exception('Expected 1 package directory, found $length'); |
| 390 } |
| 391 return _packagesDirectories[0].getPath(); |
| 392 } |
| 393 |
| 394 /** |
| 395 * Answer the canonical file for the specified package. |
| 396 * |
| 397 * @param packagesDirectory the "packages" directory (not `null`) |
| 398 * @param pkgName the package name (not `null`, not empty) |
| 399 * @param relPath the path relative to the package directory (not `null`, no l
eading slash, |
| 400 * but may be empty string) |
| 401 * @return the file (not `null`) |
| 402 */ |
| 403 JavaFile getCanonicalFile( |
| 404 JavaFile packagesDirectory, String pkgName, String relPath) { |
| 405 JavaFile pkgDir = new JavaFile.relative(packagesDirectory, pkgName); |
| 406 try { |
| 407 pkgDir = pkgDir.getCanonicalFile(); |
| 408 } on JavaIOException catch (exception, stackTrace) { |
| 409 if (!exception.toString().contains("Required key not available")) { |
| 410 AnalysisEngine.instance.logger.logError("Canonical failed: $pkgDir", |
| 411 new CaughtException(exception, stackTrace)); |
| 412 } else if (_CanLogRequiredKeyIoException) { |
| 413 _CanLogRequiredKeyIoException = false; |
| 414 AnalysisEngine.instance.logger.logError("Canonical failed: $pkgDir", |
| 415 new CaughtException(exception, stackTrace)); |
| 416 } |
| 417 } |
| 418 return new JavaFile.relative(pkgDir, relPath.replaceAll( |
| 419 '/', new String.fromCharCode(JavaFile.separatorChar))); |
| 420 } |
| 421 |
| 422 @override |
| 423 Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| 424 if (!isPackageUri(uri)) { |
| 425 return null; |
| 426 } |
| 427 String path = uri.path; |
| 428 if (path == null) { |
| 429 path = uri.path; |
| 430 if (path == null) { |
| 431 return null; |
| 432 } |
| 433 } |
| 434 String pkgName; |
| 435 String relPath; |
| 436 int index = path.indexOf('/'); |
| 437 if (index == -1) { |
| 438 // No slash |
| 439 pkgName = path; |
| 440 relPath = ""; |
| 441 } else if (index == 0) { |
| 442 // Leading slash is invalid |
| 443 return null; |
| 444 } else { |
| 445 // <pkgName>/<relPath> |
| 446 pkgName = path.substring(0, index); |
| 447 relPath = path.substring(index + 1); |
| 448 } |
| 449 for (JavaFile packagesDirectory in _packagesDirectories) { |
| 450 JavaFile resolvedFile = new JavaFile.relative(packagesDirectory, path); |
| 451 if (resolvedFile.exists()) { |
| 452 JavaFile canonicalFile = |
| 453 getCanonicalFile(packagesDirectory, pkgName, relPath); |
| 454 if (_isSelfReference(packagesDirectory, canonicalFile)) { |
| 455 uri = canonicalFile.toURI(); |
| 456 } |
| 457 return new FileBasedSource( |
| 458 canonicalFile, actualUri != null ? actualUri : uri); |
| 459 } |
| 460 } |
| 461 return new FileBasedSource( |
| 462 getCanonicalFile(_packagesDirectories[0], pkgName, relPath), |
| 463 actualUri != null ? actualUri : uri); |
| 464 } |
| 465 |
| 466 @override |
| 467 Uri restoreAbsolute(Source source) { |
| 468 String sourcePath = source.fullName; |
| 469 for (JavaFile packagesDirectory in _packagesDirectories) { |
| 470 List<JavaFile> pkgFolders = packagesDirectory.listFiles(); |
| 471 if (pkgFolders != null) { |
| 472 for (JavaFile pkgFolder in pkgFolders) { |
| 473 try { |
| 474 String pkgCanonicalPath = pkgFolder.getCanonicalPath(); |
| 475 if (sourcePath.startsWith(pkgCanonicalPath)) { |
| 476 String relPath = sourcePath.substring(pkgCanonicalPath.length); |
| 477 return parseUriWithException( |
| 478 "$PACKAGE_SCHEME:${pkgFolder.getName()}$relPath"); |
| 479 } |
| 480 } catch (e) {} |
| 481 } |
| 482 } |
| 483 } |
| 484 return null; |
| 485 } |
| 486 |
| 487 /** |
| 488 * @return `true` if "file" was found in "packagesDir", and it is part of the
"lib" folder |
| 489 * of the application that contains in this "packagesDir". |
| 490 */ |
| 491 bool _isSelfReference(JavaFile packagesDir, JavaFile file) { |
| 492 JavaFile rootDir = packagesDir.getParentFile(); |
| 493 if (rootDir == null) { |
| 494 return false; |
| 495 } |
| 496 String rootPath = rootDir.getAbsolutePath(); |
| 497 String filePath = file.getAbsolutePath(); |
| 498 return filePath.startsWith("$rootPath/lib"); |
| 499 } |
| 500 |
| 501 /** |
| 502 * Return `true` if the given URI is a `package` URI. |
| 503 * |
| 504 * @param uri the URI being tested |
| 505 * @return `true` if the given URI is a `package` URI |
| 506 */ |
| 507 static bool isPackageUri(Uri uri) => PACKAGE_SCHEME == uri.scheme; |
| 508 } |
| 509 |
| 510 /** |
| 511 * Instances of the class `RelativeFileUriResolver` resolve `file` URI's. |
| 512 */ |
| 513 class RelativeFileUriResolver extends UriResolver { |
| 514 /** |
| 515 * The name of the `file` scheme. |
| 516 */ |
| 517 static String FILE_SCHEME = "file"; |
| 518 |
| 519 /** |
| 520 * The directories for the relatvie URI's |
| 521 */ |
| 522 final List<JavaFile> _relativeDirectories; |
| 523 |
| 524 /** |
| 525 * The root directory for all the source trees |
| 526 */ |
| 527 final JavaFile _rootDirectory; |
| 528 |
| 529 /** |
| 530 * Initialize a newly created resolver to resolve `file` URI's relative to the
given root |
| 531 * directory. |
| 532 */ |
| 533 RelativeFileUriResolver(this._rootDirectory, this._relativeDirectories) |
| 534 : super(); |
| 535 |
| 536 @override |
| 537 Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| 538 String rootPath = _rootDirectory.toURI().path; |
| 539 String uriPath = uri.path; |
| 540 if (uriPath != null && uriPath.startsWith(rootPath)) { |
| 541 String filePath = uri.path.substring(rootPath.length); |
| 542 for (JavaFile dir in _relativeDirectories) { |
| 543 JavaFile file = new JavaFile.relative(dir, filePath); |
| 544 if (file.exists()) { |
| 545 return new FileBasedSource(file, actualUri != null ? actualUri : uri); |
| 546 } |
| 547 } |
| 548 } |
| 549 return null; |
| 550 } |
| 551 |
| 552 /** |
| 553 * Return `true` if the given URI is a `file` URI. |
| 554 * |
| 555 * @param uri the URI being tested |
| 556 * @return `true` if the given URI is a `file` URI |
| 557 */ |
| 558 static bool isFileUri(Uri uri) => uri.scheme == FILE_SCHEME; |
| 559 } |
OLD | NEW |