blob: cbd2a607da7da79633d78556226df82cc1c4cdad [file] [log] [blame]
// 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 LibraryMirror _root = currentMirrorSystem().isolate.rootLibrary;
final Map<Uri, LibraryMirror> _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>();
_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.
var targetLibrary = dependency.targetLibrary;
if (targetLibrary == null || targetLibrary.uri.scheme == '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;
}
/// 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) {
_annotationsFound.putIfAbsent(
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');