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 memory_file_system; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 |
| 10 import 'package:analyzer/src/generated/engine.dart' show TimestampedData; |
| 11 import 'package:analyzer/src/generated/source_io.dart'; |
| 12 import 'package:path/path.dart'; |
| 13 import 'package:watcher/watcher.dart'; |
| 14 |
| 15 import 'file_system.dart'; |
| 16 |
| 17 |
| 18 /** |
| 19 * Exception thrown when a memory [Resource] file operation fails. |
| 20 */ |
| 21 class MemoryResourceException { |
| 22 final path; |
| 23 final message; |
| 24 |
| 25 MemoryResourceException(this.path, this.message); |
| 26 |
| 27 @override |
| 28 String toString() { |
| 29 return "MemoryResourceException(path=$path; message=$message)"; |
| 30 } |
| 31 } |
| 32 |
| 33 |
| 34 /** |
| 35 * An in-memory implementation of [ResourceProvider]. |
| 36 * Use `/` as a path separator. |
| 37 */ |
| 38 class MemoryResourceProvider implements ResourceProvider { |
| 39 final Map<String, _MemoryResource> _pathToResource = |
| 40 new HashMap<String, _MemoryResource>(); |
| 41 final Map<String, String> _pathToContent = new HashMap<String, String>(); |
| 42 final Map<String, int> _pathToTimestamp = new HashMap<String, int>(); |
| 43 final Map<String, List<StreamController<WatchEvent>>> _pathToWatchers = |
| 44 new HashMap<String, List<StreamController<WatchEvent>>>(); |
| 45 int nextStamp = 0; |
| 46 |
| 47 @override |
| 48 Context get pathContext => posix; |
| 49 |
| 50 void deleteFile(String path) { |
| 51 _checkFileAtPath(path); |
| 52 _pathToResource.remove(path); |
| 53 _pathToContent.remove(path); |
| 54 _pathToTimestamp.remove(path); |
| 55 _notifyWatchers(path, ChangeType.REMOVE); |
| 56 } |
| 57 |
| 58 @override |
| 59 Resource getResource(String path) { |
| 60 path = posix.normalize(path); |
| 61 Resource resource = _pathToResource[path]; |
| 62 if (resource == null) { |
| 63 resource = new _MemoryFile(this, path); |
| 64 } |
| 65 return resource; |
| 66 } |
| 67 |
| 68 void modifyFile(String path, String content) { |
| 69 _checkFileAtPath(path); |
| 70 _pathToContent[path] = content; |
| 71 _pathToTimestamp[path] = nextStamp++; |
| 72 _notifyWatchers(path, ChangeType.MODIFY); |
| 73 } |
| 74 |
| 75 /** |
| 76 * Create a resource representing a dummy link (that is, a File object which |
| 77 * appears in its parent directory, but whose `exists` property is false) |
| 78 */ |
| 79 File newDummyLink(String path) { |
| 80 path = posix.normalize(path); |
| 81 newFolder(posix.dirname(path)); |
| 82 _MemoryDummyLink link = new _MemoryDummyLink(this, path); |
| 83 _pathToResource[path] = link; |
| 84 _pathToTimestamp[path] = nextStamp++; |
| 85 _notifyWatchers(path, ChangeType.ADD); |
| 86 return link; |
| 87 } |
| 88 |
| 89 File newFile(String path, String content) { |
| 90 path = posix.normalize(path); |
| 91 newFolder(posix.dirname(path)); |
| 92 _MemoryFile file = new _MemoryFile(this, path); |
| 93 _pathToResource[path] = file; |
| 94 _pathToContent[path] = content; |
| 95 _pathToTimestamp[path] = nextStamp++; |
| 96 _notifyWatchers(path, ChangeType.ADD); |
| 97 return file; |
| 98 } |
| 99 |
| 100 Folder newFolder(String path) { |
| 101 path = posix.normalize(path); |
| 102 if (!path.startsWith('/')) { |
| 103 throw new ArgumentError("Path must start with '/'"); |
| 104 } |
| 105 _MemoryResource resource = _pathToResource[path]; |
| 106 if (resource == null) { |
| 107 String parentPath = posix.dirname(path); |
| 108 if (parentPath != path) { |
| 109 newFolder(parentPath); |
| 110 } |
| 111 _MemoryFolder folder = new _MemoryFolder(this, path); |
| 112 _pathToResource[path] = folder; |
| 113 _pathToTimestamp[path] = nextStamp++; |
| 114 return folder; |
| 115 } else if (resource is _MemoryFolder) { |
| 116 return resource; |
| 117 } else { |
| 118 String message = |
| 119 'Folder expected at ' "'$path'" 'but ${resource.runtimeType} found'; |
| 120 throw new ArgumentError(message); |
| 121 } |
| 122 } |
| 123 |
| 124 void _checkFileAtPath(String path) { |
| 125 _MemoryResource resource = _pathToResource[path]; |
| 126 if (resource is! _MemoryFile) { |
| 127 throw new ArgumentError( |
| 128 'File expected at "$path" but ${resource.runtimeType} found'); |
| 129 } |
| 130 } |
| 131 |
| 132 void _notifyWatchers(String path, ChangeType changeType) { |
| 133 _pathToWatchers.forEach( |
| 134 (String watcherPath, List<StreamController<WatchEvent>> streamController
s) { |
| 135 if (posix.isWithin(watcherPath, path)) { |
| 136 for (StreamController<WatchEvent> streamController in streamControllers) |
| 137 { |
| 138 streamController.add(new WatchEvent(changeType, path)); |
| 139 } |
| 140 } |
| 141 }); |
| 142 } |
| 143 } |
| 144 |
| 145 |
| 146 /** |
| 147 * An in-memory implementation of [File] which acts like a symbolic link to a |
| 148 * non-existent file. |
| 149 */ |
| 150 class _MemoryDummyLink extends _MemoryResource implements File { |
| 151 _MemoryDummyLink(MemoryResourceProvider provider, String path) : super( |
| 152 provider, |
| 153 path); |
| 154 |
| 155 @override |
| 156 bool get exists => false; |
| 157 |
| 158 String get _content { |
| 159 throw new MemoryResourceException(path, "File '$path' could not be read"); |
| 160 } |
| 161 |
| 162 int get _timestamp => _provider._pathToTimestamp[path]; |
| 163 |
| 164 @override |
| 165 Source createSource([Uri uri]) { |
| 166 throw new MemoryResourceException(path, "File '$path' could not be read"); |
| 167 } |
| 168 |
| 169 @override |
| 170 bool isOrContains(String path) { |
| 171 return path == this.path; |
| 172 } |
| 173 } |
| 174 |
| 175 |
| 176 /** |
| 177 * An in-memory implementation of [File]. |
| 178 */ |
| 179 class _MemoryFile extends _MemoryResource implements File { |
| 180 _MemoryFile(MemoryResourceProvider provider, String path) : super( |
| 181 provider, |
| 182 path); |
| 183 |
| 184 String get _content { |
| 185 String content = _provider._pathToContent[path]; |
| 186 if (content == null) { |
| 187 throw new MemoryResourceException(path, "File '$path' does not exist"); |
| 188 } |
| 189 return content; |
| 190 } |
| 191 |
| 192 int get _timestamp => _provider._pathToTimestamp[path]; |
| 193 |
| 194 @override |
| 195 Source createSource([Uri uri]) { |
| 196 if (uri == null) { |
| 197 uri = posix.toUri(path); |
| 198 } |
| 199 return new _MemoryFileSource(this, uri); |
| 200 } |
| 201 |
| 202 @override |
| 203 bool isOrContains(String path) { |
| 204 return path == this.path; |
| 205 } |
| 206 } |
| 207 |
| 208 |
| 209 /** |
| 210 * An in-memory implementation of [Source]. |
| 211 */ |
| 212 class _MemoryFileSource implements Source { |
| 213 final _MemoryFile _file; |
| 214 |
| 215 final Uri uri; |
| 216 |
| 217 _MemoryFileSource(this._file, this.uri); |
| 218 |
| 219 @override |
| 220 TimestampedData<String> get contents { |
| 221 return new TimestampedData<String>(modificationStamp, _file._content); |
| 222 } |
| 223 |
| 224 @override |
| 225 String get encoding { |
| 226 return uri.toString(); |
| 227 } |
| 228 |
| 229 @override |
| 230 String get fullName => _file.path; |
| 231 |
| 232 @override |
| 233 int get hashCode => _file.hashCode; |
| 234 |
| 235 @override |
| 236 bool get isInSystemLibrary => uriKind == UriKind.DART_URI; |
| 237 |
| 238 @override |
| 239 int get modificationStamp => _file._timestamp; |
| 240 |
| 241 @override |
| 242 String get shortName => _file.shortName; |
| 243 |
| 244 @override |
| 245 UriKind get uriKind { |
| 246 String scheme = uri.scheme; |
| 247 if (scheme == PackageUriResolver.PACKAGE_SCHEME) { |
| 248 return UriKind.PACKAGE_URI; |
| 249 } else if (scheme == DartUriResolver.DART_SCHEME) { |
| 250 return UriKind.DART_URI; |
| 251 } else if (scheme == FileUriResolver.FILE_SCHEME) { |
| 252 return UriKind.FILE_URI; |
| 253 } |
| 254 return UriKind.FILE_URI; |
| 255 } |
| 256 |
| 257 @override |
| 258 bool operator ==(other) { |
| 259 if (other is _MemoryFileSource) { |
| 260 return other._file == _file; |
| 261 } |
| 262 return false; |
| 263 } |
| 264 |
| 265 @override |
| 266 bool exists() => _file.exists; |
| 267 |
| 268 @override |
| 269 Uri resolveRelativeUri(Uri relativeUri) { |
| 270 return uri.resolveUri(relativeUri); |
| 271 } |
| 272 |
| 273 @override |
| 274 String toString() => _file.toString(); |
| 275 } |
| 276 |
| 277 |
| 278 /** |
| 279 * An in-memory implementation of [Folder]. |
| 280 */ |
| 281 class _MemoryFolder extends _MemoryResource implements Folder { |
| 282 _MemoryFolder(MemoryResourceProvider provider, String path) : super( |
| 283 provider, |
| 284 path); |
| 285 @override |
| 286 Stream<WatchEvent> get changes { |
| 287 StreamController<WatchEvent> streamController = |
| 288 new StreamController<WatchEvent>(); |
| 289 if (!_provider._pathToWatchers.containsKey(path)) { |
| 290 _provider._pathToWatchers[path] = <StreamController<WatchEvent>>[]; |
| 291 } |
| 292 _provider._pathToWatchers[path].add(streamController); |
| 293 streamController.done.then((_) { |
| 294 _provider._pathToWatchers[path].remove(streamController); |
| 295 if (_provider._pathToWatchers[path].isEmpty) { |
| 296 _provider._pathToWatchers.remove(path); |
| 297 } |
| 298 }); |
| 299 return streamController.stream; |
| 300 } |
| 301 |
| 302 @override |
| 303 String canonicalizePath(String relPath) { |
| 304 relPath = posix.normalize(relPath); |
| 305 String childPath = posix.join(path, relPath); |
| 306 childPath = posix.normalize(childPath); |
| 307 return childPath; |
| 308 } |
| 309 |
| 310 @override |
| 311 bool contains(String path) { |
| 312 return posix.isWithin(this.path, path); |
| 313 } |
| 314 |
| 315 @override |
| 316 Resource getChild(String relPath) { |
| 317 String childPath = canonicalizePath(relPath); |
| 318 _MemoryResource resource = _provider._pathToResource[childPath]; |
| 319 if (resource == null) { |
| 320 resource = new _MemoryFile(_provider, childPath); |
| 321 } |
| 322 return resource; |
| 323 } |
| 324 |
| 325 @override |
| 326 List<Resource> getChildren() { |
| 327 List<Resource> children = <Resource>[]; |
| 328 _provider._pathToResource.forEach((resourcePath, resource) { |
| 329 if (posix.dirname(resourcePath) == path) { |
| 330 children.add(resource); |
| 331 } |
| 332 }); |
| 333 return children; |
| 334 } |
| 335 |
| 336 @override |
| 337 bool isOrContains(String path) { |
| 338 if (path == this.path) { |
| 339 return true; |
| 340 } |
| 341 return contains(path); |
| 342 } |
| 343 } |
| 344 |
| 345 |
| 346 /** |
| 347 * An in-memory implementation of [Resource]. |
| 348 */ |
| 349 abstract class _MemoryResource implements Resource { |
| 350 final MemoryResourceProvider _provider; |
| 351 final String path; |
| 352 |
| 353 _MemoryResource(this._provider, this.path); |
| 354 |
| 355 @override |
| 356 bool get exists => _provider._pathToResource.containsKey(path); |
| 357 |
| 358 @override |
| 359 get hashCode => path.hashCode; |
| 360 |
| 361 @override |
| 362 Folder get parent { |
| 363 String parentPath = posix.dirname(path); |
| 364 if (parentPath == path) { |
| 365 return null; |
| 366 } |
| 367 return _provider.getResource(parentPath); |
| 368 } |
| 369 |
| 370 @override |
| 371 String get shortName => posix.basename(path); |
| 372 |
| 373 @override |
| 374 bool operator ==(other) { |
| 375 if (runtimeType != other.runtimeType) { |
| 376 return false; |
| 377 } |
| 378 return path == other.path; |
| 379 } |
| 380 |
| 381 @override |
| 382 String toString() => path; |
| 383 } |
OLD | NEW |