| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library analysis_server.src.get_handler; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 import 'dart:convert'; | |
| 10 import 'dart:io'; | |
| 11 import 'dart:math'; | |
| 12 | |
| 13 import 'package:analysis_server/plugin/protocol/protocol.dart' hide Element; | |
| 14 import 'package:analysis_server/src/analysis_server.dart'; | |
| 15 import 'package:analysis_server/src/domain_completion.dart'; | |
| 16 import 'package:analysis_server/src/domain_execution.dart'; | |
| 17 import 'package:analysis_server/src/operation/operation.dart'; | |
| 18 import 'package:analysis_server/src/operation/operation_analysis.dart'; | |
| 19 import 'package:analysis_server/src/operation/operation_queue.dart'; | |
| 20 import 'package:analysis_server/src/services/index/index.dart'; | |
| 21 import 'package:analysis_server/src/services/index/local_index.dart'; | |
| 22 import 'package:analysis_server/src/services/index/store/split_store.dart'; | |
| 23 import 'package:analysis_server/src/socket_server.dart'; | |
| 24 import 'package:analysis_server/src/status/ast_writer.dart'; | |
| 25 import 'package:analysis_server/src/status/element_writer.dart'; | |
| 26 import 'package:analyzer/file_system/file_system.dart'; | |
| 27 import 'package:analyzer/src/generated/ast.dart'; | |
| 28 import 'package:analyzer/src/generated/element.dart'; | |
| 29 import 'package:analyzer/src/generated/engine.dart'; | |
| 30 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 31 import 'package:analyzer/src/generated/source.dart'; | |
| 32 import 'package:analyzer/src/generated/utilities_general.dart'; | |
| 33 import 'package:analyzer/task/model.dart' as newTask; | |
| 34 import 'package:plugin/plugin.dart'; | |
| 35 | |
| 36 /** | |
| 37 * A function that can be used to generate HTML output into the given [buffer]. | |
| 38 * The HTML that is generated must be valid (special characters must already be | |
| 39 * encoded). | |
| 40 */ | |
| 41 typedef void HtmlGenerator(StringBuffer buffer); | |
| 42 | |
| 43 /** | |
| 44 * Instances of the class [GetHandler] handle GET requests. | |
| 45 */ | |
| 46 class GetHandler { | |
| 47 /** | |
| 48 * The path used to request overall performance information. | |
| 49 */ | |
| 50 static const String ANALYSIS_PERFORMANCE_PATH = '/perf/analysis'; | |
| 51 | |
| 52 /** | |
| 53 * The path used to request information about a element model. | |
| 54 */ | |
| 55 static const String AST_PATH = '/ast'; | |
| 56 | |
| 57 /** | |
| 58 * The path used to request information about the cache entry corresponding | |
| 59 * to a single file. | |
| 60 */ | |
| 61 static const String CACHE_ENTRY_PATH = '/cache_entry'; | |
| 62 | |
| 63 /** | |
| 64 * The path used to request the list of source files in a certain cache | |
| 65 * state. | |
| 66 */ | |
| 67 static const String CACHE_STATE_PATH = '/cache_state'; | |
| 68 | |
| 69 /** | |
| 70 * The path used to request code completion information. | |
| 71 */ | |
| 72 static const String COMPLETION_PATH = '/completion'; | |
| 73 | |
| 74 /** | |
| 75 * The path used to request communication performance information. | |
| 76 */ | |
| 77 static const String COMMUNICATION_PERFORMANCE_PATH = '/perf/communication'; | |
| 78 | |
| 79 /** | |
| 80 * The path used to request information about a specific context. | |
| 81 */ | |
| 82 static const String CONTEXT_PATH = '/context'; | |
| 83 | |
| 84 /** | |
| 85 * The path used to request information about a element model. | |
| 86 */ | |
| 87 static const String ELEMENT_PATH = '/element'; | |
| 88 | |
| 89 /** | |
| 90 * The path used to request information about elements with the given name. | |
| 91 */ | |
| 92 static const String INDEX_ELEMENT_BY_NAME = '/index/element-by-name'; | |
| 93 | |
| 94 /** | |
| 95 * The path used to request an overlay contents. | |
| 96 */ | |
| 97 static const String OVERLAY_PATH = '/overlay'; | |
| 98 | |
| 99 /** | |
| 100 * The path used to request overlays information. | |
| 101 */ | |
| 102 static const String OVERLAYS_PATH = '/overlays'; | |
| 103 | |
| 104 /** | |
| 105 * The path used to request the status of the analysis server as a whole. | |
| 106 */ | |
| 107 static const String STATUS_PATH = '/status'; | |
| 108 | |
| 109 /** | |
| 110 * Query parameter used to represent the context to search for, when | |
| 111 * accessing [CACHE_ENTRY_PATH] or [CACHE_STATE_PATH]. | |
| 112 */ | |
| 113 static const String CONTEXT_QUERY_PARAM = 'context'; | |
| 114 | |
| 115 /** | |
| 116 * Query parameter used to represent the descriptor to search for, when | |
| 117 * accessing [CACHE_STATE_PATH]. | |
| 118 */ | |
| 119 static const String DESCRIPTOR_QUERY_PARAM = 'descriptor'; | |
| 120 | |
| 121 /** | |
| 122 * Query parameter used to represent the name of elements to search for, when | |
| 123 * accessing [INDEX_ELEMENT_BY_NAME]. | |
| 124 */ | |
| 125 static const String INDEX_ELEMENT_NAME = 'name'; | |
| 126 | |
| 127 /** | |
| 128 * Query parameter used to represent the path of an overlayed file. | |
| 129 */ | |
| 130 static const String PATH_PARAM = 'path'; | |
| 131 | |
| 132 /** | |
| 133 * Query parameter used to represent the source to search for, when accessing | |
| 134 * [CACHE_ENTRY_PATH]. | |
| 135 */ | |
| 136 static const String SOURCE_QUERY_PARAM = 'entry'; | |
| 137 | |
| 138 /** | |
| 139 * Query parameter used to represent the cache state to search for, when | |
| 140 * accessing [CACHE_STATE_PATH]. | |
| 141 */ | |
| 142 static const String STATE_QUERY_PARAM = 'state'; | |
| 143 | |
| 144 static final ContentType _htmlContent = | |
| 145 new ContentType("text", "html", charset: "utf-8"); | |
| 146 | |
| 147 /** | |
| 148 * The socket server whose status is to be reported on. | |
| 149 */ | |
| 150 SocketServer _server; | |
| 151 | |
| 152 /** | |
| 153 * Buffer containing strings printed by the analysis server. | |
| 154 */ | |
| 155 List<String> _printBuffer; | |
| 156 | |
| 157 /** | |
| 158 * Contents of overlay files. | |
| 159 */ | |
| 160 final Map<String, String> _overlayContents = <String, String>{}; | |
| 161 | |
| 162 /** | |
| 163 * Initialize a newly created handler for GET requests. | |
| 164 */ | |
| 165 GetHandler(this._server, this._printBuffer); | |
| 166 | |
| 167 /** | |
| 168 * Return the active [CompletionDomainHandler] | |
| 169 * or `null` if either analysis server is not running | |
| 170 * or there is no completion domain handler. | |
| 171 */ | |
| 172 CompletionDomainHandler get _completionDomainHandler { | |
| 173 AnalysisServer analysisServer = _server.analysisServer; | |
| 174 if (analysisServer == null) { | |
| 175 return null; | |
| 176 } | |
| 177 return analysisServer.handlers | |
| 178 .firstWhere((h) => h is CompletionDomainHandler, orElse: () => null); | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Handle a GET request received by the HTTP server. | |
| 183 */ | |
| 184 void handleGetRequest(HttpRequest request) { | |
| 185 String path = request.uri.path; | |
| 186 if (path == STATUS_PATH) { | |
| 187 _returnServerStatus(request); | |
| 188 } else if (path == ANALYSIS_PERFORMANCE_PATH) { | |
| 189 _returnAnalysisPerformance(request); | |
| 190 } else if (path == AST_PATH) { | |
| 191 _returnAst(request); | |
| 192 } else if (path == CACHE_STATE_PATH) { | |
| 193 _returnCacheState(request); | |
| 194 } else if (path == CACHE_ENTRY_PATH) { | |
| 195 _returnCacheEntry(request); | |
| 196 } else if (path == COMPLETION_PATH) { | |
| 197 _returnCompletionInfo(request); | |
| 198 } else if (path == COMMUNICATION_PERFORMANCE_PATH) { | |
| 199 _returnCommunicationPerformance(request); | |
| 200 } else if (path == CONTEXT_PATH) { | |
| 201 _returnContextInfo(request); | |
| 202 } else if (path == ELEMENT_PATH) { | |
| 203 _returnElement(request); | |
| 204 } else if (path == INDEX_ELEMENT_BY_NAME) { | |
| 205 _returnIndexElementByName(request); | |
| 206 } else if (path == OVERLAY_PATH) { | |
| 207 _returnOverlayContents(request); | |
| 208 } else if (path == OVERLAYS_PATH) { | |
| 209 _returnOverlaysInfo(request); | |
| 210 } else { | |
| 211 _returnUnknownRequest(request); | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 /** | |
| 216 * Return the folder being managed by the given [analysisServer] that matches | |
| 217 * the given [contextFilter], or `null` if there is none. | |
| 218 */ | |
| 219 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) { | |
| 220 return analysisServer.folderMap.keys.firstWhere( | |
| 221 (Folder folder) => folder.path == contextFilter, | |
| 222 orElse: () => null); | |
| 223 } | |
| 224 | |
| 225 /** | |
| 226 * Return `true` if the given analysis [context] has at least one entry with | |
| 227 * an exception. | |
| 228 */ | |
| 229 bool _hasException(AnalysisContextImpl context) { | |
| 230 bool hasException = false; | |
| 231 context.visitCacheItems((Source source, SourceEntry sourceEntry, | |
| 232 DataDescriptor rowDesc, CacheState state) { | |
| 233 if (sourceEntry.exception != null) { | |
| 234 hasException = true; | |
| 235 } | |
| 236 }); | |
| 237 return hasException; | |
| 238 } | |
| 239 | |
| 240 /** | |
| 241 * Return the folder in the [folderMap] with which the given [context] is | |
| 242 * associated. | |
| 243 */ | |
| 244 Folder _keyForValue( | |
| 245 Map<Folder, AnalysisContext> folderMap, AnalysisContext context) { | |
| 246 for (Folder folder in folderMap.keys) { | |
| 247 if (folderMap[folder] == context) { | |
| 248 return folder; | |
| 249 } | |
| 250 } | |
| 251 return null; | |
| 252 } | |
| 253 | |
| 254 /** | |
| 255 * Return a response displaying overall performance information. | |
| 256 */ | |
| 257 void _returnAnalysisPerformance(HttpRequest request) { | |
| 258 AnalysisServer analysisServer = _server.analysisServer; | |
| 259 if (analysisServer == null) { | |
| 260 return _returnFailure(request, 'Analysis server is not running'); | |
| 261 } | |
| 262 _writeResponse(request, (StringBuffer buffer) { | |
| 263 _writePage(buffer, 'Analysis Server - Analysis Performance', [], | |
| 264 (StringBuffer buffer) { | |
| 265 buffer.write('<h3>Analysis Performance</h3>'); | |
| 266 | |
| 267 // | |
| 268 // Write performance tags. | |
| 269 // | |
| 270 { | |
| 271 buffer.write('<p><b>Time spent in each phase of analysis</b></p>'); | |
| 272 buffer.write( | |
| 273 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 274 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Analysis Phase'], | |
| 275 header: true); | |
| 276 // prepare sorted tags | |
| 277 List<PerformanceTag> tags = PerformanceTag.all.toList(); | |
| 278 tags.remove(ServerPerformanceStatistics.idle); | |
| 279 tags.sort((a, b) => b.elapsedMs - a.elapsedMs); | |
| 280 // prepare total time | |
| 281 int totalTime = 0; | |
| 282 tags.forEach((PerformanceTag tag) { | |
| 283 totalTime += tag.elapsedMs; | |
| 284 }); | |
| 285 // write rows | |
| 286 void writeRow(PerformanceTag tag) { | |
| 287 double percent = (tag.elapsedMs * 100) / totalTime; | |
| 288 String percentStr = '${percent.toStringAsFixed(2)}%'; | |
| 289 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label], | |
| 290 classes: ["right", "right", null]); | |
| 291 } | |
| 292 tags.forEach(writeRow); | |
| 293 buffer.write('</table>'); | |
| 294 } | |
| 295 | |
| 296 // | |
| 297 // Write new task model timing information. | |
| 298 // | |
| 299 if (AnalysisEngine.instance.useTaskModel) { | |
| 300 buffer.write('<p><b>Task performace data</b></p>'); | |
| 301 buffer.write( | |
| 302 '<table style="border-collapse: separate; border-spacing: 10px 5px
;">'); | |
| 303 _writeRow( | |
| 304 buffer, | |
| 305 [ | |
| 306 'Task Name', | |
| 307 'Count', | |
| 308 'Total Time (in ms)', | |
| 309 'Average Time (in ms)' | |
| 310 ], | |
| 311 header: true); | |
| 312 | |
| 313 Map<Type, int> countMap = newTask.AnalysisTask.countMap; | |
| 314 Map<Type, Stopwatch> stopwatchMap = newTask.AnalysisTask.stopwatchMap; | |
| 315 List<Type> taskClasses = stopwatchMap.keys.toList(); | |
| 316 taskClasses.sort((Type first, Type second) => | |
| 317 first.toString().compareTo(second.toString())); | |
| 318 int totalTime = 0; | |
| 319 taskClasses.forEach((Type taskClass) { | |
| 320 int count = countMap[taskClass]; | |
| 321 if (count == null) { | |
| 322 count = 0; | |
| 323 } | |
| 324 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds; | |
| 325 totalTime += taskTime; | |
| 326 _writeRow(buffer, [ | |
| 327 taskClass.toString(), | |
| 328 count, | |
| 329 taskTime, | |
| 330 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3) | |
| 331 ], classes: [ | |
| 332 null, | |
| 333 "right", | |
| 334 "right", | |
| 335 "right" | |
| 336 ]); | |
| 337 }); | |
| 338 _writeRow(buffer, ['Total', '-', totalTime, '-'], | |
| 339 classes: [null, "right", "right", "right"]); | |
| 340 buffer.write('</table>'); | |
| 341 } | |
| 342 | |
| 343 // | |
| 344 // Write old task model transition information. | |
| 345 // | |
| 346 { | |
| 347 Map<DataDescriptor, Map<CacheState, int>> transitionMap = | |
| 348 SourceEntry.transitionMap; | |
| 349 buffer.write( | |
| 350 '<p><b>Number of times a state transitioned to VALID (grouped by d
escriptor)</b></p>'); | |
| 351 if (transitionMap.isEmpty) { | |
| 352 buffer.write('<p>none</p>'); | |
| 353 } else { | |
| 354 List<DataDescriptor> descriptors = transitionMap.keys.toList(); | |
| 355 descriptors.sort((DataDescriptor first, DataDescriptor second) => | |
| 356 first.toString().compareTo(second.toString())); | |
| 357 for (DataDescriptor key in descriptors) { | |
| 358 Map<CacheState, int> countMap = transitionMap[key]; | |
| 359 List<CacheState> oldStates = countMap.keys.toList(); | |
| 360 oldStates.sort((CacheState first, CacheState second) => | |
| 361 first.toString().compareTo(second.toString())); | |
| 362 buffer.write('<p>${key.toString()}</p>'); | |
| 363 buffer.write( | |
| 364 '<table style="border-collapse: separate; border-spacing: 10px
5px;">'); | |
| 365 _writeRow(buffer, ['Count', 'Previous State'], header: true); | |
| 366 for (CacheState state in oldStates) { | |
| 367 _writeRow(buffer, [countMap[state], state.toString()], | |
| 368 classes: ["right", null]); | |
| 369 } | |
| 370 buffer.write('</table>'); | |
| 371 } | |
| 372 } | |
| 373 } | |
| 374 }); | |
| 375 }); | |
| 376 } | |
| 377 | |
| 378 /** | |
| 379 * Return a response containing information about an AST structure. | |
| 380 */ | |
| 381 void _returnAst(HttpRequest request) { | |
| 382 AnalysisServer analysisServer = _server.analysisServer; | |
| 383 if (analysisServer == null) { | |
| 384 return _returnFailure(request, 'Analysis server not running'); | |
| 385 } | |
| 386 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 387 if (contextFilter == null) { | |
| 388 return _returnFailure( | |
| 389 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 390 } | |
| 391 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 392 if (folder == null) { | |
| 393 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 394 } | |
| 395 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | |
| 396 if (sourceUri == null) { | |
| 397 return _returnFailure( | |
| 398 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | |
| 399 } | |
| 400 | |
| 401 AnalysisContextImpl context = analysisServer.folderMap[folder]; | |
| 402 | |
| 403 _writeResponse(request, (StringBuffer buffer) { | |
| 404 _writePage(buffer, 'Analysis Server - AST Structure', | |
| 405 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) { | |
| 406 Source source = context.sourceFactory.forUri(sourceUri); | |
| 407 if (source == null) { | |
| 408 buffer.write('<p>Not found.</p>'); | |
| 409 return; | |
| 410 } | |
| 411 SourceEntry entry = context.getReadableSourceEntryOrNull(source); | |
| 412 if (entry == null) { | |
| 413 buffer.write('<p>Not found.</p>'); | |
| 414 return; | |
| 415 } | |
| 416 CompilationUnit ast = (entry as DartEntry).anyParsedCompilationUnit; | |
| 417 if (ast == null) { | |
| 418 buffer.write('<p>null</p>'); | |
| 419 return; | |
| 420 } | |
| 421 AstWriter writer = new AstWriter(buffer); | |
| 422 ast.accept(writer); | |
| 423 if (writer.exceptions.isNotEmpty) { | |
| 424 buffer.write('<h3>Exceptions while creating page</h3>'); | |
| 425 for (CaughtException exception in writer.exceptions) { | |
| 426 _writeException(buffer, exception); | |
| 427 } | |
| 428 } | |
| 429 }); | |
| 430 }); | |
| 431 } | |
| 432 | |
| 433 /** | |
| 434 * Return a response containing information about a single source file in the | |
| 435 * cache. | |
| 436 */ | |
| 437 void _returnCacheEntry(HttpRequest request) { | |
| 438 AnalysisServer analysisServer = _server.analysisServer; | |
| 439 if (analysisServer == null) { | |
| 440 return _returnFailure(request, 'Analysis server not running'); | |
| 441 } | |
| 442 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 443 if (contextFilter == null) { | |
| 444 return _returnFailure( | |
| 445 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 446 } | |
| 447 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 448 if (folder == null) { | |
| 449 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 450 } | |
| 451 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | |
| 452 if (sourceUri == null) { | |
| 453 return _returnFailure( | |
| 454 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | |
| 455 } | |
| 456 | |
| 457 List<Folder> allContexts = <Folder>[]; | |
| 458 Map<Folder, SourceEntry> entryMap = new HashMap<Folder, SourceEntry>(); | |
| 459 analysisServer.folderMap | |
| 460 .forEach((Folder folder, AnalysisContextImpl context) { | |
| 461 Source source = context.sourceFactory.forUri(sourceUri); | |
| 462 if (source != null) { | |
| 463 SourceEntry entry = context.getReadableSourceEntryOrNull(source); | |
| 464 if (entry != null) { | |
| 465 allContexts.add(folder); | |
| 466 entryMap[folder] = entry; | |
| 467 } | |
| 468 } | |
| 469 }); | |
| 470 allContexts.sort((Folder firstFolder, Folder secondFolder) => | |
| 471 firstFolder.path.compareTo(secondFolder.path)); | |
| 472 AnalysisContextImpl context = analysisServer.folderMap[folder]; | |
| 473 | |
| 474 _writeResponse(request, (StringBuffer buffer) { | |
| 475 _writePage(buffer, 'Analysis Server - Cache Entry', | |
| 476 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) { | |
| 477 buffer.write('<h3>Analyzing Contexts</h3><p>'); | |
| 478 bool first = true; | |
| 479 allContexts.forEach((Folder folder) { | |
| 480 if (first) { | |
| 481 first = false; | |
| 482 } else { | |
| 483 buffer.write('<br>'); | |
| 484 } | |
| 485 AnalysisContextImpl analyzingContext = | |
| 486 analysisServer.folderMap[folder]; | |
| 487 if (analyzingContext == context) { | |
| 488 buffer.write(folder.path); | |
| 489 } else { | |
| 490 buffer.write(makeLink( | |
| 491 CACHE_ENTRY_PATH, | |
| 492 { | |
| 493 CONTEXT_QUERY_PARAM: folder.path, | |
| 494 SOURCE_QUERY_PARAM: sourceUri | |
| 495 }, | |
| 496 HTML_ESCAPE.convert(folder.path))); | |
| 497 } | |
| 498 if (entryMap[folder].explicitlyAdded) { | |
| 499 buffer.write(' (explicit)'); | |
| 500 } else { | |
| 501 buffer.write(' (implicit)'); | |
| 502 } | |
| 503 }); | |
| 504 buffer.write('</p>'); | |
| 505 | |
| 506 SourceEntry entry = entryMap[folder]; | |
| 507 if (entry == null) { | |
| 508 buffer.write('<p>Not being analyzed in this context.</p>'); | |
| 509 return; | |
| 510 } | |
| 511 Map<String, String> linkParameters = <String, String>{ | |
| 512 CONTEXT_QUERY_PARAM: folder.path, | |
| 513 SOURCE_QUERY_PARAM: sourceUri | |
| 514 }; | |
| 515 | |
| 516 buffer.write('<h3>Library Independent</h3>'); | |
| 517 _writeDescriptorTable(buffer, entry.descriptors, entry.getState, | |
| 518 entry.getValue, linkParameters); | |
| 519 if (entry is DartEntry) { | |
| 520 for (Source librarySource in entry.containingLibraries) { | |
| 521 String libraryName = HTML_ESCAPE.convert(librarySource.fullName); | |
| 522 buffer.write('<h3>In library $libraryName:</h3>'); | |
| 523 _writeDescriptorTable( | |
| 524 buffer, | |
| 525 entry.libraryDescriptors, | |
| 526 (DataDescriptor descriptor) => | |
| 527 entry.getStateInLibrary(descriptor, librarySource), | |
| 528 (DataDescriptor descriptor) => | |
| 529 entry.getValueInLibrary(descriptor, librarySource), | |
| 530 linkParameters); | |
| 531 } | |
| 532 } | |
| 533 if (entry.exception != null) { | |
| 534 buffer.write('<h3>Exception</h3>'); | |
| 535 _writeException(buffer, entry.exception); | |
| 536 } | |
| 537 }); | |
| 538 }); | |
| 539 } | |
| 540 | |
| 541 /** | |
| 542 * Return a response indicating the set of source files in a certain cache | |
| 543 * state. | |
| 544 */ | |
| 545 void _returnCacheState(HttpRequest request) { | |
| 546 AnalysisServer analysisServer = _server.analysisServer; | |
| 547 if (analysisServer == null) { | |
| 548 return _returnFailure(request, 'Analysis server not running'); | |
| 549 } | |
| 550 // Figure out which context is being searched within. | |
| 551 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 552 if (contextFilter == null) { | |
| 553 return _returnFailure( | |
| 554 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 555 } | |
| 556 // Figure out what CacheState is being searched for. | |
| 557 String stateQueryParam = request.uri.queryParameters[STATE_QUERY_PARAM]; | |
| 558 if (stateQueryParam == null) { | |
| 559 return _returnFailure( | |
| 560 request, 'Query parameter $STATE_QUERY_PARAM required'); | |
| 561 } | |
| 562 CacheState stateFilter = null; | |
| 563 for (CacheState value in CacheState.values) { | |
| 564 if (value.toString() == stateQueryParam) { | |
| 565 stateFilter = value; | |
| 566 } | |
| 567 } | |
| 568 if (stateFilter == null) { | |
| 569 return _returnFailure( | |
| 570 request, 'Query parameter $STATE_QUERY_PARAM is invalid'); | |
| 571 } | |
| 572 // Figure out which descriptor is being searched for. | |
| 573 String descriptorFilter = | |
| 574 request.uri.queryParameters[DESCRIPTOR_QUERY_PARAM]; | |
| 575 if (descriptorFilter == null) { | |
| 576 return _returnFailure( | |
| 577 request, 'Query parameter $DESCRIPTOR_QUERY_PARAM required'); | |
| 578 } | |
| 579 | |
| 580 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 581 AnalysisContextImpl context = analysisServer.folderMap[folder]; | |
| 582 List<String> links = <String>[]; | |
| 583 context.visitCacheItems((Source source, SourceEntry dartEntry, | |
| 584 DataDescriptor rowDesc, CacheState state) { | |
| 585 if (state != stateFilter || rowDesc.toString() != descriptorFilter) { | |
| 586 return; | |
| 587 } | |
| 588 String link = makeLink( | |
| 589 CACHE_ENTRY_PATH, | |
| 590 { | |
| 591 CONTEXT_QUERY_PARAM: folder.path, | |
| 592 SOURCE_QUERY_PARAM: source.uri.toString() | |
| 593 }, | |
| 594 HTML_ESCAPE.convert(source.fullName)); | |
| 595 links.add(link); | |
| 596 }); | |
| 597 | |
| 598 _writeResponse(request, (StringBuffer buffer) { | |
| 599 _writePage(buffer, 'Analysis Server - Cache Search', [ | |
| 600 'Context: $contextFilter', | |
| 601 'Descriptor: ${HTML_ESCAPE.convert(descriptorFilter)}', | |
| 602 'State: ${HTML_ESCAPE.convert(stateQueryParam)}' | |
| 603 ], (StringBuffer buffer) { | |
| 604 buffer.write('<p>${links.length} files found</p>'); | |
| 605 buffer.write('<ul>'); | |
| 606 links.forEach((String link) { | |
| 607 buffer.write('<li>$link</li>'); | |
| 608 }); | |
| 609 buffer.write('</ul>'); | |
| 610 }); | |
| 611 }); | |
| 612 } | |
| 613 | |
| 614 /** | |
| 615 * Return a response displaying overall performance information. | |
| 616 */ | |
| 617 void _returnCommunicationPerformance(HttpRequest request) { | |
| 618 AnalysisServer analysisServer = _server.analysisServer; | |
| 619 if (analysisServer == null) { | |
| 620 return _returnFailure(request, 'Analysis server is not running'); | |
| 621 } | |
| 622 _writeResponse(request, (StringBuffer buffer) { | |
| 623 _writePage(buffer, 'Analysis Server - Communication Performance', [], | |
| 624 (StringBuffer buffer) { | |
| 625 buffer.write('<h3>Communication Performance</h3>'); | |
| 626 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 627 ServerPerformance perf = analysisServer.performanceDuringStartup; | |
| 628 int requestCount = perf.requestCount; | |
| 629 num averageLatency = requestCount > 0 | |
| 630 ? (perf.requestLatency / requestCount).round() | |
| 631 : 0; | |
| 632 int maximumLatency = perf.maxLatency; | |
| 633 num slowRequestPercent = requestCount > 0 | |
| 634 ? (perf.slowRequestCount * 100 / requestCount).round() | |
| 635 : 0; | |
| 636 buffer.write('<h4>Startup</h4>'); | |
| 637 buffer.write('<table>'); | |
| 638 _writeRow(buffer, [requestCount, 'requests'], | |
| 639 classes: ["right", null]); | |
| 640 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
| 641 classes: ["right", null]); | |
| 642 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
| 643 classes: ["right", null]); | |
| 644 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
| 645 classes: ["right", null]); | |
| 646 if (analysisServer.performanceAfterStartup != null) { | |
| 647 int startupTime = analysisServer.performanceAfterStartup.startTime - | |
| 648 perf.startTime; | |
| 649 _writeRow( | |
| 650 buffer, [startupTime, 'ms for initial analysis to complete']); | |
| 651 } | |
| 652 buffer.write('</table>'); | |
| 653 }, (StringBuffer buffer) { | |
| 654 ServerPerformance perf = analysisServer.performanceAfterStartup; | |
| 655 if (perf == null) { | |
| 656 return; | |
| 657 } | |
| 658 int requestCount = perf.requestCount; | |
| 659 num averageLatency = requestCount > 0 | |
| 660 ? (perf.requestLatency * 10 / requestCount).round() / 10 | |
| 661 : 0; | |
| 662 int maximumLatency = perf.maxLatency; | |
| 663 num slowRequestPercent = requestCount > 0 | |
| 664 ? (perf.slowRequestCount * 100 / requestCount).round() | |
| 665 : 0; | |
| 666 buffer.write('<h4>Current</h4>'); | |
| 667 buffer.write('<table>'); | |
| 668 _writeRow(buffer, [requestCount, 'requests'], | |
| 669 classes: ["right", null]); | |
| 670 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
| 671 classes: ["right", null]); | |
| 672 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
| 673 classes: ["right", null]); | |
| 674 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
| 675 classes: ["right", null]); | |
| 676 buffer.write('</table>'); | |
| 677 }); | |
| 678 }); | |
| 679 }); | |
| 680 } | |
| 681 | |
| 682 /** | |
| 683 * Return a response displaying code completion information. | |
| 684 */ | |
| 685 void _returnCompletionInfo(HttpRequest request) { | |
| 686 String value = request.requestedUri.queryParameters['index']; | |
| 687 int index = value != null ? int.parse(value, onError: (_) => 0) : 0; | |
| 688 _writeResponse(request, (StringBuffer buffer) { | |
| 689 _writePage(buffer, 'Analysis Server - Completion Stats', [], | |
| 690 (StringBuffer buffer) { | |
| 691 _writeCompletionPerformanceDetail(buffer, index); | |
| 692 _writeCompletionPerformanceList(buffer); | |
| 693 }); | |
| 694 }); | |
| 695 } | |
| 696 | |
| 697 /** | |
| 698 * Return a response containing information about a single source file in the | |
| 699 * cache. | |
| 700 */ | |
| 701 void _returnContextInfo(HttpRequest request) { | |
| 702 AnalysisServer analysisServer = _server.analysisServer; | |
| 703 if (analysisServer == null) { | |
| 704 return _returnFailure(request, 'Analysis server not running'); | |
| 705 } | |
| 706 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 707 if (contextFilter == null) { | |
| 708 return _returnFailure( | |
| 709 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 710 } | |
| 711 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 712 if (folder == null) { | |
| 713 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 714 } | |
| 715 | |
| 716 List<String> priorityNames; | |
| 717 List<String> explicitNames = <String>[]; | |
| 718 List<String> implicitNames = <String>[]; | |
| 719 Map<String, String> links = new HashMap<String, String>(); | |
| 720 List<CaughtException> exceptions = <CaughtException>[]; | |
| 721 AnalysisContextImpl context = analysisServer.folderMap[folder]; | |
| 722 priorityNames = context.prioritySources | |
| 723 .map((Source source) => source.fullName) | |
| 724 .toList(); | |
| 725 context.visitCacheItems((Source source, SourceEntry sourceEntry, | |
| 726 DataDescriptor rowDesc, CacheState state) { | |
| 727 String sourceName = source.fullName; | |
| 728 if (!links.containsKey(sourceName)) { | |
| 729 CaughtException exception = sourceEntry.exception; | |
| 730 if (exception != null) { | |
| 731 exceptions.add(exception); | |
| 732 } | |
| 733 String link = makeLink( | |
| 734 CACHE_ENTRY_PATH, | |
| 735 { | |
| 736 CONTEXT_QUERY_PARAM: folder.path, | |
| 737 SOURCE_QUERY_PARAM: source.uri.toString() | |
| 738 }, | |
| 739 sourceName, | |
| 740 exception != null); | |
| 741 if (sourceEntry.explicitlyAdded) { | |
| 742 explicitNames.add(sourceName); | |
| 743 } else { | |
| 744 implicitNames.add(sourceName); | |
| 745 } | |
| 746 links[sourceName] = link; | |
| 747 } | |
| 748 }); | |
| 749 explicitNames.sort(); | |
| 750 implicitNames.sort(); | |
| 751 | |
| 752 _overlayContents.clear(); | |
| 753 context.visitContentCache((String fullName, int stamp, String contents) { | |
| 754 _overlayContents[fullName] = contents; | |
| 755 }); | |
| 756 | |
| 757 void _writeFiles( | |
| 758 StringBuffer buffer, String title, List<String> fileNames) { | |
| 759 buffer.write('<h3>$title</h3>'); | |
| 760 if (fileNames == null || fileNames.isEmpty) { | |
| 761 buffer.write('<p>None</p>'); | |
| 762 } else { | |
| 763 buffer.write('<table style="width: 100%">'); | |
| 764 for (String fileName in fileNames) { | |
| 765 buffer.write('<tr><td>'); | |
| 766 buffer.write(links[fileName]); | |
| 767 buffer.write('</td><td>'); | |
| 768 if (_overlayContents.containsKey(fileName)) { | |
| 769 buffer.write( | |
| 770 makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay')); | |
| 771 } | |
| 772 buffer.write('</td></tr>'); | |
| 773 } | |
| 774 buffer.write('</table>'); | |
| 775 } | |
| 776 } | |
| 777 | |
| 778 _writeResponse(request, (StringBuffer buffer) { | |
| 779 _writePage( | |
| 780 buffer, 'Analysis Server - Context', ['Context: $contextFilter'], | |
| 781 (StringBuffer buffer) { | |
| 782 List headerRowText = ['Context']; | |
| 783 headerRowText.addAll(CacheState.values); | |
| 784 buffer.write('<h3>Summary</h3>'); | |
| 785 buffer.write('<table>'); | |
| 786 _writeRow(buffer, headerRowText, header: true); | |
| 787 AnalysisContextStatistics statistics = context.statistics; | |
| 788 statistics.cacheRows.forEach((AnalysisContextStatistics_CacheRow row) { | |
| 789 List rowText = [row.name]; | |
| 790 for (CacheState state in CacheState.values) { | |
| 791 String text = row.getCount(state).toString(); | |
| 792 Map<String, String> params = <String, String>{ | |
| 793 STATE_QUERY_PARAM: state.toString(), | |
| 794 CONTEXT_QUERY_PARAM: folder.path, | |
| 795 DESCRIPTOR_QUERY_PARAM: row.name | |
| 796 }; | |
| 797 rowText.add(makeLink(CACHE_STATE_PATH, params, text)); | |
| 798 } | |
| 799 _writeRow(buffer, rowText, classes: [null, "right"]); | |
| 800 }); | |
| 801 buffer.write('</table>'); | |
| 802 | |
| 803 _writeFiles(buffer, 'Priority Files', priorityNames); | |
| 804 _writeFiles(buffer, 'Explicitly Analyzed Files', explicitNames); | |
| 805 _writeFiles(buffer, 'Implicitly Analyzed Files', implicitNames); | |
| 806 | |
| 807 buffer.write('<h3>Exceptions</h3>'); | |
| 808 if (exceptions.isEmpty) { | |
| 809 buffer.write('<p>None</p>'); | |
| 810 } else { | |
| 811 exceptions.forEach((CaughtException exception) { | |
| 812 _writeException(buffer, exception); | |
| 813 }); | |
| 814 } | |
| 815 }); | |
| 816 }); | |
| 817 } | |
| 818 | |
| 819 /** | |
| 820 * Return a response containing information about an element structure. | |
| 821 */ | |
| 822 void _returnElement(HttpRequest request) { | |
| 823 AnalysisServer analysisServer = _server.analysisServer; | |
| 824 if (analysisServer == null) { | |
| 825 return _returnFailure(request, 'Analysis server not running'); | |
| 826 } | |
| 827 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
| 828 if (contextFilter == null) { | |
| 829 return _returnFailure( | |
| 830 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
| 831 } | |
| 832 Folder folder = _findFolder(analysisServer, contextFilter); | |
| 833 if (folder == null) { | |
| 834 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
| 835 } | |
| 836 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM]; | |
| 837 if (sourceUri == null) { | |
| 838 return _returnFailure( | |
| 839 request, 'Query parameter $SOURCE_QUERY_PARAM required'); | |
| 840 } | |
| 841 | |
| 842 AnalysisContextImpl context = analysisServer.folderMap[folder]; | |
| 843 | |
| 844 _writeResponse(request, (StringBuffer buffer) { | |
| 845 _writePage(buffer, 'Analysis Server - Element Model', [ | |
| 846 'Context: $contextFilter', | |
| 847 'File: $sourceUri' | |
| 848 ], (StringBuffer buffer) { | |
| 849 Source source = context.sourceFactory.forUri(sourceUri); | |
| 850 if (source == null) { | |
| 851 buffer.write('<p>Not found.</p>'); | |
| 852 return; | |
| 853 } | |
| 854 SourceEntry entry = context.getReadableSourceEntryOrNull(source); | |
| 855 if (entry == null) { | |
| 856 buffer.write('<p>Not found.</p>'); | |
| 857 return; | |
| 858 } | |
| 859 LibraryElement element = entry.getValue(DartEntry.ELEMENT); | |
| 860 if (element == null) { | |
| 861 buffer.write('<p>null</p>'); | |
| 862 return; | |
| 863 } | |
| 864 element.accept(new ElementWriter(buffer)); | |
| 865 }); | |
| 866 }); | |
| 867 } | |
| 868 | |
| 869 void _returnFailure(HttpRequest request, String message) { | |
| 870 _writeResponse(request, (StringBuffer buffer) { | |
| 871 _writePage(buffer, 'Analysis Server - Failure', [], | |
| 872 (StringBuffer buffer) { | |
| 873 buffer.write(HTML_ESCAPE.convert(message)); | |
| 874 }); | |
| 875 }); | |
| 876 } | |
| 877 | |
| 878 /** | |
| 879 * Return a response containing information about elements with the given | |
| 880 * name. | |
| 881 */ | |
| 882 Future _returnIndexElementByName(HttpRequest request) async { | |
| 883 AnalysisServer analysisServer = _server.analysisServer; | |
| 884 if (analysisServer == null) { | |
| 885 return _returnFailure(request, 'Analysis server not running'); | |
| 886 } | |
| 887 Index index = analysisServer.index; | |
| 888 if (index == null) { | |
| 889 return _returnFailure(request, 'Indexing is disabled'); | |
| 890 } | |
| 891 String name = request.uri.queryParameters[INDEX_ELEMENT_NAME]; | |
| 892 if (name == null) { | |
| 893 return _returnFailure( | |
| 894 request, 'Query parameter $INDEX_ELEMENT_NAME required'); | |
| 895 } | |
| 896 if (index is LocalIndex) { | |
| 897 Map<List<String>, List<InspectLocation>> relations = | |
| 898 await index.findElementsByName(name); | |
| 899 _writeResponse(request, (StringBuffer buffer) { | |
| 900 _writePage(buffer, 'Analysis Server - Index Elements', ['Name: $name'], | |
| 901 (StringBuffer buffer) { | |
| 902 buffer.write('<table border="1">'); | |
| 903 _writeRow(buffer, ['Element', 'Relationship', 'Location'], | |
| 904 header: true); | |
| 905 relations.forEach( | |
| 906 (List<String> elementPath, List<InspectLocation> relations) { | |
| 907 String elementLocation = elementPath.join(' '); | |
| 908 relations.forEach((InspectLocation location) { | |
| 909 var relString = location.relationship.identifier; | |
| 910 var locString = '${location.path} offset=${location.offset} ' | |
| 911 'length=${location.length} flags=${location.flags}'; | |
| 912 _writeRow(buffer, [elementLocation, relString, locString]); | |
| 913 }); | |
| 914 }); | |
| 915 buffer.write('</table>'); | |
| 916 }); | |
| 917 }); | |
| 918 } else { | |
| 919 return _returnFailure(request, 'LocalIndex expected, but $index found.'); | |
| 920 } | |
| 921 } | |
| 922 | |
| 923 void _returnOverlayContents(HttpRequest request) { | |
| 924 String path = request.requestedUri.queryParameters[PATH_PARAM]; | |
| 925 if (path == null) { | |
| 926 return _returnFailure(request, 'Query parameter $PATH_PARAM required'); | |
| 927 } | |
| 928 String contents = _overlayContents[path]; | |
| 929 | |
| 930 _writeResponse(request, (StringBuffer buffer) { | |
| 931 _writePage(buffer, 'Analysis Server - Overlay', [], | |
| 932 (StringBuffer buffer) { | |
| 933 buffer.write('<pre>${HTML_ESCAPE.convert(contents)}</pre>'); | |
| 934 }); | |
| 935 }); | |
| 936 } | |
| 937 | |
| 938 /** | |
| 939 * Return a response displaying overlays information. | |
| 940 */ | |
| 941 void _returnOverlaysInfo(HttpRequest request) { | |
| 942 AnalysisServer analysisServer = _server.analysisServer; | |
| 943 if (analysisServer == null) { | |
| 944 return _returnFailure(request, 'Analysis server is not running'); | |
| 945 } | |
| 946 | |
| 947 _writeResponse(request, (StringBuffer buffer) { | |
| 948 _writePage(buffer, 'Analysis Server - Overlays', [], | |
| 949 (StringBuffer buffer) { | |
| 950 buffer.write('<table border="1">'); | |
| 951 _overlayContents.clear(); | |
| 952 ContentCache overlayState = analysisServer.overlayState; | |
| 953 overlayState.accept((String fullName, int stamp, String contents) { | |
| 954 buffer.write('<tr>'); | |
| 955 String link = | |
| 956 makeLink(OVERLAY_PATH, {PATH_PARAM: fullName}, fullName); | |
| 957 DateTime time = new DateTime.fromMillisecondsSinceEpoch(stamp); | |
| 958 _writeRow(buffer, [link, time]); | |
| 959 _overlayContents[fullName] = contents; | |
| 960 }); | |
| 961 int count = _overlayContents.length; | |
| 962 buffer.write('<tr><td colspan="2">Total: $count entries.</td></tr>'); | |
| 963 buffer.write('</table>'); | |
| 964 }); | |
| 965 }); | |
| 966 } | |
| 967 | |
| 968 /** | |
| 969 * Return a response indicating the status of the analysis server. | |
| 970 */ | |
| 971 void _returnServerStatus(HttpRequest request) { | |
| 972 _writeResponse(request, (StringBuffer buffer) { | |
| 973 _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) { | |
| 974 if (_writeServerStatus(buffer)) { | |
| 975 _writeAnalysisStatus(buffer); | |
| 976 _writeEditStatus(buffer); | |
| 977 _writeExecutionStatus(buffer); | |
| 978 _writePluginStatus(buffer); | |
| 979 _writeRecentOutput(buffer); | |
| 980 } | |
| 981 }); | |
| 982 }); | |
| 983 } | |
| 984 | |
| 985 /** | |
| 986 * Return an error in response to an unrecognized request received by the HTTP | |
| 987 * server. | |
| 988 */ | |
| 989 void _returnUnknownRequest(HttpRequest request) { | |
| 990 _writeResponse(request, (StringBuffer buffer) { | |
| 991 _writePage(buffer, 'Analysis Server', [], (StringBuffer buffer) { | |
| 992 buffer.write('<h3>Pages</h3>'); | |
| 993 buffer.write('<p>'); | |
| 994 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data')); | |
| 995 buffer.write('</p>'); | |
| 996 buffer.write('<p>'); | |
| 997 buffer | |
| 998 .write(makeLink(COMMUNICATION_PERFORMANCE_PATH, {}, 'Performance')); | |
| 999 buffer.write('</p>'); | |
| 1000 buffer.write('<p>'); | |
| 1001 buffer.write(makeLink(STATUS_PATH, {}, 'Server status')); | |
| 1002 buffer.write('</p>'); | |
| 1003 buffer.write('<p>'); | |
| 1004 buffer.write(makeLink(OVERLAYS_PATH, {}, 'File overlays')); | |
| 1005 buffer.write('</p>'); | |
| 1006 }); | |
| 1007 }); | |
| 1008 } | |
| 1009 | |
| 1010 /** | |
| 1011 * Return a two digit decimal representation of the given non-negative integer | |
| 1012 * [value]. | |
| 1013 */ | |
| 1014 String _twoDigit(int value) { | |
| 1015 if (value < 10) { | |
| 1016 return '0$value'; | |
| 1017 } | |
| 1018 return value.toString(); | |
| 1019 } | |
| 1020 | |
| 1021 /** | |
| 1022 * Write the status of the analysis domain (on the main status page) to the | |
| 1023 * given [buffer] object. | |
| 1024 */ | |
| 1025 void _writeAnalysisStatus(StringBuffer buffer) { | |
| 1026 AnalysisServer analysisServer = _server.analysisServer; | |
| 1027 Map<Folder, AnalysisContext> folderMap = analysisServer.folderMap; | |
| 1028 List<Folder> folders = folderMap.keys.toList(); | |
| 1029 folders.sort((Folder first, Folder second) => | |
| 1030 first.shortName.compareTo(second.shortName)); | |
| 1031 AnalysisOptionsImpl options = analysisServer.defaultContextOptions; | |
| 1032 ServerOperationQueue operationQueue = analysisServer.operationQueue; | |
| 1033 | |
| 1034 buffer.write('<h3>Analysis Domain</h3>'); | |
| 1035 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1036 if (operationQueue.isEmpty) { | |
| 1037 buffer.write('<p>Status: Done analyzing</p>'); | |
| 1038 } else { | |
| 1039 ServerOperation operation = operationQueue.peek(); | |
| 1040 if (operation is PerformAnalysisOperation) { | |
| 1041 Folder folder = _keyForValue(folderMap, operation.context); | |
| 1042 if (folder == null) { | |
| 1043 buffer.write('<p>Status: Analyzing in unmapped context</p>'); | |
| 1044 } else { | |
| 1045 buffer.write('<p>Status: Analyzing in ${folder.path}</p>'); | |
| 1046 } | |
| 1047 } else { | |
| 1048 buffer.write('<p>Status: Analyzing</p>'); | |
| 1049 } | |
| 1050 } | |
| 1051 | |
| 1052 buffer.write('<p><b>Analysis Contexts</b></p>'); | |
| 1053 buffer.write('<p>'); | |
| 1054 bool first = true; | |
| 1055 folders.forEach((Folder folder) { | |
| 1056 if (first) { | |
| 1057 first = false; | |
| 1058 } else { | |
| 1059 buffer.write('<br>'); | |
| 1060 } | |
| 1061 String key = folder.shortName; | |
| 1062 buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path}, | |
| 1063 key, _hasException(folderMap[folder]))); | |
| 1064 }); | |
| 1065 buffer.write('</p>'); | |
| 1066 | |
| 1067 buffer.write('<p><b>Options</b></p>'); | |
| 1068 buffer.write('<p>'); | |
| 1069 _writeOption( | |
| 1070 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies); | |
| 1071 _writeOption(buffer, 'Cache size', options.cacheSize); | |
| 1072 _writeOption( | |
| 1073 buffer, 'Enable strict call checks', options.enableStrictCallChecks); | |
| 1074 _writeOption(buffer, 'Enable super mixins', options.enableSuperMixins); | |
| 1075 _writeOption(buffer, 'Generate hints', options.hint); | |
| 1076 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint); | |
| 1077 _writeOption(buffer, 'Generate errors in implicit files', | |
| 1078 options.generateImplicitErrors); | |
| 1079 _writeOption( | |
| 1080 buffer, 'Generate errors in SDK files', options.generateSdkErrors); | |
| 1081 _writeOption(buffer, 'Incremental resolution', options.incremental); | |
| 1082 _writeOption(buffer, 'Incremental resolution with API changes', | |
| 1083 options.incrementalApi); | |
| 1084 _writeOption(buffer, 'Preserve comments', options.preserveComments, | |
| 1085 last: true); | |
| 1086 buffer.write('</p>'); | |
| 1087 int freq = AnalysisServer.performOperationDelayFreqency; | |
| 1088 String delay = freq > 0 ? '1 ms every $freq ms' : 'off'; | |
| 1089 buffer.write('<p><b>perform operation delay:</b> $delay</p>'); | |
| 1090 | |
| 1091 buffer.write('<p><b>Performance Data</b></p>'); | |
| 1092 buffer.write('<p>'); | |
| 1093 buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data')); | |
| 1094 buffer.write('</p>'); | |
| 1095 }, (StringBuffer buffer) { | |
| 1096 _writeSubscriptionMap( | |
| 1097 buffer, AnalysisService.VALUES, analysisServer.analysisServices); | |
| 1098 }); | |
| 1099 } | |
| 1100 | |
| 1101 /** | |
| 1102 * Write performance information about a specific completion request | |
| 1103 * to the given [buffer] object. | |
| 1104 */ | |
| 1105 void _writeCompletionPerformanceDetail(StringBuffer buffer, int index) { | |
| 1106 CompletionDomainHandler handler = _completionDomainHandler; | |
| 1107 CompletionPerformance performance; | |
| 1108 if (handler != null) { | |
| 1109 List<CompletionPerformance> list = handler.performanceList; | |
| 1110 if (list != null && list.isNotEmpty) { | |
| 1111 performance = list[max(0, min(list.length - 1, index))]; | |
| 1112 } | |
| 1113 } | |
| 1114 if (performance == null) { | |
| 1115 buffer.write('<h3>Completion Performance Detail</h3>'); | |
| 1116 buffer.write('<p>No completions yet</p>'); | |
| 1117 return; | |
| 1118 } | |
| 1119 buffer.write('<h3>Completion Performance Detail</h3>'); | |
| 1120 buffer.write('<p>${performance.startTimeAndMs} for ${performance.source}'); | |
| 1121 buffer.write('<table>'); | |
| 1122 _writeRow(buffer, ['Elapsed', '', 'Operation'], header: true); | |
| 1123 performance.operations.forEach((OperationPerformance op) { | |
| 1124 String elapsed = op.elapsed != null ? op.elapsed.toString() : '???'; | |
| 1125 _writeRow(buffer, [elapsed, ' ', op.name]); | |
| 1126 }); | |
| 1127 buffer.write('</table>'); | |
| 1128 buffer.write('<p><b>Compute Cache Performance</b>: '); | |
| 1129 if (handler.computeCachePerformance == null) { | |
| 1130 buffer.write('none'); | |
| 1131 } else { | |
| 1132 int elapsed = handler.computeCachePerformance.elapsedInMilliseconds; | |
| 1133 Source source = handler.computeCachePerformance.source; | |
| 1134 buffer.write(' $elapsed ms for $source'); | |
| 1135 } | |
| 1136 buffer.write('</p>'); | |
| 1137 } | |
| 1138 | |
| 1139 /** | |
| 1140 * Write a table showing summary information for the last several | |
| 1141 * completion requests to the given [buffer] object. | |
| 1142 */ | |
| 1143 void _writeCompletionPerformanceList(StringBuffer buffer) { | |
| 1144 CompletionDomainHandler handler = _completionDomainHandler; | |
| 1145 buffer.write('<h3>Completion Performance List</h3>'); | |
| 1146 if (handler == null) { | |
| 1147 return; | |
| 1148 } | |
| 1149 buffer.write('<table>'); | |
| 1150 _writeRow( | |
| 1151 buffer, | |
| 1152 [ | |
| 1153 'Start Time', | |
| 1154 '', | |
| 1155 'First (ms)', | |
| 1156 '', | |
| 1157 'Complete (ms)', | |
| 1158 '', | |
| 1159 '# Notifications', | |
| 1160 '', | |
| 1161 '# Suggestions', | |
| 1162 '', | |
| 1163 'Snippet' | |
| 1164 ], | |
| 1165 header: true); | |
| 1166 int index = 0; | |
| 1167 for (CompletionPerformance performance in handler.performanceList) { | |
| 1168 String link = makeLink(COMPLETION_PATH, {'index': '$index'}, | |
| 1169 '${performance.startTimeAndMs}'); | |
| 1170 _writeRow(buffer, [ | |
| 1171 link, | |
| 1172 ' ', | |
| 1173 performance.firstNotificationInMilliseconds, | |
| 1174 ' ', | |
| 1175 performance.elapsedInMilliseconds, | |
| 1176 ' ', | |
| 1177 performance.notificationCount, | |
| 1178 ' ', | |
| 1179 performance.suggestionCount, | |
| 1180 ' ', | |
| 1181 HTML_ESCAPE.convert(performance.snippet) | |
| 1182 ]); | |
| 1183 ++index; | |
| 1184 } | |
| 1185 | |
| 1186 buffer.write('</table>'); | |
| 1187 buffer.write(''' | |
| 1188 <p><strong>First (ms)</strong> - the number of milliseconds | |
| 1189 from when completion received the request until the first notification | |
| 1190 with completion results was queued for sending back to the client. | |
| 1191 <p><strong>Complete (ms)</strong> - the number of milliseconds | |
| 1192 from when completion received the request until the final notification | |
| 1193 with completion results was queued for sending back to the client. | |
| 1194 <p><strong># Notifications</strong> - the total number of notifications | |
| 1195 sent to the client with completion results for this request. | |
| 1196 <p><strong># Suggestions</strong> - the number of suggestions | |
| 1197 sent to the client in the first notification, followed by a comma, | |
| 1198 followed by the number of suggestions send to the client | |
| 1199 in the last notification. If there is only one notification, | |
| 1200 then there will be only one number in this column.'''); | |
| 1201 } | |
| 1202 | |
| 1203 /** | |
| 1204 * Generate a table showing the cache values corresponding to the given | |
| 1205 * [descriptors], using [getState] to get the cache state corresponding to | |
| 1206 * each descriptor, and [getValue] to get the cached value corresponding to | |
| 1207 * each descriptor. Append the resulting HTML to the given [buffer]. The | |
| 1208 * [linkParameters] will be used if the value is too large to be displayed on | |
| 1209 * the current page and needs to be linked to a separate page. | |
| 1210 */ | |
| 1211 void _writeDescriptorTable( | |
| 1212 StringBuffer buffer, | |
| 1213 List<DataDescriptor> descriptors, | |
| 1214 CacheState getState(DataDescriptor), | |
| 1215 dynamic getValue(DataDescriptor), | |
| 1216 Map<String, String> linkParameters) { | |
| 1217 buffer.write('<dl>'); | |
| 1218 for (DataDescriptor descriptor in descriptors) { | |
| 1219 String descriptorName = HTML_ESCAPE.convert(descriptor.toString()); | |
| 1220 String descriptorState = | |
| 1221 HTML_ESCAPE.convert(getState(descriptor).toString()); | |
| 1222 buffer.write('<dt>$descriptorName ($descriptorState)</dt><dd>'); | |
| 1223 try { | |
| 1224 _writeValueAsHtml(buffer, getValue(descriptor), linkParameters); | |
| 1225 } catch (exception) { | |
| 1226 buffer.write('(${HTML_ESCAPE.convert(exception.toString())})'); | |
| 1227 } | |
| 1228 buffer.write('</dd>'); | |
| 1229 } | |
| 1230 buffer.write('</dl>'); | |
| 1231 } | |
| 1232 | |
| 1233 /** | |
| 1234 * Write the status of the edit domain (on the main status page) to the given | |
| 1235 * [buffer]. | |
| 1236 */ | |
| 1237 void _writeEditStatus(StringBuffer buffer) { | |
| 1238 buffer.write('<h3>Edit Domain</h3>'); | |
| 1239 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1240 buffer.write('<p><b>Performance Data</b></p>'); | |
| 1241 buffer.write('<p>'); | |
| 1242 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data')); | |
| 1243 buffer.write('</p>'); | |
| 1244 }, (StringBuffer buffer) {}); | |
| 1245 } | |
| 1246 | |
| 1247 /** | |
| 1248 * Write a representation of the given [caughtException] to the given | |
| 1249 * [buffer]. If [isCause] is `true`, then the exception was a cause for | |
| 1250 * another exception. | |
| 1251 */ | |
| 1252 void _writeException(StringBuffer buffer, CaughtException caughtException, | |
| 1253 {bool isCause: false}) { | |
| 1254 Object exception = caughtException.exception; | |
| 1255 | |
| 1256 if (exception is AnalysisException) { | |
| 1257 buffer.write('<p>'); | |
| 1258 if (isCause) { | |
| 1259 buffer.write('Caused by '); | |
| 1260 } | |
| 1261 buffer.write(exception.message); | |
| 1262 buffer.write('</p>'); | |
| 1263 _writeStackTrace(buffer, caughtException.stackTrace); | |
| 1264 CaughtException cause = exception.cause; | |
| 1265 if (cause != null) { | |
| 1266 buffer.write('<blockquote>'); | |
| 1267 _writeException(buffer, cause, isCause: true); | |
| 1268 buffer.write('</blockquote>'); | |
| 1269 } | |
| 1270 } else { | |
| 1271 buffer.write('<p>'); | |
| 1272 if (isCause) { | |
| 1273 buffer.write('Caused by '); | |
| 1274 } | |
| 1275 buffer.write(exception.toString()); | |
| 1276 buffer.write('<p>'); | |
| 1277 _writeStackTrace(buffer, caughtException.stackTrace); | |
| 1278 } | |
| 1279 } | |
| 1280 | |
| 1281 /** | |
| 1282 * Write the status of the execution domain (on the main status page) to the | |
| 1283 * given [buffer]. | |
| 1284 */ | |
| 1285 void _writeExecutionStatus(StringBuffer buffer) { | |
| 1286 AnalysisServer analysisServer = _server.analysisServer; | |
| 1287 ExecutionDomainHandler handler = analysisServer.handlers.firstWhere( | |
| 1288 (RequestHandler handler) => handler is ExecutionDomainHandler, | |
| 1289 orElse: () => null); | |
| 1290 Set<ExecutionService> services = new Set<ExecutionService>(); | |
| 1291 if (handler.onFileAnalyzed != null) { | |
| 1292 services.add(ExecutionService.LAUNCH_DATA); | |
| 1293 } | |
| 1294 | |
| 1295 if (handler != null) { | |
| 1296 buffer.write('<h3>Execution Domain</h3>'); | |
| 1297 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1298 _writeSubscriptionList(buffer, ExecutionService.VALUES, services); | |
| 1299 }, (StringBuffer buffer) {}); | |
| 1300 } | |
| 1301 } | |
| 1302 | |
| 1303 /** | |
| 1304 * Write a representation of an analysis option with the given [name] and | |
| 1305 * [value] to the given [buffer]. The option should be separated from other | |
| 1306 * options unless the [last] flag is true, indicating that this is the last | |
| 1307 * option in the list of options. | |
| 1308 */ | |
| 1309 void _writeOption(StringBuffer buffer, String name, Object value, | |
| 1310 {bool last: false}) { | |
| 1311 buffer.write(name); | |
| 1312 buffer.write(' = '); | |
| 1313 buffer.write(value.toString()); | |
| 1314 if (!last) { | |
| 1315 buffer.write('<br>'); | |
| 1316 } | |
| 1317 } | |
| 1318 | |
| 1319 /** | |
| 1320 * Write a standard HTML page to the given [buffer]. The page will have the | |
| 1321 * given [title] and a body that is generated by the given [body] generator. | |
| 1322 */ | |
| 1323 void _writePage(StringBuffer buffer, String title, List<String> subtitles, | |
| 1324 HtmlGenerator body) { | |
| 1325 DateTime now = new DateTime.now(); | |
| 1326 String date = "${now.month}/${now.day}/${now.year}"; | |
| 1327 String time = | |
| 1328 "${now.hour}:${_twoDigit(now.minute)}:${_twoDigit(now.second)}.${now.mil
lisecond}"; | |
| 1329 | |
| 1330 buffer.write('<!DOCTYPE html>'); | |
| 1331 buffer.write('<html>'); | |
| 1332 buffer.write('<head>'); | |
| 1333 buffer.write('<meta charset="utf-8">'); | |
| 1334 buffer.write( | |
| 1335 '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
); | |
| 1336 buffer.write('<title>$title</title>'); | |
| 1337 buffer.write('<style>'); | |
| 1338 buffer.write('a {color: #0000DD; text-decoration: none;}'); | |
| 1339 buffer.write('a:link.error {background-color: #FFEEEE;}'); | |
| 1340 buffer.write('a:visited.error {background-color: #FFEEEE;}'); | |
| 1341 buffer.write('a:hover.error {background-color: #FFEEEE;}'); | |
| 1342 buffer.write('a:active.error {background-color: #FFEEEE;}'); | |
| 1343 buffer.write( | |
| 1344 'h3 {background-color: #DDDDDD; margin-top: 0em; margin-bottom: 0em;}'); | |
| 1345 buffer.write('p {margin-top: 0.5em; margin-bottom: 0.5em;}'); | |
| 1346 // response.write('span.error {text-decoration-line: underline; text-decorati
on-color: red; text-decoration-style: wavy;}'); | |
| 1347 buffer.write( | |
| 1348 'table.column {border: 0px solid black; width: 100%; table-layout: fixed
;}'); | |
| 1349 buffer.write('td.column {vertical-align: top; width: 50%;}'); | |
| 1350 buffer.write('td.right {text-align: right;}'); | |
| 1351 buffer.write('</style>'); | |
| 1352 buffer.write('</head>'); | |
| 1353 | |
| 1354 buffer.write('<body>'); | |
| 1355 buffer.write( | |
| 1356 '<h2>$title <small><small>(as of $time on $date)</small></small></h2>'); | |
| 1357 if (subtitles != null && subtitles.isNotEmpty) { | |
| 1358 buffer.write('<blockquote>'); | |
| 1359 bool first = true; | |
| 1360 for (String subtitle in subtitles) { | |
| 1361 if (first) { | |
| 1362 first = false; | |
| 1363 } else { | |
| 1364 buffer.write('<br>'); | |
| 1365 } | |
| 1366 buffer.write('<b>'); | |
| 1367 buffer.write(subtitle); | |
| 1368 buffer.write('</b>'); | |
| 1369 } | |
| 1370 buffer.write('</blockquote>'); | |
| 1371 } | |
| 1372 try { | |
| 1373 body(buffer); | |
| 1374 } catch (exception, stackTrace) { | |
| 1375 buffer.write('<h3>Exception while creating page</h3>'); | |
| 1376 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
| 1377 } | |
| 1378 buffer.write('</body>'); | |
| 1379 buffer.write('</html>'); | |
| 1380 } | |
| 1381 | |
| 1382 /** | |
| 1383 * Write the recent output section (on the main status page) to the given | |
| 1384 * [buffer] object. | |
| 1385 */ | |
| 1386 void _writePluginStatus(StringBuffer buffer) { | |
| 1387 void writePlugin(Plugin plugin) { | |
| 1388 buffer.write(plugin.uniqueIdentifier); | |
| 1389 buffer.write(' ('); | |
| 1390 buffer.write(plugin.runtimeType); | |
| 1391 buffer.write(')<br>'); | |
| 1392 } | |
| 1393 buffer.write('<h3>Plugin Status</h3><p>'); | |
| 1394 writePlugin(AnalysisEngine.instance.enginePlugin); | |
| 1395 writePlugin(_server.serverPlugin); | |
| 1396 for (Plugin plugin in _server.analysisServer.userDefinedPlugins) { | |
| 1397 writePlugin(plugin); | |
| 1398 } | |
| 1399 buffer.write('<p>'); | |
| 1400 } | |
| 1401 | |
| 1402 /** | |
| 1403 * Write the recent output section (on the main status page) to the given | |
| 1404 * [buffer] object. | |
| 1405 */ | |
| 1406 void _writeRecentOutput(StringBuffer buffer) { | |
| 1407 buffer.write('<h3>Recent Output</h3>'); | |
| 1408 String output = HTML_ESCAPE.convert(_printBuffer.join('\n')); | |
| 1409 if (output.isEmpty) { | |
| 1410 buffer.write('<i>none</i>'); | |
| 1411 } else { | |
| 1412 buffer.write('<pre>'); | |
| 1413 buffer.write(output); | |
| 1414 buffer.write('</pre>'); | |
| 1415 } | |
| 1416 } | |
| 1417 | |
| 1418 void _writeResponse(HttpRequest request, HtmlGenerator writePage) { | |
| 1419 HttpResponse response = request.response; | |
| 1420 response.statusCode = HttpStatus.OK; | |
| 1421 response.headers.contentType = _htmlContent; | |
| 1422 try { | |
| 1423 StringBuffer buffer = new StringBuffer(); | |
| 1424 try { | |
| 1425 writePage(buffer); | |
| 1426 } catch (exception, stackTrace) { | |
| 1427 buffer.clear(); | |
| 1428 _writePage(buffer, 'Internal Exception', [], (StringBuffer buffer) { | |
| 1429 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
| 1430 }); | |
| 1431 } | |
| 1432 response.write(buffer.toString()); | |
| 1433 } finally { | |
| 1434 response.close(); | |
| 1435 } | |
| 1436 } | |
| 1437 | |
| 1438 /** | |
| 1439 * Write a single row within a table to the given [buffer]. The row will have | |
| 1440 * one cell for each of the [columns], and will be a header row if [header] is | |
| 1441 * `true`. | |
| 1442 */ | |
| 1443 void _writeRow(StringBuffer buffer, List<Object> columns, | |
| 1444 {bool header: false, List<String> classes}) { | |
| 1445 buffer.write('<tr>'); | |
| 1446 int count = columns.length; | |
| 1447 int maxClassIndex = classes == null ? 0 : classes.length - 1; | |
| 1448 for (int i = 0; i < count; i++) { | |
| 1449 String classAttribute = ''; | |
| 1450 if (classes != null) { | |
| 1451 String className = classes[min(i, maxClassIndex)]; | |
| 1452 if (className != null) { | |
| 1453 classAttribute = ' class="$className"'; | |
| 1454 } | |
| 1455 } | |
| 1456 if (header) { | |
| 1457 buffer.write('<th$classAttribute>'); | |
| 1458 } else { | |
| 1459 buffer.write('<td$classAttribute>'); | |
| 1460 } | |
| 1461 buffer.write(columns[i]); | |
| 1462 if (header) { | |
| 1463 buffer.write('</th>'); | |
| 1464 } else { | |
| 1465 buffer.write('</td>'); | |
| 1466 } | |
| 1467 } | |
| 1468 buffer.write('</tr>'); | |
| 1469 } | |
| 1470 | |
| 1471 /** | |
| 1472 * Write the status of the service domain (on the main status page) to the | |
| 1473 * given [response] object. | |
| 1474 */ | |
| 1475 bool _writeServerStatus(StringBuffer buffer) { | |
| 1476 AnalysisServer analysisServer = _server.analysisServer; | |
| 1477 Set<ServerService> services = analysisServer.serverServices; | |
| 1478 | |
| 1479 buffer.write('<h3>Server Domain</h3>'); | |
| 1480 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
| 1481 if (analysisServer == null) { | |
| 1482 buffer.write('Status: <span style="color:red">Not running</span>'); | |
| 1483 return false; | |
| 1484 } | |
| 1485 buffer.write('<p>'); | |
| 1486 buffer.write('Status: Running<br>'); | |
| 1487 buffer.write('Instrumentation: '); | |
| 1488 if (AnalysisEngine.instance.instrumentationService.isActive) { | |
| 1489 buffer.write('<span style="color:red">Active</span>'); | |
| 1490 } else { | |
| 1491 buffer.write('Inactive'); | |
| 1492 } | |
| 1493 buffer.write('<br>'); | |
| 1494 buffer.write('Version: '); | |
| 1495 buffer.write(AnalysisServer.VERSION); | |
| 1496 buffer.write('</p>'); | |
| 1497 | |
| 1498 buffer.write('<p><b>Performance Data</b></p>'); | |
| 1499 buffer.write('<p>'); | |
| 1500 buffer.write(makeLink( | |
| 1501 COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance')); | |
| 1502 buffer.write('</p>'); | |
| 1503 }, (StringBuffer buffer) { | |
| 1504 _writeSubscriptionList(buffer, ServerService.VALUES, services); | |
| 1505 }); | |
| 1506 return true; | |
| 1507 } | |
| 1508 | |
| 1509 /** | |
| 1510 * Write a representation of the given [stackTrace] to the given [buffer]. | |
| 1511 */ | |
| 1512 void _writeStackTrace(StringBuffer buffer, StackTrace stackTrace) { | |
| 1513 if (stackTrace != null) { | |
| 1514 String trace = stackTrace.toString().replaceAll('#', '<br>#'); | |
| 1515 if (trace.startsWith('<br>#')) { | |
| 1516 trace = trace.substring(4); | |
| 1517 } | |
| 1518 buffer.write('<p>'); | |
| 1519 buffer.write(trace); | |
| 1520 buffer.write('</p>'); | |
| 1521 } | |
| 1522 } | |
| 1523 | |
| 1524 /** | |
| 1525 * Given a [service] that could be subscribed to and a set of the services | |
| 1526 * that are actually subscribed to ([subscribedServices]), write a | |
| 1527 * representation of the service to the given [buffer]. | |
| 1528 */ | |
| 1529 void _writeSubscriptionInList( | |
| 1530 StringBuffer buffer, Enum service, Set<Enum> subscribedServices) { | |
| 1531 if (subscribedServices.contains(service)) { | |
| 1532 buffer.write('<code>+ </code>'); | |
| 1533 } else { | |
| 1534 buffer.write('<code>- </code>'); | |
| 1535 } | |
| 1536 buffer.write(service.name); | |
| 1537 buffer.write('<br>'); | |
| 1538 } | |
| 1539 | |
| 1540 /** | |
| 1541 * Given a [service] that could be subscribed to and a set of paths that are | |
| 1542 * subscribed to the services ([subscribedPaths]), write a representation of | |
| 1543 * the service to the given [buffer]. | |
| 1544 */ | |
| 1545 void _writeSubscriptionInMap( | |
| 1546 StringBuffer buffer, Enum service, Set<String> subscribedPaths) { | |
| 1547 buffer.write('<p>'); | |
| 1548 buffer.write(service.name); | |
| 1549 buffer.write('</p>'); | |
| 1550 if (subscribedPaths == null || subscribedPaths.isEmpty) { | |
| 1551 buffer.write('none'); | |
| 1552 } else { | |
| 1553 List<String> paths = subscribedPaths.toList(); | |
| 1554 paths.sort(); | |
| 1555 for (String path in paths) { | |
| 1556 buffer.write('<p>'); | |
| 1557 buffer.write(path); | |
| 1558 buffer.write('</p>'); | |
| 1559 } | |
| 1560 } | |
| 1561 } | |
| 1562 | |
| 1563 /** | |
| 1564 * Given a list containing all of the services that can be subscribed to in a | |
| 1565 * single domain ([allServices]) and a set of the services that are actually | |
| 1566 * subscribed to ([subscribedServices]), write a representation of the | |
| 1567 * subscriptions to the given [buffer]. | |
| 1568 */ | |
| 1569 void _writeSubscriptionList(StringBuffer buffer, List<Enum> allServices, | |
| 1570 Set<Enum> subscribedServices) { | |
| 1571 buffer.write('<p><b>Subscriptions</b></p>'); | |
| 1572 buffer.write('<p>'); | |
| 1573 for (Enum service in allServices) { | |
| 1574 _writeSubscriptionInList(buffer, service, subscribedServices); | |
| 1575 } | |
| 1576 buffer.write('</p>'); | |
| 1577 } | |
| 1578 | |
| 1579 /** | |
| 1580 * Given a list containing all of the services that can be subscribed to in a | |
| 1581 * single domain ([allServices]) and a set of the services that are actually | |
| 1582 * subscribed to ([subscribedServices]), write a representation of the | |
| 1583 * subscriptions to the given [buffer]. | |
| 1584 */ | |
| 1585 void _writeSubscriptionMap(StringBuffer buffer, List<Enum> allServices, | |
| 1586 Map<Enum, Set<String>> subscribedServices) { | |
| 1587 buffer.write('<p><b>Subscriptions</b></p>'); | |
| 1588 for (Enum service in allServices) { | |
| 1589 _writeSubscriptionInMap(buffer, service, subscribedServices[service]); | |
| 1590 } | |
| 1591 } | |
| 1592 | |
| 1593 /** | |
| 1594 * Write two columns of information to the given [buffer], where the | |
| 1595 * [leftColumn] and [rightColumn] functions are used to generate the content | |
| 1596 * of those columns. | |
| 1597 */ | |
| 1598 void _writeTwoColumns(StringBuffer buffer, HtmlGenerator leftColumn, | |
| 1599 HtmlGenerator rightColumn) { | |
| 1600 buffer | |
| 1601 .write('<table class="column"><tr class="column"><td class="column">'); | |
| 1602 leftColumn(buffer); | |
| 1603 buffer.write('</td><td class="column">'); | |
| 1604 rightColumn(buffer); | |
| 1605 buffer.write('</td></tr></table>'); | |
| 1606 } | |
| 1607 | |
| 1608 /** | |
| 1609 * Render the given [value] as HTML and append it to the given [buffer]. The | |
| 1610 * [linkParameters] will be used if the value is too large to be displayed on | |
| 1611 * the current page and needs to be linked to a separate page. | |
| 1612 */ | |
| 1613 void _writeValueAsHtml( | |
| 1614 StringBuffer buffer, Object value, Map<String, String> linkParameters) { | |
| 1615 if (value == null) { | |
| 1616 buffer.write('<i>null</i>'); | |
| 1617 } else if (value is String) { | |
| 1618 buffer.write('<pre>${HTML_ESCAPE.convert(value)}</pre>'); | |
| 1619 } else if (value is List) { | |
| 1620 buffer.write('List containing ${value.length} entries'); | |
| 1621 buffer.write('<ul>'); | |
| 1622 for (var entry in value) { | |
| 1623 buffer.write('<li>'); | |
| 1624 _writeValueAsHtml(buffer, entry, linkParameters); | |
| 1625 buffer.write('</li>'); | |
| 1626 } | |
| 1627 buffer.write('</ul>'); | |
| 1628 } else if (value is AstNode) { | |
| 1629 String link = | |
| 1630 makeLink(AST_PATH, linkParameters, value.runtimeType.toString()); | |
| 1631 buffer.write('<i>$link</i>'); | |
| 1632 } else if (value is Element) { | |
| 1633 String link = | |
| 1634 makeLink(ELEMENT_PATH, linkParameters, value.runtimeType.toString()); | |
| 1635 buffer.write('<i>$link</i>'); | |
| 1636 } else { | |
| 1637 buffer.write(HTML_ESCAPE.convert(value.toString())); | |
| 1638 buffer.write(' <i>(${value.runtimeType.toString()})</i>'); | |
| 1639 } | |
| 1640 } | |
| 1641 | |
| 1642 /** | |
| 1643 * Create a link to [path] with query parameters [params], with inner HTML | |
| 1644 * [innerHtml]. If [hasError] is `true`, then the link will have the class | |
| 1645 * 'error'. | |
| 1646 */ | |
| 1647 static String makeLink( | |
| 1648 String path, Map<String, String> params, String innerHtml, | |
| 1649 [bool hasError = false]) { | |
| 1650 Uri uri = new Uri(path: path, queryParameters: params); | |
| 1651 String href = HTML_ESCAPE.convert(uri.toString()); | |
| 1652 String classAttribute = hasError ? ' class="error"' : ''; | |
| 1653 return '<a href="$href"$classAttribute>$innerHtml</a>'; | |
| 1654 } | |
| 1655 } | |
| OLD | NEW |