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 library analysis_server.src.status.get_handler; | 5 library analysis_server.src.status.get_handler; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 import 'dart:convert'; | 9 import 'dart:convert'; |
10 import 'dart:io'; | 10 import 'dart:io'; |
(...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
172 /** | 172 /** |
173 * Return the active [CompletionDomainHandler] | 173 * Return the active [CompletionDomainHandler] |
174 * or `null` if either analysis server is not running | 174 * or `null` if either analysis server is not running |
175 * or there is no completion domain handler. | 175 * or there is no completion domain handler. |
176 */ | 176 */ |
177 CompletionDomainHandler get _completionDomainHandler { | 177 CompletionDomainHandler get _completionDomainHandler { |
178 AnalysisServer analysisServer = _server.analysisServer; | 178 AnalysisServer analysisServer = _server.analysisServer; |
179 if (analysisServer == null) { | 179 if (analysisServer == null) { |
180 return null; | 180 return null; |
181 } | 181 } |
182 return analysisServer.handlers.firstWhere( | 182 return analysisServer.handlers |
183 (h) => h is CompletionDomainHandler, orElse: () => null); | 183 .firstWhere((h) => h is CompletionDomainHandler, orElse: () => null); |
184 } | 184 } |
185 | 185 |
186 /** | 186 /** |
187 * Handle a GET request received by the HTTP server. | 187 * Handle a GET request received by the HTTP server. |
188 */ | 188 */ |
189 void handleGetRequest(HttpRequest request) { | 189 void handleGetRequest(HttpRequest request) { |
190 String path = request.uri.path; | 190 String path = request.uri.path; |
191 if (path == STATUS_PATH) { | 191 if (path == STATUS_PATH) { |
192 _returnServerStatus(request); | 192 _returnServerStatus(request); |
193 } else if (path == ANALYSIS_PERFORMANCE_PATH) { | 193 } else if (path == ANALYSIS_PERFORMANCE_PATH) { |
(...skipping 22 matching lines...) Expand all Loading... |
216 _returnUnknownRequest(request); | 216 _returnUnknownRequest(request); |
217 } | 217 } |
218 } | 218 } |
219 | 219 |
220 /** | 220 /** |
221 * Return the folder being managed by the given [analysisServer] that matches | 221 * Return the folder being managed by the given [analysisServer] that matches |
222 * the given [contextFilter], or `null` if there is none. | 222 * the given [contextFilter], or `null` if there is none. |
223 */ | 223 */ |
224 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) { | 224 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) { |
225 return analysisServer.folderMap.keys.firstWhere( | 225 return analysisServer.folderMap.keys.firstWhere( |
226 (Folder folder) => folder.path == contextFilter, orElse: () => null); | 226 (Folder folder) => folder.path == contextFilter, |
| 227 orElse: () => null); |
227 } | 228 } |
228 | 229 |
229 /** | 230 /** |
230 * Return any AST structure stored in the given [entry]. | 231 * Return any AST structure stored in the given [entry]. |
231 */ | 232 */ |
232 CompilationUnit _getAnyAst(CacheEntry entry) { | 233 CompilationUnit _getAnyAst(CacheEntry entry) { |
233 CompilationUnit unit = entry.getValue(PARSED_UNIT); | 234 CompilationUnit unit = entry.getValue(PARSED_UNIT); |
234 if (unit != null) { | 235 if (unit != null) { |
235 return unit; | 236 return unit; |
236 } | 237 } |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
335 classes: ["right", "right", null]); | 336 classes: ["right", "right", null]); |
336 } | 337 } |
337 tags.forEach(writeRow); | 338 tags.forEach(writeRow); |
338 buffer.write('</table>'); | 339 buffer.write('</table>'); |
339 // | 340 // |
340 // Write task model timing information. | 341 // Write task model timing information. |
341 // | 342 // |
342 buffer.write('<p><b>Task performace data</b></p>'); | 343 buffer.write('<p><b>Task performace data</b></p>'); |
343 buffer.write( | 344 buffer.write( |
344 '<table style="border-collapse: separate; border-spacing: 10px 5px;"
>'); | 345 '<table style="border-collapse: separate; border-spacing: 10px 5px;"
>'); |
345 _writeRow(buffer, [ | 346 _writeRow( |
346 'Task Name', | 347 buffer, |
347 'Count', | 348 [ |
348 'Total Time (in ms)', | 349 'Task Name', |
349 'Average Time (in ms)' | 350 'Count', |
350 ], header: true); | 351 'Total Time (in ms)', |
| 352 'Average Time (in ms)' |
| 353 ], |
| 354 header: true); |
351 | 355 |
352 Map<Type, int> countMap = AnalysisTask.countMap; | 356 Map<Type, int> countMap = AnalysisTask.countMap; |
353 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap; | 357 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap; |
354 List<Type> taskClasses = stopwatchMap.keys.toList(); | 358 List<Type> taskClasses = stopwatchMap.keys.toList(); |
355 taskClasses.sort((Type first, Type second) => | 359 taskClasses.sort((Type first, Type second) => |
356 first.toString().compareTo(second.toString())); | 360 first.toString().compareTo(second.toString())); |
357 int totalTaskTime = 0; | 361 int totalTaskTime = 0; |
358 taskClasses.forEach((Type taskClass) { | 362 taskClasses.forEach((Type taskClass) { |
359 int count = countMap[taskClass]; | 363 int count = countMap[taskClass]; |
360 if (count == null) { | 364 if (count == null) { |
361 count = 0; | 365 count = 0; |
362 } | 366 } |
363 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds; | 367 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds; |
364 totalTaskTime += taskTime; | 368 totalTaskTime += taskTime; |
365 _writeRow(buffer, [ | 369 _writeRow(buffer, [ |
366 taskClass.toString(), | 370 taskClass.toString(), |
367 count, | 371 count, |
368 taskTime, | 372 taskTime, |
369 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3) | 373 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3) |
370 ], classes: [null, "right", "right", "right"]); | 374 ], classes: [ |
| 375 null, |
| 376 "right", |
| 377 "right", |
| 378 "right" |
| 379 ]); |
371 }); | 380 }); |
372 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'], | 381 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'], |
373 classes: [null, "right", "right", "right"]); | 382 classes: [null, "right", "right", "right"]); |
374 buffer.write('</table>'); | 383 buffer.write('</table>'); |
375 }); | 384 }); |
376 }); | 385 }); |
377 } | 386 } |
378 | 387 |
379 /** | 388 /** |
380 * Return a response containing information about an AST structure. | 389 * Return a response containing information about an AST structure. |
(...skipping 14 matching lines...) Expand all Loading... |
395 } | 404 } |
396 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | 405 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; |
397 if (sourceUri == null) { | 406 if (sourceUri == null) { |
398 return _returnFailure( | 407 return _returnFailure( |
399 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | 408 request, 'Query parameter $SOURCE_QUERY_PARAM required'); |
400 } | 409 } |
401 | 410 |
402 InternalAnalysisContext context = analysisServer.folderMap[folder]; | 411 InternalAnalysisContext context = analysisServer.folderMap[folder]; |
403 | 412 |
404 _writeResponse(request, (StringBuffer buffer) { | 413 _writeResponse(request, (StringBuffer buffer) { |
405 _writePage(buffer, 'Analysis Server - AST Structure', [ | 414 _writePage(buffer, 'Analysis Server - AST Structure', |
406 'Context: $contextFilter', | 415 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) { |
407 'File: $sourceUri' | |
408 ], (HttpResponse) { | |
409 Source source = context.sourceFactory.forUri(sourceUri); | 416 Source source = context.sourceFactory.forUri(sourceUri); |
410 if (source == null) { | 417 if (source == null) { |
411 buffer.write('<p>Not found.</p>'); | 418 buffer.write('<p>Not found.</p>'); |
412 return; | 419 return; |
413 } | 420 } |
414 CacheEntry entry = context.analysisCache.get(source); | 421 CacheEntry entry = context.analysisCache.get(source); |
415 if (entry == null) { | 422 if (entry == null) { |
416 buffer.write('<p>Not found.</p>'); | 423 buffer.write('<p>Not found.</p>'); |
417 return; | 424 return; |
418 } | 425 } |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
479 entries.add(iterator.value); | 486 entries.add(iterator.value); |
480 } | 487 } |
481 } | 488 } |
482 } | 489 } |
483 }); | 490 }); |
484 allContexts.sort((Folder firstFolder, Folder secondFolder) => | 491 allContexts.sort((Folder firstFolder, Folder secondFolder) => |
485 firstFolder.path.compareTo(secondFolder.path)); | 492 firstFolder.path.compareTo(secondFolder.path)); |
486 InternalAnalysisContext context = analysisServer.folderMap[folder]; | 493 InternalAnalysisContext context = analysisServer.folderMap[folder]; |
487 | 494 |
488 _writeResponse(request, (StringBuffer buffer) { | 495 _writeResponse(request, (StringBuffer buffer) { |
489 _writePage(buffer, 'Analysis Server - Cache Entry', [ | 496 _writePage(buffer, 'Analysis Server - Cache Entry', |
490 'Context: $contextFilter', | 497 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) { |
491 'File: $sourceUri' | |
492 ], (HttpResponse) { | |
493 buffer.write('<h3>Analyzing Contexts</h3><p>'); | 498 buffer.write('<h3>Analyzing Contexts</h3><p>'); |
494 bool first = true; | 499 bool first = true; |
495 allContexts.forEach((Folder folder) { | 500 allContexts.forEach((Folder folder) { |
496 if (first) { | 501 if (first) { |
497 first = false; | 502 first = false; |
498 } else { | 503 } else { |
499 buffer.write('<br>'); | 504 buffer.write('<br>'); |
500 } | 505 } |
501 InternalAnalysisContext analyzingContext = | 506 InternalAnalysisContext analyzingContext = |
502 analysisServer.folderMap[folder]; | 507 analysisServer.folderMap[folder]; |
503 if (analyzingContext == context) { | 508 if (analyzingContext == context) { |
504 buffer.write(folder.path); | 509 buffer.write(folder.path); |
505 } else { | 510 } else { |
506 buffer.write(makeLink(CACHE_ENTRY_PATH, { | 511 buffer.write(makeLink( |
507 CONTEXT_QUERY_PARAM: folder.path, | 512 CACHE_ENTRY_PATH, |
508 SOURCE_QUERY_PARAM: sourceUri | 513 { |
509 }, HTML_ESCAPE.convert(folder.path))); | 514 CONTEXT_QUERY_PARAM: folder.path, |
| 515 SOURCE_QUERY_PARAM: sourceUri |
| 516 }, |
| 517 HTML_ESCAPE.convert(folder.path))); |
510 } | 518 } |
511 if (entryMap[folder][0].explicitlyAdded) { | 519 if (entryMap[folder][0].explicitlyAdded) { |
512 buffer.write(' (explicit)'); | 520 buffer.write(' (explicit)'); |
513 } else { | 521 } else { |
514 buffer.write(' (implicit)'); | 522 buffer.write(' (implicit)'); |
515 } | 523 } |
516 }); | 524 }); |
517 buffer.write('</p>'); | 525 buffer.write('</p>'); |
518 | 526 |
519 List<CacheEntry> entries = entryMap[folder]; | 527 List<CacheEntry> entries = entryMap[folder]; |
(...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
753 while (iterator.moveNext()) { | 761 while (iterator.moveNext()) { |
754 Source source = iterator.key.source; | 762 Source source = iterator.key.source; |
755 if (source != null) { | 763 if (source != null) { |
756 CacheEntry entry = iterator.value; | 764 CacheEntry entry = iterator.value; |
757 String sourceName = source.fullName; | 765 String sourceName = source.fullName; |
758 if (!links.containsKey(sourceName)) { | 766 if (!links.containsKey(sourceName)) { |
759 CaughtException exception = entry.exception; | 767 CaughtException exception = entry.exception; |
760 if (exception != null) { | 768 if (exception != null) { |
761 exceptions.add(exception); | 769 exceptions.add(exception); |
762 } | 770 } |
763 String link = makeLink(CACHE_ENTRY_PATH, { | 771 String link = makeLink( |
764 CONTEXT_QUERY_PARAM: folder.path, | 772 CACHE_ENTRY_PATH, |
765 SOURCE_QUERY_PARAM: source.uri.toString() | 773 { |
766 }, sourceName, exception != null); | 774 CONTEXT_QUERY_PARAM: folder.path, |
| 775 SOURCE_QUERY_PARAM: source.uri.toString() |
| 776 }, |
| 777 sourceName, |
| 778 exception != null); |
767 if (entry.explicitlyAdded) { | 779 if (entry.explicitlyAdded) { |
768 explicitNames.add(sourceName); | 780 explicitNames.add(sourceName); |
769 } else { | 781 } else { |
770 implicitNames.add(sourceName); | 782 implicitNames.add(sourceName); |
771 } | 783 } |
772 links[sourceName] = link; | 784 links[sourceName] = link; |
773 } | 785 } |
774 } | 786 } |
775 } | 787 } |
776 explicitNames.sort(); | 788 explicitNames.sort(); |
(...skipping 19 matching lines...) Expand all Loading... |
796 buffer.write( | 808 buffer.write( |
797 makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay')); | 809 makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay')); |
798 } | 810 } |
799 buffer.write('</td></tr>'); | 811 buffer.write('</td></tr>'); |
800 } | 812 } |
801 buffer.write('</table>'); | 813 buffer.write('</table>'); |
802 } | 814 } |
803 } | 815 } |
804 | 816 |
805 _writeResponse(request, (StringBuffer buffer) { | 817 _writeResponse(request, (StringBuffer buffer) { |
806 _writePage(buffer, 'Analysis Server - Context', | 818 _writePage( |
807 ['Context: $contextFilter'], (StringBuffer buffer) { | 819 buffer, 'Analysis Server - Context', ['Context: $contextFilter'], |
| 820 (StringBuffer buffer) { |
808 List headerRowText = ['Context']; | 821 List headerRowText = ['Context']; |
809 headerRowText.addAll(CacheState.values); | 822 headerRowText.addAll(CacheState.values); |
810 buffer.write('<h3>Summary</h3>'); | 823 buffer.write('<h3>Summary</h3>'); |
811 buffer.write('<table>'); | 824 buffer.write('<table>'); |
812 _writeRow(buffer, headerRowText, header: true); | 825 _writeRow(buffer, headerRowText, header: true); |
813 AnalysisContextStatistics statistics = context.statistics; | 826 AnalysisContextStatistics statistics = context.statistics; |
814 statistics.cacheRows.forEach((AnalysisContextStatistics_CacheRow row) { | 827 statistics.cacheRows.forEach((AnalysisContextStatistics_CacheRow row) { |
815 List rowText = [row.name]; | 828 List rowText = [row.name]; |
816 for (CacheState state in CacheState.values) { | 829 for (CacheState state in CacheState.values) { |
817 String text = row.getCount(state).toString(); | 830 String text = row.getCount(state).toString(); |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
921 } | 934 } |
922 if (index is LocalIndex) { | 935 if (index is LocalIndex) { |
923 Map<List<String>, List<InspectLocation>> relations = | 936 Map<List<String>, List<InspectLocation>> relations = |
924 await index.findElementsByName(name); | 937 await index.findElementsByName(name); |
925 _writeResponse(request, (StringBuffer buffer) { | 938 _writeResponse(request, (StringBuffer buffer) { |
926 _writePage(buffer, 'Analysis Server - Index Elements', ['Name: $name'], | 939 _writePage(buffer, 'Analysis Server - Index Elements', ['Name: $name'], |
927 (StringBuffer buffer) { | 940 (StringBuffer buffer) { |
928 buffer.write('<table border="1">'); | 941 buffer.write('<table border="1">'); |
929 _writeRow(buffer, ['Element', 'Relationship', 'Location'], | 942 _writeRow(buffer, ['Element', 'Relationship', 'Location'], |
930 header: true); | 943 header: true); |
931 relations.forEach((List<String> elementPath, | 944 relations.forEach( |
932 List<InspectLocation> relations) { | 945 (List<String> elementPath, List<InspectLocation> relations) { |
933 String elementLocation = elementPath.join(' '); | 946 String elementLocation = elementPath.join(' '); |
934 relations.forEach((InspectLocation location) { | 947 relations.forEach((InspectLocation location) { |
935 var relString = location.relationship.identifier; | 948 var relString = location.relationship.identifier; |
936 var locString = '${location.path} offset=${location.offset} ' | 949 var locString = '${location.path} offset=${location.offset} ' |
937 'length=${location.length} flags=${location.flags}'; | 950 'length=${location.length} flags=${location.flags}'; |
938 _writeRow(buffer, [elementLocation, relString, locString]); | 951 _writeRow(buffer, [elementLocation, relString, locString]); |
939 }); | 952 }); |
940 }); | 953 }); |
941 buffer.write('</table>'); | 954 buffer.write('</table>'); |
942 }); | 955 }); |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1078 buffer.write('<p><b>Analysis Contexts</b></p>'); | 1091 buffer.write('<p><b>Analysis Contexts</b></p>'); |
1079 buffer.write('<p>'); | 1092 buffer.write('<p>'); |
1080 bool first = true; | 1093 bool first = true; |
1081 folders.forEach((Folder folder) { | 1094 folders.forEach((Folder folder) { |
1082 if (first) { | 1095 if (first) { |
1083 first = false; | 1096 first = false; |
1084 } else { | 1097 } else { |
1085 buffer.write('<br>'); | 1098 buffer.write('<br>'); |
1086 } | 1099 } |
1087 String key = folder.shortName; | 1100 String key = folder.shortName; |
1088 buffer.write(makeLink(CONTEXT_PATH, { | 1101 buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path}, |
1089 CONTEXT_QUERY_PARAM: folder.path | 1102 key, _hasException(folderMap[folder]))); |
1090 }, key, _hasException(folderMap[folder]))); | |
1091 }); | 1103 }); |
1092 buffer.write('</p>'); | 1104 buffer.write('</p>'); |
1093 | 1105 |
1094 buffer.write('<p><b>Options</b></p>'); | 1106 buffer.write('<p><b>Options</b></p>'); |
1095 buffer.write('<p>'); | 1107 buffer.write('<p>'); |
1096 _writeOption( | 1108 _writeOption( |
1097 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies); | 1109 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies); |
1098 _writeOption(buffer, 'Cache size', options.cacheSize); | 1110 _writeOption(buffer, 'Cache size', options.cacheSize); |
1099 _writeOption( | 1111 _writeOption( |
1100 buffer, 'Enable strict call checks', options.enableStrictCallChecks); | 1112 buffer, 'Enable strict call checks', options.enableStrictCallChecks); |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1167 * Write a table showing summary information for the last several | 1179 * Write a table showing summary information for the last several |
1168 * completion requests to the given [buffer] object. | 1180 * completion requests to the given [buffer] object. |
1169 */ | 1181 */ |
1170 void _writeCompletionPerformanceList(StringBuffer buffer) { | 1182 void _writeCompletionPerformanceList(StringBuffer buffer) { |
1171 CompletionDomainHandler handler = _completionDomainHandler; | 1183 CompletionDomainHandler handler = _completionDomainHandler; |
1172 buffer.write('<h3>Completion Performance List</h3>'); | 1184 buffer.write('<h3>Completion Performance List</h3>'); |
1173 if (handler == null) { | 1185 if (handler == null) { |
1174 return; | 1186 return; |
1175 } | 1187 } |
1176 buffer.write('<table>'); | 1188 buffer.write('<table>'); |
1177 _writeRow(buffer, [ | 1189 _writeRow( |
1178 'Start Time', | 1190 buffer, |
1179 '', | 1191 [ |
1180 'First (ms)', | 1192 'Start Time', |
1181 '', | 1193 '', |
1182 'Complete (ms)', | 1194 'First (ms)', |
1183 '', | 1195 '', |
1184 '# Notifications', | 1196 'Complete (ms)', |
1185 '', | 1197 '', |
1186 '# Suggestions', | 1198 '# Notifications', |
1187 '', | 1199 '', |
1188 'Snippet' | 1200 '# Suggestions', |
1189 ], header: true); | 1201 '', |
| 1202 'Snippet' |
| 1203 ], |
| 1204 header: true); |
1190 int index = 0; | 1205 int index = 0; |
1191 for (CompletionPerformance performance in handler.performanceList) { | 1206 for (CompletionPerformance performance in handler.performanceList) { |
1192 String link = makeLink(COMPLETION_PATH, { | 1207 String link = makeLink(COMPLETION_PATH, {'index': '$index'}, |
1193 'index': '$index' | 1208 '${performance.startTimeAndMs}'); |
1194 }, '${performance.startTimeAndMs}'); | |
1195 _writeRow(buffer, [ | 1209 _writeRow(buffer, [ |
1196 link, | 1210 link, |
1197 ' ', | 1211 ' ', |
1198 performance.firstNotificationInMilliseconds, | 1212 performance.firstNotificationInMilliseconds, |
1199 ' ', | 1213 ' ', |
1200 performance.elapsedInMilliseconds, | 1214 performance.elapsedInMilliseconds, |
1201 ' ', | 1215 ' ', |
1202 performance.notificationCount, | 1216 performance.notificationCount, |
1203 ' ', | 1217 ' ', |
1204 performance.suggestionCount, | 1218 performance.suggestionCount, |
(...skipping 436 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1641 */ | 1655 */ |
1642 static String makeLink( | 1656 static String makeLink( |
1643 String path, Map<String, String> params, String innerHtml, | 1657 String path, Map<String, String> params, String innerHtml, |
1644 [bool hasError = false]) { | 1658 [bool hasError = false]) { |
1645 Uri uri = new Uri(path: path, queryParameters: params); | 1659 Uri uri = new Uri(path: path, queryParameters: params); |
1646 String href = HTML_ESCAPE.convert(uri.toString()); | 1660 String href = HTML_ESCAPE.convert(uri.toString()); |
1647 String classAttribute = hasError ? ' class="error"' : ''; | 1661 String classAttribute = hasError ? ' class="error"' : ''; |
1648 return '<a href="$href"$classAttribute>$innerHtml</a>'; | 1662 return '<a href="$href"$classAttribute>$innerHtml</a>'; |
1649 } | 1663 } |
1650 } | 1664 } |
OLD | NEW |