OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, 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 |
| 5 /// Static implementation of smoke services using code-generated data. |
| 6 library smoke.static; |
| 7 |
| 8 import 'dart:math' as math; |
| 9 |
| 10 import 'package:logging/logging.dart'; |
| 11 import 'package:smoke/smoke.dart'; |
| 12 |
| 13 import 'src/common.dart'; |
| 14 |
| 15 typedef T Getter<T>(object); |
| 16 typedef void Setter<T>(object, value); |
| 17 |
| 18 class StaticConfiguration { |
| 19 /// Maps symbol to a function that reads that symbol of an object. For |
| 20 /// instance, `#i: (o) => o.i`. |
| 21 final Map<Symbol, Getter> getters; |
| 22 |
| 23 /// Maps symbol to a function that updates that symbol of an object. For |
| 24 /// instance, `#i: (o, v) { o.i = v; }`. |
| 25 final Map<Symbol, Setter> setters; |
| 26 |
| 27 /// Maps a type to its super class. For example, String: Object. |
| 28 final Map<Type, Type> parents; |
| 29 |
| 30 /// For each type, a map of declarations per symbol (property or method). |
| 31 final Map<Type, Map<Symbol, Declaration>> declarations; |
| 32 |
| 33 /// Static methods for each type. |
| 34 // TODO(sigmund): should we add static getters & setters too? |
| 35 final Map<Type, Map<Symbol, Function>> staticMethods; |
| 36 |
| 37 /// A map from symbol to strings. |
| 38 final Map<Symbol, String> names; |
| 39 |
| 40 /// A map from strings to symbols (the reverse of [names]). |
| 41 final Map<String, Symbol> _symbols = {}; |
| 42 |
| 43 |
| 44 /// Whether to check for missing declarations, otherwise, return default |
| 45 /// values (for example a missing parent class can be treated as Object) |
| 46 final bool checkedMode; |
| 47 |
| 48 StaticConfiguration({ |
| 49 Map<Symbol, Getter> getters, |
| 50 Map<Symbol, Setter> setters, |
| 51 Map<Type, Type> parents, |
| 52 Map<Type, Map<Symbol, Declaration>> declarations, |
| 53 Map<Type, Map<Symbol, Function>> staticMethods, |
| 54 Map<Symbol, String> names, |
| 55 this.checkedMode: true}) |
| 56 : getters = getters != null ? getters : {}, |
| 57 setters = setters != null ? setters : {}, |
| 58 parents = parents != null ? parents : {}, |
| 59 declarations = declarations != null ? declarations : {}, |
| 60 staticMethods = staticMethods != null ? staticMethods : {}, |
| 61 names = names != null ? names : {} { |
| 62 this.names.forEach((k, v) { _symbols[v] = k; }); |
| 63 } |
| 64 |
| 65 void addAll(StaticConfiguration other) { |
| 66 getters.addAll(other.getters); |
| 67 setters.addAll(other.setters); |
| 68 parents.addAll(other.parents); |
| 69 _nestedAddAll(declarations, other.declarations); |
| 70 _nestedAddAll(staticMethods, other.staticMethods); |
| 71 names.addAll(other.names); |
| 72 other.names.forEach((k, v) { _symbols[v] = k; }); |
| 73 } |
| 74 |
| 75 static _nestedAddAll(Map a, Map b) { |
| 76 for (var key in b.keys) { |
| 77 a.putIfAbsent(key, () => {}); |
| 78 a[key].addAll(b[key]); |
| 79 } |
| 80 } |
| 81 } |
| 82 |
| 83 /// Set up the smoke package to use a static implementation based on the given |
| 84 /// [configuration]. |
| 85 useGeneratedCode(StaticConfiguration configuration) { |
| 86 configure(new GeneratedObjectAccessorService(configuration), |
| 87 new GeneratedTypeInspectorService(configuration), |
| 88 new GeneratedSymbolConverterService(configuration)); |
| 89 } |
| 90 |
| 91 /// Implements [ObjectAccessorService] using a static configuration. |
| 92 class GeneratedObjectAccessorService implements ObjectAccessorService { |
| 93 final StaticConfiguration _configuration; |
| 94 Map<Symbol, Getter> get _getters => _configuration.getters; |
| 95 Map<Symbol, Setter> get _setters => _configuration.setters; |
| 96 Map<Type, Map<Symbol, Function>> get _staticMethods => |
| 97 _configuration.staticMethods; |
| 98 |
| 99 GeneratedObjectAccessorService(this._configuration); |
| 100 |
| 101 read(Object object, Symbol name) { |
| 102 var getter = _getters[name]; |
| 103 if (getter == null) { |
| 104 throw new MissingCodeException('getter "$name" in $object'); |
| 105 } |
| 106 return getter(object); |
| 107 } |
| 108 |
| 109 void write(Object object, Symbol name, value) { |
| 110 var setter = _setters[name]; |
| 111 if (setter == null) { |
| 112 throw new MissingCodeException('setter "$name" in $object'); |
| 113 } |
| 114 setter(object, value); |
| 115 } |
| 116 |
| 117 invoke(object, Symbol name, List args, {Map namedArgs, bool adjust: false}) { |
| 118 var method; |
| 119 if (object is Type && name != #toString) { |
| 120 var classMethods = _staticMethods[object]; |
| 121 method = classMethods == null ? null : classMethods[name]; |
| 122 } else { |
| 123 var getter = _getters[name]; |
| 124 method = getter == null ? null : getter(object); |
| 125 } |
| 126 if (method == null) { |
| 127 throw new MissingCodeException('method "$name" in $object'); |
| 128 } |
| 129 var tentativeError; |
| 130 if (adjust) { |
| 131 var min = minArgs(method); |
| 132 if (min > SUPPORTED_ARGS) { |
| 133 tentativeError = 'we tried to adjust the arguments for calling "$name"' |
| 134 ', but we couldn\'t determine the exact number of arguments it ' |
| 135 'expects (it is more than $SUPPORTED_ARGS).'; |
| 136 // The argument list might be correct, so we still invoke the function |
| 137 // and let the user see the error. |
| 138 args = adjustList(args, min, math.max(min, args.length)); |
| 139 } else { |
| 140 var max = maxArgs(method); |
| 141 args = adjustList(args, min, max >= 0 ? max : args.length); |
| 142 } |
| 143 } |
| 144 if (namedArgs != null) { |
| 145 throw new UnsupportedError( |
| 146 'smoke.static doesn\'t support namedArguments in invoke'); |
| 147 } |
| 148 try { |
| 149 return Function.apply(method, args); |
| 150 } on NoSuchMethodError catch (e) { |
| 151 // TODO(sigmund): consider whether this should just be in a logger or if |
| 152 // we should wrap `e` as a new exception (what's the best way to let users |
| 153 // know about this tentativeError?) |
| 154 if (tentativeError != null) print(tentativeError); |
| 155 rethrow; |
| 156 } |
| 157 } |
| 158 } |
| 159 |
| 160 /// Implements [TypeInspectorService] using a static configuration. |
| 161 class GeneratedTypeInspectorService implements TypeInspectorService { |
| 162 final StaticConfiguration _configuration; |
| 163 |
| 164 Map<Type, Type> get _parents => _configuration.parents; |
| 165 Map<Type, Map<Symbol, Declaration>> get _declarations => |
| 166 _configuration.declarations; |
| 167 bool get _checkedMode => _configuration.checkedMode; |
| 168 |
| 169 GeneratedTypeInspectorService(this._configuration); |
| 170 |
| 171 bool isSubclassOf(Type type, Type supertype) { |
| 172 if (type == supertype || supertype == Object) return true; |
| 173 while (type != Object) { |
| 174 var parentType = _parents[type]; |
| 175 if (parentType == supertype) return true; |
| 176 if (parentType == null) { |
| 177 if (!_checkedMode) return false; |
| 178 throw new MissingCodeException('superclass of "$type" ($parentType)'); |
| 179 } |
| 180 type = parentType; |
| 181 } |
| 182 return false; |
| 183 } |
| 184 |
| 185 bool hasGetter(Type type, Symbol name) { |
| 186 var decl = _findDeclaration(type, name); |
| 187 // No need to check decl.isProperty because methods are also automatically |
| 188 // considered getters (auto-closures). |
| 189 return decl != null && !decl.isStatic; |
| 190 } |
| 191 |
| 192 bool hasSetter(Type type, Symbol name) { |
| 193 var decl = _findDeclaration(type, name); |
| 194 return decl != null && !decl.isMethod && !decl.isFinal && !decl.isStatic; |
| 195 } |
| 196 |
| 197 bool hasInstanceMethod(Type type, Symbol name) { |
| 198 var decl = _findDeclaration(type, name); |
| 199 return decl != null && decl.isMethod && !decl.isStatic; |
| 200 } |
| 201 |
| 202 bool hasStaticMethod(Type type, Symbol name) { |
| 203 final map = _declarations[type]; |
| 204 if (map == null) { |
| 205 if (!_checkedMode) return false; |
| 206 throw new MissingCodeException('declarations for $type'); |
| 207 } |
| 208 final decl = map[name]; |
| 209 return decl != null && decl.isMethod && decl.isStatic; |
| 210 } |
| 211 |
| 212 Declaration getDeclaration(Type type, Symbol name) { |
| 213 var decl = _findDeclaration(type, name); |
| 214 if (decl == null) { |
| 215 if (!_checkedMode) return null; |
| 216 throw new MissingCodeException('declaration for $type.$name'); |
| 217 } |
| 218 return decl; |
| 219 } |
| 220 |
| 221 List<Declaration> query(Type type, QueryOptions options) { |
| 222 var result = []; |
| 223 if (options.includeInherited) { |
| 224 var superclass = _parents[type]; |
| 225 if (superclass == null) { |
| 226 if (_checkedMode) { |
| 227 throw new MissingCodeException('superclass of "$type"'); |
| 228 } |
| 229 } else if (superclass != options.includeUpTo) { |
| 230 result = query(superclass, options); |
| 231 } |
| 232 } |
| 233 var map = _declarations[type]; |
| 234 if (map == null) { |
| 235 if (!_checkedMode) return result; |
| 236 throw new MissingCodeException('declarations for $type'); |
| 237 } |
| 238 for (var decl in map.values) { |
| 239 if (!options.includeFields && decl.isField) continue; |
| 240 if (!options.includeProperties && decl.isProperty) continue; |
| 241 if (options.excludeFinal && decl.isFinal) continue; |
| 242 if (!options.includeMethods && decl.isMethod) continue; |
| 243 if (options.matches != null && !options.matches(decl.name)) continue; |
| 244 if (options.withAnnotations != null && |
| 245 !matchesAnnotation(decl.annotations, options.withAnnotations)) { |
| 246 continue; |
| 247 } |
| 248 result.add(decl); |
| 249 } |
| 250 return result; |
| 251 } |
| 252 |
| 253 Declaration _findDeclaration(Type type, Symbol name) { |
| 254 while (type != Object) { |
| 255 final declarations = _declarations[type]; |
| 256 if (declarations != null) { |
| 257 final declaration = declarations[name]; |
| 258 if (declaration != null) return declaration; |
| 259 } |
| 260 var parentType = _parents[type]; |
| 261 if (parentType == null) { |
| 262 if (!_checkedMode) return null; |
| 263 throw new MissingCodeException('superclass of "$type"'); |
| 264 } |
| 265 type = parentType; |
| 266 } |
| 267 return null; |
| 268 } |
| 269 } |
| 270 |
| 271 /// Implements [SymbolConverterService] using a static configuration. |
| 272 class GeneratedSymbolConverterService implements SymbolConverterService { |
| 273 final StaticConfiguration _configuration; |
| 274 Map<Symbol, String> get _names => _configuration.names; |
| 275 Map<String, Symbol> get _symbols => _configuration._symbols; |
| 276 |
| 277 GeneratedSymbolConverterService(this._configuration); |
| 278 |
| 279 String symbolToName(Symbol symbol) => _names[symbol]; |
| 280 Symbol nameToSymbol(String name) => _symbols[name]; |
| 281 } |
| 282 |
| 283 |
| 284 /// Exception thrown when trynig to access something that should be there, but |
| 285 /// the code generator didn't include it. |
| 286 class MissingCodeException implements Exception { |
| 287 final String description; |
| 288 MissingCodeException(this.description); |
| 289 |
| 290 String toString() => 'Missing $description. ' |
| 291 'Code generation for the smoke package seems incomplete.'; |
| 292 } |
OLD | NEW |