| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:async' show | 5 import 'dart:async' show EventSink; |
| 6 EventSink; | |
| 7 import 'dart:collection'; | 6 import 'dart:collection'; |
| 8 import 'dart:convert'; | 7 import 'dart:convert'; |
| 9 | 8 |
| 10 import '../../compiler.dart'; | 9 import '../../compiler.dart'; |
| 11 import '../common.dart'; | 10 import '../common.dart'; |
| 12 import '../compiler.dart' show | 11 import '../compiler.dart' show Compiler; |
| 13 Compiler; | |
| 14 import '../util/util.dart'; | 12 import '../util/util.dart'; |
| 15 | 13 |
| 16 | |
| 17 // Helper methods for statistics. | 14 // Helper methods for statistics. |
| 18 | 15 |
| 19 /// Current stats collector. Use [enableStatsOutput] to enable recording of | 16 /// Current stats collector. Use [enableStatsOutput] to enable recording of |
| 20 /// stats. | 17 /// stats. |
| 21 Stats get stats { | 18 Stats get stats { |
| 22 enableDebugMode(); | 19 enableDebugMode(); |
| 23 if (_stats == null) { | 20 if (_stats == null) { |
| 24 _stats = const Stats(); | 21 _stats = const Stats(); |
| 25 } | 22 } |
| 26 return _stats; | 23 return _stats; |
| 27 } | 24 } |
| 28 | 25 |
| 29 Stats _stats; | 26 Stats _stats; |
| 30 | 27 |
| 31 /// Enable recording of stats. Use [Stats.dumpStats] to output the record stats. | 28 /// Enable recording of stats. Use [Stats.dumpStats] to output the record stats. |
| 32 /// | 29 /// |
| 33 /// Pass the [outputProvider] of [Compiler] to generate stats into a separate | 30 /// Pass the [outputProvider] of [Compiler] to generate stats into a separate |
| 34 /// file using [name] and [extension] for the filename. If omitted, stats are | 31 /// file using [name] and [extension] for the filename. If omitted, stats are |
| 35 /// printed on standard out. | 32 /// printed on standard out. |
| 36 /// | 33 /// |
| 37 /// If [xml] is `true`, stats output is formatted as XML with a default | 34 /// If [xml] is `true`, stats output is formatted as XML with a default |
| 38 /// extension of 'xml', otherwise the output is indented text with a default | 35 /// extension of 'xml', otherwise the output is indented text with a default |
| 39 /// extension of 'log'. | 36 /// extension of 'log'. |
| 40 void enableStatsOutput({CompilerOutputProvider outputProvider, | 37 void enableStatsOutput( |
| 41 bool xml: true, | 38 {CompilerOutputProvider outputProvider, |
| 42 String name: 'stats', | 39 bool xml: true, |
| 43 String extension, | 40 String name: 'stats', |
| 44 int examples: 10}) { | 41 String extension, |
| 42 int examples: 10}) { |
| 45 if (_stats != null) { | 43 if (_stats != null) { |
| 46 throw new StateError('Stats have already been initialized.'); | 44 throw new StateError('Stats have already been initialized.'); |
| 47 } | 45 } |
| 48 enableDebugMode(); | 46 enableDebugMode(); |
| 49 | 47 |
| 50 StatsOutput output; | 48 StatsOutput output; |
| 51 if (outputProvider != null) { | 49 if (outputProvider != null) { |
| 52 if (extension == null) { | 50 if (extension == null) { |
| 53 extension = xml ? 'xml' : 'log'; | 51 extension = xml ? 'xml' : 'log'; |
| 54 } | 52 } |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 190 /// [a] and [b]: | 188 /// [a] and [b]: |
| 191 /// | 189 /// |
| 192 /// 'idA && idB' lists the elements both in [a] and [b], | 190 /// 'idA && idB' lists the elements both in [a] and [b], |
| 193 /// '!idA && idB' lists the elements not in [a] but in [b], and | 191 /// '!idA && idB' lists the elements not in [a] but in [b], and |
| 194 /// 'idA && !idB' lists the elements in [a] but not in [b]. | 192 /// 'idA && !idB' lists the elements in [a] but not in [b]. |
| 195 /// | 193 /// |
| 196 /// If [dataA] and/or [dataB] are provided, additional information on the | 194 /// If [dataA] and/or [dataB] are provided, additional information on the |
| 197 /// elements are looked up in [dataA] or [dataB] using [dataA] as the primary | 195 /// elements are looked up in [dataA] or [dataB] using [dataA] as the primary |
| 198 /// source. | 196 /// source. |
| 199 void dumpCorrelation(idA, Iterable a, idB, Iterable b, | 197 void dumpCorrelation(idA, Iterable a, idB, Iterable b, |
| 200 {Map dataA, Map dataB}) {} | 198 {Map dataA, Map dataB}) {} |
| 201 } | 199 } |
| 202 | 200 |
| 203 /// Interface for printing output data. | 201 /// Interface for printing output data. |
| 204 /// | 202 /// |
| 205 /// This class serves as the disabled output. | 203 /// This class serves as the disabled output. |
| 206 class StatsOutput { | 204 class StatsOutput { |
| 207 const StatsOutput(); | 205 const StatsOutput(); |
| 208 | 206 |
| 209 /// Print [text] as on a separate line. | 207 /// Print [text] as on a separate line. |
| 210 void println(String text) {} | 208 void println(String text) {} |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 243 start(id); | 241 start(id); |
| 244 createGroupContent(); | 242 createGroupContent(); |
| 245 end(id); | 243 end(id); |
| 246 } | 244 } |
| 247 | 245 |
| 248 /// End a group [id]. | 246 /// End a group [id]. |
| 249 void end(String id) {} | 247 void end(String id) {} |
| 250 | 248 |
| 251 /// Start a stat entry for [id] with additional [data]. | 249 /// Start a stat entry for [id] with additional [data]. |
| 252 void open(String id, | 250 void open(String id, |
| 253 [Map<String, dynamic> data = const <String, dynamic>{}]) {} | 251 [Map<String, dynamic> data = const <String, dynamic>{}]) {} |
| 254 | 252 |
| 255 /// Create a stat entry for [id] with additional [data] and content created by | 253 /// Create a stat entry for [id] with additional [data] and content created by |
| 256 /// [createChildContent]. | 254 /// [createChildContent]. |
| 257 void child(String id, | 255 void child(String id, |
| 258 [Map<String, dynamic> data = const <String, dynamic>{}, | 256 [Map<String, dynamic> data = const <String, dynamic>{}, |
| 259 void createChildContent()]) { | 257 void createChildContent()]) { |
| 260 open(id, data); | 258 open(id, data); |
| 261 if (createChildContent != null) createChildContent(); | 259 if (createChildContent != null) createChildContent(); |
| 262 close(id); | 260 close(id); |
| 263 } | 261 } |
| 264 | 262 |
| 265 /// End a stat entry for [id]. | 263 /// End a stat entry for [id]. |
| 266 void close(String id) {} | 264 void close(String id) {} |
| 267 | 265 |
| 268 /// Starts a group of additional information. | 266 /// Starts a group of additional information. |
| 269 void beginExtra() {} | 267 void beginExtra() {} |
| 270 | 268 |
| 271 /// Starts a group of additional information. | 269 /// Starts a group of additional information. |
| 272 void endExtra() {} | 270 void endExtra() {} |
| 273 } | 271 } |
| 274 | 272 |
| 275 /// Abstract base class for [ConsolePrinter] and [XMLPrinter]. | 273 /// Abstract base class for [ConsolePrinter] and [XMLPrinter]. |
| 276 abstract class BasePrinter extends StatsPrinter with Indentation { | 274 abstract class BasePrinter extends StatsPrinter with Indentation { |
| 277 final int examples; | 275 final int examples; |
| 278 final StatsOutput output; | 276 final StatsOutput output; |
| 279 | 277 |
| 280 BasePrinter({this.output: const DebugOutput(), | 278 BasePrinter({this.output: const DebugOutput(), this.examples: 10}) { |
| 281 this.examples: 10}) { | |
| 282 indentationUnit = " "; | 279 indentationUnit = " "; |
| 283 } | 280 } |
| 284 } | 281 } |
| 285 | 282 |
| 286 /// [StatsPrinter] that displays stats in console lines. | 283 /// [StatsPrinter] that displays stats in console lines. |
| 287 class ConsolePrinter extends BasePrinter { | 284 class ConsolePrinter extends BasePrinter { |
| 288 int extraLevel = 0; | 285 int extraLevel = 0; |
| 289 | 286 |
| 290 ConsolePrinter({StatsOutput output: const DebugOutput(), | 287 ConsolePrinter({StatsOutput output: const DebugOutput(), int examples: 10}) |
| 291 int examples: 10}) | |
| 292 : super(output: output, examples: examples); | 288 : super(output: output, examples: examples); |
| 293 | 289 |
| 294 void open(String id, | 290 void open(String id, |
| 295 [Map<String, dynamic> data = const <String, dynamic>{}]) { | 291 [Map<String, dynamic> data = const <String, dynamic>{}]) { |
| 296 if (extraLevel > 0) return; | 292 if (extraLevel > 0) return; |
| 297 | 293 |
| 298 StringBuffer sb = new StringBuffer(); | 294 StringBuffer sb = new StringBuffer(); |
| 299 sb.write(indentation); | 295 sb.write(indentation); |
| 300 String space = ''; | 296 String space = ''; |
| 301 if (data['title'] != null) { | 297 if (data['title'] != null) { |
| 302 sb.write('${data['title']}:'); | 298 sb.write('${data['title']}:'); |
| 303 space = ' '; | 299 space = ' '; |
| 304 data.remove('title'); | 300 data.remove('title'); |
| 305 } else if (data['name'] != null) { | 301 } else if (data['name'] != null) { |
| (...skipping 28 matching lines...) Expand all Loading... |
| 334 void endExtra() { | 330 void endExtra() { |
| 335 extraLevel--; | 331 extraLevel--; |
| 336 } | 332 } |
| 337 } | 333 } |
| 338 | 334 |
| 339 /// [StatsPrinter] that displays stats in XML format. | 335 /// [StatsPrinter] that displays stats in XML format. |
| 340 class XMLPrinter extends BasePrinter { | 336 class XMLPrinter extends BasePrinter { |
| 341 static const HtmlEscape escape = const HtmlEscape(); | 337 static const HtmlEscape escape = const HtmlEscape(); |
| 342 bool opened = false; | 338 bool opened = false; |
| 343 | 339 |
| 344 XMLPrinter({output: const DebugOutput(), | 340 XMLPrinter({output: const DebugOutput(), int examples: 10}) |
| 345 int examples: 10}) | |
| 346 : super(output: output, examples: examples); | 341 : super(output: output, examples: examples); |
| 347 | 342 |
| 348 void start(String id) { | 343 void start(String id) { |
| 349 if (!opened) { | 344 if (!opened) { |
| 350 output.println('<?xml version="1.0" encoding="UTF-8"?>'); | 345 output.println('<?xml version="1.0" encoding="UTF-8"?>'); |
| 351 opened = true; | 346 opened = true; |
| 352 } | 347 } |
| 353 open(id); | 348 open(id); |
| 354 } | 349 } |
| 355 | 350 |
| 356 void end(String id) { | 351 void end(String id) { |
| 357 close(id); | 352 close(id); |
| 358 } | 353 } |
| 359 | 354 |
| 360 void open(String id, | 355 void open(String id, |
| 361 [Map<String, dynamic> data = const <String, dynamic>{}]) { | 356 [Map<String, dynamic> data = const <String, dynamic>{}]) { |
| 362 StringBuffer sb = new StringBuffer(); | 357 StringBuffer sb = new StringBuffer(); |
| 363 sb.write(indentation); | 358 sb.write(indentation); |
| 364 sb.write('<$id'); | 359 sb.write('<$id'); |
| 365 data.forEach((key, value) { | 360 data.forEach((key, value) { |
| 366 if (value != null) { | 361 if (value != null) { |
| 367 sb.write(' $key="${escape.convert('$value')}"'); | 362 sb.write(' $key="${escape.convert('$value')}"'); |
| 368 } | 363 } |
| 369 }); | 364 }); |
| 370 sb.write('>'); | 365 sb.write('>'); |
| 371 output.println(sb.toString()); | 366 output.println(sb.toString()); |
| (...skipping 21 matching lines...) Expand all Loading... |
| 393 List<StackTraceLine> commonPrefix; | 388 List<StackTraceLine> commonPrefix; |
| 394 List<_StackTraceNode> subtraces; | 389 List<_StackTraceNode> subtraces; |
| 395 | 390 |
| 396 _StackTraceNode(this.commonPrefix, this.count, this.subtraces); | 391 _StackTraceNode(this.commonPrefix, this.count, this.subtraces); |
| 397 | 392 |
| 398 _StackTraceNode.root() : this([], 0, []); | 393 _StackTraceNode.root() : this([], 0, []); |
| 399 | 394 |
| 400 _StackTraceNode.leaf(StackTraceLines stackTrace) | 395 _StackTraceNode.leaf(StackTraceLines stackTrace) |
| 401 : this(stackTrace.lines, 1, const []); | 396 : this(stackTrace.lines, 1, const []); |
| 402 | 397 |
| 403 _StackTraceNode.node(List<StackTraceLine> commonPrefix, | 398 _StackTraceNode.node(List<StackTraceLine> commonPrefix, _StackTraceNode first, |
| 404 _StackTraceNode first, | 399 _StackTraceNode second) |
| 405 _StackTraceNode second) | |
| 406 : this(commonPrefix, first.count + second.count, [first, second]); | 400 : this(commonPrefix, first.count + second.count, [first, second]); |
| 407 | 401 |
| 408 void add(StackTraceLines stackTrace) { | 402 void add(StackTraceLines stackTrace) { |
| 409 count++; | 403 count++; |
| 410 if (!stackTrace.lines.isEmpty) { | 404 if (!stackTrace.lines.isEmpty) { |
| 411 addSubtrace(stackTrace); | 405 addSubtrace(stackTrace); |
| 412 } | 406 } |
| 413 } | 407 } |
| 414 | 408 |
| 415 void addSubtrace(StackTraceLines stackTrace) { | 409 void addSubtrace(StackTraceLines stackTrace) { |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 496 final id; | 490 final id; |
| 497 int totalCount = 0; | 491 int totalCount = 0; |
| 498 final int sampleFrequency; | 492 final int sampleFrequency; |
| 499 | 493 |
| 500 _StackTraceTree(this.id, this.sampleFrequency) : super.root(); | 494 _StackTraceTree(this.id, this.sampleFrequency) : super.root(); |
| 501 | 495 |
| 502 void dumpTraces(StatsPrinter printer) { | 496 void dumpTraces(StatsPrinter printer) { |
| 503 printer.open('trace', { | 497 printer.open('trace', { |
| 504 'id': id, | 498 'id': id, |
| 505 'totalCount': totalCount, | 499 'totalCount': totalCount, |
| 506 'sampleFrequency': sampleFrequency}); | 500 'sampleFrequency': sampleFrequency |
| 501 }); |
| 507 dumpSubtraces(printer); | 502 dumpSubtraces(printer); |
| 508 printer.close('trace'); | 503 printer.close('trace'); |
| 509 } | 504 } |
| 510 | 505 |
| 511 void sample() { | 506 void sample() { |
| 512 if (totalCount++ % sampleFrequency == 0) { | 507 if (totalCount++ % sampleFrequency == 0) { |
| 513 add(stackTrace(offset: 3)); | 508 add(stackTrace(offset: 3)); |
| 514 } | 509 } |
| 515 } | 510 } |
| 516 } | 511 } |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 569 } | 564 } |
| 570 | 565 |
| 571 void recordElement(key, element, {data}) { | 566 void recordElement(key, element, {data}) { |
| 572 setsMap.putIfAbsent(key, () => new Map())[element] = data; | 567 setsMap.putIfAbsent(key, () => new Map())[element] = data; |
| 573 } | 568 } |
| 574 | 569 |
| 575 void recordTrace(key, {int sampleFrequency}) { | 570 void recordTrace(key, {int sampleFrequency}) { |
| 576 if (sampleFrequency == null) { | 571 if (sampleFrequency == null) { |
| 577 sampleFrequency = stackTraceSampleFrequency; | 572 sampleFrequency = stackTraceSampleFrequency; |
| 578 } | 573 } |
| 579 traceMap.putIfAbsent(key, | 574 traceMap |
| 580 () => new _StackTraceTree(key, sampleFrequency)).sample(); | 575 .putIfAbsent(key, () => new _StackTraceTree(key, sampleFrequency)) |
| 581 | 576 .sample(); |
| 582 } | 577 } |
| 583 | 578 |
| 584 Iterable getList(String key) { | 579 Iterable getList(String key) { |
| 585 Map map = setsMap[key]; | 580 Map map = setsMap[key]; |
| 586 if (map == null) return const []; | 581 if (map == null) return const []; |
| 587 return map.keys; | 582 return map.keys; |
| 588 } | 583 } |
| 589 | 584 |
| 590 void dumpStats({void beforeClose()}) { | 585 void dumpStats({void beforeClose()}) { |
| 591 printer.start('stats'); | 586 printer.start('stats'); |
| 592 dumpFrequencies(); | 587 dumpFrequencies(); |
| 593 dumpSets(); | 588 dumpSets(); |
| 594 dumpCounters(); | 589 dumpCounters(); |
| 595 dumpTraces(); | 590 dumpTraces(); |
| 596 if (beforeClose != null) { | 591 if (beforeClose != null) { |
| 597 beforeClose(); | 592 beforeClose(); |
| 598 } | 593 } |
| 599 printer.end('stats'); | 594 printer.end('stats'); |
| 600 } | 595 } |
| 601 | 596 |
| 602 void dumpSets() { | 597 void dumpSets() { |
| 603 printer.group('sets', () { | 598 printer.group('sets', () { |
| 604 setsMap.forEach((k, set) { | 599 setsMap.forEach((k, set) { |
| 605 dumpIterable('examples', '$k', set.keys, | 600 dumpIterable('examples', '$k', set.keys, |
| 606 limit: printer.examples, dataMap: set); | 601 limit: printer.examples, dataMap: set); |
| 607 }); | 602 }); |
| 608 }); | 603 }); |
| 609 | |
| 610 } | 604 } |
| 611 | 605 |
| 612 void dumpFrequencies() { | 606 void dumpFrequencies() { |
| 613 printer.group('frequencies', () { | 607 printer.group('frequencies', () { |
| 614 frequencyMaps.forEach((key, Map<dynamic, List> map) { | 608 frequencyMaps.forEach((key, Map<dynamic, List> map) { |
| 615 printer.child('frequency', {'title': '$key'}, () { | 609 printer.child('frequency', {'title': '$key'}, () { |
| 616 dumpFrequency(map); | 610 dumpFrequency(map); |
| 617 }); | 611 }); |
| 618 }); | 612 }); |
| 619 }); | 613 }); |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 660 printer.open('counter', {'title': '$id', 'count': count}); | 654 printer.open('counter', {'title': '$id', 'count': count}); |
| 661 result.forEach((int count, Set examples) { | 655 result.forEach((int count, Set examples) { |
| 662 if (counter == printer.examples) { | 656 if (counter == printer.examples) { |
| 663 printer.beginExtra(); | 657 printer.beginExtra(); |
| 664 hasMore = true; | 658 hasMore = true; |
| 665 } | 659 } |
| 666 if (examples.length == 1 && | 660 if (examples.length == 1 && |
| 667 (examplesLimit == 0 || !hasData(examples.first))) { | 661 (examplesLimit == 0 || !hasData(examples.first))) { |
| 668 printer.child('examples', {'count': count, 'example': examples.first}); | 662 printer.child('examples', {'count': count, 'example': examples.first}); |
| 669 } else { | 663 } else { |
| 670 printer.child('examples', | 664 printer.child('examples', {'count': count, 'examples': examples.length}, |
| 671 {'count': count, 'examples': examples.length}, | |
| 672 () { | 665 () { |
| 673 examples.forEach((example) { | 666 examples.forEach((example) { |
| 674 dumpIterable( | 667 dumpIterable('examples', '$example', map[example], |
| 675 'examples', '$example', map[example], | 668 limit: examplesLimit, includeCount: false); |
| 676 limit: examplesLimit, | |
| 677 includeCount: false); | |
| 678 }); | 669 }); |
| 679 }); | 670 }); |
| 680 } | 671 } |
| 681 counter++; | 672 counter++; |
| 682 }); | 673 }); |
| 683 if (hasMore) { | 674 if (hasMore) { |
| 684 printer.endExtra(); | 675 printer.endExtra(); |
| 685 } | 676 } |
| 686 printer.close('counter'); | 677 printer.close('counter'); |
| 687 } | 678 } |
| 688 | 679 |
| 689 void dumpTraces() { | 680 void dumpTraces() { |
| 690 printer.group('traces', () { | 681 printer.group('traces', () { |
| 691 traceMap.keys.forEach(dumpTrace); | 682 traceMap.keys.forEach(dumpTrace); |
| 692 }); | 683 }); |
| 693 } | 684 } |
| 694 | 685 |
| 695 void dumpTrace(key) { | 686 void dumpTrace(key) { |
| 696 _StackTraceTree tree = traceMap[key]; | 687 _StackTraceTree tree = traceMap[key]; |
| 697 tree.dumpTraces(printer); | 688 tree.dumpTraces(printer); |
| 698 } | 689 } |
| 699 | 690 |
| 700 void dumpCorrelation(keyA, Iterable a, keyB, Iterable b, | 691 void dumpCorrelation(keyA, Iterable a, keyB, Iterable b, |
| 701 {Map dataA, Map dataB}) { | 692 {Map dataA, Map dataB}) { |
| 702 printer.child('correlations', {'title': '$keyA vs $keyB'}, () { | 693 printer.child('correlations', {'title': '$keyA vs $keyB'}, () { |
| 703 List aAndB = a.where((e) => e != null && b.contains(e)).toList(); | 694 List aAndB = a.where((e) => e != null && b.contains(e)).toList(); |
| 704 List aAndNotB = a.where((e) => e != null && !b.contains(e)).toList(); | 695 List aAndNotB = a.where((e) => e != null && !b.contains(e)).toList(); |
| 705 List notAandB = b.where((e) => e != null && !a.contains(e)).toList(); | 696 List notAandB = b.where((e) => e != null && !a.contains(e)).toList(); |
| 706 dumpIterable('correlation', '$keyA && $keyB', aAndB, dataMap: dataA, | 697 dumpIterable('correlation', '$keyA && $keyB', aAndB, |
| 707 limit: printer.examples); | 698 dataMap: dataA, limit: printer.examples); |
| 708 dumpIterable('correlation', '$keyA && !$keyB', aAndNotB, dataMap: dataA, | 699 dumpIterable('correlation', '$keyA && !$keyB', aAndNotB, |
| 709 limit: printer.examples); | 700 dataMap: dataA, limit: printer.examples); |
| 710 dumpIterable('correlation', '!$keyA && $keyB', notAandB, dataMap: dataB, | 701 dumpIterable('correlation', '!$keyA && $keyB', notAandB, |
| 711 limit: printer.examples); | 702 dataMap: dataB, limit: printer.examples); |
| 712 }); | 703 }); |
| 713 } | 704 } |
| 714 | 705 |
| 715 void dumpIterable(String tag, String title, Iterable iterable, | 706 void dumpIterable(String tag, String title, Iterable iterable, |
| 716 {int limit, Map dataMap, bool includeCount: true}) { | 707 {int limit, Map dataMap, bool includeCount: true}) { |
| 717 if (limit == 0) return; | 708 if (limit == 0) return; |
| 718 | 709 |
| 719 Map childData = {}; | 710 Map childData = {}; |
| 720 Iterable nonNullIterable = iterable.where((e) => e != null); | 711 Iterable nonNullIterable = iterable.where((e) => e != null); |
| 721 if (nonNullIterable.isEmpty && !includeCount) { | 712 if (nonNullIterable.isEmpty && !includeCount) { |
| 722 childData['name'] = title; | 713 childData['name'] = title; |
| 723 } else { | 714 } else { |
| 724 childData['title'] = title; | 715 childData['title'] = title; |
| 725 } | 716 } |
| 726 if (includeCount) { | 717 if (includeCount) { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 751 | 742 |
| 752 /// Returns a map that is an inversion of [map], where the keys are the values | 743 /// Returns a map that is an inversion of [map], where the keys are the values |
| 753 /// of [map] and the values are the set of keys in [map] that share values. | 744 /// of [map] and the values are the set of keys in [map] that share values. |
| 754 /// | 745 /// |
| 755 /// If [equals] and [hashCode] are provided, these are used to determine | 746 /// If [equals] and [hashCode] are provided, these are used to determine |
| 756 /// equality among the values of [map]. | 747 /// equality among the values of [map]. |
| 757 /// | 748 /// |
| 758 /// If [isValidKey] is provided, this is used to determine with a value of [map] | 749 /// If [isValidKey] is provided, this is used to determine with a value of [map] |
| 759 /// is a potential key of the inversion map. | 750 /// is a potential key of the inversion map. |
| 760 Map<dynamic, Set> inverseMap(Map map, | 751 Map<dynamic, Set> inverseMap(Map map, |
| 761 {bool equals(key1, key2), | 752 {bool equals(key1, key2), |
| 762 int hashCode(key), | 753 int hashCode(key), |
| 763 bool isValidKey(potentialKey)}) { | 754 bool isValidKey(potentialKey)}) { |
| 764 Map<dynamic, Set> result = new LinkedHashMap<dynamic, Set>( | 755 Map<dynamic, Set> result = new LinkedHashMap<dynamic, Set>( |
| 765 equals: equals, hashCode: hashCode, isValidKey: isValidKey); | 756 equals: equals, hashCode: hashCode, isValidKey: isValidKey); |
| 766 map.forEach((k, v) { | 757 map.forEach((k, v) { |
| 767 if (isValidKey == null || isValidKey(v)) { | 758 if (isValidKey == null || isValidKey(v)) { |
| 768 result.putIfAbsent(v, () => new Set()).add(k); | 759 result.putIfAbsent(v, () => new Set()).add(k); |
| 769 } | 760 } |
| 770 }); | 761 }); |
| 771 return result; | 762 return result; |
| 772 } | 763 } |
| 773 | 764 |
| 774 /// Return a new map heuristically sorted by the keys of [map]. If the first | 765 /// Return a new map heuristically sorted by the keys of [map]. If the first |
| 775 /// key of [map] is [Comparable], the keys are sorted using [sortMap] under | 766 /// key of [map] is [Comparable], the keys are sorted using [sortMap] under |
| 776 /// the assumption that all keys are [Comparable]. | 767 /// the assumption that all keys are [Comparable]. |
| 777 /// Otherwise, the keys are sorted as string using their `toString` | 768 /// Otherwise, the keys are sorted as string using their `toString` |
| 778 /// representation. | 769 /// representation. |
| 779 Map trySortMap(Map map) { | 770 Map trySortMap(Map map) { |
| 780 Iterable iterable = map.keys.where((k) => k != null); | 771 Iterable iterable = map.keys.where((k) => k != null); |
| 781 if (iterable.isEmpty) return map; | 772 if (iterable.isEmpty) return map; |
| 782 var key = iterable.first; | 773 var key = iterable.first; |
| 783 if (key is Comparable) { | 774 if (key is Comparable) { |
| 784 return sortMap(map); | 775 return sortMap(map); |
| 785 } | 776 } |
| 786 return sortMap(map, (a, b) => '$a'.compareTo('$b')); | 777 return sortMap(map, (a, b) => '$a'.compareTo('$b')); |
| 787 } | 778 } |
| 788 | 779 |
| 789 /// Returns a new map in which the keys of [map] are sorted using [compare]. | 780 /// Returns a new map in which the keys of [map] are sorted using [compare]. |
| 790 /// If [compare] is null, the keys must be [Comparable]. | 781 /// If [compare] is null, the keys must be [Comparable]. |
| 791 Map sortMap(Map map, [int compare(a,b)]) { | 782 Map sortMap(Map map, [int compare(a, b)]) { |
| 792 List keys = map.keys.toList(); | 783 List keys = map.keys.toList(); |
| 793 keys.sort(compare); | 784 keys.sort(compare); |
| 794 Map sortedMap = new Map(); | 785 Map sortedMap = new Map(); |
| 795 keys.forEach((k) => sortedMap[k] = map[k]); | 786 keys.forEach((k) => sortedMap[k] = map[k]); |
| 796 return sortedMap; | 787 return sortedMap; |
| 797 } | 788 } |
| 798 | |
| OLD | NEW |