Index: observatory_pub_packages/analyzer/file_system/memory_file_system.dart |
=================================================================== |
--- observatory_pub_packages/analyzer/file_system/memory_file_system.dart (revision 0) |
+++ observatory_pub_packages/analyzer/file_system/memory_file_system.dart (working copy) |
@@ -0,0 +1,383 @@ |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library memory_file_system; |
+ |
+import 'dart:async'; |
+import 'dart:collection'; |
+ |
+import 'package:analyzer/src/generated/engine.dart' show TimestampedData; |
+import 'package:analyzer/src/generated/source_io.dart'; |
+import 'package:path/path.dart'; |
+import 'package:watcher/watcher.dart'; |
+ |
+import 'file_system.dart'; |
+ |
+ |
+/** |
+ * Exception thrown when a memory [Resource] file operation fails. |
+ */ |
+class MemoryResourceException { |
+ final path; |
+ final message; |
+ |
+ MemoryResourceException(this.path, this.message); |
+ |
+ @override |
+ String toString() { |
+ return "MemoryResourceException(path=$path; message=$message)"; |
+ } |
+} |
+ |
+ |
+/** |
+ * An in-memory implementation of [ResourceProvider]. |
+ * Use `/` as a path separator. |
+ */ |
+class MemoryResourceProvider implements ResourceProvider { |
+ final Map<String, _MemoryResource> _pathToResource = |
+ new HashMap<String, _MemoryResource>(); |
+ final Map<String, String> _pathToContent = new HashMap<String, String>(); |
+ final Map<String, int> _pathToTimestamp = new HashMap<String, int>(); |
+ final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers = |
+ new HashMap<String, List<StreamController<WatchEvent>>>(); |
+ int nextStamp = 0; |
+ |
+ @override |
+ Context get pathContext => posix; |
+ |
+ void deleteFile(String path) { |
+ _checkFileAtPath(path); |
+ _pathToResource.remove(path); |
+ _pathToContent.remove(path); |
+ _pathToTimestamp.remove(path); |
+ _notifyWatchers(path, ChangeType.REMOVE); |
+ } |
+ |
+ @override |
+ Resource getResource(String path) { |
+ path = posix.normalize(path); |
+ Resource resource = _pathToResource[path]; |
+ if (resource == null) { |
+ resource = new _MemoryFile(this, path); |
+ } |
+ return resource; |
+ } |
+ |
+ void modifyFile(String path, String content) { |
+ _checkFileAtPath(path); |
+ _pathToContent[path] = content; |
+ _pathToTimestamp[path] = nextStamp++; |
+ _notifyWatchers(path, ChangeType.MODIFY); |
+ } |
+ |
+ /** |
+ * Create a resource representing a dummy link (that is, a File object which |
+ * appears in its parent directory, but whose `exists` property is false) |
+ */ |
+ File newDummyLink(String path) { |
+ path = posix.normalize(path); |
+ newFolder(posix.dirname(path)); |
+ _MemoryDummyLink link = new _MemoryDummyLink(this, path); |
+ _pathToResource[path] = link; |
+ _pathToTimestamp[path] = nextStamp++; |
+ _notifyWatchers(path, ChangeType.ADD); |
+ return link; |
+ } |
+ |
+ File newFile(String path, String content) { |
+ path = posix.normalize(path); |
+ newFolder(posix.dirname(path)); |
+ _MemoryFile file = new _MemoryFile(this, path); |
+ _pathToResource[path] = file; |
+ _pathToContent[path] = content; |
+ _pathToTimestamp[path] = nextStamp++; |
+ _notifyWatchers(path, ChangeType.ADD); |
+ return file; |
+ } |
+ |
+ Folder newFolder(String path) { |
+ path = posix.normalize(path); |
+ if (!path.startsWith('/')) { |
+ throw new ArgumentError("Path must start with '/'"); |
+ } |
+ _MemoryResource resource = _pathToResource[path]; |
+ if (resource == null) { |
+ String parentPath = posix.dirname(path); |
+ if (parentPath != path) { |
+ newFolder(parentPath); |
+ } |
+ _MemoryFolder folder = new _MemoryFolder(this, path); |
+ _pathToResource[path] = folder; |
+ _pathToTimestamp[path] = nextStamp++; |
+ return folder; |
+ } else if (resource is _MemoryFolder) { |
+ return resource; |
+ } else { |
+ String message = |
+ 'Folder expected at ' "'$path'" 'but ${resource.runtimeType} found'; |
+ throw new ArgumentError(message); |
+ } |
+ } |
+ |
+ void _checkFileAtPath(String path) { |
+ _MemoryResource resource = _pathToResource[path]; |
+ if (resource is! _MemoryFile) { |
+ throw new ArgumentError( |
+ 'File expected at "$path" but ${resource.runtimeType} found'); |
+ } |
+ } |
+ |
+ void _notifyWatchers(String path, ChangeType changeType) { |
+ _pathToWatchers.forEach( |
+ (String watcherPath, List<StreamController<WatchEvent>> streamControllers) { |
+ if (posix.isWithin(watcherPath, path)) { |
+ for (StreamController<WatchEvent> streamController in streamControllers) |
+ { |
+ streamController.add(new WatchEvent(changeType, path)); |
+ } |
+ } |
+ }); |
+ } |
+} |
+ |
+ |
+/** |
+ * An in-memory implementation of [File] which acts like a symbolic link to a |
+ * non-existent file. |
+ */ |
+class _MemoryDummyLink extends _MemoryResource implements File { |
+ _MemoryDummyLink(MemoryResourceProvider provider, String path) : super( |
+ provider, |
+ path); |
+ |
+ @override |
+ bool get exists => false; |
+ |
+ String get _content { |
+ throw new MemoryResourceException(path, "File '$path' could not be read"); |
+ } |
+ |
+ int get _timestamp => _provider._pathToTimestamp[path]; |
+ |
+ @override |
+ Source createSource([Uri uri]) { |
+ throw new MemoryResourceException(path, "File '$path' could not be read"); |
+ } |
+ |
+ @override |
+ bool isOrContains(String path) { |
+ return path == this.path; |
+ } |
+} |
+ |
+ |
+/** |
+ * An in-memory implementation of [File]. |
+ */ |
+class _MemoryFile extends _MemoryResource implements File { |
+ _MemoryFile(MemoryResourceProvider provider, String path) : super( |
+ provider, |
+ path); |
+ |
+ String get _content { |
+ String content = _provider._pathToContent[path]; |
+ if (content == null) { |
+ throw new MemoryResourceException(path, "File '$path' does not exist"); |
+ } |
+ return content; |
+ } |
+ |
+ int get _timestamp => _provider._pathToTimestamp[path]; |
+ |
+ @override |
+ Source createSource([Uri uri]) { |
+ if (uri == null) { |
+ uri = posix.toUri(path); |
+ } |
+ return new _MemoryFileSource(this, uri); |
+ } |
+ |
+ @override |
+ bool isOrContains(String path) { |
+ return path == this.path; |
+ } |
+} |
+ |
+ |
+/** |
+ * An in-memory implementation of [Source]. |
+ */ |
+class _MemoryFileSource implements Source { |
+ final _MemoryFile _file; |
+ |
+ final Uri uri; |
+ |
+ _MemoryFileSource(this._file, this.uri); |
+ |
+ @override |
+ TimestampedData<String> get contents { |
+ return new TimestampedData<String>(modificationStamp, _file._content); |
+ } |
+ |
+ @override |
+ String get encoding { |
+ return uri.toString(); |
+ } |
+ |
+ @override |
+ String get fullName => _file.path; |
+ |
+ @override |
+ int get hashCode => _file.hashCode; |
+ |
+ @override |
+ bool get isInSystemLibrary => uriKind == UriKind.DART_URI; |
+ |
+ @override |
+ int get modificationStamp => _file._timestamp; |
+ |
+ @override |
+ String get shortName => _file.shortName; |
+ |
+ @override |
+ UriKind get uriKind { |
+ String scheme = uri.scheme; |
+ if (scheme == PackageUriResolver.PACKAGE_SCHEME) { |
+ return UriKind.PACKAGE_URI; |
+ } else if (scheme == DartUriResolver.DART_SCHEME) { |
+ return UriKind.DART_URI; |
+ } else if (scheme == FileUriResolver.FILE_SCHEME) { |
+ return UriKind.FILE_URI; |
+ } |
+ return UriKind.FILE_URI; |
+ } |
+ |
+ @override |
+ bool operator ==(other) { |
+ if (other is _MemoryFileSource) { |
+ return other._file == _file; |
+ } |
+ return false; |
+ } |
+ |
+ @override |
+ bool exists() => _file.exists; |
+ |
+ @override |
+ Uri resolveRelativeUri(Uri relativeUri) { |
+ return uri.resolveUri(relativeUri); |
+ } |
+ |
+ @override |
+ String toString() => _file.toString(); |
+} |
+ |
+ |
+/** |
+ * An in-memory implementation of [Folder]. |
+ */ |
+class _MemoryFolder extends _MemoryResource implements Folder { |
+ _MemoryFolder(MemoryResourceProvider provider, String path) : super( |
+ provider, |
+ path); |
+ @override |
+ Stream<WatchEvent> get changes { |
+ StreamController<WatchEvent> streamController = |
+ new StreamController<WatchEvent>(); |
+ if (!_provider._pathToWatchers.containsKey(path)) { |
+ _provider._pathToWatchers[path] = <StreamController<WatchEvent>>[]; |
+ } |
+ _provider._pathToWatchers[path].add(streamController); |
+ streamController.done.then((_) { |
+ _provider._pathToWatchers[path].remove(streamController); |
+ if (_provider._pathToWatchers[path].isEmpty) { |
+ _provider._pathToWatchers.remove(path); |
+ } |
+ }); |
+ return streamController.stream; |
+ } |
+ |
+ @override |
+ String canonicalizePath(String relPath) { |
+ relPath = posix.normalize(relPath); |
+ String childPath = posix.join(path, relPath); |
+ childPath = posix.normalize(childPath); |
+ return childPath; |
+ } |
+ |
+ @override |
+ bool contains(String path) { |
+ return posix.isWithin(this.path, path); |
+ } |
+ |
+ @override |
+ Resource getChild(String relPath) { |
+ String childPath = canonicalizePath(relPath); |
+ _MemoryResource resource = _provider._pathToResource[childPath]; |
+ if (resource == null) { |
+ resource = new _MemoryFile(_provider, childPath); |
+ } |
+ return resource; |
+ } |
+ |
+ @override |
+ List<Resource> getChildren() { |
+ List<Resource> children = <Resource>[]; |
+ _provider._pathToResource.forEach((resourcePath, resource) { |
+ if (posix.dirname(resourcePath) == path) { |
+ children.add(resource); |
+ } |
+ }); |
+ return children; |
+ } |
+ |
+ @override |
+ bool isOrContains(String path) { |
+ if (path == this.path) { |
+ return true; |
+ } |
+ return contains(path); |
+ } |
+} |
+ |
+ |
+/** |
+ * An in-memory implementation of [Resource]. |
+ */ |
+abstract class _MemoryResource implements Resource { |
+ final MemoryResourceProvider _provider; |
+ final String path; |
+ |
+ _MemoryResource(this._provider, this.path); |
+ |
+ @override |
+ bool get exists => _provider._pathToResource.containsKey(path); |
+ |
+ @override |
+ get hashCode => path.hashCode; |
+ |
+ @override |
+ Folder get parent { |
+ String parentPath = posix.dirname(path); |
+ if (parentPath == path) { |
+ return null; |
+ } |
+ return _provider.getResource(parentPath); |
+ } |
+ |
+ @override |
+ String get shortName => posix.basename(path); |
+ |
+ @override |
+ bool operator ==(other) { |
+ if (runtimeType != other.runtimeType) { |
+ return false; |
+ } |
+ return path == other.path; |
+ } |
+ |
+ @override |
+ String toString() => path; |
+} |