OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, 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 library analyzer.task.model; | |
6 | |
7 import 'dart:collection'; | |
8 | |
9 import 'package:analyzer/src/generated/engine.dart' hide AnalysisTask; | |
10 import 'package:analyzer/src/generated/java_engine.dart'; | |
11 import 'package:analyzer/src/generated/source.dart'; | |
12 import 'package:analyzer/src/task/driver.dart'; | |
13 import 'package:analyzer/src/task/model.dart'; | |
14 | |
15 /** | |
16 * A function that converts the given [key] and [value] into a [TaskInput]. | |
17 */ | |
18 typedef TaskInput<E> BinaryFunction<K, V, E>(K key, V value); | |
19 | |
20 /** | |
21 * A function that takes an analysis [context] and an analysis [target] and | |
22 * returns an analysis task. Such functions are passed to a [TaskDescriptor] to | |
23 * be used to create the described task. | |
24 */ | |
25 typedef AnalysisTask BuildTask(AnalysisContext context, AnalysisTarget target); | |
26 | |
27 /** | |
28 * A function that takes the target for which a task will produce results and | |
29 * returns a map from input names to descriptions of the analysis results needed | |
30 * by the task in order for the task to be performed. Such functions are passed | |
31 * to a [TaskDescriptor] to be used to determine the inputs needed by the task. | |
32 */ | |
33 typedef Map<String, TaskInput> CreateTaskInputs(AnalysisTarget target); | |
34 | |
35 /** | |
36 * A function that converts an object of the type [B] into a [TaskInput]. | |
37 * This is used, for example, by a [ListTaskInput] to create task inputs | |
38 * for each value in a list of values. | |
39 */ | |
40 typedef TaskInput<E> UnaryFunction<B, E>(B object); | |
41 | |
42 /** | |
43 * An [AnalysisTarget] wrapper for an [AnalysisContext]. | |
44 */ | |
45 class AnalysisContextTarget implements AnalysisTarget { | |
46 static final AnalysisContextTarget request = new AnalysisContextTarget(null); | |
47 | |
48 final AnalysisContext context; | |
49 | |
50 AnalysisContextTarget(this.context); | |
51 | |
52 @override | |
53 Source get source => null; | |
54 } | |
55 | |
56 /** | |
57 * An object with which an analysis result can be associated. | |
58 * | |
59 * Clients are allowed to subtype this class when creating new kinds of targets. | |
60 * Instances of this type are used in hashed data structures, so subtypes are | |
61 * required to correctly implement [==] and [hashCode]. | |
62 */ | |
63 abstract class AnalysisTarget { | |
64 /** | |
65 * Return the source associated with this target, or `null` if this target is | |
66 * not associated with a source. | |
67 */ | |
68 Source get source; | |
69 } | |
70 | |
71 /** | |
72 * An object used to compute one or more analysis results associated with a | |
73 * single target. | |
74 * | |
75 * Clients are expected to extend this class when creating new tasks. | |
76 */ | |
77 abstract class AnalysisTask { | |
78 /** | |
79 * A table mapping the types of analysis tasks to the number of times each | |
80 * kind of task has been performed. | |
81 */ | |
82 static final Map<Type, int> countMap = new HashMap<Type, int>(); | |
83 | |
84 /** | |
85 * A table mapping the types of analysis tasks to stopwatches used to compute | |
86 * how much time was spent executing each kind of task. | |
87 */ | |
88 static final Map<Type, Stopwatch> stopwatchMap = | |
89 new HashMap<Type, Stopwatch>(); | |
90 | |
91 /** | |
92 * The context in which the task is to be performed. | |
93 */ | |
94 final AnalysisContext context; | |
95 | |
96 /** | |
97 * The target for which result values are being produced. | |
98 */ | |
99 final AnalysisTarget target; | |
100 | |
101 /** | |
102 * A table mapping input names to input values. | |
103 */ | |
104 Map<String, dynamic> inputs; | |
105 | |
106 /** | |
107 * A table mapping result descriptors whose values are produced by this task | |
108 * to the values that were produced. | |
109 */ | |
110 Map<ResultDescriptor, dynamic> outputs = | |
111 new HashMap<ResultDescriptor, dynamic>(); | |
112 | |
113 /** | |
114 * The exception that was thrown while performing this task, or `null` if the | |
115 * task completed successfully. | |
116 */ | |
117 CaughtException caughtException; | |
118 | |
119 /** | |
120 * If a dependency cycle was found while computing the inputs for the task, | |
121 * the set of [WorkItem]s contained in the cycle (if there are overlapping | |
122 * cycles, this is the set of all [WorkItem]s in the entire strongly | |
123 * connected component). Otherwise, `null`. | |
124 */ | |
125 List<WorkItem> dependencyCycle; | |
126 | |
127 /** | |
128 * Initialize a newly created task to perform analysis within the given | |
129 * [context] in order to produce results for the given [target]. | |
130 */ | |
131 AnalysisTask(this.context, this.target); | |
132 | |
133 /** | |
134 * Return a textual description of this task. | |
135 */ | |
136 String get description; | |
137 | |
138 /** | |
139 * Return the descriptor that describes this task. | |
140 */ | |
141 TaskDescriptor get descriptor; | |
142 | |
143 /** | |
144 * Indicates whether the task is capable of handling dependency cycles. A | |
145 * task that overrides this getter to return `true` must be prepared for the | |
146 * possibility that it will be invoked with a non-`null` value of | |
147 * [dependencyCycle], and with not all of its inputs computed. | |
148 */ | |
149 bool get handlesDependencyCycles => false; | |
150 | |
151 /** | |
152 * Return the value of the input with the given [name]. Throw an exception if | |
153 * the input value is not defined. | |
154 */ | |
155 Object getRequiredInput(String name) { | |
156 if (inputs == null || !inputs.containsKey(name)) { | |
157 throw new AnalysisException("Could not $description: missing $name"); | |
158 } | |
159 return inputs[name]; | |
160 } | |
161 | |
162 /** | |
163 * Return the source associated with the target. Throw an exception if | |
164 * the target is not associated with a source. | |
165 */ | |
166 Source getRequiredSource() { | |
167 Source source = target.source; | |
168 if (source == null) { | |
169 throw new AnalysisException("Could not $description: missing source"); | |
170 } | |
171 return source; | |
172 } | |
173 | |
174 /** | |
175 * Perform this analysis task, protected by an exception handler. | |
176 * | |
177 * This method should throw an [AnalysisException] if an exception occurs | |
178 * while performing the task. If other kinds of exceptions are thrown they | |
179 * will be wrapped in an [AnalysisException]. | |
180 * | |
181 * If no exception is thrown, this method must fully populate the [outputs] | |
182 * map (have a key/value pair for each result that this task is expected to | |
183 * produce). | |
184 */ | |
185 void internalPerform(); | |
186 | |
187 /** | |
188 * Perform this analysis task. When this method returns, either the [outputs] | |
189 * map should be fully populated (have a key/value pair for each result that | |
190 * this task is expected to produce) or the [caughtException] should be set. | |
191 * | |
192 * Clients should not override this method. | |
193 */ | |
194 void perform() { | |
195 try { | |
196 _safelyPerform(); | |
197 } on AnalysisException catch (exception, stackTrace) { | |
198 caughtException = new CaughtException(exception, stackTrace); | |
199 AnalysisEngine.instance.logger.logInformation( | |
200 "Task failed: ${description}", caughtException); | |
201 } | |
202 } | |
203 | |
204 @override | |
205 String toString() => description; | |
206 | |
207 /** | |
208 * Perform this analysis task, ensuring that all exceptions are wrapped in an | |
209 * [AnalysisException]. | |
210 * | |
211 * Clients should not override this method. | |
212 */ | |
213 void _safelyPerform() { | |
214 try { | |
215 // | |
216 // Report that this task is being performed. | |
217 // | |
218 String contextName = context.name; | |
219 if (contextName == null) { | |
220 contextName = 'unnamed'; | |
221 } | |
222 AnalysisEngine.instance.instrumentationService.logAnalysisTask( | |
223 contextName, this); | |
224 // | |
225 // Gather statistics on the performance of the task. | |
226 // | |
227 int count = countMap[runtimeType]; | |
228 countMap[runtimeType] = count == null ? 1 : count + 1; | |
229 Stopwatch stopwatch = stopwatchMap[runtimeType]; | |
230 if (stopwatch == null) { | |
231 stopwatch = new Stopwatch(); | |
232 stopwatchMap[runtimeType] = stopwatch; | |
233 } | |
234 stopwatch.start(); | |
235 // | |
236 // Actually perform the task. | |
237 // | |
238 try { | |
239 if (dependencyCycle != null && !handlesDependencyCycles) { | |
240 throw new InfiniteTaskLoopException(this, dependencyCycle); | |
241 } | |
242 internalPerform(); | |
243 } finally { | |
244 stopwatch.stop(); | |
245 } | |
246 } on AnalysisException { | |
247 rethrow; | |
248 } catch (exception, stackTrace) { | |
249 throw new AnalysisException( | |
250 'Unexpected exception while performing $description', | |
251 new CaughtException(exception, stackTrace)); | |
252 } | |
253 } | |
254 } | |
255 | |
256 /** | |
257 * A description of a [List]-based analysis result that can be computed by an | |
258 * [AnalysisTask]. | |
259 * | |
260 * Clients are not expected to subtype this class. | |
261 */ | |
262 abstract class ListResultDescriptor<E> implements ResultDescriptor<List<E>> { | |
263 /** | |
264 * Initialize a newly created analysis result to have the given [name] and | |
265 * [defaultValue]. If a [cachingPolicy] is provided, it will control how long | |
266 * values associated with this result will remain in the cache. | |
267 */ | |
268 factory ListResultDescriptor(String name, List<E> defaultValue, | |
269 {ResultCachingPolicy<List<E>> cachingPolicy}) = ListResultDescriptorImpl<E
>; | |
270 | |
271 @override | |
272 ListTaskInput<E> of(AnalysisTarget target); | |
273 } | |
274 | |
275 /** | |
276 * A description of an input to an [AnalysisTask] that can be used to compute | |
277 * that input. | |
278 * | |
279 * Clients are not expected to subtype this class. | |
280 */ | |
281 abstract class ListTaskInput<E> extends TaskInput<List<E>> { | |
282 /** | |
283 * Return a task input that can be used to compute a list whose elements are | |
284 * the result of passing the elements of this input to the [mapper] function. | |
285 */ | |
286 ListTaskInput /*<V>*/ toList(UnaryFunction<E, dynamic /*<V>*/ > mapper); | |
287 | |
288 /** | |
289 * Return a task input that can be used to compute a list whose elements are | |
290 * [valueResult]'s associated with those elements. | |
291 */ | |
292 ListTaskInput /*<V>*/ toListOf(ResultDescriptor /*<V>*/ valueResult); | |
293 | |
294 /** | |
295 * Return a task input that can be used to compute a map whose keys are the | |
296 * elements of this input and whose values are the result of passing the | |
297 * corresponding key to the [mapper] function. | |
298 */ | |
299 MapTaskInput<E, dynamic /*V*/ > toMap( | |
300 UnaryFunction<E, dynamic /*<V>*/ > mapper); | |
301 | |
302 /** | |
303 * Return a task input that can be used to compute a map whose keys are the | |
304 * elements of this input and whose values are the [valueResult]'s associated | |
305 * with those elements. | |
306 */ | |
307 MapTaskInput<AnalysisTarget, dynamic /*V*/ > toMapOf( | |
308 ResultDescriptor /*<V>*/ valueResult); | |
309 } | |
310 | |
311 /** | |
312 * A description of an input with a [Map] based values. | |
313 * | |
314 * Clients are not expected to subtype this class. | |
315 */ | |
316 abstract class MapTaskInput<K, V> extends TaskInput<Map<K, V>> { | |
317 /** | |
318 * [V] must be a [List]. | |
319 * Return a task input that can be used to compute a list whose elements are | |
320 * the result of passing keys [K] and the corresponding elements of [V] to | |
321 * the [mapper] function. | |
322 */ | |
323 TaskInput<List /*<E>*/ > toFlattenList( | |
324 BinaryFunction<K, dynamic /*element of V*/, dynamic /*<E>*/ > mapper); | |
325 } | |
326 | |
327 /** | |
328 * A policy object that can compute sizes of results and provide the maximum | |
329 * active and idle sizes that can be kept in the cache. | |
330 * | |
331 * All the [ResultDescriptor]s with the same [ResultCachingPolicy] instance | |
332 * share the same total size in a cache. | |
333 */ | |
334 abstract class ResultCachingPolicy<T> { | |
335 /** | |
336 * Return the maximum total size of results that can be kept in the cache | |
337 * while analysis is in progress. | |
338 */ | |
339 int get maxActiveSize; | |
340 | |
341 /** | |
342 * Return the maximum total size of results that can be kept in the cache | |
343 * while analysis is idle. | |
344 */ | |
345 int get maxIdleSize; | |
346 | |
347 /** | |
348 * Return the size of the given [object]. | |
349 */ | |
350 int measure(T object); | |
351 } | |
352 | |
353 /** | |
354 * A description of an analysis result that can be computed by an [AnalysisTask]
. | |
355 * | |
356 * Clients are not expected to subtype this class. | |
357 */ | |
358 abstract class ResultDescriptor<V> { | |
359 /** | |
360 * Initialize a newly created analysis result to have the given [name] and | |
361 * [defaultValue]. | |
362 * | |
363 * The given [cachingPolicy] is used to limit the total size of results | |
364 * described by this descriptor. If no policy is specified, the results are | |
365 * never evicted from the cache, and removed only when they are invalidated. | |
366 */ | |
367 factory ResultDescriptor(String name, V defaultValue, | |
368 {ResultCachingPolicy<V> cachingPolicy}) = ResultDescriptorImpl; | |
369 | |
370 /** | |
371 * Return the caching policy for results described by this descriptor. | |
372 */ | |
373 ResultCachingPolicy<V> get cachingPolicy; | |
374 | |
375 /** | |
376 * Return the default value for results described by this descriptor. | |
377 */ | |
378 V get defaultValue; | |
379 | |
380 /** | |
381 * Return the name of this descriptor. | |
382 */ | |
383 String get name; | |
384 | |
385 /** | |
386 * Return a task input that can be used to compute this result for the given | |
387 * [target]. | |
388 */ | |
389 TaskInput<V> of(AnalysisTarget target); | |
390 } | |
391 | |
392 /** | |
393 * A description of an [AnalysisTask]. | |
394 */ | |
395 abstract class TaskDescriptor { | |
396 /** | |
397 * Initialize a newly created task descriptor to have the given [name] and to | |
398 * describe a task that takes the inputs built using the given [inputBuilder], | |
399 * and produces the given [results]. The [buildTask] will be used to create | |
400 * the instance of [AnalysisTask] thusly described. | |
401 */ | |
402 factory TaskDescriptor(String name, BuildTask buildTask, | |
403 CreateTaskInputs inputBuilder, | |
404 List<ResultDescriptor> results) = TaskDescriptorImpl; | |
405 | |
406 /** | |
407 * Return the builder used to build the inputs to the task. | |
408 */ | |
409 CreateTaskInputs get createTaskInputs; | |
410 | |
411 /** | |
412 * Return the name of the task being described. | |
413 */ | |
414 String get name; | |
415 | |
416 /** | |
417 * Return a list of the analysis results that will be computed by this task. | |
418 */ | |
419 List<ResultDescriptor> get results; | |
420 | |
421 /** | |
422 * Create and return a task that is described by this descriptor that can be | |
423 * used to compute results based on the given [inputs]. | |
424 */ | |
425 AnalysisTask createTask(AnalysisContext context, AnalysisTarget target, | |
426 Map<String, dynamic> inputs); | |
427 } | |
428 | |
429 /** | |
430 * A description of an input to an [AnalysisTask] that can be used to compute | |
431 * that input. | |
432 * | |
433 * Clients are not expected to subtype this class. | |
434 */ | |
435 abstract class TaskInput<V> { | |
436 /** | |
437 * Create and return a builder that can be used to build this task input. | |
438 */ | |
439 TaskInputBuilder<V> createBuilder(); | |
440 | |
441 /** | |
442 * Return a task input that can be used to compute a list whose elements are | |
443 * the result of passing the result of this input to the [mapper] function. | |
444 */ | |
445 ListTaskInput /*<E>*/ mappedToList(List /*<E>*/ mapper(V value)); | |
446 } | |
447 | |
448 /** | |
449 * An object used to build the value associated with a single [TaskInput]. | |
450 * | |
451 * All builders work by requesting one or more results (each result being | |
452 * associated with a target). The interaction pattern is modeled after the class | |
453 * [Iterator], in which the method [moveNext] is invoked to move from one result | |
454 * request to the next. The getters [currentResult] and [currentTarget] are used | |
455 * to get the result and target of the current request. The value of the result | |
456 * must be supplied using the [currentValue] setter before [moveNext] can be | |
457 * invoked to move to the next request. When [moveNext] returns `false`, | |
458 * indicating that there are no more requests, the method [inputValue] can be | |
459 * used to access the value of the input that was built. | |
460 * | |
461 * Clients are not expected to subtype this class. | |
462 */ | |
463 abstract class TaskInputBuilder<V> { | |
464 /** | |
465 * Return the result that needs to be computed, or `null` if [moveNext] has | |
466 * not been invoked or if the last invocation of [moveNext] returned `false`. | |
467 */ | |
468 ResultDescriptor get currentResult; | |
469 | |
470 /** | |
471 * Return the target for which the result needs to be computed, or `null` if | |
472 * [moveNext] has not been invoked or if the last invocation of [moveNext] | |
473 * returned `false`. | |
474 */ | |
475 AnalysisTarget get currentTarget; | |
476 | |
477 /** | |
478 * Set the [value] that was computed for the current result. | |
479 * | |
480 * Throws a [StateError] if [moveNext] has not been invoked or if the last | |
481 * invocation of [moveNext] returned `false`. | |
482 */ | |
483 void set currentValue(Object value); | |
484 | |
485 /** | |
486 * Return the [value] that was computed by this builder. | |
487 * | |
488 * Throws a [StateError] if [moveNext] has not been invoked or if the last | |
489 * invocation of [moveNext] returned `true`. | |
490 * | |
491 * Returns `null` if no value could be computed due to a circular dependency. | |
492 */ | |
493 V get inputValue; | |
494 | |
495 /** | |
496 * Record that no value is available for the current result, due to a | |
497 * circular dependency. | |
498 * | |
499 * Throws a [StateError] if [moveNext] has not been invoked or if the last | |
500 * invocation of [moveNext] returned `false`. | |
501 */ | |
502 void currentValueNotAvailable(); | |
503 | |
504 /** | |
505 * Move to the next result that needs to be computed in order to build the | |
506 * inputs for a task. Return `true` if there is another result that needs to | |
507 * be computed, or `false` if the inputs have been computed. | |
508 * | |
509 * It is safe to invoke [moveNext] after it has returned `false`. In this case | |
510 * [moveNext] has no effect and will again return `false`. | |
511 * | |
512 * Throws a [StateError] if the value of the current result has not been | |
513 * provided using [currentValue]. | |
514 */ | |
515 bool moveNext(); | |
516 } | |
OLD | NEW |