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 |