| // Copyright (c) 2014, 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. |
| |
| /// Static implementation of smoke services using code-generated data. |
| library smoke.static; |
| |
| import 'dart:math' as math; |
| |
| import 'package:logging/logging.dart'; |
| import 'package:smoke/smoke.dart'; |
| |
| import 'src/common.dart'; |
| |
| typedef T Getter<T>(object); |
| typedef void Setter<T>(object, value); |
| |
| class StaticConfiguration { |
| /// Maps symbol to a function that reads that symbol of an object. For |
| /// instance, `#i: (o) => o.i`. |
| final Map<Symbol, Getter> getters; |
| |
| /// Maps symbol to a function that updates that symbol of an object. For |
| /// instance, `#i: (o, v) { o.i = v; }`. |
| final Map<Symbol, Setter> setters; |
| |
| /// Maps a type to its super class. For example, String: Object. |
| final Map<Type, Type> parents; |
| |
| /// For each type, a map of declarations per symbol (property or method). |
| final Map<Type, Map<Symbol, Declaration>> declarations; |
| |
| /// Static methods for each type. |
| // TODO(sigmund): should we add static getters & setters too? |
| final Map<Type, Map<Symbol, Function>> staticMethods; |
| |
| /// A map from symbol to strings. |
| final Map<Symbol, String> names; |
| |
| /// Whether to check for missing declarations, otherwise, return default |
| /// values (for example a missing parent class can be treated as Object) |
| final bool checkedMode; |
| |
| StaticConfiguration({ |
| this.getters: const {}, this.setters: const {}, this.parents: const {}, |
| this.declarations: const {}, this.staticMethods: const {}, |
| this.names: const {}, this.checkedMode: true}); |
| } |
| |
| /// Set up the smoke package to use a static implementation based on the given |
| /// [configuration]. |
| useGeneratedCode(StaticConfiguration configuration) { |
| configure(new GeneratedObjectAccessorService(configuration), |
| new GeneratedTypeInspectorService(configuration), |
| new GeneratedSymbolConverterService(configuration)); |
| } |
| |
| /// Implements [ObjectAccessorService] using a static configuration. |
| class GeneratedObjectAccessorService implements ObjectAccessorService { |
| final Map<Symbol, Getter> _getters; |
| final Map<Symbol, Setter> _setters; |
| final Map<Type, Map<Symbol, Function>> _staticMethods; |
| |
| GeneratedObjectAccessorService(StaticConfiguration configuration) |
| : _getters = configuration.getters, |
| _setters = configuration.setters, |
| _staticMethods = configuration.staticMethods; |
| |
| read(Object object, Symbol name) { |
| var getter = _getters[name]; |
| if (getter == null) { |
| throw new MissingCodeException('getter "$name" in $object'); |
| } |
| return getter(object); |
| } |
| |
| void write(Object object, Symbol name, value) { |
| var setter = _setters[name]; |
| if (setter == null) { |
| throw new MissingCodeException('setter "$name" in $object'); |
| } |
| setter(object, value); |
| } |
| |
| invoke(object, Symbol name, List args, {Map namedArgs, bool adjust: false}) { |
| var method; |
| if (object is Type) { |
| var classMethods = _staticMethods[object]; |
| method = classMethods == null ? null : classMethods[name]; |
| } else { |
| var getter = _getters[name]; |
| method = getter == null ? null : getter(object); |
| } |
| if (method == null) { |
| throw new MissingCodeException('method "$name" in $object'); |
| } |
| var tentativeError; |
| if (adjust) { |
| var min = minArgs(method); |
| if (min > SUPPORTED_ARGS) { |
| tentativeError = 'we tried to adjust the arguments for calling "$name"' |
| ', but we couldn\'t determine the exact number of arguments it ' |
| 'expects (it is more than $SUPPORTED_ARGS).'; |
| // The argument list might be correct, so we still invoke the function |
| // and let the user see the error. |
| args = adjustList(args, min, math.max(min, args.length)); |
| } else { |
| var max = maxArgs(method); |
| args = adjustList(args, min, max >= 0 ? max : args.length); |
| } |
| } |
| if (namedArgs != null) { |
| throw new UnsupportedError( |
| 'smoke.static doesn\'t support namedArguments in invoke'); |
| } |
| try { |
| return Function.apply(method, args); |
| } on NoSuchMethodError catch (e) { |
| // TODO(sigmund): consider whether this should just be in a logger or if |
| // we should wrap `e` as a new exception (what's the best way to let users |
| // know about this tentativeError?) |
| if (tentativeError != null) print(tentativeError); |
| rethrow; |
| } |
| } |
| } |
| |
| /// Implements [TypeInspectorService] using a static configuration. |
| class GeneratedTypeInspectorService implements TypeInspectorService { |
| final Map<Type, Type> _parents; |
| final Map<Type, Map<Symbol, Declaration>> _declarations; |
| final bool _checkedMode; |
| |
| GeneratedTypeInspectorService(StaticConfiguration configuration) |
| : _parents = configuration.parents, |
| _declarations = configuration.declarations, |
| _checkedMode = configuration.checkedMode; |
| bool isSubclassOf(Type type, Type supertype) { |
| if (type == supertype || supertype == Object) return true; |
| while (type != Object) { |
| var parentType = _parents[type]; |
| if (parentType == supertype) return true; |
| if (parentType == null) { |
| if (!_checkedMode) return false; |
| throw new MissingCodeException('superclass of "$type" ($parentType)'); |
| } |
| type = parentType; |
| } |
| return false; |
| } |
| |
| bool hasGetter(Type type, Symbol name) { |
| var decl = _findDeclaration(type, name); |
| // No need to check decl.isProperty because methods are also automatically |
| // considered getters (auto-closures). |
| return decl != null && !decl.isStatic; |
| } |
| |
| bool hasSetter(Type type, Symbol name) { |
| var decl = _findDeclaration(type, name); |
| return decl != null && !decl.isMethod && !decl.isFinal && !decl.isStatic; |
| } |
| |
| bool hasInstanceMethod(Type type, Symbol name) { |
| var decl = _findDeclaration(type, name); |
| return decl != null && decl.isMethod && !decl.isStatic; |
| } |
| |
| bool hasStaticMethod(Type type, Symbol name) { |
| final map = _declarations[type]; |
| if (map == null) { |
| if (!_checkedMode) return false; |
| throw new MissingCodeException('declarations for $type'); |
| } |
| final decl = map[name]; |
| return decl != null && decl.isMethod && decl.isStatic; |
| } |
| |
| Declaration getDeclaration(Type type, Symbol name) { |
| var decl = _findDeclaration(type, name); |
| if (decl == null) { |
| if (!_checkedMode) return null; |
| throw new MissingCodeException('declaration for $type.$name'); |
| } |
| return decl; |
| } |
| |
| List<Declaration> query(Type type, QueryOptions options) { |
| var result = []; |
| if (options.includeInherited) { |
| var superclass = _parents[type]; |
| if (superclass == null) { |
| if (_checkedMode) { |
| throw new MissingCodeException('superclass of "$type"'); |
| } |
| } else if (superclass != options.includeUpTo) { |
| result = query(superclass, options); |
| } |
| } |
| var map = _declarations[type]; |
| if (map == null) { |
| if (!_checkedMode) return result; |
| throw new MissingCodeException('declarations for $type'); |
| } |
| for (var decl in map.values) { |
| if (!options.includeFields && decl.isField) continue; |
| if (!options.includeProperties && decl.isProperty) continue; |
| if (options.excludeFinal && decl.isFinal) continue; |
| if (!options.includeMethods && decl.isMethod) continue; |
| if (options.matches != null && !options.matches(decl.name)) continue; |
| if (options.withAnnotations != null && |
| !matchesAnnotation(decl.annotations, options.withAnnotations)) { |
| continue; |
| } |
| result.add(decl); |
| } |
| return result; |
| } |
| |
| Declaration _findDeclaration(Type type, Symbol name) { |
| while (type != Object) { |
| final declarations = _declarations[type]; |
| if (declarations != null) { |
| final declaration = declarations[name]; |
| if (declaration != null) return declaration; |
| } |
| var parentType = _parents[type]; |
| if (parentType == null) { |
| if (!_checkedMode) return null; |
| throw new MissingCodeException('superclass of "$type"'); |
| } |
| type = parentType; |
| } |
| return null; |
| } |
| } |
| |
| /// Implements [SymbolConverterService] using a static configuration. |
| class GeneratedSymbolConverterService implements SymbolConverterService { |
| Map<Symbol, String> _names; |
| |
| /// A map from strings to symbols (the reverse of [names]). |
| final Map<String, Symbol> _symbols; |
| |
| GeneratedSymbolConverterService(StaticConfiguration configuration) |
| : _names = configuration.names, |
| _symbols = {} { |
| _names.forEach((k, v) { _symbols[v] = k; }); |
| } |
| |
| String symbolToName(Symbol symbol) => _names[symbol]; |
| Symbol nameToSymbol(String name) => _symbols[name]; |
| } |
| |
| |
| /// Exception thrown when trynig to access something that should be there, but |
| /// the code generator didn't include it. |
| class MissingCodeException implements Exception { |
| final String description; |
| MissingCodeException(this.description); |
| |
| String toString() => 'Missing $description. ' |
| 'Code generation for the smoke package seems incomplete.'; |
| } |