| 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 /** | |
| 6 * Support for client code that needs to interact with the requests, responses | |
| 7 * and notifications that are part of the analysis server's wire protocol. | |
| 8 */ | |
| 9 library analysis_server.plugin.protocol.protocol; | |
| 10 | |
| 11 import 'dart:collection'; | |
| 12 import 'dart:convert' hide JsonDecoder; | |
| 13 | |
| 14 import 'package:analysis_server/src/protocol/protocol_internal.dart'; | |
| 15 | |
| 16 part 'generated_protocol.dart'; | |
| 17 | |
| 18 /** | |
| 19 * A [RequestHandler] that supports [startup] and [shutdown] methods. | |
| 20 * | |
| 21 * Clients may not extend, implement or mix-in this class. | |
| 22 */ | |
| 23 abstract class DomainHandler implements RequestHandler { | |
| 24 /** | |
| 25 * Perform any operations associated with the shutdown of the domain. It is | |
| 26 * not guaranteed that this method will be called. If it is, it will be | |
| 27 * called after the last [Request] has been made. | |
| 28 */ | |
| 29 void shutdown() {} | |
| 30 | |
| 31 /** | |
| 32 * Perform any operations associated with the startup of the domain. This | |
| 33 * will be called before the first [Request]. | |
| 34 */ | |
| 35 void startup() {} | |
| 36 } | |
| 37 | |
| 38 /** | |
| 39 * An interface for enumerated types in the protocol. | |
| 40 * | |
| 41 * Clients may not extend, implement or mix-in this class. | |
| 42 */ | |
| 43 abstract class Enum { | |
| 44 /** | |
| 45 * The name of the enumerated value. This should match the name of the | |
| 46 * static getter which provides access to this enumerated value. | |
| 47 */ | |
| 48 String get name; | |
| 49 } | |
| 50 | |
| 51 /** | |
| 52 * A notification from the server about an event that occurred. | |
| 53 * | |
| 54 * Clients may not extend, implement or mix-in this class. | |
| 55 */ | |
| 56 class Notification { | |
| 57 /** | |
| 58 * The name of the JSON attribute containing the name of the event that | |
| 59 * triggered the notification. | |
| 60 */ | |
| 61 static const String EVENT = 'event'; | |
| 62 | |
| 63 /** | |
| 64 * The name of the JSON attribute containing the result values. | |
| 65 */ | |
| 66 static const String PARAMS = 'params'; | |
| 67 | |
| 68 /** | |
| 69 * The name of the event that triggered the notification. | |
| 70 */ | |
| 71 final String event; | |
| 72 | |
| 73 /** | |
| 74 * A table mapping the names of notification parameters to their values, or | |
| 75 * `null` if there are no notification parameters. | |
| 76 */ | |
| 77 Map<String, Object> _params; | |
| 78 | |
| 79 /** | |
| 80 * Initialize a newly created [Notification] to have the given [event] name. | |
| 81 * If [_params] is provided, it will be used as the params; otherwise no | |
| 82 * params will be used. | |
| 83 */ | |
| 84 Notification(this.event, [this._params]); | |
| 85 | |
| 86 /** | |
| 87 * Initialize a newly created instance based on the given JSON data. | |
| 88 */ | |
| 89 factory Notification.fromJson(Map json) { | |
| 90 return new Notification(json[Notification.EVENT], | |
| 91 json[Notification.PARAMS] as Map<String, Object>); | |
| 92 } | |
| 93 | |
| 94 /** | |
| 95 * Return a table representing the structure of the Json object that will be | |
| 96 * sent to the client to represent this response. | |
| 97 */ | |
| 98 Map<String, Object> toJson() { | |
| 99 Map<String, Object> jsonObject = {}; | |
| 100 jsonObject[EVENT] = event; | |
| 101 if (_params != null) { | |
| 102 jsonObject[PARAMS] = _params; | |
| 103 } | |
| 104 return jsonObject; | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 /** | |
| 109 * A request that was received from the client. | |
| 110 * | |
| 111 * Clients may not extend, implement or mix-in this class. | |
| 112 */ | |
| 113 class Request { | |
| 114 /** | |
| 115 * The name of the JSON attribute containing the id of the request. | |
| 116 */ | |
| 117 static const String ID = 'id'; | |
| 118 | |
| 119 /** | |
| 120 * The name of the JSON attribute containing the name of the request. | |
| 121 */ | |
| 122 static const String METHOD = 'method'; | |
| 123 | |
| 124 /** | |
| 125 * The name of the JSON attribute containing the request parameters. | |
| 126 */ | |
| 127 static const String PARAMS = 'params'; | |
| 128 | |
| 129 /** | |
| 130 * The name of the optional JSON attribute indicating the time (milliseconds | |
| 131 * since epoch) at which the client made the request. | |
| 132 */ | |
| 133 static const String CLIENT_REQUEST_TIME = 'clientRequestTime'; | |
| 134 | |
| 135 /** | |
| 136 * The unique identifier used to identify this request. | |
| 137 */ | |
| 138 final String id; | |
| 139 | |
| 140 /** | |
| 141 * The method being requested. | |
| 142 */ | |
| 143 final String method; | |
| 144 | |
| 145 /** | |
| 146 * A table mapping the names of request parameters to their values. | |
| 147 */ | |
| 148 final Map<String, Object> _params; | |
| 149 | |
| 150 /** | |
| 151 * The time (milliseconds since epoch) at which the client made the request | |
| 152 * or `null` if this information is not provided by the client. | |
| 153 */ | |
| 154 final int clientRequestTime; | |
| 155 | |
| 156 /** | |
| 157 * Initialize a newly created [Request] to have the given [id] and [method] | |
| 158 * name. If [params] is supplied, it is used as the "params" map for the | |
| 159 * request. Otherwise an empty "params" map is allocated. | |
| 160 */ | |
| 161 Request(this.id, this.method, | |
| 162 [Map<String, Object> params, this.clientRequestTime]) | |
| 163 : _params = params ?? new HashMap<String, Object>(); | |
| 164 | |
| 165 /** | |
| 166 * Return a request parsed from the given json, or `null` if the [data] is | |
| 167 * not a valid json representation of a request. The [data] is expected to | |
| 168 * have the following format: | |
| 169 * | |
| 170 * { | |
| 171 * 'clientRequestTime': millisecondsSinceEpoch | |
| 172 * 'id': String, | |
| 173 * 'method': methodName, | |
| 174 * 'params': { | |
| 175 * paramter_name: value | |
| 176 * } | |
| 177 * } | |
| 178 * | |
| 179 * where both the parameters and clientRequestTime are optional. | |
| 180 * | |
| 181 * The parameters can contain any number of name/value pairs. The | |
| 182 * clientRequestTime must be an int representing the time at which the client | |
| 183 * issued the request (milliseconds since epoch). | |
| 184 */ | |
| 185 factory Request.fromJson(Map<String, dynamic> result) { | |
| 186 var id = result[Request.ID]; | |
| 187 var method = result[Request.METHOD]; | |
| 188 if (id is! String || method is! String) { | |
| 189 return null; | |
| 190 } | |
| 191 var time = result[Request.CLIENT_REQUEST_TIME]; | |
| 192 if (time != null && time is! int) { | |
| 193 return null; | |
| 194 } | |
| 195 var params = result[Request.PARAMS]; | |
| 196 if (params is Map || params == null) { | |
| 197 return new Request(id, method, params as Map<String, Object>, time); | |
| 198 } else { | |
| 199 return null; | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 /** | |
| 204 * Return a request parsed from the given [data], or `null` if the [data] is | |
| 205 * not a valid json representation of a request. The [data] is expected to | |
| 206 * have the following format: | |
| 207 * | |
| 208 * { | |
| 209 * 'clientRequestTime': millisecondsSinceEpoch | |
| 210 * 'id': String, | |
| 211 * 'method': methodName, | |
| 212 * 'params': { | |
| 213 * paramter_name: value | |
| 214 * } | |
| 215 * } | |
| 216 * | |
| 217 * where both the parameters and clientRequestTime are optional. | |
| 218 * | |
| 219 * The parameters can contain any number of name/value pairs. The | |
| 220 * clientRequestTime must be an int representing the time at which the client | |
| 221 * issued the request (milliseconds since epoch). | |
| 222 */ | |
| 223 factory Request.fromString(String data) { | |
| 224 try { | |
| 225 var result = JSON.decode(data); | |
| 226 if (result is Map) { | |
| 227 return new Request.fromJson(result as Map<String, dynamic>); | |
| 228 } | |
| 229 return null; | |
| 230 } catch (exception) { | |
| 231 return null; | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 /** | |
| 236 * Return a table representing the structure of the Json object that will be | |
| 237 * sent to the client to represent this response. | |
| 238 */ | |
| 239 Map<String, Object> toJson() { | |
| 240 Map<String, Object> jsonObject = new HashMap<String, Object>(); | |
| 241 jsonObject[ID] = id; | |
| 242 jsonObject[METHOD] = method; | |
| 243 if (_params.isNotEmpty) { | |
| 244 jsonObject[PARAMS] = _params; | |
| 245 } | |
| 246 if (clientRequestTime != null) { | |
| 247 jsonObject[CLIENT_REQUEST_TIME] = clientRequestTime; | |
| 248 } | |
| 249 return jsonObject; | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 /** | |
| 254 * An exception that occurred during the handling of a request that requires | |
| 255 * that an error be returned to the client. | |
| 256 * | |
| 257 * Clients may not extend, implement or mix-in this class. | |
| 258 */ | |
| 259 class RequestFailure implements Exception { | |
| 260 /** | |
| 261 * The response to be returned as a result of the failure. | |
| 262 */ | |
| 263 final Response response; | |
| 264 | |
| 265 /** | |
| 266 * Initialize a newly created exception to return the given reponse. | |
| 267 */ | |
| 268 RequestFailure(this.response); | |
| 269 } | |
| 270 | |
| 271 /** | |
| 272 * An object that can handle requests and produce responses for them. | |
| 273 * | |
| 274 * Clients may not extend, implement or mix-in this class. | |
| 275 */ | |
| 276 abstract class RequestHandler { | |
| 277 /** | |
| 278 * Attempt to handle the given [request]. If the request is not recognized by | |
| 279 * this handler, return `null` so that other handlers will be given a chance | |
| 280 * to handle it. Otherwise, return the response that should be passed back to | |
| 281 * the client. | |
| 282 */ | |
| 283 Response handleRequest(Request request); | |
| 284 } | |
| 285 | |
| 286 /** | |
| 287 * A response to a request. | |
| 288 * | |
| 289 * Clients may not extend, implement or mix-in this class. | |
| 290 */ | |
| 291 class Response { | |
| 292 /** | |
| 293 * The [Response] instance that is returned when a real [Response] cannot | |
| 294 * be provided at the moment. | |
| 295 */ | |
| 296 static final Response DELAYED_RESPONSE = new Response('DELAYED_RESPONSE'); | |
| 297 | |
| 298 /** | |
| 299 * The name of the JSON attribute containing the id of the request for which | |
| 300 * this is a response. | |
| 301 */ | |
| 302 static const String ID = 'id'; | |
| 303 | |
| 304 /** | |
| 305 * The name of the JSON attribute containing the error message. | |
| 306 */ | |
| 307 static const String ERROR = 'error'; | |
| 308 | |
| 309 /** | |
| 310 * The name of the JSON attribute containing the result values. | |
| 311 */ | |
| 312 static const String RESULT = 'result'; | |
| 313 | |
| 314 /** | |
| 315 * The unique identifier used to identify the request that this response is | |
| 316 * associated with. | |
| 317 */ | |
| 318 final String id; | |
| 319 | |
| 320 /** | |
| 321 * The error that was caused by attempting to handle the request, or `null` if | |
| 322 * there was no error. | |
| 323 */ | |
| 324 final RequestError error; | |
| 325 | |
| 326 /** | |
| 327 * A table mapping the names of result fields to their values. Should be | |
| 328 * `null` if there is no result to send. | |
| 329 */ | |
| 330 Map<String, Object> _result; | |
| 331 | |
| 332 /** | |
| 333 * Initialize a newly created instance to represent a response to a request | |
| 334 * with the given [id]. If [_result] is provided, it will be used as the | |
| 335 * result; otherwise an empty result will be used. If an [error] is provided | |
| 336 * then the response will represent an error condition. | |
| 337 */ | |
| 338 Response(this.id, {Map<String, Object> result, this.error}) | |
| 339 : _result = result; | |
| 340 | |
| 341 /** | |
| 342 * Create and return the `DEBUG_PORT_COULD_NOT_BE_OPENED` error response. | |
| 343 */ | |
| 344 Response.debugPortCouldNotBeOpened(Request request, dynamic error) | |
| 345 : this(request.id, | |
| 346 error: new RequestError( | |
| 347 RequestErrorCode.DEBUG_PORT_COULD_NOT_BE_OPENED, '$error')); | |
| 348 | |
| 349 /** | |
| 350 * Initialize a newly created instance to represent the FILE_NOT_ANALYZED | |
| 351 * error condition. | |
| 352 */ | |
| 353 Response.fileNotAnalyzed(Request request, String file) | |
| 354 : this(request.id, | |
| 355 error: new RequestError(RequestErrorCode.FILE_NOT_ANALYZED, | |
| 356 'File is not analyzed: $file.')); | |
| 357 | |
| 358 /** | |
| 359 * Initialize a newly created instance to represent the FORMAT_INVALID_FILE | |
| 360 * error condition. | |
| 361 */ | |
| 362 Response.formatInvalidFile(Request request) | |
| 363 : this(request.id, | |
| 364 error: new RequestError(RequestErrorCode.FORMAT_INVALID_FILE, | |
| 365 'Error during `edit.format`: invalid file.')); | |
| 366 | |
| 367 /** | |
| 368 * Initialize a newly created instance to represent the FORMAT_WITH_ERROR | |
| 369 * error condition. | |
| 370 */ | |
| 371 Response.formatWithErrors(Request request) | |
| 372 : this(request.id, | |
| 373 error: new RequestError(RequestErrorCode.FORMAT_WITH_ERRORS, | |
| 374 'Error during `edit.format`: source contains syntax errors.')); | |
| 375 | |
| 376 /** | |
| 377 * Initialize a newly created instance based on the given JSON data. | |
| 378 */ | |
| 379 factory Response.fromJson(Map json) { | |
| 380 try { | |
| 381 Object id = json[Response.ID]; | |
| 382 if (id is! String) { | |
| 383 return null; | |
| 384 } | |
| 385 Object error = json[Response.ERROR]; | |
| 386 RequestError decodedError; | |
| 387 if (error is Map) { | |
| 388 decodedError = new RequestError.fromJson( | |
| 389 new ResponseDecoder(null), '.error', error); | |
| 390 } | |
| 391 Object result = json[Response.RESULT]; | |
| 392 Map<String, Object> decodedResult; | |
| 393 if (result is Map) { | |
| 394 decodedResult = result as Map<String, Object>; | |
| 395 } | |
| 396 return new Response(id, error: decodedError, result: decodedResult); | |
| 397 } catch (exception) { | |
| 398 return null; | |
| 399 } | |
| 400 } | |
| 401 | |
| 402 /** | |
| 403 * Initialize a newly created instance to represent the | |
| 404 * GET_ERRORS_INVALID_FILE error condition. | |
| 405 */ | |
| 406 Response.getErrorsInvalidFile(Request request) | |
| 407 : this(request.id, | |
| 408 error: new RequestError(RequestErrorCode.GET_ERRORS_INVALID_FILE, | |
| 409 'Error during `analysis.getErrors`: invalid file.')); | |
| 410 | |
| 411 /** | |
| 412 * Initialize a newly created instance to represent the | |
| 413 * GET_NAVIGATION_INVALID_FILE error condition. | |
| 414 */ | |
| 415 Response.getNavigationInvalidFile(Request request) | |
| 416 : this(request.id, | |
| 417 error: new RequestError( | |
| 418 RequestErrorCode.GET_NAVIGATION_INVALID_FILE, | |
| 419 'Error during `analysis.getNavigation`: invalid file.')); | |
| 420 | |
| 421 /** | |
| 422 * Initialize a newly created instance to represent the | |
| 423 * GET_REACHABLE_SOURCES_INVALID_FILE error condition. | |
| 424 */ | |
| 425 Response.getReachableSourcesInvalidFile(Request request) | |
| 426 : this(request.id, | |
| 427 error: new RequestError( | |
| 428 RequestErrorCode.GET_REACHABLE_SOURCES_INVALID_FILE, | |
| 429 'Error during `analysis.getReachableSources`: invalid file.')); | |
| 430 | |
| 431 /** | |
| 432 * Initialize a newly created instance to represent an error condition caused | |
| 433 * by an analysis.reanalyze [request] that specifies an analysis root that is | |
| 434 * not in the current list of analysis roots. | |
| 435 */ | |
| 436 Response.invalidAnalysisRoot(Request request, String rootPath) | |
| 437 : this(request.id, | |
| 438 error: new RequestError(RequestErrorCode.INVALID_ANALYSIS_ROOT, | |
| 439 "Invalid analysis root: $rootPath")); | |
| 440 | |
| 441 /** | |
| 442 * Initialize a newly created instance to represent an error condition caused | |
| 443 * by a [request] that specifies an execution context whose context root does | |
| 444 * not exist. | |
| 445 */ | |
| 446 Response.invalidExecutionContext(Request request, String contextId) | |
| 447 : this(request.id, | |
| 448 error: new RequestError(RequestErrorCode.INVALID_EXECUTION_CONTEXT, | |
| 449 "Invalid execution context: $contextId")); | |
| 450 | |
| 451 /** | |
| 452 * Initialize a newly created instance to represent the | |
| 453 * INVALID_FILE_PATH_FORMAT error condition. | |
| 454 */ | |
| 455 Response.invalidFilePathFormat(Request request, path) | |
| 456 : this(request.id, | |
| 457 error: new RequestError(RequestErrorCode.INVALID_FILE_PATH_FORMAT, | |
| 458 'Invalid file path format: $path')); | |
| 459 | |
| 460 /** | |
| 461 * Initialize a newly created instance to represent an error condition caused | |
| 462 * by a [request] that had invalid parameter. [path] is the path to the | |
| 463 * invalid parameter, in Javascript notation (e.g. "foo.bar" means that the | |
| 464 * parameter "foo" contained a key "bar" whose value was the wrong type). | |
| 465 * [expectation] is a description of the type of data that was expected. | |
| 466 */ | |
| 467 Response.invalidParameter(Request request, String path, String expectation) | |
| 468 : this(request.id, | |
| 469 error: new RequestError(RequestErrorCode.INVALID_PARAMETER, | |
| 470 "Invalid parameter '$path'. $expectation.")); | |
| 471 | |
| 472 /** | |
| 473 * Initialize a newly created instance to represent an error condition caused | |
| 474 * by a malformed request. | |
| 475 */ | |
| 476 Response.invalidRequestFormat() | |
| 477 : this('', | |
| 478 error: new RequestError( | |
| 479 RequestErrorCode.INVALID_REQUEST, 'Invalid request')); | |
| 480 | |
| 481 /** | |
| 482 * Initialize a newly created instance to represent an error condition caused | |
| 483 * by a request that requires an index, but indexing is disabled. | |
| 484 */ | |
| 485 Response.noIndexGenerated(Request request) | |
| 486 : this(request.id, | |
| 487 error: new RequestError( | |
| 488 RequestErrorCode.NO_INDEX_GENERATED, 'Indexing is disabled')); | |
| 489 | |
| 490 /** | |
| 491 * Initialize a newly created instance to represent the | |
| 492 * ORGANIZE_DIRECTIVES_ERROR error condition. | |
| 493 */ | |
| 494 Response.organizeDirectivesError(Request request, String message) | |
| 495 : this(request.id, | |
| 496 error: new RequestError( | |
| 497 RequestErrorCode.ORGANIZE_DIRECTIVES_ERROR, message)); | |
| 498 | |
| 499 /** | |
| 500 * Initialize a newly created instance to represent the | |
| 501 * REFACTORING_REQUEST_CANCELLED error condition. | |
| 502 */ | |
| 503 Response.refactoringRequestCancelled(Request request) | |
| 504 : this(request.id, | |
| 505 error: new RequestError( | |
| 506 RequestErrorCode.REFACTORING_REQUEST_CANCELLED, | |
| 507 'The `edit.getRefactoring` request was cancelled.')); | |
| 508 | |
| 509 /** | |
| 510 * Initialize a newly created instance to represent the SERVER_ERROR error | |
| 511 * condition. | |
| 512 */ | |
| 513 factory Response.serverError(Request request, exception, stackTrace) { | |
| 514 RequestError error = | |
| 515 new RequestError(RequestErrorCode.SERVER_ERROR, exception.toString()); | |
| 516 if (stackTrace != null) { | |
| 517 error.stackTrace = stackTrace.toString(); | |
| 518 } | |
| 519 return new Response(request.id, error: error); | |
| 520 } | |
| 521 | |
| 522 /** | |
| 523 * Initialize a newly created instance to represent the | |
| 524 * SORT_MEMBERS_INVALID_FILE error condition. | |
| 525 */ | |
| 526 Response.sortMembersInvalidFile(Request request) | |
| 527 : this(request.id, | |
| 528 error: new RequestError(RequestErrorCode.SORT_MEMBERS_INVALID_FILE, | |
| 529 'Error during `edit.sortMembers`: invalid file.')); | |
| 530 | |
| 531 /** | |
| 532 * Initialize a newly created instance to represent the | |
| 533 * SORT_MEMBERS_PARSE_ERRORS error condition. | |
| 534 */ | |
| 535 Response.sortMembersParseErrors(Request request, int numErrors) | |
| 536 : this(request.id, | |
| 537 error: new RequestError(RequestErrorCode.SORT_MEMBERS_PARSE_ERRORS, | |
| 538 'Error during `edit.sortMembers`: file has $numErrors scan/parse
errors.')); | |
| 539 | |
| 540 /** | |
| 541 * Initialize a newly created instance to represent an error condition caused | |
| 542 * by a `analysis.setPriorityFiles` [request] that includes one or more files | |
| 543 * that are not being analyzed. | |
| 544 */ | |
| 545 Response.unanalyzedPriorityFiles(String requestId, String fileNames) | |
| 546 : this(requestId, | |
| 547 error: new RequestError(RequestErrorCode.UNANALYZED_PRIORITY_FILES, | |
| 548 "Unanalyzed files cannot be a priority: '$fileNames'")); | |
| 549 | |
| 550 /** | |
| 551 * Initialize a newly created instance to represent an error condition caused | |
| 552 * by a [request] that cannot be handled by any known handlers. | |
| 553 */ | |
| 554 Response.unknownRequest(Request request) | |
| 555 : this(request.id, | |
| 556 error: new RequestError( | |
| 557 RequestErrorCode.UNKNOWN_REQUEST, 'Unknown request')); | |
| 558 | |
| 559 /** | |
| 560 * Initialize a newly created instance to represent an error condition caused | |
| 561 * by a [request] referencing a source that does not exist. | |
| 562 */ | |
| 563 Response.unknownSource(Request request) | |
| 564 : this(request.id, | |
| 565 error: new RequestError( | |
| 566 RequestErrorCode.UNKNOWN_SOURCE, 'Unknown source')); | |
| 567 | |
| 568 /** | |
| 569 * Initialize a newly created instance to represent an error condition caused | |
| 570 * by a [request] for a service that is not supported. | |
| 571 */ | |
| 572 Response.unsupportedFeature(String requestId, String message) | |
| 573 : this(requestId, | |
| 574 error: new RequestError( | |
| 575 RequestErrorCode.UNSUPPORTED_FEATURE, message)); | |
| 576 | |
| 577 /** | |
| 578 * Return a table mapping the names of result fields to their values. Should | |
| 579 * be `null` if there is no result to send. | |
| 580 */ | |
| 581 Map<String, Object> get result => _result; | |
| 582 | |
| 583 /** | |
| 584 * Return a table representing the structure of the Json object that will be | |
| 585 * sent to the client to represent this response. | |
| 586 */ | |
| 587 Map<String, Object> toJson() { | |
| 588 Map<String, Object> jsonObject = new HashMap<String, Object>(); | |
| 589 jsonObject[ID] = id; | |
| 590 if (error != null) { | |
| 591 jsonObject[ERROR] = error.toJson(); | |
| 592 } | |
| 593 if (_result != null) { | |
| 594 jsonObject[RESULT] = _result; | |
| 595 } | |
| 596 return jsonObject; | |
| 597 } | |
| 598 } | |
| OLD | NEW |