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