| 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.src.context.cache; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 | |
| 10 import 'package:analyzer/src/generated/engine.dart' | |
| 11 show AnalysisEngine, CacheState, InternalAnalysisContext, RetentionPriority; | |
| 12 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 13 import 'package:analyzer/src/generated/source.dart'; | |
| 14 import 'package:analyzer/src/generated/utilities_collection.dart'; | |
| 15 import 'package:analyzer/src/generated/utilities_general.dart'; | |
| 16 import 'package:analyzer/src/task/model.dart'; | |
| 17 import 'package:analyzer/task/model.dart'; | |
| 18 | |
| 19 /** | |
| 20 * Return `true` if the given [target] is a priority one. | |
| 21 */ | |
| 22 typedef bool IsPriorityAnalysisTarget(AnalysisTarget target); | |
| 23 | |
| 24 /** | |
| 25 * An LRU cache of results produced by analysis. | |
| 26 */ | |
| 27 class AnalysisCache { | |
| 28 /** | |
| 29 * A flag used to control whether trace information should be produced when | |
| 30 * the content of the cache is modified. | |
| 31 */ | |
| 32 static bool _TRACE_CHANGES = false; | |
| 33 | |
| 34 /** | |
| 35 * An array containing the partitions of which this cache is comprised. | |
| 36 */ | |
| 37 final List<CachePartition> _partitions; | |
| 38 | |
| 39 /** | |
| 40 * The [StreamController] reporting [InvalidatedResult]s. | |
| 41 */ | |
| 42 final StreamController<InvalidatedResult> _onResultInvalidated = | |
| 43 new StreamController<InvalidatedResult>.broadcast(sync: true); | |
| 44 | |
| 45 /** | |
| 46 * Initialize a newly created cache to have the given [partitions]. The | |
| 47 * partitions will be searched in the order in which they appear in the array, | |
| 48 * so the most specific partition (usually an [SdkCachePartition]) should be | |
| 49 * first and the most general (usually a [UniversalCachePartition]) last. | |
| 50 */ | |
| 51 AnalysisCache(this._partitions) { | |
| 52 for (CachePartition partition in _partitions) { | |
| 53 partition.onResultInvalidated.listen((InvalidatedResult event) { | |
| 54 _onResultInvalidated.add(event); | |
| 55 }); | |
| 56 } | |
| 57 } | |
| 58 | |
| 59 /** | |
| 60 * Return the stream that is notified when a value is invalidated. | |
| 61 */ | |
| 62 Stream<InvalidatedResult> get onResultInvalidated => | |
| 63 _onResultInvalidated.stream; | |
| 64 | |
| 65 // TODO(brianwilkerson) Implement or delete this. | |
| 66 // /** | |
| 67 // * Return information about each of the partitions in this cache. | |
| 68 // */ | |
| 69 // List<AnalysisContextStatistics_PartitionData> get partitionData { | |
| 70 // int count = _partitions.length; | |
| 71 // List<AnalysisContextStatistics_PartitionData> data = | |
| 72 // new List<AnalysisContextStatistics_PartitionData>(count); | |
| 73 // for (int i = 0; i < count; i++) { | |
| 74 // CachePartition partition = _partitions[i]; | |
| 75 // data[i] = new AnalysisContextStatisticsImpl_PartitionDataImpl( | |
| 76 // partition.astSize, | |
| 77 // partition.map.length); | |
| 78 // } | |
| 79 // return data; | |
| 80 // } | |
| 81 | |
| 82 /** | |
| 83 * Return an iterator returning all of the [Source] targets. | |
| 84 */ | |
| 85 Iterable<Source> get sources { | |
| 86 return _partitions | |
| 87 .map((CachePartition partition) => partition._sources) | |
| 88 .expand((Iterable<Source> sources) => sources); | |
| 89 } | |
| 90 | |
| 91 /** | |
| 92 * Return the entry associated with the given [target]. | |
| 93 */ | |
| 94 CacheEntry get(AnalysisTarget target) { | |
| 95 int count = _partitions.length; | |
| 96 for (int i = 0; i < count; i++) { | |
| 97 CachePartition partition = _partitions[i]; | |
| 98 if (partition.isResponsibleFor(target)) { | |
| 99 return partition.get(target); | |
| 100 } | |
| 101 } | |
| 102 // | |
| 103 // We should never get to this point because the last partition should | |
| 104 // always be a universal partition, except in the case of the SDK context, | |
| 105 // in which case the target should always be part of the SDK. | |
| 106 // | |
| 107 return null; | |
| 108 } | |
| 109 | |
| 110 /** | |
| 111 * Return the context to which the given [target] was explicitly added. | |
| 112 */ | |
| 113 InternalAnalysisContext getContextFor(AnalysisTarget target) { | |
| 114 int count = _partitions.length; | |
| 115 for (int i = 0; i < count; i++) { | |
| 116 CachePartition partition = _partitions[i]; | |
| 117 if (partition.isResponsibleFor(target)) { | |
| 118 return partition.context; | |
| 119 } | |
| 120 } | |
| 121 // | |
| 122 // We should never get to this point because the last partition should | |
| 123 // always be a universal partition, except in the case of the SDK context, | |
| 124 // in which case the target should always be part of the SDK. | |
| 125 // | |
| 126 // TODO(brianwilkerson) Throw an exception here. | |
| 127 AnalysisEngine.instance.logger.logInformation( | |
| 128 'Could not find context for $target', | |
| 129 new CaughtException(new AnalysisException(), null)); | |
| 130 return null; | |
| 131 } | |
| 132 | |
| 133 /** | |
| 134 * Return [Source]s whose full path is equal to the given [path]. | |
| 135 * Maybe empty, but not `null`. | |
| 136 */ | |
| 137 List<Source> getSourcesWithFullName(String path) { | |
| 138 List<Source> sources = <Source>[]; | |
| 139 for (CachePartition partition in _partitions) { | |
| 140 List<Source> partitionSources = partition.getSourcesWithFullName(path); | |
| 141 sources.addAll(partitionSources); | |
| 142 } | |
| 143 return sources; | |
| 144 } | |
| 145 | |
| 146 /** | |
| 147 * Return the state of the given [result] for the given [target]. | |
| 148 * | |
| 149 * It does not update the cache, if the corresponding [CacheEntry] does not | |
| 150 * exist, then [CacheState.INVALID] is returned. | |
| 151 */ | |
| 152 CacheState getState(AnalysisTarget target, ResultDescriptor result) { | |
| 153 CacheEntry entry = get(target); | |
| 154 if (entry == null) { | |
| 155 return CacheState.INVALID; | |
| 156 } | |
| 157 return entry.getState(result); | |
| 158 } | |
| 159 | |
| 160 /** | |
| 161 * Return the value of the given [result] for the given [target]. | |
| 162 * | |
| 163 * It does not update the cache, if the corresponding [CacheEntry] does not | |
| 164 * exist, then the default value is returned. | |
| 165 */ | |
| 166 Object getValue(AnalysisTarget target, ResultDescriptor result) { | |
| 167 CacheEntry entry = get(target); | |
| 168 if (entry == null) { | |
| 169 return result.defaultValue; | |
| 170 } | |
| 171 return entry.getValue(result); | |
| 172 } | |
| 173 | |
| 174 /** | |
| 175 * Return an iterator returning all of the map entries mapping targets to | |
| 176 * cache entries. | |
| 177 */ | |
| 178 MapIterator<AnalysisTarget, CacheEntry> iterator() { | |
| 179 int count = _partitions.length; | |
| 180 List<Map<AnalysisTarget, CacheEntry>> maps = | |
| 181 new List<Map<AnalysisTarget, CacheEntry>>(count); | |
| 182 for (int i = 0; i < count; i++) { | |
| 183 maps[i] = _partitions[i].map; | |
| 184 } | |
| 185 return new MultipleMapIterator<AnalysisTarget, CacheEntry>(maps); | |
| 186 } | |
| 187 | |
| 188 /** | |
| 189 * Puts the given [entry] into the cache. | |
| 190 */ | |
| 191 void put(CacheEntry entry) { | |
| 192 AnalysisTarget target = entry.target; | |
| 193 entry.fixExceptionState(); | |
| 194 int count = _partitions.length; | |
| 195 for (int i = 0; i < count; i++) { | |
| 196 CachePartition partition = _partitions[i]; | |
| 197 if (partition.isResponsibleFor(target)) { | |
| 198 if (_TRACE_CHANGES) { | |
| 199 CacheEntry oldEntry = partition.get(target); | |
| 200 if (oldEntry == null) { | |
| 201 AnalysisEngine.instance.logger | |
| 202 .logInformation('Added a cache entry for $target.'); | |
| 203 } else { | |
| 204 AnalysisEngine.instance.logger | |
| 205 .logInformation('Modified the cache entry for $target.'); | |
| 206 // 'Diff = ${entry.getDiff(oldEntry)}'); | |
| 207 } | |
| 208 } | |
| 209 partition.put(entry); | |
| 210 return; | |
| 211 } | |
| 212 } | |
| 213 // TODO(brianwilkerson) Handle the case where no partition was found, | |
| 214 // possibly by throwing an exception. | |
| 215 } | |
| 216 | |
| 217 /** | |
| 218 * Remove all information related to the given [target] from this cache. | |
| 219 */ | |
| 220 void remove(AnalysisTarget target) { | |
| 221 int count = _partitions.length; | |
| 222 for (int i = 0; i < count; i++) { | |
| 223 CachePartition partition = _partitions[i]; | |
| 224 if (partition.isResponsibleFor(target)) { | |
| 225 if (_TRACE_CHANGES) { | |
| 226 AnalysisEngine.instance.logger | |
| 227 .logInformation('Removed the cache entry for $target.'); | |
| 228 } | |
| 229 partition.remove(target); | |
| 230 return; | |
| 231 } | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 /** | |
| 236 * Return the number of targets that are mapped to cache entries. | |
| 237 */ | |
| 238 int size() { | |
| 239 int size = 0; | |
| 240 int count = _partitions.length; | |
| 241 for (int i = 0; i < count; i++) { | |
| 242 size += _partitions[i].size(); | |
| 243 } | |
| 244 return size; | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 /** | |
| 249 * The information cached by an analysis context about an individual target. | |
| 250 */ | |
| 251 class CacheEntry { | |
| 252 /** | |
| 253 * The index of the flag indicating whether the source was explicitly added to | |
| 254 * the context or whether the source was implicitly added because it was | |
| 255 * referenced by another source. | |
| 256 */ | |
| 257 static int _EXPLICITLY_ADDED_FLAG = 0; | |
| 258 | |
| 259 /** | |
| 260 * The target this entry is about. | |
| 261 */ | |
| 262 final AnalysisTarget target; | |
| 263 | |
| 264 /** | |
| 265 * The partition that is responsible for this entry. | |
| 266 */ | |
| 267 CachePartition _partition; | |
| 268 | |
| 269 /** | |
| 270 * The most recent time at which the state of the target matched the state | |
| 271 * represented by this entry, `-1` if the target does not exist. | |
| 272 */ | |
| 273 int modificationTime = -1; | |
| 274 | |
| 275 /** | |
| 276 * The exception that caused one or more values to have a state of | |
| 277 * [CacheState.ERROR]. | |
| 278 */ | |
| 279 CaughtException _exception; | |
| 280 | |
| 281 /** | |
| 282 * A bit-encoding of boolean flags associated with this entry's target. | |
| 283 */ | |
| 284 int _flags = 0; | |
| 285 | |
| 286 /** | |
| 287 * A table mapping result descriptors to the cached values of those results. | |
| 288 */ | |
| 289 Map<ResultDescriptor, ResultData> _resultMap = | |
| 290 new HashMap<ResultDescriptor, ResultData>(); | |
| 291 | |
| 292 CacheEntry(this.target); | |
| 293 | |
| 294 /** | |
| 295 * The exception that caused one or more values to have a state of | |
| 296 * [CacheState.ERROR]. | |
| 297 */ | |
| 298 CaughtException get exception => _exception; | |
| 299 | |
| 300 /** | |
| 301 * Return `true` if the source was explicitly added to the context or `false` | |
| 302 * if the source was implicitly added because it was referenced by another | |
| 303 * source. | |
| 304 */ | |
| 305 bool get explicitlyAdded => _getFlag(_EXPLICITLY_ADDED_FLAG); | |
| 306 | |
| 307 /** | |
| 308 * Set whether the source was explicitly added to the context to match the | |
| 309 * [explicitlyAdded] flag. | |
| 310 */ | |
| 311 void set explicitlyAdded(bool explicitlyAdded) { | |
| 312 _setFlag(_EXPLICITLY_ADDED_FLAG, explicitlyAdded); | |
| 313 } | |
| 314 | |
| 315 /** | |
| 316 * Return a list of result descriptors for results whose state is not | |
| 317 * [CacheState.INVALID]. | |
| 318 */ | |
| 319 List<ResultDescriptor> get nonInvalidResults => _resultMap.keys.toList(); | |
| 320 | |
| 321 /** | |
| 322 * Notifies the entry that the client is going to stop using it. | |
| 323 */ | |
| 324 void dispose() { | |
| 325 _resultMap.forEach((descriptor, data) { | |
| 326 TargetedResult result = new TargetedResult(target, descriptor); | |
| 327 for (TargetedResult dependedOnResult in data.dependedOnResults) { | |
| 328 ResultData dependedOnData = _partition._getDataFor(dependedOnResult); | |
| 329 if (dependedOnData != null) { | |
| 330 dependedOnData.dependentResults.remove(result); | |
| 331 } | |
| 332 } | |
| 333 }); | |
| 334 _resultMap.clear(); | |
| 335 } | |
| 336 | |
| 337 /** | |
| 338 * Fix the state of the [exception] to match the current state of the entry. | |
| 339 */ | |
| 340 void fixExceptionState() { | |
| 341 if (!hasErrorState()) { | |
| 342 _exception = null; | |
| 343 } | |
| 344 } | |
| 345 | |
| 346 /** | |
| 347 * Look up the [ResultData] of [descriptor], or add a new one if it isn't | |
| 348 * there. | |
| 349 */ | |
| 350 ResultData getResultData(ResultDescriptor descriptor) { | |
| 351 return _resultMap.putIfAbsent(descriptor, () => new ResultData(descriptor)); | |
| 352 } | |
| 353 | |
| 354 /** | |
| 355 * Return the state of the result represented by the given [descriptor]. | |
| 356 */ | |
| 357 CacheState getState(ResultDescriptor descriptor) { | |
| 358 ResultData data = _resultMap[descriptor]; | |
| 359 if (data == null) { | |
| 360 return CacheState.INVALID; | |
| 361 } | |
| 362 return data.state; | |
| 363 } | |
| 364 | |
| 365 /** | |
| 366 * Return the value of the result represented by the given [descriptor], or | |
| 367 * the default value for the result if this entry does not have a valid value. | |
| 368 */ | |
| 369 /*<V>*/ dynamic /*V*/ getValue(ResultDescriptor /*<V>*/ descriptor) { | |
| 370 ResultData data = _resultMap[descriptor]; | |
| 371 if (data == null) { | |
| 372 return descriptor.defaultValue; | |
| 373 } | |
| 374 if (_partition != null) { | |
| 375 _partition.resultAccessed(target, descriptor); | |
| 376 } | |
| 377 return data.value; | |
| 378 } | |
| 379 | |
| 380 /** | |
| 381 * Return `true` if the state of any data value is [CacheState.ERROR]. | |
| 382 */ | |
| 383 bool hasErrorState() { | |
| 384 for (ResultData data in _resultMap.values) { | |
| 385 if (data.state == CacheState.ERROR) { | |
| 386 return true; | |
| 387 } | |
| 388 } | |
| 389 return false; | |
| 390 } | |
| 391 | |
| 392 /** | |
| 393 * Invalidate all of the information associated with this entry's target. | |
| 394 */ | |
| 395 void invalidateAllInformation() { | |
| 396 _resultMap.clear(); | |
| 397 _exception = null; | |
| 398 } | |
| 399 | |
| 400 /** | |
| 401 * Return `true` if the state of the result represented by the given | |
| 402 * [descriptor] is [CacheState.INVALID]. | |
| 403 */ | |
| 404 bool isInvalid(ResultDescriptor descriptor) => | |
| 405 getState(descriptor) == CacheState.INVALID; | |
| 406 | |
| 407 /** | |
| 408 * Return `true` if the state of the result represented by the given | |
| 409 * [descriptor] is [CacheState.VALID]. | |
| 410 */ | |
| 411 bool isValid(ResultDescriptor descriptor) => | |
| 412 getState(descriptor) == CacheState.VALID; | |
| 413 | |
| 414 /** | |
| 415 * For each of the given [descriptors], set their states to | |
| 416 * [CacheState.ERROR], their values to the corresponding default values, and | |
| 417 * remember the [exception] that caused this state. | |
| 418 */ | |
| 419 void setErrorState( | |
| 420 CaughtException exception, List<ResultDescriptor> descriptors) { | |
| 421 if (descriptors == null || descriptors.isEmpty) { | |
| 422 throw new ArgumentError('at least one descriptor is expected'); | |
| 423 } | |
| 424 if (exception == null) { | |
| 425 throw new ArgumentError('an exception is expected'); | |
| 426 } | |
| 427 this._exception = exception; | |
| 428 for (ResultDescriptor descriptor in descriptors) { | |
| 429 _setErrorState(descriptor, exception); | |
| 430 } | |
| 431 } | |
| 432 | |
| 433 /** | |
| 434 * Set the state of the result represented by the given [descriptor] to the | |
| 435 * given [state]. | |
| 436 */ | |
| 437 void setState(ResultDescriptor descriptor, CacheState state, {Delta delta}) { | |
| 438 if (state == CacheState.ERROR) { | |
| 439 throw new ArgumentError('use setErrorState() to set the state to ERROR'); | |
| 440 } | |
| 441 if (state == CacheState.VALID) { | |
| 442 throw new ArgumentError('use setValue() to set the state to VALID'); | |
| 443 } | |
| 444 _validateStateChange(descriptor, state); | |
| 445 if (state == CacheState.INVALID) { | |
| 446 ResultData data = _resultMap[descriptor]; | |
| 447 if (data != null) { | |
| 448 _invalidate(descriptor, delta); | |
| 449 } | |
| 450 } else { | |
| 451 ResultData data = getResultData(descriptor); | |
| 452 data.state = state; | |
| 453 if (state != CacheState.IN_PROCESS) { | |
| 454 // | |
| 455 // If the state is in-process, we can leave the current value in the | |
| 456 // cache for any 'get' methods to access. | |
| 457 // | |
| 458 data.value = descriptor.defaultValue; | |
| 459 } | |
| 460 } | |
| 461 } | |
| 462 | |
| 463 /** | |
| 464 * Set the value of the result represented by the given [descriptor] to the | |
| 465 * given [value]. | |
| 466 */ | |
| 467 /*<V>*/ void setValue(ResultDescriptor /*<V>*/ descriptor, dynamic /*V*/ | |
| 468 value, List<TargetedResult> dependedOn) { | |
| 469 // { | |
| 470 // String valueStr = '$value'; | |
| 471 // if (valueStr.length > 20) { | |
| 472 // valueStr = valueStr.substring(0, 20) + '...'; | |
| 473 // } | |
| 474 // valueStr = valueStr.replaceAll('\n', '\\n'); | |
| 475 // print( | |
| 476 // 'setValue $descriptor for $target value=$valueStr deps=$dependedOn')
; | |
| 477 // } | |
| 478 _validateStateChange(descriptor, CacheState.VALID); | |
| 479 TargetedResult thisResult = new TargetedResult(target, descriptor); | |
| 480 if (_partition != null) { | |
| 481 _partition.resultStored(thisResult, value); | |
| 482 } | |
| 483 ResultData data = getResultData(descriptor); | |
| 484 _setDependedOnResults(data, thisResult, dependedOn); | |
| 485 data.state = CacheState.VALID; | |
| 486 data.value = value == null ? descriptor.defaultValue : value; | |
| 487 } | |
| 488 | |
| 489 /** | |
| 490 * Set the value of the result represented by the given [descriptor] to the | |
| 491 * given [value], keep its dependency, invalidate all the dependent result. | |
| 492 */ | |
| 493 void setValueIncremental(ResultDescriptor descriptor, dynamic value) { | |
| 494 ResultData data = getResultData(descriptor); | |
| 495 List<TargetedResult> dependedOn = data.dependedOnResults; | |
| 496 _invalidate(descriptor, null); | |
| 497 setValue(descriptor, value, dependedOn); | |
| 498 } | |
| 499 | |
| 500 @override | |
| 501 String toString() { | |
| 502 StringBuffer buffer = new StringBuffer(); | |
| 503 _writeOn(buffer); | |
| 504 return buffer.toString(); | |
| 505 } | |
| 506 | |
| 507 /** | |
| 508 * Return the value of the flag with the given [index]. | |
| 509 */ | |
| 510 bool _getFlag(int index) => BooleanArray.get(_flags, index); | |
| 511 | |
| 512 /** | |
| 513 * Invalidate the result represented by the given [descriptor] and propagate | |
| 514 * invalidation to other results that depend on it. | |
| 515 */ | |
| 516 void _invalidate(ResultDescriptor descriptor, Delta delta) { | |
| 517 DeltaResult deltaResult = null; | |
| 518 if (delta != null) { | |
| 519 deltaResult = delta.validate(_partition.context, target, descriptor); | |
| 520 if (deltaResult == DeltaResult.STOP) { | |
| 521 // print('not-invalidate $descriptor for $target'); | |
| 522 return; | |
| 523 } | |
| 524 } | |
| 525 // print('invalidate $descriptor for $target'); | |
| 526 ResultData thisData; | |
| 527 if (deltaResult == null || deltaResult == DeltaResult.INVALIDATE) { | |
| 528 thisData = _resultMap.remove(descriptor); | |
| 529 } | |
| 530 if (deltaResult == DeltaResult.KEEP_CONTINUE) { | |
| 531 thisData = _resultMap[descriptor]; | |
| 532 } | |
| 533 if (thisData == null) { | |
| 534 return; | |
| 535 } | |
| 536 // Stop depending on other results. | |
| 537 TargetedResult thisResult = new TargetedResult(target, descriptor); | |
| 538 for (TargetedResult dependedOnResult in thisData.dependedOnResults) { | |
| 539 ResultData data = _partition._getDataFor(dependedOnResult); | |
| 540 if (data != null) { | |
| 541 data.dependentResults.remove(thisResult); | |
| 542 } | |
| 543 } | |
| 544 // Invalidate results that depend on this result. | |
| 545 List<TargetedResult> dependentResults = thisData.dependentResults.toList(); | |
| 546 for (TargetedResult dependentResult in dependentResults) { | |
| 547 CacheEntry entry = _partition.get(dependentResult.target); | |
| 548 if (entry != null) { | |
| 549 entry._invalidate(dependentResult.result, delta); | |
| 550 } | |
| 551 } | |
| 552 // If empty, remove the entry altogether. | |
| 553 if (_resultMap.isEmpty) { | |
| 554 _partition._targetMap.remove(target); | |
| 555 _partition._removeIfSource(target); | |
| 556 } | |
| 557 // Notify controller. | |
| 558 _partition._onResultInvalidated | |
| 559 .add(new InvalidatedResult(this, descriptor)); | |
| 560 } | |
| 561 | |
| 562 /** | |
| 563 * Invalidates all the results of this entry, with propagation. | |
| 564 */ | |
| 565 void _invalidateAll() { | |
| 566 List<ResultDescriptor> results = _resultMap.keys.toList(); | |
| 567 for (ResultDescriptor result in results) { | |
| 568 _invalidate(result, null); | |
| 569 } | |
| 570 } | |
| 571 | |
| 572 /** | |
| 573 * Set the [dependedOn] on which this result depends. | |
| 574 */ | |
| 575 void _setDependedOnResults(ResultData thisData, TargetedResult thisResult, | |
| 576 List<TargetedResult> dependedOn) { | |
| 577 thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) { | |
| 578 ResultData data = _partition._getDataFor(dependedOnResult); | |
| 579 if (data != null) { | |
| 580 data.dependentResults.remove(thisResult); | |
| 581 } | |
| 582 }); | |
| 583 thisData.dependedOnResults = dependedOn; | |
| 584 thisData.dependedOnResults.forEach((TargetedResult dependedOnResult) { | |
| 585 ResultData data = _partition._getDataFor(dependedOnResult); | |
| 586 if (data != null) { | |
| 587 data.dependentResults.add(thisResult); | |
| 588 } | |
| 589 }); | |
| 590 } | |
| 591 | |
| 592 /** | |
| 593 * Set states of the given and dependent results to [CacheState.ERROR] and | |
| 594 * their values to the corresponding default values | |
| 595 */ | |
| 596 void _setErrorState(ResultDescriptor descriptor, CaughtException exception) { | |
| 597 ResultData thisData = getResultData(descriptor); | |
| 598 // Set the error state. | |
| 599 _exception = exception; | |
| 600 thisData.state = CacheState.ERROR; | |
| 601 thisData.value = descriptor.defaultValue; | |
| 602 // Propagate the error state. | |
| 603 thisData.dependentResults.forEach((TargetedResult dependentResult) { | |
| 604 CacheEntry entry = _partition.get(dependentResult.target); | |
| 605 entry._setErrorState(dependentResult.result, exception); | |
| 606 }); | |
| 607 } | |
| 608 | |
| 609 /** | |
| 610 * Set the value of the flag with the given [index] to the given [value]. | |
| 611 */ | |
| 612 void _setFlag(int index, bool value) { | |
| 613 _flags = BooleanArray.set(_flags, index, value); | |
| 614 } | |
| 615 | |
| 616 /** | |
| 617 * If the state of the value described by the given [descriptor] is changing | |
| 618 * from ERROR to anything else, capture the information. This is an attempt to | |
| 619 * discover the underlying cause of a long-standing bug. | |
| 620 */ | |
| 621 void _validateStateChange(ResultDescriptor descriptor, CacheState newState) { | |
| 622 // TODO(brianwilkerson) Decide whether we still want to capture this data. | |
| 623 // if (descriptor != CONTENT) { | |
| 624 // return; | |
| 625 // } | |
| 626 // ResultData data = resultMap[CONTENT]; | |
| 627 // if (data != null && data.state == CacheState.ERROR) { | |
| 628 // String message = | |
| 629 // 'contentState changing from ${data.state} to $newState'; | |
| 630 // InstrumentationBuilder builder = | |
| 631 // Instrumentation.builder2('CacheEntry-validateStateChange'); | |
| 632 // builder.data3('message', message); | |
| 633 // //builder.data('source', source.getFullName()); | |
| 634 // builder.record(new CaughtException(new AnalysisException(message), null)
); | |
| 635 // builder.log(); | |
| 636 // } | |
| 637 } | |
| 638 | |
| 639 /** | |
| 640 * Write a textual representation of this entry to the given [buffer]. The | |
| 641 * result should only be used for debugging purposes. | |
| 642 */ | |
| 643 void _writeOn(StringBuffer buffer) { | |
| 644 buffer.write('time = '); | |
| 645 buffer.write(modificationTime); | |
| 646 List<ResultDescriptor> results = _resultMap.keys.toList(); | |
| 647 results.sort((ResultDescriptor first, ResultDescriptor second) => | |
| 648 first.toString().compareTo(second.toString())); | |
| 649 for (ResultDescriptor result in results) { | |
| 650 ResultData data = _resultMap[result]; | |
| 651 buffer.write('; '); | |
| 652 buffer.write(result.toString()); | |
| 653 buffer.write(' = '); | |
| 654 buffer.write(data.state); | |
| 655 } | |
| 656 } | |
| 657 } | |
| 658 | |
| 659 /** | |
| 660 * An object that controls flushing of analysis results from the cache. | |
| 661 */ | |
| 662 class CacheFlushManager<T> { | |
| 663 final IsPriorityAnalysisTarget isPriorityAnalysisTarget; | |
| 664 final ResultCachingPolicy<T> policy; | |
| 665 final int maxActiveSize; | |
| 666 final int maxIdleSize; | |
| 667 | |
| 668 /** | |
| 669 * A map of the stored [TargetedResult] to their sizes. | |
| 670 */ | |
| 671 final HashMap<TargetedResult, int> resultSizeMap = | |
| 672 new HashMap<TargetedResult, int>(); | |
| 673 | |
| 674 /** | |
| 675 * A linked set containing the most recently accessed results with the most | |
| 676 * recently used at the end of the list. When more results are added than the | |
| 677 * maximum size allowed then the least recently used results will be flushed | |
| 678 * from the cache. | |
| 679 */ | |
| 680 final LinkedHashSet<TargetedResult> recentlyUsed = | |
| 681 new LinkedHashSet<TargetedResult>(); | |
| 682 | |
| 683 /** | |
| 684 * The current size of stored results. | |
| 685 */ | |
| 686 int currentSize = 0; | |
| 687 | |
| 688 /** | |
| 689 * The current maximum cache size. | |
| 690 */ | |
| 691 int maxSize; | |
| 692 | |
| 693 CacheFlushManager( | |
| 694 ResultCachingPolicy<T> policy, this.isPriorityAnalysisTarget) | |
| 695 : policy = policy, | |
| 696 maxActiveSize = policy.maxActiveSize, | |
| 697 maxIdleSize = policy.maxIdleSize, | |
| 698 maxSize = policy.maxIdleSize; | |
| 699 | |
| 700 /** | |
| 701 * If [currentSize] is already less than [maxSize], returns an empty list. | |
| 702 * Otherwise returns [TargetedResult]s to flush from the cache to make | |
| 703 * [currentSize] less or equal to [maxSize]. | |
| 704 * | |
| 705 * Results for priority files are never flushed, so this method might leave | |
| 706 * [currentSize] greater than [maxSize]. | |
| 707 */ | |
| 708 List<TargetedResult> flushToSize() { | |
| 709 // If still under the cap, done. | |
| 710 if (currentSize <= maxSize) { | |
| 711 return TargetedResult.EMPTY_LIST; | |
| 712 } | |
| 713 // Flush results until we are under the cap. | |
| 714 List<TargetedResult> resultsToFlush = <TargetedResult>[]; | |
| 715 for (TargetedResult result in recentlyUsed) { | |
| 716 if (isPriorityAnalysisTarget(result.target)) { | |
| 717 continue; | |
| 718 } | |
| 719 resultsToFlush.add(result); | |
| 720 int size = resultSizeMap.remove(result); | |
| 721 assert(size != null); | |
| 722 currentSize -= size; | |
| 723 if (currentSize <= maxSize) { | |
| 724 break; | |
| 725 } | |
| 726 } | |
| 727 recentlyUsed.removeAll(resultsToFlush); | |
| 728 return resultsToFlush; | |
| 729 } | |
| 730 | |
| 731 /** | |
| 732 * Notifies this manager that the corresponding analysis context is active. | |
| 733 */ | |
| 734 void madeActive() { | |
| 735 maxSize = maxActiveSize; | |
| 736 } | |
| 737 | |
| 738 /** | |
| 739 * Notifies this manager that the corresponding analysis context is idle. | |
| 740 * Returns [TargetedResult]s that should be flushed from the cache. | |
| 741 */ | |
| 742 List<TargetedResult> madeIdle() { | |
| 743 maxSize = maxIdleSize; | |
| 744 return flushToSize(); | |
| 745 } | |
| 746 | |
| 747 /** | |
| 748 * Records that the given [result] was just read from the cache. | |
| 749 */ | |
| 750 void resultAccessed(TargetedResult result) { | |
| 751 if (recentlyUsed.remove(result)) { | |
| 752 recentlyUsed.add(result); | |
| 753 } | |
| 754 } | |
| 755 | |
| 756 /** | |
| 757 * Records that the given [newResult] and [newValue] were stored to the cache. | |
| 758 * Returns [TargetedResult]s that should be flushed from the cache. | |
| 759 */ | |
| 760 List<TargetedResult> resultStored(TargetedResult newResult, T newValue) { | |
| 761 if (!recentlyUsed.remove(newResult)) { | |
| 762 int size = policy.measure(newValue); | |
| 763 resultSizeMap[newResult] = size; | |
| 764 currentSize += size; | |
| 765 } | |
| 766 recentlyUsed.add(newResult); | |
| 767 return flushToSize(); | |
| 768 } | |
| 769 | |
| 770 /** | |
| 771 * Records that the given [target] was just removed from to the cache. | |
| 772 */ | |
| 773 void targetRemoved(AnalysisTarget target) { | |
| 774 List<TargetedResult> resultsToRemove = <TargetedResult>[]; | |
| 775 for (TargetedResult result in recentlyUsed) { | |
| 776 if (result.target == target) { | |
| 777 resultsToRemove.add(result); | |
| 778 int size = resultSizeMap.remove(result); | |
| 779 assert(size != null); | |
| 780 currentSize -= size; | |
| 781 } | |
| 782 } | |
| 783 recentlyUsed.removeAll(resultsToRemove); | |
| 784 } | |
| 785 } | |
| 786 | |
| 787 /** | |
| 788 * A single partition in an LRU cache of information related to analysis. | |
| 789 */ | |
| 790 abstract class CachePartition { | |
| 791 /** | |
| 792 * The context that owns this partition. Multiple contexts can reference a | |
| 793 * partition, but only one context can own it. | |
| 794 */ | |
| 795 final InternalAnalysisContext context; | |
| 796 | |
| 797 /** | |
| 798 * A table mapping caching policies to the cache flush managers. | |
| 799 */ | |
| 800 final HashMap<ResultCachingPolicy, CacheFlushManager> _flushManagerMap = | |
| 801 new HashMap<ResultCachingPolicy, CacheFlushManager>(); | |
| 802 | |
| 803 /** | |
| 804 * The [StreamController] reporting [InvalidatedResult]s. | |
| 805 */ | |
| 806 final StreamController<InvalidatedResult> _onResultInvalidated = | |
| 807 new StreamController<InvalidatedResult>.broadcast(sync: true); | |
| 808 | |
| 809 /** | |
| 810 * A table mapping the targets belonging to this partition to the information | |
| 811 * known about those targets. | |
| 812 */ | |
| 813 HashMap<AnalysisTarget, CacheEntry> _targetMap = | |
| 814 new HashMap<AnalysisTarget, CacheEntry>(); | |
| 815 | |
| 816 /** | |
| 817 * A set of the [Source] targets. | |
| 818 */ | |
| 819 final HashSet<Source> _sources = new HashSet<Source>(); | |
| 820 | |
| 821 /** | |
| 822 * A table mapping full paths to lists of [Source]s with these full paths. | |
| 823 */ | |
| 824 final Map<String, List<Source>> _pathToSources = <String, List<Source>>{}; | |
| 825 | |
| 826 /** | |
| 827 * Initialize a newly created cache partition, belonging to the given | |
| 828 * [context]. | |
| 829 */ | |
| 830 CachePartition(this.context); | |
| 831 | |
| 832 /** | |
| 833 * Return a table mapping the targets known to the context to the information | |
| 834 * known about the target. | |
| 835 * | |
| 836 * <b>Note:</b> This method is only visible for use by [AnalysisCache] and | |
| 837 * should not be used for any other purpose. | |
| 838 */ | |
| 839 Map<AnalysisTarget, CacheEntry> get map => _targetMap; | |
| 840 | |
| 841 /** | |
| 842 * Return the stream that is notified when a value is invalidated. | |
| 843 */ | |
| 844 Stream<InvalidatedResult> get onResultInvalidated => | |
| 845 _onResultInvalidated.stream; | |
| 846 | |
| 847 /** | |
| 848 * Notifies the partition that the client is going to stop using it. | |
| 849 */ | |
| 850 void dispose() { | |
| 851 for (CacheEntry entry in _targetMap.values) { | |
| 852 entry.dispose(); | |
| 853 } | |
| 854 _targetMap.clear(); | |
| 855 } | |
| 856 | |
| 857 /** | |
| 858 * Return the entry associated with the given [target]. | |
| 859 */ | |
| 860 CacheEntry get(AnalysisTarget target) => _targetMap[target]; | |
| 861 | |
| 862 /** | |
| 863 * Return [Source]s whose full path is equal to the given [path]. | |
| 864 * Maybe empty, but not `null`. | |
| 865 */ | |
| 866 List<Source> getSourcesWithFullName(String path) { | |
| 867 List<Source> sources = _pathToSources[path]; | |
| 868 return sources != null ? sources : Source.EMPTY_LIST; | |
| 869 } | |
| 870 | |
| 871 /** | |
| 872 * Return `true` if this partition is responsible for the given [target]. | |
| 873 */ | |
| 874 bool isResponsibleFor(AnalysisTarget target); | |
| 875 | |
| 876 /** | |
| 877 * Return an iterator returning all of the map entries mapping targets to | |
| 878 * cache entries. | |
| 879 */ | |
| 880 MapIterator<AnalysisTarget, CacheEntry> iterator() => | |
| 881 new SingleMapIterator<AnalysisTarget, CacheEntry>(_targetMap); | |
| 882 | |
| 883 /** | |
| 884 * Puts the given [entry] into the partition. | |
| 885 */ | |
| 886 void put(CacheEntry entry) { | |
| 887 AnalysisTarget target = entry.target; | |
| 888 if (entry._partition != null) { | |
| 889 throw new StateError( | |
| 890 'The entry for $target is already in ${entry._partition}'); | |
| 891 } | |
| 892 entry._partition = this; | |
| 893 entry.fixExceptionState(); | |
| 894 _targetMap[target] = entry; | |
| 895 _addIfSource(target); | |
| 896 } | |
| 897 | |
| 898 /** | |
| 899 * Remove all information related to the given [target] from this cache. | |
| 900 */ | |
| 901 void remove(AnalysisTarget target) { | |
| 902 for (CacheFlushManager flushManager in _flushManagerMap.values) { | |
| 903 flushManager.targetRemoved(target); | |
| 904 } | |
| 905 CacheEntry entry = _targetMap.remove(target); | |
| 906 if (entry != null) { | |
| 907 entry._invalidateAll(); | |
| 908 } | |
| 909 _removeIfSource(target); | |
| 910 } | |
| 911 | |
| 912 /** | |
| 913 * Records that a value of the result described by the given [descriptor] | |
| 914 * for the given [target] was just read from the cache. | |
| 915 */ | |
| 916 void resultAccessed(AnalysisTarget target, ResultDescriptor descriptor) { | |
| 917 CacheFlushManager flushManager = _getFlushManager(descriptor); | |
| 918 TargetedResult result = new TargetedResult(target, descriptor); | |
| 919 flushManager.resultAccessed(result); | |
| 920 } | |
| 921 | |
| 922 /** | |
| 923 * Records that the given [result] was just stored into the cache. | |
| 924 */ | |
| 925 void resultStored(TargetedResult result, Object value) { | |
| 926 CacheFlushManager flushManager = _getFlushManager(result.result); | |
| 927 List<TargetedResult> resultsToFlush = | |
| 928 flushManager.resultStored(result, value); | |
| 929 for (TargetedResult result in resultsToFlush) { | |
| 930 CacheEntry entry = get(result.target); | |
| 931 if (entry != null) { | |
| 932 ResultData data = entry._resultMap[result.result]; | |
| 933 if (data != null) { | |
| 934 data.flush(); | |
| 935 } | |
| 936 } | |
| 937 } | |
| 938 } | |
| 939 | |
| 940 /** | |
| 941 * Return the number of targets that are mapped to cache entries. | |
| 942 */ | |
| 943 int size() => _targetMap.length; | |
| 944 | |
| 945 /** | |
| 946 * If the given [target] is a [Source], adds it to [_sources]. | |
| 947 */ | |
| 948 void _addIfSource(AnalysisTarget target) { | |
| 949 if (target is Source) { | |
| 950 _sources.add(target); | |
| 951 { | |
| 952 String fullName = target.fullName; | |
| 953 _pathToSources.putIfAbsent(fullName, () => <Source>[]).add(target); | |
| 954 } | |
| 955 } | |
| 956 } | |
| 957 | |
| 958 ResultData _getDataFor(TargetedResult result) { | |
| 959 CacheEntry entry = context.analysisCache.get(result.target); | |
| 960 return entry != null ? entry._resultMap[result.result] : null; | |
| 961 } | |
| 962 | |
| 963 /** | |
| 964 * Return the [CacheFlushManager] for the given [descriptor], not `null`. | |
| 965 */ | |
| 966 CacheFlushManager _getFlushManager(ResultDescriptor descriptor) { | |
| 967 ResultCachingPolicy policy = descriptor.cachingPolicy; | |
| 968 if (identical(policy, DEFAULT_CACHING_POLICY)) { | |
| 969 return UnlimitedCacheFlushManager.INSTANCE; | |
| 970 } | |
| 971 CacheFlushManager manager = _flushManagerMap[policy]; | |
| 972 if (manager == null) { | |
| 973 manager = new CacheFlushManager(policy, _isPriorityAnalysisTarget); | |
| 974 _flushManagerMap[policy] = manager; | |
| 975 } | |
| 976 return manager; | |
| 977 } | |
| 978 | |
| 979 bool _isPriorityAnalysisTarget(AnalysisTarget target) { | |
| 980 return context.priorityTargets.contains(target); | |
| 981 } | |
| 982 | |
| 983 /** | |
| 984 * If the given [target] is a [Source], removes it from [_sources]. | |
| 985 */ | |
| 986 void _removeIfSource(AnalysisTarget target) { | |
| 987 if (target is Source) { | |
| 988 _sources.remove(target); | |
| 989 { | |
| 990 String fullName = target.fullName; | |
| 991 List<Source> sources = _pathToSources[fullName]; | |
| 992 if (sources != null) { | |
| 993 sources.remove(target); | |
| 994 if (sources.isEmpty) { | |
| 995 _pathToSources.remove(fullName); | |
| 996 } | |
| 997 } | |
| 998 } | |
| 999 } | |
| 1000 } | |
| 1001 } | |
| 1002 | |
| 1003 /** | |
| 1004 * The description for a change. | |
| 1005 */ | |
| 1006 class Delta { | |
| 1007 final Source source; | |
| 1008 | |
| 1009 Delta(this.source); | |
| 1010 | |
| 1011 /** | |
| 1012 * Check whether this delta affects the result described by the given | |
| 1013 * [descriptor] and [target]. | |
| 1014 */ | |
| 1015 DeltaResult validate(InternalAnalysisContext context, AnalysisTarget target, | |
| 1016 ResultDescriptor descriptor) { | |
| 1017 return DeltaResult.INVALIDATE; | |
| 1018 } | |
| 1019 } | |
| 1020 | |
| 1021 /** | |
| 1022 * The possible results of validating analysis results againt a [Delta]. | |
| 1023 */ | |
| 1024 enum DeltaResult { INVALIDATE, KEEP_CONTINUE, STOP } | |
| 1025 | |
| 1026 /** | |
| 1027 * [InvalidatedResult] describes an invalidated result. | |
| 1028 */ | |
| 1029 class InvalidatedResult { | |
| 1030 /** | |
| 1031 * The target in which the result was invalidated. | |
| 1032 */ | |
| 1033 final CacheEntry entry; | |
| 1034 | |
| 1035 /** | |
| 1036 * The descriptor of the result which was invalidated. | |
| 1037 */ | |
| 1038 final ResultDescriptor descriptor; | |
| 1039 | |
| 1040 InvalidatedResult(this.entry, this.descriptor); | |
| 1041 | |
| 1042 @override | |
| 1043 String toString() => '$descriptor of ${entry.target}'; | |
| 1044 } | |
| 1045 | |
| 1046 /** | |
| 1047 * The data about a single analysis result that is stored in a [CacheEntry]. | |
| 1048 */ | |
| 1049 // TODO(brianwilkerson) Consider making this a generic class so that the value | |
| 1050 // can be typed. | |
| 1051 class ResultData { | |
| 1052 /** | |
| 1053 * The [ResultDescriptor] this result is for. | |
| 1054 */ | |
| 1055 final ResultDescriptor descriptor; | |
| 1056 | |
| 1057 /** | |
| 1058 * The state of the cached value. | |
| 1059 */ | |
| 1060 CacheState state; | |
| 1061 | |
| 1062 /** | |
| 1063 * The value being cached, or the default value for the result if there is no | |
| 1064 * value (for example, when the [state] is [CacheState.INVALID]). | |
| 1065 */ | |
| 1066 Object value; | |
| 1067 | |
| 1068 /** | |
| 1069 * A list of the results on which this result depends. | |
| 1070 */ | |
| 1071 List<TargetedResult> dependedOnResults = <TargetedResult>[]; | |
| 1072 | |
| 1073 /** | |
| 1074 * A list of the results that depend on this result. | |
| 1075 */ | |
| 1076 Set<TargetedResult> dependentResults = new Set<TargetedResult>(); | |
| 1077 | |
| 1078 /** | |
| 1079 * Initialize a newly created result holder to represent the value of data | |
| 1080 * described by the given [descriptor]. | |
| 1081 */ | |
| 1082 ResultData(this.descriptor) { | |
| 1083 state = CacheState.INVALID; | |
| 1084 value = descriptor.defaultValue; | |
| 1085 } | |
| 1086 | |
| 1087 /** | |
| 1088 * Flush this value. | |
| 1089 */ | |
| 1090 void flush() { | |
| 1091 state = CacheState.FLUSHED; | |
| 1092 value = descriptor.defaultValue; | |
| 1093 } | |
| 1094 } | |
| 1095 | |
| 1096 /** | |
| 1097 * A cache partition that contains all of the targets in the SDK. | |
| 1098 */ | |
| 1099 class SdkCachePartition extends CachePartition { | |
| 1100 /** | |
| 1101 * Initialize a newly created cache partition, belonging to the given | |
| 1102 * [context]. | |
| 1103 */ | |
| 1104 SdkCachePartition(InternalAnalysisContext context) : super(context); | |
| 1105 | |
| 1106 @override | |
| 1107 bool isResponsibleFor(AnalysisTarget target) { | |
| 1108 if (target is AnalysisContextTarget) { | |
| 1109 return true; | |
| 1110 } | |
| 1111 Source source = target.source; | |
| 1112 return source != null && source.isInSystemLibrary; | |
| 1113 } | |
| 1114 } | |
| 1115 | |
| 1116 /** | |
| 1117 * A specification of a specific result computed for a specific target. | |
| 1118 */ | |
| 1119 class TargetedResult { | |
| 1120 /** | |
| 1121 * An empty list of results. | |
| 1122 */ | |
| 1123 static final List<TargetedResult> EMPTY_LIST = const <TargetedResult>[]; | |
| 1124 | |
| 1125 /** | |
| 1126 * The target with which the result is associated. | |
| 1127 */ | |
| 1128 final AnalysisTarget target; | |
| 1129 | |
| 1130 /** | |
| 1131 * The result associated with the target. | |
| 1132 */ | |
| 1133 final ResultDescriptor result; | |
| 1134 | |
| 1135 /** | |
| 1136 * Initialize a new targeted result. | |
| 1137 */ | |
| 1138 TargetedResult(this.target, this.result); | |
| 1139 | |
| 1140 @override | |
| 1141 int get hashCode { | |
| 1142 return JenkinsSmiHash.combine(target.hashCode, result.hashCode); | |
| 1143 } | |
| 1144 | |
| 1145 @override | |
| 1146 bool operator ==(other) { | |
| 1147 return other is TargetedResult && | |
| 1148 other.target == target && | |
| 1149 other.result == result; | |
| 1150 } | |
| 1151 | |
| 1152 @override | |
| 1153 String toString() => '$result for $target'; | |
| 1154 } | |
| 1155 | |
| 1156 /** | |
| 1157 * A cache partition that contains all targets not contained in other partitions
. | |
| 1158 */ | |
| 1159 class UniversalCachePartition extends CachePartition { | |
| 1160 /** | |
| 1161 * Initialize a newly created cache partition, belonging to the given | |
| 1162 * [context]. | |
| 1163 */ | |
| 1164 UniversalCachePartition(InternalAnalysisContext context) : super(context); | |
| 1165 | |
| 1166 @override | |
| 1167 bool isResponsibleFor(AnalysisTarget target) => true; | |
| 1168 } | |
| 1169 | |
| 1170 /** | |
| 1171 * [CacheFlushManager] that does nothing, results are never flushed. | |
| 1172 */ | |
| 1173 class UnlimitedCacheFlushManager extends CacheFlushManager { | |
| 1174 static final CacheFlushManager INSTANCE = new UnlimitedCacheFlushManager(); | |
| 1175 | |
| 1176 UnlimitedCacheFlushManager() : super(DEFAULT_CACHING_POLICY, (_) => false); | |
| 1177 | |
| 1178 @override | |
| 1179 void resultAccessed(TargetedResult result) {} | |
| 1180 | |
| 1181 @override | |
| 1182 List<TargetedResult> resultStored(TargetedResult newResult, newValue) { | |
| 1183 return TargetedResult.EMPTY_LIST; | |
| 1184 } | |
| 1185 | |
| 1186 @override | |
| 1187 void targetRemoved(AnalysisTarget target) {} | |
| 1188 } | |
| OLD | NEW |