| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 library initialize.mirror_loader; | |
| 5 | |
| 6 import 'dart:collection' show Queue; | |
| 7 import 'dart:mirrors'; | |
| 8 import 'package:path/path.dart' as path; | |
| 9 import 'package:initialize/initialize.dart'; | |
| 10 | |
| 11 final _root = currentMirrorSystem().isolate.rootLibrary; | |
| 12 final _libs = currentMirrorSystem().libraries; | |
| 13 | |
| 14 Queue<Function> loadInitializers( | |
| 15 {List<Type> typeFilter, InitializerFilter customFilter, Uri from}) { | |
| 16 return new InitializationCrawler(typeFilter, customFilter, from: from).run(); | |
| 17 } | |
| 18 | |
| 19 // Crawls a library and all its dependencies for `Initializer` annotations using | |
| 20 // mirrors | |
| 21 class InitializationCrawler { | |
| 22 // Set of all visited annotations, keys are the declarations that were | |
| 23 // annotated, values are the annotations that have been processed. | |
| 24 static final _annotationsFound = | |
| 25 new Map<DeclarationMirror, Set<InstanceMirror>>(); | |
| 26 | |
| 27 // If non-null, then only these annotations should be processed. | |
| 28 final List<Type> typeFilter; | |
| 29 | |
| 30 // If non-null, then only annotations which return true when passed to this | |
| 31 // function will be processed. | |
| 32 final InitializerFilter customFilter; | |
| 33 | |
| 34 /// The library to start crawling from. | |
| 35 final LibraryMirror _rootLibrary; | |
| 36 | |
| 37 /// Note: The [from] argument is only supported in the mirror_loader.dart. It | |
| 38 /// is not supported statically. | |
| 39 InitializationCrawler(this.typeFilter, this.customFilter, {Uri from}) | |
| 40 : _rootLibrary = from == null | |
| 41 ? _root | |
| 42 : _libs[from] { | |
| 43 if (_rootLibrary == null) throw 'Unable to find library at $from.'; | |
| 44 } | |
| 45 | |
| 46 // The primary function in this class, invoke it to crawl and collect all the | |
| 47 // annotations into a queue of init functions. | |
| 48 Queue<Function> run() { | |
| 49 var librariesSeen = new Set<LibraryMirror>(); | |
| 50 var queue = new Queue<Function>(); | |
| 51 var libraries = currentMirrorSystem().libraries; | |
| 52 | |
| 53 _readLibraryDeclarations(_rootLibrary, librariesSeen, queue); | |
| 54 return queue; | |
| 55 } | |
| 56 | |
| 57 /// Returns the canonical [LibraryMirror] for a given [LibraryMirror]. This | |
| 58 /// is defined as the one loaded from a `package:` url if available, otherwise | |
| 59 /// it is just [lib]. | |
| 60 LibraryMirror _canonicalLib(LibraryMirror lib) { | |
| 61 var uri = lib.uri; | |
| 62 if (_isHttpStylePackageUrl(uri)) { | |
| 63 var packageUri = _packageUriFor(uri); | |
| 64 if (_libs.containsKey(packageUri)) return _libs[packageUri]; | |
| 65 } | |
| 66 return lib; | |
| 67 } | |
| 68 | |
| 69 /// Returns the canonical [ClassMirror] for a given [ClassMirror]. This is | |
| 70 /// defined as the one that appears in the canonical owner [LibararyMirror]. | |
| 71 ClassMirror _canonicalClassDeclaration(ClassMirror declaration) => | |
| 72 _canonicalLib(declaration.owner).declarations[declaration.simpleName]; | |
| 73 | |
| 74 /// Whether [uri] is an http URI that contains a 'packages' segment, and | |
| 75 /// therefore could be converted into a 'package:' URI. | |
| 76 bool _isHttpStylePackageUrl(Uri uri) { | |
| 77 var uriPath = uri.path; | |
| 78 return uri.scheme == _root.uri.scheme && | |
| 79 // Don't process cross-domain uris. | |
| 80 uri.authority == _root.uri.authority && | |
| 81 uriPath.endsWith('.dart') && | |
| 82 (uriPath.contains('/packages/') || uriPath.startsWith('packages/')); | |
| 83 } | |
| 84 | |
| 85 /// Returns a `package:` version of [uri]. | |
| 86 Uri _packageUriFor(Uri uri) { | |
| 87 var packagePath = uri.path | |
| 88 .substring(uri.path.lastIndexOf('packages/') + 'packages/'.length); | |
| 89 return Uri.parse('package:$packagePath'); | |
| 90 } | |
| 91 | |
| 92 // Reads Initializer annotations on this library and all its dependencies in | |
| 93 // post-order. | |
| 94 Queue<Function> _readLibraryDeclarations(LibraryMirror lib, | |
| 95 Set<LibraryMirror> librariesSeen, Queue<Function> queue) { | |
| 96 lib = _canonicalLib(lib); | |
| 97 if (librariesSeen.contains(lib)) return queue; | |
| 98 librariesSeen.add(lib); | |
| 99 | |
| 100 // First visit all our dependencies. | |
| 101 for (var dependency in lib.libraryDependencies) { | |
| 102 // Skip dart: imports, they never use this package. | |
| 103 var targetLibrary = dependency.targetLibrary; | |
| 104 if (targetLibrary == null || targetLibrary.uri.scheme == 'dart') continue; | |
| 105 _readLibraryDeclarations(dependency.targetLibrary, librariesSeen, queue); | |
| 106 } | |
| 107 | |
| 108 // Second parse the library directive annotations. | |
| 109 _readAnnotations(lib, queue); | |
| 110 | |
| 111 // Last, parse all class and method annotations. | |
| 112 for (var declaration in _sortedDeclarationsWithMetadata(lib)) { | |
| 113 _readAnnotations(declaration, queue); | |
| 114 // Check classes for static annotations which are not supported | |
| 115 if (declaration is ClassMirror) { | |
| 116 for (var classDeclaration in declaration.declarations.values) { | |
| 117 _readAnnotations(classDeclaration, queue); | |
| 118 } | |
| 119 } | |
| 120 } | |
| 121 | |
| 122 return queue; | |
| 123 } | |
| 124 | |
| 125 Iterable<DeclarationMirror> _sortedDeclarationsWithMetadata( | |
| 126 LibraryMirror lib) { | |
| 127 return new List() | |
| 128 ..addAll(_sortDeclarations(lib, lib.declarations.values | |
| 129 .where((d) => d is MethodMirror && d.metadata.isNotEmpty))) | |
| 130 ..addAll(_sortDeclarations(lib, lib.declarations.values | |
| 131 .where((d) => d is ClassMirror && d.metadata.isNotEmpty))); | |
| 132 } | |
| 133 | |
| 134 List<DeclarationMirror> _sortDeclarations( | |
| 135 LibraryMirror sourceLib, Iterable<DeclarationMirror> declarations) { | |
| 136 var declarationList = declarations.toList(); | |
| 137 declarationList.sort((DeclarationMirror a, DeclarationMirror b) { | |
| 138 // If in the same file, compare by line. | |
| 139 var aSourceUri = a.location.sourceUri; | |
| 140 var bSourceUri = b.location.sourceUri; | |
| 141 if (aSourceUri == bSourceUri) { | |
| 142 return a.location.line.compareTo(b.location.line); | |
| 143 } | |
| 144 | |
| 145 // Run parts first if one is from the original library. | |
| 146 if (aSourceUri == sourceLib.uri) return 1; | |
| 147 if (bSourceUri == sourceLib.uri) return -1; | |
| 148 | |
| 149 // Sort parts alphabetically. | |
| 150 return aSourceUri.path.compareTo(bSourceUri.path); | |
| 151 }); | |
| 152 return declarationList; | |
| 153 } | |
| 154 | |
| 155 String _declarationName(DeclarationMirror declaration) => | |
| 156 MirrorSystem.getName(declaration.qualifiedName); | |
| 157 | |
| 158 /// Reads annotations on a [DeclarationMirror] and adds them to [_initQueue] | |
| 159 /// if they are [Initializer]s. | |
| 160 void _readAnnotations(DeclarationMirror declaration, Queue<Function> queue) { | |
| 161 var annotations = | |
| 162 declaration.metadata.where((m) => _filterMetadata(declaration, m)); | |
| 163 for (var meta in annotations) { | |
| 164 _annotationsFound.putIfAbsent( | |
| 165 declaration, () => new Set<InstanceMirror>()); | |
| 166 _annotationsFound[declaration].add(meta); | |
| 167 | |
| 168 // Initialize super classes first, if they are in the same library, | |
| 169 // otherwise we throw an error. This can only be the case if there are | |
| 170 // cycles in the imports. | |
| 171 if (declaration is ClassMirror && declaration.superclass != null) { | |
| 172 if (declaration.superclass.owner == declaration.owner) { | |
| 173 _readAnnotations(declaration.superclass, queue); | |
| 174 } else { | |
| 175 // Make sure to check the canonical superclass declaration, the one | |
| 176 // we get here is not always that. Specifically, this occurs if all of | |
| 177 // the following conditions are met: | |
| 178 // | |
| 179 // 1. The current library is never loaded via a `package:` dart | |
| 180 // import anywhere in the program. | |
| 181 // 2. The current library loads the superclass via a relative file | |
| 182 // import. | |
| 183 // 3. The super class is imported via a `package:` import somewhere | |
| 184 // else in the program. | |
| 185 var canonicalSuperDeclaration = | |
| 186 _canonicalClassDeclaration(declaration.superclass); | |
| 187 var superMetas = canonicalSuperDeclaration.metadata | |
| 188 .where((m) => _filterMetadata(canonicalSuperDeclaration, m)) | |
| 189 .toList(); | |
| 190 if (superMetas.isNotEmpty) { | |
| 191 throw new UnsupportedError( | |
| 192 'We have detected a cycle in your import graph when running ' | |
| 193 'initializers on ${declaration.qualifiedName}. This means the ' | |
| 194 'super class ${canonicalSuperDeclaration.qualifiedName} has a ' | |
| 195 'dependency on this library (possibly transitive).'); | |
| 196 } | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 var annotatedValue; | |
| 201 if (declaration is ClassMirror) { | |
| 202 annotatedValue = declaration.reflectedType; | |
| 203 } else if (declaration is MethodMirror) { | |
| 204 if (declaration.owner is! LibraryMirror) { | |
| 205 // TODO(jakemac): Support static class methods. | |
| 206 throw _TOP_LEVEL_FUNCTIONS_ONLY; | |
| 207 } | |
| 208 annotatedValue = (declaration.owner as ObjectMirror) | |
| 209 .getField(declaration.simpleName).reflectee; | |
| 210 } else if (declaration is LibraryMirror) { | |
| 211 var package; | |
| 212 var filePath; | |
| 213 Uri uri = declaration.uri; | |
| 214 // Convert to a package style uri if possible. | |
| 215 if (_isHttpStylePackageUrl(uri)) { | |
| 216 uri = _packageUriFor(uri); | |
| 217 } | |
| 218 if (uri.scheme == 'file' || uri.scheme.startsWith('http')) { | |
| 219 filePath = path.url.relative(uri.path, | |
| 220 from: _root.uri.path.endsWith('/') | |
| 221 ? _root.uri.path | |
| 222 : path.url.dirname(_root.uri.path)); | |
| 223 } else if (uri.scheme == 'package') { | |
| 224 var segments = uri.pathSegments; | |
| 225 package = segments[0]; | |
| 226 filePath = path.url.joinAll(segments.getRange(1, segments.length)); | |
| 227 } else { | |
| 228 throw new UnsupportedError('Unsupported uri scheme ${uri.scheme} for ' | |
| 229 'library ${declaration}.'); | |
| 230 } | |
| 231 annotatedValue = | |
| 232 new LibraryIdentifier(declaration.qualifiedName, package, filePath); | |
| 233 } else { | |
| 234 throw _UNSUPPORTED_DECLARATION; | |
| 235 } | |
| 236 queue.addLast(() => meta.reflectee.initialize(annotatedValue)); | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 // Filter function that returns true only if `meta` is an `Initializer`, | |
| 241 // it passes the `typeFilter` and `customFilter` if they exist, and it has not | |
| 242 // yet been seen. | |
| 243 bool _filterMetadata(DeclarationMirror declaration, InstanceMirror meta) { | |
| 244 if (meta.reflectee is! Initializer) return false; | |
| 245 if (typeFilter != null && | |
| 246 !typeFilter.any((t) => meta.reflectee.runtimeType == t)) { | |
| 247 return false; | |
| 248 } | |
| 249 if (customFilter != null && !customFilter(meta.reflectee)) return false; | |
| 250 if (!_annotationsFound.containsKey(declaration)) return true; | |
| 251 if (_annotationsFound[declaration].contains(meta)) return false; | |
| 252 return true; | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 final _TOP_LEVEL_FUNCTIONS_ONLY = new UnsupportedError( | |
| 257 'Only top level methods are supported for initializers'); | |
| 258 | |
| 259 final _UNSUPPORTED_DECLARATION = new UnsupportedError( | |
| 260 'Initializers are only supported on libraries, classes, and top level ' | |
| 261 'methods'); | |
| OLD | NEW |