| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library resoure.resource; | |
| 6 | |
| 7 import "dart:async" show Future, Stream; | 5 import "dart:async" show Future, Stream; |
| 8 import "dart:convert" show Encoding; | 6 import "dart:convert" show Encoding; |
| 9 import "dart:isolate" show Isolate; | 7 import "dart:isolate" show Isolate; |
| 10 import "package_resolver.dart"; | 8 import "loader.dart"; |
| 11 import "io.dart" as io; // Loading strategy. TODO: Be configuration dependent. | |
| 12 | |
| 13 /// A strategy for resolving package URIs | |
| 14 abstract class PackageResolver { | |
| 15 /// Cache of the current resolver, accessible directly after it has first | |
| 16 /// been found asynchronously. | |
| 17 static PackageResolver _current; | |
| 18 | |
| 19 /// The package resolution strategy used by the current isolate. | |
| 20 static final Future<PackageResolver> current = _findIsolateResolution(); | |
| 21 | |
| 22 PackageResolver(); | |
| 23 | |
| 24 /// Creates a resolver using a map from package name to package location. | |
| 25 factory PackageResolver.fromMap(Map<String, Uri> packages) = MapResolver; | |
| 26 | |
| 27 /// Creates a resolver using a package root. | |
| 28 factory PackageResolver.fromRoot(Uri packageRoot) = RootResolver; | |
| 29 | |
| 30 /// Resolves a package URI to its location. | |
| 31 /// | |
| 32 /// If [uri] does not have `package` as scheme, it is returned again. | |
| 33 /// Otherwise the package name is looked up, and if found, a location | |
| 34 /// for the package file is returned. | |
| 35 Future<Uri> resolve(Uri uri); | |
| 36 | |
| 37 /// Returns a [Resource] for the [uri] as resolved by this resolver. | |
| 38 Resource resource(Uri uri) { | |
| 39 return new _UriResource(this, uri); | |
| 40 } | |
| 41 | |
| 42 /// Finds the way the current isolate resolves package URIs. | |
| 43 /// | |
| 44 /// Is only called once, and when it has been called, the [_current] | |
| 45 /// resolver is initialized, so [UriResource] will be initialized | |
| 46 /// with the resolver directly. | |
| 47 static Future<PackageResolver> _findIsolateResolution() async { | |
| 48 var pair = await Future.wait([Isolate.packageRoot, Isolate.packageMap]); | |
| 49 Uri root = pair[0]; | |
| 50 if (root != null) { | |
| 51 _current = new RootResolver(root); | |
| 52 } else { | |
| 53 Map<String, Uri> map = pair[1]; | |
| 54 _current = new MapResolver(map); | |
| 55 } | |
| 56 return _current; | |
| 57 } | |
| 58 } | |
| 59 | 9 |
| 60 /// A resource that can be read into the program. | 10 /// A resource that can be read into the program. |
| 61 /// | 11 /// |
| 62 /// A resource is data that can be located using a URI and read into | 12 /// A resource is data that can be located using a URI and read into |
| 63 /// the program at runtime. | 13 /// the program at runtime. |
| 64 /// The URI may use the `package` scheme to read resources provided | 14 /// The URI may use the `package` scheme to read resources provided |
| 65 /// along with package sources. | 15 /// along with package sources. |
| 66 abstract class Resource { | 16 abstract class Resource { |
| 67 /// Creates a resource object with the given [uri] as location. | 17 /// Creates a resource object with the given [uri] as location. |
| 68 /// | 18 /// |
| 69 /// The `uri` is a string containing a valid URI. | 19 /// The [uri] must be either a [Uri] or a string containing a valid URI. |
| 70 /// If the string is not a valid URI, using any of the functions on | 20 /// If the string is not a valid URI, using any of the functions on |
| 71 /// the resource object will fail. | 21 /// the resource object will fail. |
| 72 /// | 22 /// |
| 73 /// The URI may be relative, in which case it will be resolved | 23 /// The URI may be relative, in which case it will be resolved |
| 74 /// against [Uri.base] before being used. | 24 /// against [Uri.base] before being used. |
| 75 /// | 25 /// |
| 76 /// The URI may use the `package` scheme, which is always supported. | 26 /// The URI may use the `package` scheme, which is always supported. |
| 77 /// Other schemes may also be supported where possible. | 27 /// Other schemes may also be supported where possible. |
| 78 const factory Resource(String uri) = _StringResource; | 28 /// |
| 29 /// If [loader] is provided, it is used to load absolute non-package URIs. |
| 30 /// Package: URIs are resolved to a non-package URI before being loaded, so |
| 31 /// the loader doesn't have to support package: URIs, nor does it need to |
| 32 /// support relative URI references. |
| 33 /// If [loader] is omitted, a default implementation is used which supports |
| 34 /// as many of `http`, `https`, `file` and `data` as are available on the |
| 35 /// current platform. |
| 36 const factory Resource(uri, {ResourceLoader loader}) = _Resource; |
| 79 | 37 |
| 80 /// Creates a resource object with the given [uri] as location. | 38 /// The location URI of this resource. |
| 81 /// | |
| 82 /// The URI may be relative, in which case it will be resolved | |
| 83 /// against [Uri.base] before being used. | |
| 84 /// | |
| 85 /// The URI may use the `package` scheme, which is always supported. | |
| 86 /// Other schemes may also be supported where possible. | |
| 87 factory Resource.forUri(Uri uri) => | |
| 88 new _UriResource(PackageResolver._current, uri); | |
| 89 | |
| 90 /// The location `uri` of this resource. | |
| 91 /// | 39 /// |
| 92 /// This is a [Uri] of the `uri` parameter given to the constructor. | 40 /// This is a [Uri] of the `uri` parameter given to the constructor. |
| 93 /// If the parameter was not a valid URI, reading `uri` may fail. | 41 /// If the parameter was a string that did not contain a valid URI, |
| 42 /// reading `uri` will fail. |
| 94 Uri get uri; | 43 Uri get uri; |
| 95 | 44 |
| 45 /// Reads the resource content as a stream of bytes. |
| 96 Stream<List<int>> openRead(); | 46 Stream<List<int>> openRead(); |
| 97 | 47 |
| 48 /// Reads the resource content as a single list of bytes. |
| 98 Future<List<int>> readAsBytes(); | 49 Future<List<int>> readAsBytes(); |
| 99 | 50 |
| 100 /// Read the resource content as a string. | 51 /// Reads the resource content as a string. |
| 101 /// | 52 /// |
| 102 /// The content is decoded into a string using an [Encoding]. | 53 /// The content is decoded into a string using an [Encoding]. |
| 103 /// If no other encoding is provided, it defaults to UTF-8. | 54 /// If no other encoding is provided, it defaults to UTF-8. |
| 104 Future<String> readAsString({Encoding encoding}); | 55 Future<String> readAsString({Encoding encoding}); |
| 105 } | 56 } |
| 106 | 57 |
| 107 class _StringResource implements Resource { | 58 class _Resource implements Resource { |
| 108 final String _uri; | 59 /// Loading strategy for the resource. |
| 109 | 60 final ResourceLoader _loader; |
| 110 const _StringResource(String uri) : _uri = uri; | |
| 111 | |
| 112 Uri get uri => Uri.parse(_uri); | |
| 113 | |
| 114 Stream<List<int>> openRead() { | |
| 115 return new _UriResource(PackageResolver._current, uri).openRead(); | |
| 116 } | |
| 117 Future<List<int>> readAsBytes() { | |
| 118 return new _UriResource(PackageResolver._current, uri).readAsBytes(); | |
| 119 } | |
| 120 Future<String> readAsString({Encoding encoding}) { | |
| 121 return new _UriResource(PackageResolver._current, uri) | |
| 122 .readAsString(encoding: encoding); | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 class _UriResource implements Resource { | |
| 127 /// The strategy for resolving package: URIs. | |
| 128 /// | |
| 129 /// May be null intially. If so, the [PackageResolver.current] resolver is | |
| 130 /// used (and cached for later use). | |
| 131 PackageResolver _resolver; | |
| 132 | 61 |
| 133 /// The URI of the resource. | 62 /// The URI of the resource. |
| 134 final Uri uri; | 63 final _uri; |
| 135 | 64 |
| 136 _UriResource(this.resolver, Uri uri); | 65 const _Resource(uri, {ResourceLoader loader}) |
| 66 : _uri = uri, _loader = (loader != null) ? loader : const DefaultLoader(); |
| 67 // TODO: Make this `loader ?? const DefaultLoader()` when ?? is const. |
| 68 |
| 69 Uri get uri => (_uri is String) ? Uri.parse(_uri) : (_uri as Uri); |
| 137 | 70 |
| 138 Stream<List<int>> openRead() async* { | 71 Stream<List<int>> openRead() async* { |
| 139 Uri uri = await _resolve(this.uri); | 72 Uri uri = await _resolvedUri; |
| 140 return io.readAsStream(uri); | 73 yield* _loader.openRead(uri); |
| 141 } | 74 } |
| 142 | 75 |
| 143 Future<List<int>> readAsBytes() async { | 76 Future<List<int>> readAsBytes() async { |
| 144 Uri uri = await _resolve(this.uri); | 77 Uri uri = await _resolvedUri; |
| 145 return io.readAsBytes(uri); | 78 return _loader.readAsBytes(uri); |
| 146 } | 79 } |
| 147 | 80 |
| 148 Future<String> readAsString({Encoding encoding}) async { | 81 Future<String> readAsString({Encoding encoding}) async { |
| 149 Uri uri = await _resolve(this.uri); | 82 Uri uri = await _resolvedUri; |
| 150 return io.readAsString(uri, encoding); | 83 return _loader.readAsString(uri, encoding: encoding); |
| 151 } | 84 } |
| 152 | 85 |
| 153 static void _checkPackageUri(Uri uri) { | 86 Future<Uri> get _resolvedUri => resolveUri(uri); |
| 154 if (uri.scheme != "package") { | |
| 155 throw new ArgumentError.value(uri, "Not a package: URI"); | |
| 156 } | |
| 157 if (uri.hasAuthority) { | |
| 158 throw new ArgumentError.value(uri, | |
| 159 "A package: URI must not have an authority part"); | |
| 160 } | |
| 161 if (uri.path.isEmpty || uri.path.startsWith('/')) { | |
| 162 throw new ArgumentError.value(uri, | |
| 163 "A package: URI must have the form " | |
| 164 "'package:packageName/packagePath'"); | |
| 165 } | |
| 166 if (uri.hasQuery) { | |
| 167 throw new ArgumentError.value(uri, | |
| 168 "A package: URI must not have a query part"); | |
| 169 } | |
| 170 if (uri.hasFragment) { | |
| 171 throw new ArgumentError.value(uri, | |
| 172 "A package: URI must not have a fragment part"); | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 Future<Uri> _resolve(Uri uri) async { | |
| 177 if (uri.scheme != "package") { | |
| 178 return Uri.base.resolveUri(uri); | |
| 179 } | |
| 180 _checkPackageUri(uri); | |
| 181 _resolver ??= await PackageResolver._current; | |
| 182 return _resolver.resolve(uri); | |
| 183 } | |
| 184 } | 87 } |
| 185 | |
| 186 /// A [PackageResolver] based on a packags map. | |
| 187 class MapResolver extends PackageResolver { | |
| 188 Map<String, Uri> _mapping; | |
| 189 | |
| 190 MapResolver(this._mapping); | |
| 191 | |
| 192 Uri resolve(Uri uri) { | |
| 193 if (uri.scheme != "package") return uri; | |
| 194 var path = uri.path; | |
| 195 int slashIndex = path.indexOf('/'); | |
| 196 if (slashIndex <= 0) { | |
| 197 throw new ArgumentError.value(uri, "Invalid package URI"); | |
| 198 } | |
| 199 int packageName = path.substring(0, slashIndex); | |
| 200 var base = _mapping[packageName]; | |
| 201 if (base != null) { | |
| 202 int packagePath = path.substring(slashIndex + 1); | |
| 203 return base.resolveUri(new Uri(path: packagePath)); | |
| 204 } | |
| 205 throw new UnsupportedError("No package named '$packageName' found"); | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 /// A [PackageResolver] based on a package root. | |
| 210 class RootResolver extends PackageResolver { | |
| 211 Uri _root; | |
| 212 RootResolver(this._root); | |
| 213 | |
| 214 Uri resolve(Uri uri) { | |
| 215 if (uri.scheme != "package") return uri; | |
| 216 return _root.resolve(uri.path); | |
| 217 } | |
| 218 } | |
| OLD | NEW |