| 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 protocol; | 5 library protocol; |
| 6 | 6 |
| 7 import 'dart:collection'; | 7 import 'dart:collection'; |
| 8 import 'dart:convert' show JsonDecoder; | 8 import 'dart:convert'; |
| 9 | 9 |
| 10 import 'package:analysis_server/src/services/json.dart'; | 10 import 'package:analysis_server/src/computer/element.dart' show |
| 11 elementFromEngine; |
| 12 import 'package:analysis_server/src/search/search_result.dart' show |
| 13 searchResultFromMatch; |
| 14 import 'package:analysis_server/src/services/correction/fix.dart' show Fix; |
| 15 import 'package:analysis_server/src/services/search/search_engine.dart' as |
| 16 engine; |
| 17 import 'package:analyzer/src/generated/ast.dart' as engine; |
| 18 import 'package:analyzer/src/generated/element.dart' as engine; |
| 19 import 'package:analyzer/src/generated/engine.dart' as engine; |
| 20 import 'package:analyzer/src/generated/error.dart' as engine; |
| 21 import 'package:analyzer/src/generated/source.dart' as engine; |
| 22 |
| 23 part 'generated_protocol.dart'; |
| 24 |
| 25 /** |
| 26 * Translate the input [map], applying [keyCallback] to all its keys, and |
| 27 * [valueCallback] to all its values. |
| 28 */ |
| 29 mapMap(Map map, {dynamic keyCallback(key), dynamic valueCallback(value)}) { |
| 30 Map result = {}; |
| 31 map.forEach((key, value) { |
| 32 if (keyCallback != null) { |
| 33 key = keyCallback(key); |
| 34 } |
| 35 if (valueCallback != null) { |
| 36 value = valueCallback(value); |
| 37 } |
| 38 result[key] = value; |
| 39 }); |
| 40 return result; |
| 41 } |
| 42 |
| 43 /** |
| 44 * Adds the given [sourceEdits] to the list in [sourceFileEdit]. |
| 45 */ |
| 46 void _addAllEditsForSource(SourceFileEdit sourceFileEdit, |
| 47 Iterable<SourceEdit> edits) { |
| 48 edits.forEach(sourceFileEdit.add); |
| 49 } |
| 50 |
| 51 /** |
| 52 * Adds the given [sourceEdit] to the list in [sourceFileEdit]. |
| 53 */ |
| 54 void _addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) { |
| 55 List<SourceEdit> edits = sourceFileEdit.edits; |
| 56 int index = 0; |
| 57 while (index < edits.length && edits[index].offset > sourceEdit.offset) { |
| 58 index++; |
| 59 } |
| 60 edits.insert(index, sourceEdit); |
| 61 } |
| 62 |
| 63 /** |
| 64 * Adds [edit] to the [FileEdit] for the given [file]. |
| 65 */ |
| 66 void _addEditToSourceChange(SourceChange change, String file, SourceEdit edit) { |
| 67 SourceFileEdit fileEdit = change.getFileEdit(file); |
| 68 if (fileEdit == null) { |
| 69 fileEdit = new SourceFileEdit(file); |
| 70 change.addFileEdit(fileEdit); |
| 71 } |
| 72 fileEdit.add(edit); |
| 73 } |
| 74 |
| 75 /** |
| 76 * Create an AnalysisError based on error information from the analyzer |
| 77 * engine. Access via AnalysisError.fromEngine(). |
| 78 */ |
| 79 AnalysisError _analysisErrorFromEngine(engine.LineInfo lineInfo, |
| 80 engine.AnalysisError error) { |
| 81 engine.ErrorCode errorCode = error.errorCode; |
| 82 // prepare location |
| 83 Location location; |
| 84 { |
| 85 String file = error.source.fullName; |
| 86 int offset = error.offset; |
| 87 int length = error.length; |
| 88 int startLine = -1; |
| 89 int startColumn = -1; |
| 90 if (lineInfo != null) { |
| 91 engine.LineInfo_Location lineLocation = lineInfo.getLocation(offset); |
| 92 if (lineLocation != null) { |
| 93 startLine = lineLocation.lineNumber; |
| 94 startColumn = lineLocation.columnNumber; |
| 95 } |
| 96 } |
| 97 location = new Location(file, offset, length, startLine, startColumn); |
| 98 } |
| 99 // done |
| 100 var severity = new ErrorSeverity(errorCode.errorSeverity.name); |
| 101 var type = new ErrorType(errorCode.type.name); |
| 102 String message = error.message; |
| 103 String correction = error.correction; |
| 104 return new AnalysisError( |
| 105 severity, |
| 106 type, |
| 107 location, |
| 108 message, |
| 109 correction: correction); |
| 110 } |
| 111 |
| 112 /** |
| 113 * Returns a list of AnalysisErrors correponding to the given list of Engine |
| 114 * errors. Access via AnalysisError.listFromEngine(). |
| 115 */ |
| 116 List<AnalysisError> _analysisErrorListFromEngine(engine.LineInfo lineInfo, |
| 117 List<engine.AnalysisError> errors) { |
| 118 return errors.map((engine.AnalysisError error) { |
| 119 return new AnalysisError.fromEngine(lineInfo, error); |
| 120 }).toList(); |
| 121 } |
| 122 |
| 123 /** |
| 124 * Get the result of applying the edit to the given [code]. Access via |
| 125 * SourceEdit.apply(). |
| 126 */ |
| 127 String _applyEdit(String code, SourceEdit edit) { |
| 128 return code.substring(0, edit.offset) + |
| 129 edit.replacement + |
| 130 code.substring(edit.end); |
| 131 } |
| 132 |
| 133 /** |
| 134 * Get the result of applying a set of [edits] to the given [code]. Edits |
| 135 * are applied in the order they appear in [edits]. Access via |
| 136 * SourceEdit.applySequence(). |
| 137 */ |
| 138 String _applySequence(String code, Iterable<SourceEdit> edits) { |
| 139 edits.forEach((SourceEdit edit) { |
| 140 code = edit.apply(code); |
| 141 }); |
| 142 return code; |
| 143 } |
| 144 |
| 145 /** |
| 146 * Map an element kind from the analyzer engine to a [CompletionSuggestionKind]. |
| 147 */ |
| 148 CompletionSuggestionKind _completionSuggestionKindFromElementKind(engine.Element
Kind kind) { |
| 149 // ElementKind.ANGULAR_FORMATTER, |
| 150 // ElementKind.ANGULAR_COMPONENT, |
| 151 // ElementKind.ANGULAR_CONTROLLER, |
| 152 // ElementKind.ANGULAR_DIRECTIVE, |
| 153 // ElementKind.ANGULAR_PROPERTY, |
| 154 // ElementKind.ANGULAR_SCOPE_PROPERTY, |
| 155 // ElementKind.ANGULAR_SELECTOR, |
| 156 // ElementKind.ANGULAR_VIEW, |
| 157 if (kind == engine.ElementKind.CLASS) return CompletionSuggestionKind.CLASS; |
| 158 // ElementKind.COMPILATION_UNIT, |
| 159 if (kind == engine.ElementKind.CONSTRUCTOR) return CompletionSuggestionKind.CO
NSTRUCTOR; |
| 160 // ElementKind.DYNAMIC, |
| 161 // ElementKind.EMBEDDED_HTML_SCRIPT, |
| 162 // ElementKind.ERROR, |
| 163 // ElementKind.EXPORT, |
| 164 // ElementKind.EXTERNAL_HTML_SCRIPT, |
| 165 if (kind == engine.ElementKind.FIELD) return CompletionSuggestionKind.FIELD; |
| 166 if (kind == engine.ElementKind.FUNCTION) return CompletionSuggestionKind.FUNCT
ION; |
| 167 if (kind == engine.ElementKind.FUNCTION_TYPE_ALIAS) return CompletionSuggestio
nKind.FUNCTION_TYPE_ALIAS; |
| 168 if (kind == engine.ElementKind.GETTER) return CompletionSuggestionKind.GETTER; |
| 169 // ElementKind.HTML, |
| 170 if (kind == engine.ElementKind.IMPORT) return CompletionSuggestionKind.IMPORT; |
| 171 // ElementKind.LABEL, |
| 172 // ElementKind.LIBRARY, |
| 173 if (kind == engine.ElementKind.LOCAL_VARIABLE) return CompletionSuggestionKind
.LOCAL_VARIABLE; |
| 174 if (kind == engine.ElementKind.METHOD) return CompletionSuggestionKind.METHOD; |
| 175 // ElementKind.NAME, |
| 176 if (kind == engine.ElementKind.PARAMETER) return CompletionSuggestionKind.PARA
METER; |
| 177 // ElementKind.POLYMER_ATTRIBUTE, |
| 178 // ElementKind.POLYMER_TAG_DART, |
| 179 // ElementKind.POLYMER_TAG_HTML, |
| 180 // ElementKind.PREFIX, |
| 181 if (kind == engine.ElementKind.SETTER) return CompletionSuggestionKind.SETTER; |
| 182 if (kind == engine.ElementKind.TOP_LEVEL_VARIABLE) return CompletionSuggestion
Kind.TOP_LEVEL_VARIABLE; |
| 183 // ElementKind.TYPE_PARAMETER, |
| 184 // ElementKind.UNIVERSE |
| 185 throw new ArgumentError('Unknown CompletionSuggestionKind for: $kind'); |
| 186 } |
| 187 |
| 188 /** |
| 189 * Create an ElementKind based on a value from the analyzer engine. Access |
| 190 * this function via new ElementKind.fromEngine(). |
| 191 */ |
| 192 ElementKind _elementKindFromEngine(engine.ElementKind kind) { |
| 193 if (kind == engine.ElementKind.CLASS) { |
| 194 return ElementKind.CLASS; |
| 195 } |
| 196 if (kind == engine.ElementKind.COMPILATION_UNIT) { |
| 197 return ElementKind.COMPILATION_UNIT; |
| 198 } |
| 199 if (kind == engine.ElementKind.CONSTRUCTOR) { |
| 200 return ElementKind.CONSTRUCTOR; |
| 201 } |
| 202 if (kind == engine.ElementKind.FIELD) { |
| 203 return ElementKind.FIELD; |
| 204 } |
| 205 if (kind == engine.ElementKind.FUNCTION) { |
| 206 return ElementKind.FUNCTION; |
| 207 } |
| 208 if (kind == engine.ElementKind.FUNCTION_TYPE_ALIAS) { |
| 209 return ElementKind.FUNCTION_TYPE_ALIAS; |
| 210 } |
| 211 if (kind == engine.ElementKind.GETTER) { |
| 212 return ElementKind.GETTER; |
| 213 } |
| 214 if (kind == engine.ElementKind.LIBRARY) { |
| 215 return ElementKind.LIBRARY; |
| 216 } |
| 217 if (kind == engine.ElementKind.LOCAL_VARIABLE) { |
| 218 return ElementKind.LOCAL_VARIABLE; |
| 219 } |
| 220 if (kind == engine.ElementKind.METHOD) { |
| 221 return ElementKind.METHOD; |
| 222 } |
| 223 if (kind == engine.ElementKind.PARAMETER) { |
| 224 return ElementKind.PARAMETER; |
| 225 } |
| 226 if (kind == engine.ElementKind.SETTER) { |
| 227 return ElementKind.SETTER; |
| 228 } |
| 229 if (kind == engine.ElementKind.TOP_LEVEL_VARIABLE) { |
| 230 return ElementKind.TOP_LEVEL_VARIABLE; |
| 231 } |
| 232 if (kind == engine.ElementKind.TYPE_PARAMETER) { |
| 233 return ElementKind.TYPE_PARAMETER; |
| 234 } |
| 235 return ElementKind.UNKNOWN; |
| 236 } |
| 237 |
| 238 /** |
| 239 * Returns the [FileEdit] for the given [file], maybe `null`. |
| 240 */ |
| 241 SourceFileEdit _getChangeFileEdit(SourceChange change, String file) { |
| 242 for (SourceFileEdit fileEdit in change.edits) { |
| 243 if (fileEdit.file == file) { |
| 244 return fileEdit; |
| 245 } |
| 246 } |
| 247 return null; |
| 248 } |
| 249 |
| 250 /** |
| 251 * Compare the lists [listA] and [listB], using [itemEqual] to compare |
| 252 * list elements. |
| 253 */ |
| 254 bool _listEqual(List listA, List listB, bool itemEqual(a, b)) { |
| 255 if (listA.length != listB.length) { |
| 256 return false; |
| 257 } |
| 258 for (int i = 0; i < listA.length; i++) { |
| 259 if (!itemEqual(listA[i], listB[i])) { |
| 260 return false; |
| 261 } |
| 262 } |
| 263 return true; |
| 264 } |
| 265 |
| 266 /** |
| 267 * Creates a new [Location]. |
| 268 */ |
| 269 Location _locationForArgs(engine.AnalysisContext context, engine.Source source, |
| 270 engine.SourceRange range) { |
| 271 int startLine = 0; |
| 272 int startColumn = 0; |
| 273 { |
| 274 engine.LineInfo lineInfo = context.getLineInfo(source); |
| 275 if (lineInfo != null) { |
| 276 engine.LineInfo_Location offsetLocation = |
| 277 lineInfo.getLocation(range.offset); |
| 278 startLine = offsetLocation.lineNumber; |
| 279 startColumn = offsetLocation.columnNumber; |
| 280 } |
| 281 } |
| 282 return new Location( |
| 283 source.fullName, |
| 284 range.offset, |
| 285 range.length, |
| 286 startLine, |
| 287 startColumn); |
| 288 } |
| 289 |
| 290 /** |
| 291 * Creates a new [Location] for the given [engine.Element]. |
| 292 */ |
| 293 Location _locationFromElement(engine.Element element) { |
| 294 engine.AnalysisContext context = element.context; |
| 295 engine.Source source = element.source; |
| 296 String name = element.displayName; |
| 297 int offset = element.nameOffset; |
| 298 int length = name != null ? name.length : 0; |
| 299 if (element is engine.CompilationUnitElement) { |
| 300 offset = 0; |
| 301 length = 0; |
| 302 } |
| 303 engine.SourceRange range = new engine.SourceRange(offset, length); |
| 304 return _locationForArgs(context, source, range); |
| 305 } |
| 306 |
| 307 /** |
| 308 * Creates a new [Location] for the given [engine.SearchMatch]. |
| 309 */ |
| 310 Location _locationFromMatch(engine.SearchMatch match) { |
| 311 engine.Element enclosingElement = match.element; |
| 312 return _locationForArgs( |
| 313 enclosingElement.context, |
| 314 enclosingElement.source, |
| 315 match.sourceRange); |
| 316 } |
| 317 |
| 318 /** |
| 319 * Creates a new [Location] for the given [engine.AstNode]. |
| 320 */ |
| 321 Location _locationFromNode(engine.AstNode node) { |
| 322 engine.CompilationUnit unit = |
| 323 node.getAncestor((node) => node is engine.CompilationUnit); |
| 324 engine.CompilationUnitElement unitElement = unit.element; |
| 325 engine.AnalysisContext context = unitElement.context; |
| 326 engine.Source source = unitElement.source; |
| 327 engine.SourceRange range = new engine.SourceRange(node.offset, node.length); |
| 328 return _locationForArgs(context, source, range); |
| 329 } |
| 330 |
| 331 /** |
| 332 * Creates a new [Location] for the given [engine.CompilationUnit]. |
| 333 */ |
| 334 Location _locationFromUnit(engine.CompilationUnit unit, |
| 335 engine.SourceRange range) { |
| 336 engine.CompilationUnitElement unitElement = unit.element; |
| 337 engine.AnalysisContext context = unitElement.context; |
| 338 engine.Source source = unitElement.source; |
| 339 return _locationForArgs(context, source, range); |
| 340 } |
| 341 |
| 342 /** |
| 343 * Compare the maps [mapA] and [mapB], using [valueEqual] to compare map |
| 344 * values. |
| 345 */ |
| 346 bool _mapEqual(Map mapA, Map mapB, bool valueEqual(a, b)) { |
| 347 if (mapA.length != mapB.length) { |
| 348 return false; |
| 349 } |
| 350 for (var key in mapA.keys) { |
| 351 if (!mapB.containsKey(key)) { |
| 352 return false; |
| 353 } |
| 354 if (!valueEqual(mapA[key], mapB[key])) { |
| 355 return false; |
| 356 } |
| 357 } |
| 358 return true; |
| 359 } |
| 360 |
| 361 RefactoringProblemSeverity |
| 362 _maxRefactoringProblemSeverity(RefactoringProblemSeverity a, |
| 363 RefactoringProblemSeverity b) { |
| 364 if (b == null) { |
| 365 return a; |
| 366 } |
| 367 if (a == null) { |
| 368 return b; |
| 369 } else if (a == RefactoringProblemSeverity.INFO) { |
| 370 return b; |
| 371 } else if (a == RefactoringProblemSeverity.WARNING) { |
| 372 if (b == RefactoringProblemSeverity.ERROR || |
| 373 b == RefactoringProblemSeverity.FATAL) { |
| 374 return b; |
| 375 } |
| 376 } else if (a == RefactoringProblemSeverity.ERROR) { |
| 377 if (b == RefactoringProblemSeverity.FATAL) { |
| 378 return b; |
| 379 } |
| 380 } |
| 381 return a; |
| 382 } |
| 383 |
| 384 /** |
| 385 * Create an OverriddenMember based on an element from the analyzer engine. |
| 386 */ |
| 387 OverriddenMember _overriddenMemberFromEngine(engine.Element member) { |
| 388 Element element = elementFromEngine(member); |
| 389 String className = member.enclosingElement.displayName; |
| 390 return new OverriddenMember(element, className); |
| 391 } |
| 392 |
| 393 |
| 394 /** |
| 395 * Create a SearchResultKind based on a value from the search engine. |
| 396 */ |
| 397 SearchResultKind _searchResultKindFromEngine(engine.MatchKind kind) { |
| 398 if (kind == engine.MatchKind.DECLARATION) { |
| 399 return SearchResultKind.DECLARATION; |
| 400 } |
| 401 if (kind == engine.MatchKind.READ) { |
| 402 return SearchResultKind.READ; |
| 403 } |
| 404 if (kind == engine.MatchKind.READ_WRITE) { |
| 405 return SearchResultKind.READ_WRITE; |
| 406 } |
| 407 if (kind == engine.MatchKind.WRITE) { |
| 408 return SearchResultKind.WRITE; |
| 409 } |
| 410 if (kind == engine.MatchKind.INVOCATION) { |
| 411 return SearchResultKind.INVOCATION; |
| 412 } |
| 413 if (kind == engine.MatchKind.REFERENCE) { |
| 414 return SearchResultKind.REFERENCE; |
| 415 } |
| 416 return SearchResultKind.UNKNOWN; |
| 417 } |
| 418 |
| 419 |
| 420 /** |
| 421 * Type of callbacks used to decode parts of JSON objects. [jsonPath] is a |
| 422 * string describing the part of the JSON object being decoded, and [value] is |
| 423 * the part to decode. |
| 424 */ |
| 425 typedef Object JsonDecoderCallback(String jsonPath, Object value); |
| 426 |
| 427 /** |
| 428 * Base class for decoding JSON objects. The derived class must implement |
| 429 * error reporting logic. |
| 430 */ |
| 431 abstract class JsonDecoder { |
| 432 /** |
| 433 * Create an exception to throw if the JSON object at [jsonPath] fails to |
| 434 * match the API definition of [expected]. |
| 435 */ |
| 436 dynamic mismatch(String jsonPath, String expected); |
| 437 |
| 438 /** |
| 439 * Create an exception to throw if the JSON object at [jsonPath] is missing |
| 440 * the key [key]. |
| 441 */ |
| 442 dynamic missingKey(String jsonPath, String key); |
| 443 |
| 444 /** |
| 445 * Decode a JSON object that is expected to be a boolean. The strings "true" |
| 446 * and "false" are also accepted. |
| 447 */ |
| 448 bool _decodeBool(String jsonPath, Object json) { |
| 449 if (json is bool) { |
| 450 return json; |
| 451 } else if (json == 'true') { |
| 452 return true; |
| 453 } else if (json == 'false') { |
| 454 return false; |
| 455 } |
| 456 throw mismatch(jsonPath, 'bool'); |
| 457 } |
| 458 |
| 459 /** |
| 460 * Decode a JSON object that is expected to be an integer. A string |
| 461 * representation of an integer is also accepted. |
| 462 */ |
| 463 int _decodeInt(String jsonPath, Object json) { |
| 464 if (json is int) { |
| 465 return json; |
| 466 } else if (json is String) { |
| 467 return int.parse(json, onError: (String value) { |
| 468 throw mismatch(jsonPath, 'int'); |
| 469 }); |
| 470 } |
| 471 throw mismatch(jsonPath, 'int'); |
| 472 } |
| 473 |
| 474 /** |
| 475 * Decode a JSON object that is expected to be a List. [decoder] is used to |
| 476 * decode the items in the list. |
| 477 */ |
| 478 List _decodeList(String jsonPath, Object json, |
| 479 [JsonDecoderCallback decoder]) { |
| 480 if (json == null) { |
| 481 return []; |
| 482 } else if (json is List) { |
| 483 List result = []; |
| 484 for (int i = 0; i < json.length; i++) { |
| 485 result.add(decoder('$jsonPath[$i]', json[i])); |
| 486 } |
| 487 return result; |
| 488 } else { |
| 489 throw mismatch(jsonPath, 'List'); |
| 490 } |
| 491 } |
| 492 |
| 493 /** |
| 494 * Decode a JSON object that is expected to be a Map. [keyDecoder] is used |
| 495 * to decode the keys, and [valueDecoder] is used to decode the values. |
| 496 */ |
| 497 Map _decodeMap(String jsonPath, Object json, {JsonDecoderCallback keyDecoder, |
| 498 JsonDecoderCallback valueDecoder}) { |
| 499 if (json == null) { |
| 500 return {}; |
| 501 } else if (json is Map) { |
| 502 Map result = {}; |
| 503 json.forEach((String key, value) { |
| 504 Object decodedKey; |
| 505 if (keyDecoder != null) { |
| 506 decodedKey = keyDecoder('$jsonPath.key', key); |
| 507 } else { |
| 508 decodedKey = key; |
| 509 } |
| 510 if (valueDecoder != null) { |
| 511 value = valueDecoder('$jsonPath[${JSON.encode(key)}]', value); |
| 512 } |
| 513 result[decodedKey] = value; |
| 514 }); |
| 515 return result; |
| 516 } else { |
| 517 throw mismatch(jsonPath, 'Map'); |
| 518 } |
| 519 } |
| 520 |
| 521 /** |
| 522 * Decode a JSON object that is expected to be a string. |
| 523 */ |
| 524 String _decodeString(String jsonPath, Object json) { |
| 525 if (json is String) { |
| 526 return json; |
| 527 } else { |
| 528 throw mismatch(jsonPath, 'String'); |
| 529 } |
| 530 } |
| 531 |
| 532 /** |
| 533 * Decode a JSON object that is expected to be one of several choices, |
| 534 * where the choices are disambiguated by the contents of the field [field]. |
| 535 * [decoders] is a map from each possible string in the field to the decoder |
| 536 * that should be used to decode the JSON object. |
| 537 */ |
| 538 Object _decodeUnion(String jsonPath, Map json, String field, Map<String, |
| 539 JsonDecoderCallback> decoders) { |
| 540 if (json is Map) { |
| 541 if (!json.containsKey(field)) { |
| 542 throw missingKey(jsonPath, field); |
| 543 } |
| 544 var disambiguatorPath = '$jsonPath[${JSON.encode(field)}]'; |
| 545 String disambiguator = _decodeString(disambiguatorPath, json[field]); |
| 546 if (!decoders.containsKey(disambiguator)) { |
| 547 throw mismatch(disambiguatorPath, 'One of: ${decoders.keys.toList()}'); |
| 548 } |
| 549 return decoders[disambiguator](jsonPath, json); |
| 550 } else { |
| 551 throw mismatch(jsonPath, 'Map'); |
| 552 } |
| 553 } |
| 554 } |
| 555 |
| 556 |
| 557 /** |
| 558 * Instances of the class [Notification] represent a notification from the |
| 559 * server about an event that occurred. |
| 560 */ |
| 561 class Notification { |
| 562 /** |
| 563 * The name of the JSON attribute containing the name of the event that |
| 564 * triggered the notification. |
| 565 */ |
| 566 static const String EVENT = 'event'; |
| 567 |
| 568 /** |
| 569 * The name of the JSON attribute containing the result values. |
| 570 */ |
| 571 static const String PARAMS = 'params'; |
| 572 |
| 573 /** |
| 574 * The name of the event that triggered the notification. |
| 575 */ |
| 576 final String event; |
| 577 |
| 578 /** |
| 579 * A table mapping the names of notification parameters to their values, or |
| 580 * null if there are no notification parameters. |
| 581 */ |
| 582 Map<String, Object> _params; |
| 583 |
| 584 /** |
| 585 * Initialize a newly created [Notification] to have the given [event] name. |
| 586 * If [_params] is provided, it will be used as the params; otherwise no |
| 587 * params will be used. |
| 588 */ |
| 589 Notification(this.event, [this._params]); |
| 590 |
| 591 /** |
| 592 * Initialize a newly created instance based upon the given JSON data |
| 593 */ |
| 594 factory Notification.fromJson(Map<String, Object> json) { |
| 595 return new Notification(json[Notification.EVENT], |
| 596 json[Notification.PARAMS]); |
| 597 } |
| 598 |
| 599 /** |
| 600 * Return a table representing the structure of the Json object that will be |
| 601 * sent to the client to represent this response. |
| 602 */ |
| 603 Map<String, Object> toJson() { |
| 604 Map<String, Object> jsonObject = {}; |
| 605 jsonObject[EVENT] = event; |
| 606 if (_params != null) { |
| 607 jsonObject[PARAMS] = _params; |
| 608 } |
| 609 return jsonObject; |
| 610 } |
| 611 } |
| 612 |
| 11 | 613 |
| 12 /** | 614 /** |
| 13 * Instances of the class [Request] represent a request that was received. | 615 * Instances of the class [Request] represent a request that was received. |
| 14 */ | 616 */ |
| 15 class Request { | 617 class Request { |
| 16 /** | 618 /** |
| 17 * The name of the JSON attribute containing the id of the request. | 619 * The name of the JSON attribute containing the id of the request. |
| 18 */ | 620 */ |
| 19 static const String ID = 'id'; | 621 static const String ID = 'id'; |
| 20 | 622 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 34 final String id; | 636 final String id; |
| 35 | 637 |
| 36 /** | 638 /** |
| 37 * The method being requested. | 639 * The method being requested. |
| 38 */ | 640 */ |
| 39 final String method; | 641 final String method; |
| 40 | 642 |
| 41 /** | 643 /** |
| 42 * A table mapping the names of request parameters to their values. | 644 * A table mapping the names of request parameters to their values. |
| 43 */ | 645 */ |
| 44 final Map<String, Object> params; | 646 final Map<String, Object> _params; |
| 45 | |
| 46 /** | |
| 47 * A decoder that can be used to decode strings into JSON objects. | |
| 48 */ | |
| 49 static const JsonDecoder DECODER = const JsonDecoder(null); | |
| 50 | 647 |
| 51 /** | 648 /** |
| 52 * Initialize a newly created [Request] to have the given [id] and [method] | 649 * Initialize a newly created [Request] to have the given [id] and [method] |
| 53 * name. If [params] is supplied, it is used as the "params" map for the | 650 * name. If [params] is supplied, it is used as the "params" map for the |
| 54 * request. Otherwise an empty "params" map is allocated. | 651 * request. Otherwise an empty "params" map is allocated. |
| 55 */ | 652 */ |
| 56 Request(this.id, this.method, [Map<String, Object> params]) | 653 Request(this.id, this.method, [Map<String, Object> params]) |
| 57 : params = params != null ? params : new HashMap<String, Object>(); | 654 : _params = params != null ? params : new HashMap<String, Object>(); |
| 58 | 655 |
| 59 /** | 656 /** |
| 60 * Return a request parsed from the given [data], or `null` if the [data] is | 657 * Return a request parsed from the given [data], or `null` if the [data] is |
| 61 * not a valid json representation of a request. The [data] is expected to | 658 * not a valid json representation of a request. The [data] is expected to |
| 62 * have the following format: | 659 * have the following format: |
| 63 * | 660 * |
| 64 * { | 661 * { |
| 65 * 'id': String, | 662 * 'id': String, |
| 66 * 'method': methodName, | 663 * 'method': methodName, |
| 67 * 'params': { | 664 * 'params': { |
| 68 * paramter_name: value | 665 * paramter_name: value |
| 69 * } | 666 * } |
| 70 * } | 667 * } |
| 71 * | 668 * |
| 72 * where the parameters are optional and can contain any number of name/value | 669 * where the parameters are optional and can contain any number of name/value |
| 73 * pairs. | 670 * pairs. |
| 74 */ | 671 */ |
| 75 factory Request.fromString(String data) { | 672 factory Request.fromString(String data) { |
| 76 try { | 673 try { |
| 77 var result = DECODER.convert(data); | 674 var result = JSON.decode(data); |
| 78 if (result is! Map) { | 675 if (result is! Map) { |
| 79 return null; | 676 return null; |
| 80 } | 677 } |
| 81 var id = result[Request.ID]; | 678 var id = result[Request.ID]; |
| 82 var method = result[Request.METHOD]; | 679 var method = result[Request.METHOD]; |
| 83 if (id is! String || method is! String) { | 680 if (id is! String || method is! String) { |
| 84 return null; | 681 return null; |
| 85 } | 682 } |
| 86 var params = result[Request.PARAMS]; | 683 var params = result[Request.PARAMS]; |
| 87 if (params is Map || params == null) { | 684 if (params is Map || params == null) { |
| 88 return new Request(id, method, params); | 685 return new Request(id, method, params); |
| 89 } else { | 686 } else { |
| 90 return null; | 687 return null; |
| 91 } | 688 } |
| 92 } catch (exception) { | 689 } catch (exception) { |
| 93 return null; | 690 return null; |
| 94 } | 691 } |
| 95 } | 692 } |
| 96 | 693 |
| 97 /** | 694 /** |
| 98 * Return a table representing the structure of the Json object that will be | 695 * Return a table representing the structure of the Json object that will be |
| 99 * sent to the client to represent this response. | 696 * sent to the client to represent this response. |
| 100 */ | 697 */ |
| 101 Map<String, Object> toJson() { | 698 Map<String, Object> toJson() { |
| 102 Map<String, Object> jsonObject = new HashMap<String, Object>(); | 699 Map<String, Object> jsonObject = new HashMap<String, Object>(); |
| 103 jsonObject[ID] = id; | 700 jsonObject[ID] = id; |
| 104 jsonObject[METHOD] = method; | 701 jsonObject[METHOD] = method; |
| 105 if (params.isNotEmpty) { | 702 if (_params.isNotEmpty) { |
| 106 jsonObject[PARAMS] = params; | 703 jsonObject[PARAMS] = _params; |
| 107 } | 704 } |
| 108 return jsonObject; | 705 return jsonObject; |
| 109 } | 706 } |
| 110 } | 707 } |
| 111 | 708 |
| 112 /** | 709 /** |
| 113 * Instances of the class [Response] represent a response to a request. | 710 * JsonDecoder for decoding requests. Errors are reporting by throwing a |
| 711 * [RequestFailure]. |
| 114 */ | 712 */ |
| 115 class Response { | 713 class RequestDecoder extends JsonDecoder { |
| 116 /** | 714 /** |
| 117 * The [Response] instance that is returned when a real [Response] cannot | 715 * The request being deserialized. |
| 118 * be provided at the moment. | |
| 119 */ | 716 */ |
| 120 static final Response DELAYED_RESPONSE = new Response('DELAYED_RESPONSE'); | 717 final Request _request; |
| 121 | 718 |
| 122 /** | 719 RequestDecoder(this._request); |
| 123 * The name of the JSON attribute containing the id of the request for which | |
| 124 * this is a response. | |
| 125 */ | |
| 126 static const String ID = 'id'; | |
| 127 | 720 |
| 128 /** | 721 @override |
| 129 * The name of the JSON attribute containing the error message. | 722 dynamic mismatch(String jsonPath, String expected) { |
| 130 */ | 723 return new RequestFailure( |
| 131 static const String ERROR = 'error'; | 724 new Response.invalidParameter(_request, jsonPath, 'be $expected')); |
| 132 | |
| 133 /** | |
| 134 * The name of the JSON attribute containing the result values. | |
| 135 */ | |
| 136 static const String RESULT = 'result'; | |
| 137 | |
| 138 /** | |
| 139 * The unique identifier used to identify the request that this response is | |
| 140 * associated with. | |
| 141 */ | |
| 142 final String id; | |
| 143 | |
| 144 /** | |
| 145 * The error that was caused by attempting to handle the request, or `null` if | |
| 146 * there was no error. | |
| 147 */ | |
| 148 final RequestError error; | |
| 149 | |
| 150 /** | |
| 151 * A table mapping the names of result fields to their values. Should be | |
| 152 * null if there is no result to send. | |
| 153 */ | |
| 154 Map<String, Object> result; | |
| 155 | |
| 156 /** | |
| 157 * Initialize a newly created instance to represent a response to a request | |
| 158 * with the given [id]. If [result] is provided, it will be used as the | |
| 159 * result; otherwise an empty result will be used. If an [error] is provided | |
| 160 * then the response will represent an error condition. | |
| 161 */ | |
| 162 Response(this.id, {this.result, this.error}); | |
| 163 | |
| 164 /** | |
| 165 * Initialize a newly created instance to represent an error condition caused | |
| 166 * by a [request] referencing a context that does not exist. | |
| 167 */ | |
| 168 Response.contextDoesNotExist(Request request) | |
| 169 : this(request.id, error: new RequestError('NONEXISTENT_CONTEXT', 'Context d
oes not exist')); | |
| 170 | |
| 171 /** | |
| 172 * Initialize a newly created instance to represent an error condition caused | |
| 173 * by a [request] that had invalid parameter. [path] is the path to the | |
| 174 * invalid parameter, in Javascript notation (e.g. "foo.bar" means that the | |
| 175 * parameter "foo" contained a key "bar" whose value was the wrong type). | |
| 176 * [expectation] is a description of the type of data that was expected. | |
| 177 */ | |
| 178 Response.invalidParameter(Request request, String path, String expectation) | |
| 179 : this(request.id, error: new RequestError('INVALID_PARAMETER', | |
| 180 "Expected parameter $path to $expectation")); | |
| 181 | |
| 182 /** | |
| 183 * Initialize a newly created instance to represent an error condition caused | |
| 184 * by a malformed request. | |
| 185 */ | |
| 186 Response.invalidRequestFormat() | |
| 187 : this('', error: new RequestError('INVALID_REQUEST', 'Invalid request')); | |
| 188 | |
| 189 /** | |
| 190 * Initialize a newly created instance to represent an error condition caused | |
| 191 * by a [request] that does not have a required parameter. | |
| 192 */ | |
| 193 Response.missingRequiredParameter(Request request, String parameterName) | |
| 194 : this(request.id, error: new RequestError('MISSING_PARAMETER', 'Missing req
uired parameter: $parameterName')); | |
| 195 | |
| 196 /** | |
| 197 * Initialize a newly created instance to represent an error condition caused | |
| 198 * by a [request] that takes a set of analysis options but for which an | |
| 199 * unknown analysis option was provided. | |
| 200 */ | |
| 201 Response.unknownAnalysisOption(Request request, String optionName) | |
| 202 : this(request.id, error: new RequestError('UNKNOWN_ANALYSIS_OPTION', 'Unkno
wn analysis option: "$optionName"')); | |
| 203 | |
| 204 /** | |
| 205 * Initialize a newly created instance to represent an error condition caused | |
| 206 * by a [request] that cannot be handled by any known handlers. | |
| 207 */ | |
| 208 Response.unknownRequest(Request request) | |
| 209 : this(request.id, error: new RequestError('UNKNOWN_REQUEST', 'Unknown reque
st')); | |
| 210 | |
| 211 Response.contextAlreadyExists(Request request) | |
| 212 : this(request.id, error: new RequestError('CONTENT_ALREADY_EXISTS', 'Contex
t already exists')); | |
| 213 | |
| 214 Response.unsupportedFeature(String requestId, String message) | |
| 215 : this(requestId, error: new RequestError('UNSUPPORTED_FEATURE', message)); | |
| 216 | |
| 217 /** | |
| 218 * Initialize a newly created instance to represent an error condition caused | |
| 219 * by a `analysis.setSubscriptions` [request] that includes an unknown | |
| 220 * analysis service name. | |
| 221 */ | |
| 222 Response.unknownAnalysisService(Request request, String name) | |
| 223 : this(request.id, error: new RequestError('UNKNOWN_ANALYSIS_SERVICE', 'Unkn
own analysis service: "$name"')); | |
| 224 | |
| 225 /** | |
| 226 * Initialize a newly created instance to represent an error condition caused | |
| 227 * by a `analysis.setPriorityFiles` [request] that includes one or more files | |
| 228 * that are not being analyzed. | |
| 229 */ | |
| 230 Response.unanalyzedPriorityFiles(Request request, String fileNames) | |
| 231 : this(request.id, error: new RequestError('UNANALYZED_PRIORITY_FILES', "Una
nalyzed files cannot be a priority: '$fileNames'")); | |
| 232 | |
| 233 /** | |
| 234 * Initialize a newly created instance to represent an error condition caused | |
| 235 * by a `analysis.updateOptions` [request] that includes an unknown analysis | |
| 236 * option. | |
| 237 */ | |
| 238 Response.unknownOptionName(Request request, String optionName) | |
| 239 : this(request.id, error: new RequestError('UNKNOWN_OPTION_NAME', 'Unknown a
nalysis option: "$optionName"')); | |
| 240 | |
| 241 /** | |
| 242 * Initialize a newly created instance to represent an error condition caused | |
| 243 * by an error during `analysis.getErrors`. | |
| 244 */ | |
| 245 Response.getErrorsError(Request request, String message, | |
| 246 Map<String, Object> result) | |
| 247 : this( | |
| 248 request.id, | |
| 249 error: new RequestError('GET_ERRORS_ERROR', 'Error during `analysis.getE
rrors`: $message.'), | |
| 250 result: result); | |
| 251 | |
| 252 /** | |
| 253 * Initialize a newly created instance based upon the given JSON data | |
| 254 */ | |
| 255 factory Response.fromJson(Map<String, Object> json) { | |
| 256 try { | |
| 257 Object id = json[Response.ID]; | |
| 258 if (id is! String) { | |
| 259 return null; | |
| 260 } | |
| 261 Object error = json[Response.ERROR]; | |
| 262 RequestError decodedError; | |
| 263 if (error is Map) { | |
| 264 decodedError = new RequestError.fromJson(error); | |
| 265 } | |
| 266 Object result = json[Response.RESULT]; | |
| 267 Map<String, Object> decodedResult; | |
| 268 if (result is Map) { | |
| 269 decodedResult = result; | |
| 270 } | |
| 271 return new Response(id, error: decodedError, | |
| 272 result: decodedResult); | |
| 273 } catch (exception) { | |
| 274 return null; | |
| 275 } | |
| 276 } | 725 } |
| 277 | 726 |
| 278 /** | 727 @override |
| 279 * Return a table representing the structure of the Json object that will be | 728 dynamic missingKey(String jsonPath, String key) { |
| 280 * sent to the client to represent this response. | 729 return new RequestFailure( |
| 281 */ | 730 new Response.invalidParameter( |
| 282 Map<String, Object> toJson() { | 731 _request, |
| 283 Map<String, Object> jsonObject = new HashMap<String, Object>(); | 732 jsonPath, |
| 284 jsonObject[ID] = id; | 733 'contain key ${JSON.encode(key)}')); |
| 285 if (error != null) { | |
| 286 jsonObject[ERROR] = error.toJson(); | |
| 287 } | |
| 288 if (result != null) { | |
| 289 jsonObject[RESULT] = result; | |
| 290 } | |
| 291 return jsonObject; | |
| 292 } | 734 } |
| 293 } | 735 } |
| 294 | 736 |
| 295 /** | 737 /** |
| 296 * Instances of the class [RequestError] represent information about an error | 738 * Instances of the class [RequestError] represent information about an error |
| 297 * that occurred while attempting to respond to a [Request]. | 739 * that occurred while attempting to respond to a [Request]. |
| 298 */ | 740 */ |
| 299 class RequestError { | 741 class RequestError { |
| 300 /** | 742 /** |
| 301 * The name of the JSON attribute containing the code that uniquely identifies | 743 * The name of the JSON attribute containing the code that uniquely identifies |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 372 * A table mapping the names of notification parameters to their values. | 814 * A table mapping the names of notification parameters to their values. |
| 373 */ | 815 */ |
| 374 final Map<String, Object> data = new HashMap<String, Object>(); | 816 final Map<String, Object> data = new HashMap<String, Object>(); |
| 375 | 817 |
| 376 /** | 818 /** |
| 377 * Initialize a newly created [Error] to have the given [code] and [message]. | 819 * Initialize a newly created [Error] to have the given [code] and [message]. |
| 378 */ | 820 */ |
| 379 RequestError(this.code, this.message); | 821 RequestError(this.code, this.message); |
| 380 | 822 |
| 381 /** | 823 /** |
| 382 * Initialize a newly created [Error] to indicate a parse error. Invalid JSON | 824 * Initialize a newly created [Error] from the given JSON. |
| 383 * was received by the server. An error occurred on the server while parsing | |
| 384 * the JSON text. | |
| 385 */ | 825 */ |
| 386 RequestError.parseError() : this(CODE_PARSE_ERROR, "Parse error"); | 826 factory RequestError.fromJson(Map<String, Object> json) { |
| 827 try { |
| 828 String code = json[RequestError.CODE]; |
| 829 String message = json[RequestError.MESSAGE]; |
| 830 Map<String, Object> data = json[RequestError.DATA]; |
| 831 RequestError requestError = new RequestError(code, message); |
| 832 if (data != null) { |
| 833 data.forEach((String key, Object value) { |
| 834 requestError.setData(key, value); |
| 835 }); |
| 836 } |
| 837 return requestError; |
| 838 } catch (exception) { |
| 839 return null; |
| 840 } |
| 841 } |
| 387 | 842 |
| 388 /** | 843 /** |
| 389 * Initialize a newly created [Error] to indicate that the analysis server | 844 * Initialize a newly created [Error] to indicate an internal error. |
| 390 * has already been started (and hence won't accept new connections). | |
| 391 */ | 845 */ |
| 392 RequestError.serverAlreadyStarted() | 846 RequestError.internalError() : this(CODE_INTERNAL_ERROR, "Internal error"); |
| 393 : this(CODE_SERVER_ALREADY_STARTED, "Server already started"); | 847 |
| 848 /** |
| 849 * Initialize a newly created [Error] to indicate one or more invalid |
| 850 * parameters. |
| 851 */ |
| 852 RequestError.invalidParameters() : this(CODE_INVALID_PARAMS, "Invalid paramete
rs"); |
| 394 | 853 |
| 395 /** | 854 /** |
| 396 * Initialize a newly created [Error] to indicate an invalid request. The | 855 * Initialize a newly created [Error] to indicate an invalid request. The |
| 397 * JSON sent is not a valid [Request] object. | 856 * JSON sent is not a valid [Request] object. |
| 398 */ | 857 */ |
| 399 RequestError.invalidRequest() : this(CODE_INVALID_REQUEST, "Invalid request"); | 858 RequestError.invalidRequest() : this(CODE_INVALID_REQUEST, "Invalid request"); |
| 400 | 859 |
| 401 /** | 860 /** |
| 402 * Initialize a newly created [Error] to indicate that a method was not found. | 861 * Initialize a newly created [Error] to indicate that a method was not found. |
| 403 * Either the method does not exist or is not currently available. | 862 * Either the method does not exist or is not currently available. |
| 404 */ | 863 */ |
| 405 RequestError.methodNotFound() : this(CODE_METHOD_NOT_FOUND, "Method not found"
); | 864 RequestError.methodNotFound() : this(CODE_METHOD_NOT_FOUND, "Method not found"
); |
| 406 | 865 |
| 407 /** | 866 /** |
| 408 * Initialize a newly created [Error] to indicate one or more invalid | 867 * Initialize a newly created [Error] to indicate a parse error. Invalid JSON |
| 409 * parameters. | 868 * was received by the server. An error occurred on the server while parsing |
| 869 * the JSON text. |
| 410 */ | 870 */ |
| 411 RequestError.invalidParameters() : this(CODE_INVALID_PARAMS, "Invalid paramete
rs"); | 871 RequestError.parseError() : this(CODE_PARSE_ERROR, "Parse error"); |
| 412 | 872 |
| 413 /** | 873 /** |
| 414 * Initialize a newly created [Error] to indicate an internal error. | 874 * Initialize a newly created [Error] to indicate that the analysis server |
| 875 * has already been started (and hence won't accept new connections). |
| 415 */ | 876 */ |
| 416 RequestError.internalError() : this(CODE_INTERNAL_ERROR, "Internal error"); | 877 RequestError.serverAlreadyStarted() |
| 417 | 878 : this(CODE_SERVER_ALREADY_STARTED, "Server already started"); |
| 418 /** | |
| 419 * Initialize a newly created [Error] from the given JSON. | |
| 420 */ | |
| 421 factory RequestError.fromJson(Map<String, Object> json) { | |
| 422 try { | |
| 423 String code = json[RequestError.CODE]; | |
| 424 String message = json[RequestError.MESSAGE]; | |
| 425 Map<String, Object> data = json[RequestError.DATA]; | |
| 426 RequestError requestError = new RequestError(code, message); | |
| 427 if (data != null) { | |
| 428 data.forEach((String key, Object value) { | |
| 429 requestError.setData(key, value); | |
| 430 }); | |
| 431 } | |
| 432 return requestError; | |
| 433 } catch (exception) { | |
| 434 return null; | |
| 435 } | |
| 436 } | |
| 437 | 879 |
| 438 /** | 880 /** |
| 439 * Return the value of the data with the given [name], or `null` if there is | 881 * Return the value of the data with the given [name], or `null` if there is |
| 440 * no such data associated with this error. | 882 * no such data associated with this error. |
| 441 */ | 883 */ |
| 442 Object getData(String name) => data[name]; | 884 Object getData(String name) => data[name]; |
| 443 | 885 |
| 444 /** | 886 /** |
| 445 * Set the value of the data with the given [name] to the given [value]. | 887 * Set the value of the data with the given [name] to the given [value]. |
| 446 */ | 888 */ |
| (...skipping 12 matching lines...) Expand all Loading... |
| 459 if (!data.isEmpty) { | 901 if (!data.isEmpty) { |
| 460 jsonObject[DATA] = data; | 902 jsonObject[DATA] = data; |
| 461 } | 903 } |
| 462 return jsonObject; | 904 return jsonObject; |
| 463 } | 905 } |
| 464 | 906 |
| 465 @override | 907 @override |
| 466 String toString() => toJson().toString(); | 908 String toString() => toJson().toString(); |
| 467 } | 909 } |
| 468 | 910 |
| 469 /** | 911 |
| 470 * Instances of the class [Notification] represent a notification from the | 912 /** |
| 471 * server about an event that occurred. | 913 * Instances of the class [RequestFailure] represent an exception that occurred |
| 472 */ | 914 * during the handling of a request that requires that an error be returned to |
| 473 class Notification { | 915 * the client. |
| 474 /** | 916 */ |
| 475 * The name of the JSON attribute containing the name of the event that | 917 class RequestFailure implements Exception { |
| 476 * triggered the notification. | 918 /** |
| 477 */ | 919 * The response to be returned as a result of the failure. |
| 478 static const String EVENT = 'event'; | 920 */ |
| 479 | 921 final Response response; |
| 480 /** | 922 |
| 481 * The name of the JSON attribute containing the result values. | 923 /** |
| 482 */ | 924 * Initialize a newly created exception to return the given reponse. |
| 483 static const String PARAMS = 'params'; | 925 */ |
| 484 | 926 RequestFailure(this.response); |
| 485 /** | |
| 486 * The name of the event that triggered the notification. | |
| 487 */ | |
| 488 final String event; | |
| 489 | |
| 490 /** | |
| 491 * A table mapping the names of notification parameters to their values, or | |
| 492 * null if there are no notification parameters. | |
| 493 */ | |
| 494 Map<String, Object> params; | |
| 495 | |
| 496 /** | |
| 497 * Initialize a newly created [Notification] to have the given [event] name. | |
| 498 * If [params] is provided, it will be used as the params; otherwise no | |
| 499 * params will be used. | |
| 500 */ | |
| 501 Notification(this.event, [this.params]); | |
| 502 | |
| 503 /** | |
| 504 * Initialize a newly created instance based upon the given JSON data | |
| 505 */ | |
| 506 factory Notification.fromJson(Map<String, Object> json) { | |
| 507 try { | |
| 508 String event = json[Notification.EVENT]; | |
| 509 Object params = json[Notification.PARAMS]; | |
| 510 Notification notification = new Notification(event); | |
| 511 if (params is Map) { | |
| 512 params.forEach((String key, Object value) { | |
| 513 notification.setParameter(key, value); | |
| 514 }); | |
| 515 } | |
| 516 return notification; | |
| 517 } catch (exception) { | |
| 518 return null; | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 /** | |
| 523 * Set the value of the parameter with the given [name] to the given [value]. | |
| 524 */ | |
| 525 void setParameter(String name, Object value) { | |
| 526 if (params == null) { | |
| 527 params = new HashMap<String, Object>(); | |
| 528 } | |
| 529 params[name] = _toJson(value); | |
| 530 } | |
| 531 | |
| 532 /** | |
| 533 * Return a table representing the structure of the Json object that will be | |
| 534 * sent to the client to represent this response. | |
| 535 */ | |
| 536 Map<String, Object> toJson() { | |
| 537 Map<String, Object> jsonObject = {}; | |
| 538 jsonObject[EVENT] = event; | |
| 539 if (params != null) { | |
| 540 jsonObject[PARAMS] = params; | |
| 541 } | |
| 542 return jsonObject; | |
| 543 } | |
| 544 } | 927 } |
| 545 | 928 |
| 546 /** | 929 /** |
| 547 * Instances of the class [RequestHandler] implement a handler that can handle | 930 * Instances of the class [RequestHandler] implement a handler that can handle |
| 548 * requests and produce responses for them. | 931 * requests and produce responses for them. |
| 549 */ | 932 */ |
| 550 abstract class RequestHandler { | 933 abstract class RequestHandler { |
| 551 /** | 934 /** |
| 552 * Attempt to handle the given [request]. If the request is not recognized by | 935 * Attempt to handle the given [request]. If the request is not recognized by |
| 553 * this handler, return `null` so that other handlers will be given a chance | 936 * this handler, return `null` so that other handlers will be given a chance |
| 554 * to handle it. Otherwise, return the response that should be passed back to | 937 * to handle it. Otherwise, return the response that should be passed back to |
| 555 * the client. | 938 * the client. |
| 556 */ | 939 */ |
| 557 Response handleRequest(Request request); | 940 Response handleRequest(Request request); |
| 558 } | 941 } |
| 559 | 942 |
| 560 /** | 943 /** |
| 561 * Instances of the class [RequestFailure] represent an exception that occurred | 944 * Instances of the class [Response] represent a response to a request. |
| 562 * during the handling of a request that requires that an error be returned to | 945 */ |
| 563 * the client. | 946 class Response { |
| 564 */ | 947 /** |
| 565 class RequestFailure implements Exception { | 948 * The [Response] instance that is returned when a real [Response] cannot |
| 566 /** | 949 * be provided at the moment. |
| 567 * The response to be returned as a result of the failure. | 950 */ |
| 568 */ | 951 static final Response DELAYED_RESPONSE = new Response('DELAYED_RESPONSE'); |
| 569 final Response response; | 952 |
| 570 | 953 /** |
| 571 /** | 954 * The name of the JSON attribute containing the id of the request for which |
| 572 * Initialize a newly created exception to return the given reponse. | 955 * this is a response. |
| 573 */ | 956 */ |
| 574 RequestFailure(this.response); | 957 static const String ID = 'id'; |
| 575 } | 958 |
| 576 | 959 /** |
| 577 /** | 960 * The name of the JSON attribute containing the error message. |
| 578 * Returns a JSON presention of [value]. | 961 */ |
| 579 */ | 962 static const String ERROR = 'error'; |
| 580 _toJson(Object value) { | 963 |
| 581 if (value is HasToJson) { | 964 /** |
| 582 return value.toJson(); | 965 * The name of the JSON attribute containing the result values. |
| 583 } | 966 */ |
| 584 if (value is Iterable) { | 967 static const String RESULT = 'result'; |
| 585 return value.map((item) => _toJson(item)).toList(); | 968 |
| 586 } | 969 /** |
| 587 return value; | 970 * The unique identifier used to identify the request that this response is |
| 588 } | 971 * associated with. |
| 972 */ |
| 973 final String id; |
| 974 |
| 975 /** |
| 976 * The error that was caused by attempting to handle the request, or `null` if |
| 977 * there was no error. |
| 978 */ |
| 979 final RequestError error; |
| 980 |
| 981 /** |
| 982 * A table mapping the names of result fields to their values. Should be |
| 983 * null if there is no result to send. |
| 984 */ |
| 985 Map<String, Object> _result; |
| 986 |
| 987 /** |
| 988 * Initialize a newly created instance to represent a response to a request |
| 989 * with the given [id]. If [_result] is provided, it will be used as the |
| 990 * result; otherwise an empty result will be used. If an [error] is provided |
| 991 * then the response will represent an error condition. |
| 992 */ |
| 993 Response(this.id, {Map<String, Object> result, this.error}) |
| 994 : _result = result; |
| 995 |
| 996 Response.contextAlreadyExists(Request request) |
| 997 : this(request.id, error: new RequestError('CONTENT_ALREADY_EXISTS', 'Contex
t already exists')); |
| 998 |
| 999 /** |
| 1000 * Initialize a newly created instance to represent an error condition caused |
| 1001 * by a [request] referencing a context that does not exist. |
| 1002 */ |
| 1003 Response.contextDoesNotExist(Request request) |
| 1004 : this(request.id, error: new RequestError('NONEXISTENT_CONTEXT', 'Context d
oes not exist')); |
| 1005 |
| 1006 /** |
| 1007 * Initialize a newly created instance based upon the given JSON data |
| 1008 */ |
| 1009 factory Response.fromJson(Map<String, Object> json) { |
| 1010 try { |
| 1011 Object id = json[Response.ID]; |
| 1012 if (id is! String) { |
| 1013 return null; |
| 1014 } |
| 1015 Object error = json[Response.ERROR]; |
| 1016 RequestError decodedError; |
| 1017 if (error is Map) { |
| 1018 decodedError = new RequestError.fromJson(error); |
| 1019 } |
| 1020 Object result = json[Response.RESULT]; |
| 1021 Map<String, Object> decodedResult; |
| 1022 if (result is Map) { |
| 1023 decodedResult = result; |
| 1024 } |
| 1025 return new Response(id, error: decodedError, |
| 1026 result: decodedResult); |
| 1027 } catch (exception) { |
| 1028 return null; |
| 1029 } |
| 1030 } |
| 1031 |
| 1032 /** |
| 1033 * Initialize a newly created instance to represent an error condition caused |
| 1034 * by an error during `analysis.getErrors`. |
| 1035 */ |
| 1036 Response.getErrorsError(Request request, String message, |
| 1037 Map<String, Object> result) |
| 1038 : this( |
| 1039 request.id, |
| 1040 error: new RequestError('GET_ERRORS_ERROR', 'Error during `analysis.getE
rrors`: $message.'), |
| 1041 result: result); |
| 1042 |
| 1043 /** |
| 1044 * Initialize a newly created instance to represent an error condition caused |
| 1045 * by a [request] that had invalid parameter. [path] is the path to the |
| 1046 * invalid parameter, in Javascript notation (e.g. "foo.bar" means that the |
| 1047 * parameter "foo" contained a key "bar" whose value was the wrong type). |
| 1048 * [expectation] is a description of the type of data that was expected. |
| 1049 */ |
| 1050 Response.invalidParameter(Request request, String path, String expectation) |
| 1051 : this(request.id, error: new RequestError('INVALID_PARAMETER', |
| 1052 "Expected parameter $path to $expectation")); |
| 1053 |
| 1054 /** |
| 1055 * Initialize a newly created instance to represent an error condition caused |
| 1056 * by a malformed request. |
| 1057 */ |
| 1058 Response.invalidRequestFormat() |
| 1059 : this('', error: new RequestError('INVALID_REQUEST', 'Invalid request')); |
| 1060 |
| 1061 /** |
| 1062 * Initialize a newly created instance to represent an error condition caused |
| 1063 * by a [request] that does not have a required parameter. |
| 1064 */ |
| 1065 Response.missingRequiredParameter(Request request, String parameterName) |
| 1066 : this(request.id, error: new RequestError('MISSING_PARAMETER', 'Missing req
uired parameter: $parameterName')); |
| 1067 |
| 1068 /** |
| 1069 * Initialize a newly created instance to represent an error condition caused |
| 1070 * by a `analysis.setPriorityFiles` [request] that includes one or more files |
| 1071 * that are not being analyzed. |
| 1072 */ |
| 1073 Response.unanalyzedPriorityFiles(Request request, String fileNames) |
| 1074 : this(request.id, error: new RequestError('UNANALYZED_PRIORITY_FILES', "Una
nalyzed files cannot be a priority: '$fileNames'")); |
| 1075 |
| 1076 /** |
| 1077 * Initialize a newly created instance to represent an error condition caused |
| 1078 * by a [request] that takes a set of analysis options but for which an |
| 1079 * unknown analysis option was provided. |
| 1080 */ |
| 1081 Response.unknownAnalysisOption(Request request, String optionName) |
| 1082 : this(request.id, error: new RequestError('UNKNOWN_ANALYSIS_OPTION', 'Unkno
wn analysis option: "$optionName"')); |
| 1083 |
| 1084 /** |
| 1085 * Initialize a newly created instance to represent an error condition caused |
| 1086 * by a `analysis.setSubscriptions` [request] that includes an unknown |
| 1087 * analysis service name. |
| 1088 */ |
| 1089 Response.unknownAnalysisService(Request request, String name) |
| 1090 : this(request.id, error: new RequestError('UNKNOWN_ANALYSIS_SERVICE', 'Unkn
own analysis service: "$name"')); |
| 1091 |
| 1092 /** |
| 1093 * Initialize a newly created instance to represent an error condition caused |
| 1094 * by a `analysis.updateOptions` [request] that includes an unknown analysis |
| 1095 * option. |
| 1096 */ |
| 1097 Response.unknownOptionName(Request request, String optionName) |
| 1098 : this(request.id, error: new RequestError('UNKNOWN_OPTION_NAME', 'Unknown a
nalysis option: "$optionName"')); |
| 1099 |
| 1100 /** |
| 1101 * Initialize a newly created instance to represent an error condition caused |
| 1102 * by a [request] that cannot be handled by any known handlers. |
| 1103 */ |
| 1104 Response.unknownRequest(Request request) |
| 1105 : this(request.id, error: new RequestError('UNKNOWN_REQUEST', 'Unknown reque
st')); |
| 1106 |
| 1107 Response.unsupportedFeature(String requestId, String message) |
| 1108 : this(requestId, error: new RequestError('UNSUPPORTED_FEATURE', message)); |
| 1109 |
| 1110 /** |
| 1111 * Return a table representing the structure of the Json object that will be |
| 1112 * sent to the client to represent this response. |
| 1113 */ |
| 1114 Map<String, Object> toJson() { |
| 1115 Map<String, Object> jsonObject = new HashMap<String, Object>(); |
| 1116 jsonObject[ID] = id; |
| 1117 if (error != null) { |
| 1118 jsonObject[ERROR] = error.toJson(); |
| 1119 } |
| 1120 if (_result != null) { |
| 1121 jsonObject[RESULT] = _result; |
| 1122 } |
| 1123 return jsonObject; |
| 1124 } |
| 1125 } |
| 1126 |
| 1127 /** |
| 1128 * JsonDecoder for decoding responses from the server. This is intended to be |
| 1129 * used only for testing. Errors are reported using bare [Exception] objects. |
| 1130 */ |
| 1131 class ResponseDecoder extends JsonDecoder { |
| 1132 @override |
| 1133 dynamic mismatch(String jsonPath, String expected) { |
| 1134 return new Exception('Expected $expected at $jsonPath'); |
| 1135 } |
| 1136 |
| 1137 @override |
| 1138 dynamic missingKey(String jsonPath, String key) { |
| 1139 return new Exception('Missing key $key at $jsonPath'); |
| 1140 } |
| 1141 } |
| 1142 |
| 1143 /** |
| 1144 * Jenkins hash function, optimized for small integers. Borrowed from |
| 1145 * sdk/lib/math/jenkins_smi_hash.dart. |
| 1146 * |
| 1147 * TODO(paulberry): Move to somewhere that can be shared with other code. |
| 1148 */ |
| 1149 class _JenkinsSmiHash { |
| 1150 static int combine(int hash, int value) { |
| 1151 hash = 0x1fffffff & (hash + value); |
| 1152 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); |
| 1153 return hash ^ (hash >> 6); |
| 1154 } |
| 1155 |
| 1156 static int finish(int hash) { |
| 1157 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); |
| 1158 hash = hash ^ (hash >> 11); |
| 1159 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); |
| 1160 } |
| 1161 |
| 1162 static int hash2(a, b) => finish(combine(combine(0, a), b)); |
| 1163 |
| 1164 static int hash4(a, b, c, d) => |
| 1165 finish(combine(combine(combine(combine(0, a), b), c), d)); |
| 1166 } |
| OLD | NEW |