blob: 76879be058c351eee0d814bc67c29ce448545908 [file] [log] [blame]
// Copyright (c) 2018, 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.
import 'dart:async';
import 'dart:core';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/source/source_resource.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as pathos;
import 'package:watcher/watcher.dart';
/**
* A resource provider that allows clients to overlay the file system provided
* by a base resource provider. These overlays allow both the contents and
* modification stamps of files to be different than what the base resource
* provider would report.
*
* This provider does not report watch events when overlays are added, modified
* or removed.
*/
class OverlayResourceProvider implements ResourceProvider {
/**
* The underlying resource provider used to access files and folders that
* do not have an overlay.
*/
final ResourceProvider baseProvider;
/**
* A map from the paths of files for which there is an overlay to the contents
* of the files.
*/
final Map<String, String> _overlayContent = <String, String>{};
/**
* A map from the paths of files for which there is an overlay to the
* modification stamps of the files.
*/
final Map<String, int> _overlayModificationStamps = <String, int>{};
/**
* Initialize a newly created resource provider to represent an overlay on the
* given [baseProvider].
*/
OverlayResourceProvider(this.baseProvider);
@override
pathos.Context get pathContext => baseProvider.pathContext;
@override
File getFile(String path) =>
new _OverlayFile(this, baseProvider.getFile(path));
@override
Folder getFolder(String path) =>
new _OverlayFolder(this, baseProvider.getFolder(path));
@override
Future<List<int>> getModificationTimes(List<Source> sources) async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
return sources.map((source) {
String path = source.fullName;
return _overlayModificationStamps[path] ??
baseProvider.getFile(path).modificationStamp;
}).toList();
}
@override
Resource getResource(String path) {
return new _OverlayResource._from(this, baseProvider.getResource(path));
}
@override
Folder getStateLocation(String pluginId) =>
new _OverlayFolder(this, baseProvider.getStateLocation(pluginId));
/**
* Return `true` if there is an overlay associated with the file at the given
* [path].
*/
bool hasOverlay(String path) => _overlayContent.containsKey(path);
/**
* Remove any overlay of the file at the given [path]. The state of the file
* in the base resource provider will not be affected.
*/
bool removeOverlay(String path) {
bool hadOverlay = _overlayContent.containsKey(path);
_overlayContent.remove(path);
_overlayModificationStamps.remove(path);
return hadOverlay;
}
/**
* Overlay the content of the file at the given [path]. The file will appear
* to have the given [content] and [modificationStamp] even if the file is
* modified in the base resource provider.
*/
void setOverlay(String path,
{@required String content, @required int modificationStamp}) {
if (content == null) {
throw new ArgumentError(
'OverlayResourceProvider.setOverlay: content cannot be null');
} else if (modificationStamp == null) {
throw new ArgumentError(
'OverlayResourceProvider.setOverlay: modificationStamp cannot be null');
}
_overlayContent[path] = content;
_overlayModificationStamps[path] = modificationStamp;
}
/**
* Copy any overlay for the file at the [oldPath] to be an overlay for the
* file with the [newPath].
*/
void _copyOverlay(String oldPath, String newPath) {
if (hasOverlay(oldPath)) {
_overlayContent[newPath] = _overlayContent[oldPath];
_overlayModificationStamps[newPath] = _overlayModificationStamps[oldPath];
}
}
/**
* Return the content of the overlay of the file at the given [path], or
* `null` if there is no overlay for the specified file.
*/
String _getOverlayContent(String path) {
return _overlayContent[path];
}
/**
* Return the modification stamp of the overlay of the file at the given
* [path], or `null` if there is no overlay for the specified file.
*/
int _getOverlayModificationStamp(String path) {
return _overlayModificationStamps[path];
}
/**
* Return the paths of all of the overlaid files that are immediate children
* of the given [folder].
*/
Iterable<String> _overlaysInFolder(Folder folder) {
String folderPath = folder.path;
return _overlayContent.keys
.where((path) => pathContext.dirname(path) == folderPath);
}
}
/**
* A file from an [OverlayResourceProvider].
*/
class _OverlayFile extends _OverlayResource implements File {
/**
* Initialize a newly created file to have the given [provider] and to
* correspond to the given [file] from the provider's base resource provider.
*/
_OverlayFile(OverlayResourceProvider provider, File file)
: super(provider, file);
@override
Stream<WatchEvent> get changes => _file.changes;
@override
bool get exists => _provider.hasOverlay(path) || _resource.exists;
@override
int get lengthSync {
String content = _provider._getOverlayContent(path);
if (content != null) {
return content.length;
}
return _file.lengthSync;
}
@override
int get modificationStamp {
int stamp = _provider._getOverlayModificationStamp(path);
if (stamp != null) {
return stamp;
}
return _file.modificationStamp;
}
/**
* Return the file from the base resource provider that corresponds to this
* folder.
*/
File get _file => _resource as File;
@override
File copyTo(Folder parentFolder) {
String newPath = _provider.pathContext.join(parentFolder.path, shortName);
_provider._copyOverlay(path, newPath);
if (_file.exists) {
if (parentFolder is _OverlayFolder) {
return new _OverlayFile(_provider, _file.copyTo(parentFolder._folder));
}
return new _OverlayFile(_provider, _file.copyTo(parentFolder));
} else {
return new _OverlayFile(
_provider, _provider.baseProvider.getFile(newPath));
}
}
@override
Source createSource([Uri uri]) =>
new FileSource(this, uri ?? _provider.pathContext.toUri(path));
@override
void delete() {
bool hadOverlay = _provider.removeOverlay(path);
if (_resource.exists) {
_resource.delete();
} else if (!hadOverlay) {
throw new FileSystemException(path, 'does not exist');
}
}
@override
List<int> readAsBytesSync() {
String content = _provider._getOverlayContent(path);
if (content != null) {
return content.codeUnits;
}
return _file.readAsBytesSync();
}
@override
String readAsStringSync() {
String content = _provider._getOverlayContent(path);
if (content != null) {
return content;
}
return _file.readAsStringSync();
}
@override
File renameSync(String newPath) {
File newFile = _file.renameSync(newPath);
if (_provider.hasOverlay(path)) {
_provider.setOverlay(newPath,
content: _provider._getOverlayContent(path),
modificationStamp: _provider._getOverlayModificationStamp(path));
_provider.removeOverlay(path);
}
return new _OverlayFile(_provider, newFile);
}
@override
void writeAsBytesSync(List<int> bytes) {
writeAsStringSync(new String.fromCharCodes(bytes));
}
@override
void writeAsStringSync(String content) {
if (_provider.hasOverlay(path)) {
throw new FileSystemException(
path, 'Cannot write a file with an overlay');
}
_file.writeAsStringSync(content);
}
}
/**
* A folder from an [OverlayResourceProvider].
*/
class _OverlayFolder extends _OverlayResource implements Folder {
/**
* Initialize a newly created folder to have the given [provider] and to
* correspond to the given [folder] from the provider's base resource
* provider.
*/
_OverlayFolder(OverlayResourceProvider provider, Folder folder)
: super(provider, folder);
@override
Stream<WatchEvent> get changes => _folder.changes;
@override
bool get exists => _resource.exists;
/**
* Return the folder from the base resource provider that corresponds to this
* folder.
*/
Folder get _folder => _resource as Folder;
@override
String canonicalizePath(String relPath) {
pathos.Context context = _provider.pathContext;
relPath = context.normalize(relPath);
String childPath = context.join(path, relPath);
childPath = context.normalize(childPath);
return childPath;
}
@override
bool contains(String path) => _folder.contains(path);
@override
Folder copyTo(Folder parentFolder) {
Folder destination = parentFolder.getChildAssumingFolder(shortName);
destination.create();
for (Resource child in getChildren()) {
child.copyTo(destination);
}
return destination;
}
@override
void create() {
_folder.create();
}
@override
Resource getChild(String relPath) =>
new _OverlayResource._from(_provider, _folder.getChild(relPath));
@override
File getChildAssumingFile(String relPath) =>
new _OverlayFile(_provider, _folder.getChildAssumingFile(relPath));
@override
Folder getChildAssumingFolder(String relPath) =>
new _OverlayFolder(_provider, _folder.getChildAssumingFolder(relPath));
@override
List<Resource> getChildren() {
List<Resource> children = _folder
.getChildren()
.map((child) => new _OverlayResource._from(_provider, child))
.toList();
for (String overlayPath in _provider._overlaysInFolder(this)) {
children.add(_provider.getFile(overlayPath));
}
return children;
}
}
/**
* The base class for resources from an [OverlayResourceProvider].
*/
abstract class _OverlayResource implements Resource {
/**
* The resource provider associated with this resource.
*/
final OverlayResourceProvider _provider;
/**
* The resource from the provider's base provider that corresponds to this
* resource.
*/
final Resource _resource;
/**
* Initialize a newly created instance of a resource to have the given
* [_provider] and to represent the [_resource] from the provider's base
* resource provider.
*/
_OverlayResource(this._provider, this._resource);
/**
* Return an instance of the subclass of this class corresponding to the given
* [resource] that is associated with the given [provider].
*/
factory _OverlayResource._from(
OverlayResourceProvider provider, Resource resource) {
if (resource is Folder) {
return new _OverlayFolder(provider, resource);
} else if (resource is File) {
return new _OverlayFile(provider, resource);
}
throw new ArgumentError('Unknown resource type: ${resource.runtimeType}');
}
@override
int get hashCode => path.hashCode;
@override
Folder get parent {
Folder parent = _resource.parent;
if (parent == null) {
return null;
}
return new _OverlayFolder(_provider, parent);
}
@override
String get path => _resource.path;
@override
String get shortName => _resource.shortName;
@override
bool operator ==(other) {
if (runtimeType != other.runtimeType) {
return false;
}
return path == other.path;
}
@override
void delete() {
_resource.delete();
}
@override
bool isOrContains(String path) {
return _resource.isOrContains(path);
}
@override
Resource resolveSymbolicLinksSync() => new _OverlayResource._from(
_provider, _resource.resolveSymbolicLinksSync());
@override
Uri toUri() => _resource.toUri();
}