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