| // Copyright (c) 2015, 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. |
| library initialize.mirror_loader; |
| |
| import 'dart:collection' show Queue; |
| import 'dart:mirrors'; |
| import 'package:path/path.dart' as path; |
| import 'package:initialize/initialize.dart'; |
| |
| final _root = currentMirrorSystem().isolate.rootLibrary; |
| final _libs = currentMirrorSystem().libraries; |
| |
| Queue<Function> loadInitializers( |
| {List<Type> typeFilter, InitializerFilter customFilter, Uri from}) { |
| return new InitializationCrawler(typeFilter, customFilter, from: from).run(); |
| } |
| |
| // Crawls a library and all its dependencies for `Initializer` annotations using |
| // mirrors |
| class InitializationCrawler { |
| // Set of all visited annotations, keys are the declarations that were |
| // annotated, values are the annotations that have been processed. |
| static final _annotationsFound = |
| new Map<DeclarationMirror, Set<InstanceMirror>>(); |
| |
| // If non-null, then only these annotations should be processed. |
| final List<Type> typeFilter; |
| |
| // If non-null, then only annotations which return true when passed to this |
| // function will be processed. |
| final InitializerFilter customFilter; |
| |
| /// The library to start crawling from. |
| final LibraryMirror _rootLibrary; |
| |
| /// Note: The [from] argument is only supported in the mirror_loader.dart. It |
| /// is not supported statically. |
| InitializationCrawler(this.typeFilter, this.customFilter, {Uri from}) |
| : _rootLibrary = from == null |
| ? _root |
| : _libs[from] { |
| if (_rootLibrary == null) throw 'Unable to find library at $from.'; |
| } |
| |
| // The primary function in this class, invoke it to crawl and collect all the |
| // annotations into a queue of init functions. |
| Queue<Function> run() { |
| var librariesSeen = new Set<LibraryMirror>(); |
| var queue = new Queue<Function>(); |
| var libraries = currentMirrorSystem().libraries; |
| |
| _readLibraryDeclarations(_rootLibrary, librariesSeen, queue); |
| return queue; |
| } |
| |
| /// Returns the canonical [LibraryMirror] for a given [LibraryMirror]. This |
| /// is defined as the one loaded from a `package:` url if available, otherwise |
| /// it is just [lib]. |
| LibraryMirror _canonicalLib(LibraryMirror lib) { |
| var uri = lib.uri; |
| if (_isHttpStylePackageUrl(uri)) { |
| var packageUri = _packageUriFor(uri); |
| if (_libs.containsKey(packageUri)) return _libs[packageUri]; |
| } |
| return lib; |
| } |
| |
| /// Returns the canonical [ClassMirror] for a given [ClassMirror]. This is |
| /// defined as the one that appears in the canonical owner [LibararyMirror]. |
| ClassMirror _canonicalClassDeclaration(ClassMirror declaration) => |
| _canonicalLib(declaration.owner).declarations[declaration.simpleName]; |
| |
| /// Whether [uri] is an http URI that contains a 'packages' segment, and |
| /// therefore could be converted into a 'package:' URI. |
| bool _isHttpStylePackageUrl(Uri uri) { |
| var uriPath = uri.path; |
| return uri.scheme == _root.uri.scheme && |
| // Don't process cross-domain uris. |
| uri.authority == _root.uri.authority && |
| uriPath.endsWith('.dart') && |
| (uriPath.contains('/packages/') || uriPath.startsWith('packages/')); |
| } |
| |
| /// Returns a `package:` version of [uri]. |
| Uri _packageUriFor(Uri uri) { |
| var packagePath = uri.path |
| .substring(uri.path.lastIndexOf('packages/') + 'packages/'.length); |
| return Uri.parse('package:$packagePath'); |
| } |
| |
| // Reads Initializer annotations on this library and all its dependencies in |
| // post-order. |
| Queue<Function> _readLibraryDeclarations(LibraryMirror lib, |
| Set<LibraryMirror> librariesSeen, Queue<Function> queue) { |
| lib = _canonicalLib(lib); |
| if (librariesSeen.contains(lib)) return queue; |
| librariesSeen.add(lib); |
| |
| // First visit all our dependencies. |
| for (var dependency in lib.libraryDependencies) { |
| // Skip dart: imports, they never use this package. |
| if (dependency.targetLibrary.uri.toString().startsWith('dart:')) continue; |
| _readLibraryDeclarations(dependency.targetLibrary, librariesSeen, queue); |
| } |
| |
| // Second parse the library directive annotations. |
| _readAnnotations(lib, queue); |
| |
| // Last, parse all class and method annotations. |
| for (var declaration in _sortedDeclarationsWithMetadata(lib)) { |
| _readAnnotations(declaration, queue); |
| // Check classes for static annotations which are not supported |
| if (declaration is ClassMirror) { |
| for (var classDeclaration in declaration.declarations.values) { |
| _readAnnotations(classDeclaration, queue); |
| } |
| } |
| } |
| |
| return queue; |
| } |
| |
| Iterable<DeclarationMirror> _sortedDeclarationsWithMetadata( |
| LibraryMirror lib) { |
| return new List() |
| ..addAll(_sortDeclarations(lib, lib.declarations.values |
| .where((d) => d is MethodMirror && d.metadata.isNotEmpty))) |
| ..addAll(_sortDeclarations(lib, lib.declarations.values |
| .where((d) => d is ClassMirror && d.metadata.isNotEmpty))); |
| } |
| |
| List<DeclarationMirror> _sortDeclarations( |
| LibraryMirror sourceLib, Iterable<DeclarationMirror> declarations) { |
| var declarationList = declarations.toList(); |
| declarationList.sort((DeclarationMirror a, DeclarationMirror b) { |
| // If in the same file, compare by line. |
| var aSourceUri = a.location.sourceUri; |
| var bSourceUri = b.location.sourceUri; |
| if (aSourceUri == bSourceUri) { |
| return a.location.line.compareTo(b.location.line); |
| } |
| |
| // Run parts first if one is from the original library. |
| if (aSourceUri == sourceLib.uri) return 1; |
| if (bSourceUri == sourceLib.uri) return -1; |
| |
| // Sort parts alphabetically. |
| return aSourceUri.path.compareTo(bSourceUri.path); |
| }); |
| return declarationList; |
| } |
| |
| String _declarationName(DeclarationMirror declaration) => |
| MirrorSystem.getName(declaration.qualifiedName); |
| |
| /// Reads annotations on a [DeclarationMirror] and adds them to [_initQueue] |
| /// if they are [Initializer]s. |
| void _readAnnotations(DeclarationMirror declaration, Queue<Function> queue) { |
| var annotations = |
| declaration.metadata.where((m) => _filterMetadata(declaration, m)); |
| for (var meta in annotations) { |
| if (!_annotationsFound.containsKey(declaration)) { |
| _annotationsFound[declaration] = new Set<InstanceMirror>(); |
| } |
| _annotationsFound[declaration].add(meta); |
| |
| // Initialize super classes first, if they are in the same library, |
| // otherwise we throw an error. This can only be the case if there are |
| // cycles in the imports. |
| if (declaration is ClassMirror && declaration.superclass != null) { |
| if (declaration.superclass.owner == declaration.owner) { |
| _readAnnotations(declaration.superclass, queue); |
| } else { |
| // Make sure to check the canonical superclass declaration, the one |
| // we get here is not always that. Specifically, this occurs if all of |
| // the following conditions are met: |
| // |
| // 1. The current library is never loaded via a `package:` dart |
| // import anywhere in the program. |
| // 2. The current library loads the superclass via a relative file |
| // import. |
| // 3. The super class is imported via a `package:` import somewhere |
| // else in the program. |
| var canonicalSuperDeclaration = |
| _canonicalClassDeclaration(declaration.superclass); |
| var superMetas = canonicalSuperDeclaration.metadata |
| .where((m) => _filterMetadata(canonicalSuperDeclaration, m)) |
| .toList(); |
| if (superMetas.isNotEmpty) { |
| throw new UnsupportedError( |
| 'We have detected a cycle in your import graph when running ' |
| 'initializers on ${declaration.qualifiedName}. This means the ' |
| 'super class ${canonicalSuperDeclaration.qualifiedName} has a ' |
| 'dependency on this library (possibly transitive).'); |
| } |
| } |
| } |
| |
| var annotatedValue; |
| if (declaration is ClassMirror) { |
| annotatedValue = declaration.reflectedType; |
| } else if (declaration is MethodMirror) { |
| if (declaration.owner is! LibraryMirror) { |
| // TODO(jakemac): Support static class methods. |
| throw _TOP_LEVEL_FUNCTIONS_ONLY; |
| } |
| annotatedValue = (declaration.owner as ObjectMirror) |
| .getField(declaration.simpleName).reflectee; |
| } else if (declaration is LibraryMirror) { |
| var package; |
| var filePath; |
| Uri uri = declaration.uri; |
| // Convert to a package style uri if possible. |
| if (_isHttpStylePackageUrl(uri)) { |
| uri = _packageUriFor(uri); |
| } |
| if (uri.scheme == 'file' || uri.scheme.startsWith('http')) { |
| filePath = path.url.relative(uri.path, |
| from: _root.uri.path.endsWith('/') |
| ? _root.uri.path |
| : path.url.dirname(_root.uri.path)); |
| } else if (uri.scheme == 'package') { |
| var segments = uri.pathSegments; |
| package = segments[0]; |
| filePath = path.url.joinAll(segments.getRange(1, segments.length)); |
| } else { |
| throw new UnsupportedError('Unsupported uri scheme ${uri.scheme} for ' |
| 'library ${declaration}.'); |
| } |
| annotatedValue = |
| new LibraryIdentifier(declaration.qualifiedName, package, filePath); |
| } else { |
| throw _UNSUPPORTED_DECLARATION; |
| } |
| queue.addLast(() => meta.reflectee.initialize(annotatedValue)); |
| } |
| } |
| |
| // Filter function that returns true only if `meta` is an `Initializer`, |
| // it passes the `typeFilter` and `customFilter` if they exist, and it has not |
| // yet been seen. |
| bool _filterMetadata(DeclarationMirror declaration, InstanceMirror meta) { |
| if (meta.reflectee is! Initializer) return false; |
| if (typeFilter != null && |
| !typeFilter.any((t) => meta.reflectee.runtimeType == t)) { |
| return false; |
| } |
| if (customFilter != null && !customFilter(meta.reflectee)) return false; |
| if (!_annotationsFound.containsKey(declaration)) return true; |
| if (_annotationsFound[declaration].contains(meta)) return false; |
| return true; |
| } |
| } |
| |
| final _TOP_LEVEL_FUNCTIONS_ONLY = new UnsupportedError( |
| 'Only top level methods are supported for initializers'); |
| |
| final _UNSUPPORTED_DECLARATION = new UnsupportedError( |
| 'Initializers are only supported on libraries, classes, and top level ' |
| 'methods'); |