Skip to content
This repository has been archived by the owner on Jan 20, 2018. It is now read-only.

Commit

Permalink
remove mirrors from static initializers, add mirror_loader
Browse files Browse the repository at this point in the history
  • Loading branch information
jakemac53 committed Jan 8, 2015
1 parent 15c95b5 commit 6572c36
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 141 deletions.
15 changes: 3 additions & 12 deletions lib/src/init_method.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,12 @@ part of static_init;
/// automatically when calling static_init.run(). This class is private because
/// it shouldn't be used directly in annotations, instead use the `initMethod`
/// singleton below.
class _InitMethod implements StaticInitializer<MethodMirror> {
typedef dynamic _ZeroArg();
class _InitMethod implements StaticInitializer<_ZeroArg> {
const _InitMethod();

@override
initialize(MethodMirror method) {
if (!method.isStatic) {
throw 'Methods marked with @initMethod should be static, '
'${method.simpleName} is not.';
}
if (method.parameters.any((p) => !p.isOptional)) {
throw 'Methods marked with @initMethod should take no arguments, '
'${method.simpleName} expects some.';
}
(method.owner as ObjectMirror).invoke(method.simpleName, const []);
}
initialize(_ZeroArg method) => method();
}

/// We only ever need one instance of the `_InitMethod` class, this is it.
Expand Down
113 changes: 113 additions & 0 deletions lib/src/mirror_loader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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 static_init.mirror_loader;

import 'dart:collection' show ListQueue;
import 'dart:mirrors';
import 'package:static_init/static_init.dart';

// Crawls a library and all its dependencies for `StaticInitializer`
// annotations using mirrors
class StaticInitializationCrawler {
// 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;

// All the libraries we have seen so far.
final Set<LibraryMirror> _librariesSeen = new Set<LibraryMirror>();

// The root library that we start parsing from.
LibraryMirror _root;

/// Queues for pending intialization functions to run.
final _libraryQueue = new ListQueue<Function>();
final _otherQueue = new ListQueue<Function>();

StaticInitializationCrawler(
this.typeFilter, this.customFilter, {LibraryMirror root}) {
_root = root == null ? currentMirrorSystem().isolate.rootLibrary : root;
}

// The primary function in this class, invoke it to crawl and call all the
// annotations.
run() {
// Parse everything into the two queues.
_readLibraryDeclarations(_root);

// First empty the _libraryQueue, then the _otherQueue.
while (_libraryQueue.isNotEmpty) _libraryQueue.removeFirst()();
while (_otherQueue.isNotEmpty) _otherQueue.removeFirst()();
}

/// Reads and executes StaticInitializer annotations on this library and all
/// its dependencies in post-order.
void _readLibraryDeclarations(LibraryMirror lib) {
_librariesSeen.add(lib);

// First visit all our dependencies.
for (var dependency in lib.libraryDependencies) {
if (_librariesSeen.contains(dependency.targetLibrary)) continue;
_readLibraryDeclarations(dependency.targetLibrary);
}

// Second parse the library directive annotations.
_readAnnotations(lib, _libraryQueue);

// Last, parse all class and method annotations.
lib .declarations
.values
.where((d) => d is ClassMirror || d is MethodMirror)
.forEach((DeclarationMirror d) => _readAnnotations(d, _otherQueue));
}

void _readAnnotations(DeclarationMirror declaration,
ListQueue<Function> queue) {
declaration
.metadata
.where((m) {
if (m.reflectee is! StaticInitializer) return false;
if (typeFilter != null &&
!typeFilter.any((t) => m.reflectee.runtimeType == t)) {
return false;
}
if (customFilter != null && !customFilter(m.reflectee)) return false;
return true;
})
.forEach((meta) {
if (!_annotationsFound.containsKey(declaration)) {
_annotationsFound[declaration] = new Set<InstanceMirror>();
}
if (_annotationsFound[declaration].contains(meta)) return;
_annotationsFound[declaration].add(meta);

// Initialize super classes first, this is the only exception to the
// post-order rule.
if (declaration is ClassMirror && declaration.superclass != null) {
_readAnnotations(declaration.superclass, queue);
}

queue.addLast(() {
var annotatedValue;
if (declaration is ClassMirror) {
annotatedValue = declaration.reflectedType;
} else if (declaration is MethodMirror) {
annotatedValue =
() => (declaration.owner as ObjectMirror)
.invoke(declaration.simpleName, const []);
} else {
annotatedValue = declaration.qualifiedName;
}
meta.reflectee.initialize(annotatedValue);
});
});
}
}
7 changes: 5 additions & 2 deletions lib/src/static_initializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ part of static_init;
///
/// Hello world example:
///
/// class Print implements StaticInitializer<ClassMirror> {
/// class Print implements StaticInitializer<Type> {
/// final String message;
/// const Print(this.message);
///
/// @override
/// initialize(ClassMirror) => print('${t.reflectedType} says `$message`');
/// initialize(Type t) => print('$t says `$message`');
/// }
///
/// @Print('hello world!')
Expand All @@ -23,3 +23,6 @@ part of static_init;
abstract class StaticInitializer<T> {
dynamic initialize(T target);
}

/// Typedef for a custom filter function.
typedef bool InitializerFilter(StaticInitializer initializer);
102 changes: 3 additions & 99 deletions lib/static_init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,111 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
library static_init;

import 'dart:collection' show ListQueue;
import 'dart:mirrors';
// The transformer will replace this with a static_loader.
import 'src/mirror_loader.dart';

part 'src/init_method.dart';
part 'src/static_initializer.dart';

/// Typedef for a custom filter function.
typedef bool InitializerFilter(InstanceMirror meta);

/// Top level function which crawls the dependency graph and runs initializers.
/// If `typeFilter` is supplied then only those types of annotations will be
/// parsed.
void run({List<Type> typeFilter, InitializerFilter customFilter}) {
new _StaticInitializationCrawler(typeFilter, customFilter).run();
}

// Crawls a library and all its dependencies for `StaticInitializer`
// annotations.
class _StaticInitializationCrawler {
// 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;

// All the libraries we have seen so far.
final Set<LibraryMirror> _librariesSeen = new Set<LibraryMirror>();

// The root library that we start parsing from.
LibraryMirror _root;

/// Queues for pending intialization functions to run.
final _libraryQueue = new ListQueue<Function>();
final _otherQueue = new ListQueue<Function>();

_StaticInitializationCrawler(
this.typeFilter, this.customFilter, {LibraryMirror root}) {
_root = root == null ? currentMirrorSystem().isolate.rootLibrary : root;
}

// The primary function in this class, invoke it to crawl and call all the
// annotations.
run() {
// Parse everything into the two queues.
_readLibraryDeclarations(_root);

// First empty the _libraryQueue, then the _otherQueue.
while (_libraryQueue.isNotEmpty) _libraryQueue.removeFirst()();
while (_otherQueue.isNotEmpty) _otherQueue.removeFirst()();
}

/// Reads and executes StaticInitializer annotations on this library and all
/// its dependencies in post-order.
void _readLibraryDeclarations(LibraryMirror lib) {
_librariesSeen.add(lib);

// First visit all our dependencies.
for (var dependency in lib.libraryDependencies) {
if (_librariesSeen.contains(dependency.targetLibrary)) continue;
_readLibraryDeclarations(dependency.targetLibrary);
}

// Second parse the library directive annotations.
_readAnnotations(lib, _libraryQueue);

// Last, parse all class and method annotations.
lib .declarations
.values
.where((d) => d is ClassMirror || d is MethodMirror)
.forEach((DeclarationMirror d) => _readAnnotations(d, _otherQueue));
}

void _readAnnotations(DeclarationMirror declaration,
ListQueue<Function> queue) {
declaration
.metadata
.where((m) {
if (m.reflectee is! StaticInitializer) return false;
if (typeFilter != null &&
!typeFilter.any((t) => m.reflectee.runtimeType == t)) {
return false;
}
if (customFilter != null && !customFilter(m)) return false;
return true;
})
.forEach((meta) {
if (!_annotationsFound.containsKey(declaration)) {
_annotationsFound[declaration] = new Set<InstanceMirror>();
}
if (_annotationsFound[declaration].contains(meta)) return;
_annotationsFound[declaration].add(meta);

// Initialize super classes first, this is the only exception to the
// post-order rule.
if (declaration is ClassMirror && declaration.superclass != null) {
_readAnnotations(declaration.superclass, queue);
}

queue.addLast(() => meta.reflectee.initialize(declaration));
});
}
new StaticInitializationCrawler(typeFilter, customFilter).run();
}
2 changes: 1 addition & 1 deletion test/bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ import 'initialize_tracker.dart';
class Bar extends Foo {}

@initializeTracker
bar() {}
bar() => 'bar';
6 changes: 3 additions & 3 deletions test/foo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
@initializeTracker
library static_init.test.foo;

import 'bar.dart'; // For the annotations.
export 'bar.dart';
import 'initialize_tracker.dart';

@initializeTracker
class Foo {}

@initializeTracker
fooBar() {}
fooBar() => 'fooBar';

@initializeTracker
foo() {}
foo() => 'foo';
13 changes: 10 additions & 3 deletions test/initialize_tracker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import 'dart:mirrors';
import 'package:static_init/static_init.dart';

// Static Initializer that just saves everything it sees.
class InitializeTracker implements StaticInitializer<DeclarationMirror> {
static final List<DeclarationMirror> seen = [];
class InitializeTracker implements StaticInitializer<dynamic> {
static final List seen = [];

const InitializeTracker();

@override
initialize(DeclarationMirror t) => seen.add(t);
void initialize(value) {
// invoke functions and add their return value, makes testing easier.
if (value is Function) {
seen.add((value() as InstanceMirror).reflectee);
} else {
seen.add(value);
}
}
}

const initializeTracker = const InitializeTracker();
18 changes: 7 additions & 11 deletions test/static_init_custom_filter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ main() {
test('filter option limits which types of annotations will be ran', () {
runPhase(1);
// Even though Baz extends Bar, only Baz should be run.
expect(getNames(InitializeTracker.seen), [#Baz]);
expect(InitializeTracker.seen, [Baz]);
runPhase(2);
expect(getNames(InitializeTracker.seen), [#Baz, #foo]);
expect(InitializeTracker.seen, [Baz, 'foo']);
runPhase(3);
expect(getNames(InitializeTracker.seen), [#Baz, #foo, #Foo]);
expect(InitializeTracker.seen, [Baz, 'foo', Foo]);
runPhase(4);
expect(getNames(InitializeTracker.seen), [#Baz, #foo, #Foo, #Bar]);
expect(InitializeTracker.seen, [Baz, 'foo', Foo, Bar]);

// Sanity check, future calls should be no-ops
var originalSize = InitializeTracker.seen.length;
Expand All @@ -34,20 +34,16 @@ main() {
});
}

Iterable<Symbol> getNames(Iterable<DeclarationMirror> original) =>
original.map((d) => d.simpleName);

runPhase(int phase) {
run(customFilter: (InstanceMirror meta) =>
meta.reflectee is PhasedInitializer &&
(meta.reflectee as PhasedInitializer).phase == phase);
run(customFilter: (StaticInitializer meta) =>
meta is PhasedInitializer && meta.phase == phase);
}

@PhasedInitializer(3)
class Foo {}

@PhasedInitializer(2)
foo() {}
foo() => 'foo';

@PhasedInitializer(4)
class Bar {}
Expand Down
11 changes: 5 additions & 6 deletions test/static_init_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
@initializeTracker
library static_init.static_init_test;

import 'foo.dart'; // For the annotations.
import 'foo.dart';
import 'initialize_tracker.dart';
import 'package:static_init/static_init.dart';
import 'package:unittest/unittest.dart';
Expand All @@ -19,10 +19,9 @@ main() {
test('annotations are seen in post-order with superclasses first', () {
// Foo comes first because its a superclass of Bar.
var expectedNames = [#static_init.test.bar, #static_init.test.foo,
#static_init.static_init_test, #Foo, #Bar, #bar, #fooBar, #foo, #zap,
#Zap];
var actualNames = InitializeTracker.seen.map((d) => d.simpleName);
expect(actualNames, expectedNames);
#static_init.static_init_test, Foo, Bar, 'bar', 'fooBar', 'foo', 'zap',
Zap];
expect(InitializeTracker.seen, expectedNames);
});

test('annotations only run once', () {
Expand All @@ -37,4 +36,4 @@ main() {
class Zap {}

@initializeTracker
zap() {}
zap() => 'zap';
Loading

0 comments on commit 6572c36

Please sign in to comment.