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 |