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 (e) { | |
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 |