Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(306)

Side by Side Diff: pkg/analysis_server/lib/src/protocol/protocol_internal.dart

Issue 1398293002: Move the wire protocol support into the public API (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Add missed files Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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.protocol.protocol_internal;
6
7 import 'dart:collection';
8 import 'dart:convert' hide JsonDecoder;
9
10 import 'package:analysis_server/plugin/protocol/protocol.dart';
11
12 final Map<String, RefactoringKind> REQUEST_ID_REFACTORING_KINDS =
13 new HashMap<String, RefactoringKind>();
14
15 /**
16 * Adds the given [sourceEdits] to the list in [sourceFileEdit].
17 */
18 void addAllEditsForSource(
19 SourceFileEdit sourceFileEdit, Iterable<SourceEdit> edits) {
20 edits.forEach(sourceFileEdit.add);
21 }
22
23 /**
24 * Adds the given [sourceEdit] to the list in [sourceFileEdit].
25 */
26 void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) {
27 List<SourceEdit> edits = sourceFileEdit.edits;
28 int index = 0;
29 while (index < edits.length && edits[index].offset > sourceEdit.offset) {
30 index++;
31 }
32 edits.insert(index, sourceEdit);
33 }
34
35 /**
36 * Adds [edit] to the [FileEdit] for the given [file].
37 */
38 void addEditToSourceChange(
39 SourceChange change, String file, int fileStamp, SourceEdit edit) {
40 SourceFileEdit fileEdit = change.getFileEdit(file);
41 if (fileEdit == null) {
42 fileEdit = new SourceFileEdit(file, fileStamp);
43 change.addFileEdit(fileEdit);
44 }
45 fileEdit.add(edit);
46 }
47
48 /**
49 * Get the result of applying the edit to the given [code]. Access via
50 * SourceEdit.apply().
51 */
52 String applyEdit(String code, SourceEdit edit) {
53 if (edit.length < 0) {
54 throw new RangeError('length is negative');
55 }
56 return code.replaceRange(edit.offset, edit.end, edit.replacement);
57 }
58
59 /**
60 * Get the result of applying a set of [edits] to the given [code]. Edits
61 * are applied in the order they appear in [edits]. Access via
62 * SourceEdit.applySequence().
63 */
64 String applySequenceOfEdits(String code, Iterable<SourceEdit> edits) {
65 edits.forEach((SourceEdit edit) {
66 code = edit.apply(code);
67 });
68 return code;
69 }
70
71 /**
72 * Returns the [FileEdit] for the given [file], maybe `null`.
73 */
74 SourceFileEdit getChangeFileEdit(SourceChange change, String file) {
75 for (SourceFileEdit fileEdit in change.edits) {
76 if (fileEdit.file == file) {
77 return fileEdit;
78 }
79 }
80 return null;
81 }
82
83 /**
84 * Compare the lists [listA] and [listB], using [itemEqual] to compare
85 * list elements.
86 */
87 bool listEqual(List listA, List listB, bool itemEqual(a, b)) {
88 if (listA == null) {
89 return listB == null;
90 }
91 if (listB == null) {
92 return false;
93 }
94 if (listA.length != listB.length) {
95 return false;
96 }
97 for (int i = 0; i < listA.length; i++) {
98 if (!itemEqual(listA[i], listB[i])) {
99 return false;
100 }
101 }
102 return true;
103 }
104
105 /**
106 * Compare the maps [mapA] and [mapB], using [valueEqual] to compare map
107 * values.
108 */
109 bool mapEqual(Map mapA, Map mapB, bool valueEqual(a, b)) {
110 if (mapA == null) {
111 return mapB == null;
112 }
113 if (mapB == null) {
114 return false;
115 }
116 if (mapA.length != mapB.length) {
117 return false;
118 }
119 for (var key in mapA.keys) {
120 if (!mapB.containsKey(key)) {
121 return false;
122 }
123 if (!valueEqual(mapA[key], mapB[key])) {
124 return false;
125 }
126 }
127 return true;
128 }
129
130 /**
131 * Translate the input [map], applying [keyCallback] to all its keys, and
132 * [valueCallback] to all its values.
133 */
134 mapMap(Map map, {dynamic keyCallback(key), dynamic valueCallback(value)}) {
135 Map result = {};
136 map.forEach((key, value) {
137 if (keyCallback != null) {
138 key = keyCallback(key);
139 }
140 if (valueCallback != null) {
141 value = valueCallback(value);
142 }
143 result[key] = value;
144 });
145 return result;
146 }
147
148 RefactoringProblemSeverity maxRefactoringProblemSeverity(
149 RefactoringProblemSeverity a, RefactoringProblemSeverity b) {
150 if (b == null) {
151 return a;
152 }
153 if (a == null) {
154 return b;
155 } else if (a == RefactoringProblemSeverity.INFO) {
156 return b;
157 } else if (a == RefactoringProblemSeverity.WARNING) {
158 if (b == RefactoringProblemSeverity.ERROR ||
159 b == RefactoringProblemSeverity.FATAL) {
160 return b;
161 }
162 } else if (a == RefactoringProblemSeverity.ERROR) {
163 if (b == RefactoringProblemSeverity.FATAL) {
164 return b;
165 }
166 }
167 return a;
168 }
169
170 /**
171 * Create a [RefactoringFeedback] corresponding the given [kind].
172 */
173 RefactoringFeedback refactoringFeedbackFromJson(
174 JsonDecoder jsonDecoder, String jsonPath, Object json, Map feedbackJson) {
175 RefactoringKind kind = jsonDecoder.refactoringKind;
176 if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) {
177 return new ExtractLocalVariableFeedback.fromJson(
178 jsonDecoder, jsonPath, json);
179 }
180 if (kind == RefactoringKind.EXTRACT_METHOD) {
181 return new ExtractMethodFeedback.fromJson(jsonDecoder, jsonPath, json);
182 }
183 if (kind == RefactoringKind.INLINE_LOCAL_VARIABLE) {
184 return new InlineLocalVariableFeedback.fromJson(
185 jsonDecoder, jsonPath, json);
186 }
187 if (kind == RefactoringKind.INLINE_METHOD) {
188 return new InlineMethodFeedback.fromJson(jsonDecoder, jsonPath, json);
189 }
190 if (kind == RefactoringKind.RENAME) {
191 return new RenameFeedback.fromJson(jsonDecoder, jsonPath, json);
192 }
193 return null;
194 }
195
196 /**
197 * Create a [RefactoringOptions] corresponding the given [kind].
198 */
199 RefactoringOptions refactoringOptionsFromJson(JsonDecoder jsonDecoder,
200 String jsonPath, Object json, RefactoringKind kind) {
201 if (kind == RefactoringKind.EXTRACT_LOCAL_VARIABLE) {
202 return new ExtractLocalVariableOptions.fromJson(
203 jsonDecoder, jsonPath, json);
204 }
205 if (kind == RefactoringKind.EXTRACT_METHOD) {
206 return new ExtractMethodOptions.fromJson(jsonDecoder, jsonPath, json);
207 }
208 if (kind == RefactoringKind.INLINE_METHOD) {
209 return new InlineMethodOptions.fromJson(jsonDecoder, jsonPath, json);
210 }
211 if (kind == RefactoringKind.MOVE_FILE) {
212 return new MoveFileOptions.fromJson(jsonDecoder, jsonPath, json);
213 }
214 if (kind == RefactoringKind.RENAME) {
215 return new RenameOptions.fromJson(jsonDecoder, jsonPath, json);
216 }
217 return null;
218 }
219
220 /**
221 * Type of callbacks used to decode parts of JSON objects. [jsonPath] is a
222 * string describing the part of the JSON object being decoded, and [value] is
223 * the part to decode.
224 */
225 typedef Object JsonDecoderCallback(String jsonPath, Object value);
226
227 /**
228 * Instances of the class [HasToJson] implement [toJson] method that returns
229 * a JSON presentation.
230 */
231 abstract class HasToJson {
232 /**
233 * Returns a JSON presentation of the object.
234 */
235 Map<String, Object> toJson();
236 }
237
238 /**
239 * Jenkins hash function, optimized for small integers. Borrowed from
240 * sdk/lib/math/jenkins_smi_hash.dart.
241 *
242 * TODO(paulberry): Move to somewhere that can be shared with other code.
243 */
244 class JenkinsSmiHash {
245 static int combine(int hash, int value) {
246 hash = 0x1fffffff & (hash + value);
247 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
248 return hash ^ (hash >> 6);
249 }
250
251 static int finish(int hash) {
252 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
253 hash = hash ^ (hash >> 11);
254 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
255 }
256
257 static int hash2(a, b) => finish(combine(combine(0, a), b));
258
259 static int hash4(a, b, c, d) =>
260 finish(combine(combine(combine(combine(0, a), b), c), d));
261 }
262
263 /**
264 * Base class for decoding JSON objects. The derived class must implement
265 * error reporting logic.
266 */
267 abstract class JsonDecoder {
268 /**
269 * Retrieve the RefactoringKind that should be assumed when decoding
270 * refactoring feedback objects, or null if no refactoring feedback object is
271 * expected to be encountered.
272 */
273 RefactoringKind get refactoringKind;
274
275 /**
276 * Decode a JSON object that is expected to be a boolean. The strings "true"
277 * and "false" are also accepted.
278 */
279 bool decodeBool(String jsonPath, Object json) {
280 if (json is bool) {
281 return json;
282 } else if (json == 'true') {
283 return true;
284 } else if (json == 'false') {
285 return false;
286 }
287 throw mismatch(jsonPath, 'bool', json);
288 }
289
290 /**
291 * Decode a JSON object that is expected to be an integer. A string
292 * representation of an integer is also accepted.
293 */
294 int decodeInt(String jsonPath, Object json) {
295 if (json is int) {
296 return json;
297 } else if (json is String) {
298 return int.parse(json, onError: (String value) {
299 throw mismatch(jsonPath, 'int', json);
300 });
301 }
302 throw mismatch(jsonPath, 'int', json);
303 }
304
305 /**
306 * Decode a JSON object that is expected to be a List. [decoder] is used to
307 * decode the items in the list.
308 */
309 List decodeList(String jsonPath, Object json, [JsonDecoderCallback decoder]) {
310 if (json == null) {
311 return [];
312 } else if (json is List) {
313 List result = [];
314 for (int i = 0; i < json.length; i++) {
315 result.add(decoder('$jsonPath[$i]', json[i]));
316 }
317 return result;
318 } else {
319 throw mismatch(jsonPath, 'List', json);
320 }
321 }
322
323 /**
324 * Decode a JSON object that is expected to be a Map. [keyDecoder] is used
325 * to decode the keys, and [valueDecoder] is used to decode the values.
326 */
327 Map decodeMap(String jsonPath, Object json,
328 {JsonDecoderCallback keyDecoder, JsonDecoderCallback valueDecoder}) {
329 if (json == null) {
330 return {};
331 } else if (json is Map) {
332 Map result = {};
333 json.forEach((String key, value) {
334 Object decodedKey;
335 if (keyDecoder != null) {
336 decodedKey = keyDecoder('$jsonPath.key', key);
337 } else {
338 decodedKey = key;
339 }
340 if (valueDecoder != null) {
341 value = valueDecoder('$jsonPath[${JSON.encode(key)}]', value);
342 }
343 result[decodedKey] = value;
344 });
345 return result;
346 } else {
347 throw mismatch(jsonPath, 'Map', json);
348 }
349 }
350
351 /**
352 * Decode a JSON object that is expected to be a string.
353 */
354 String decodeString(String jsonPath, Object json) {
355 if (json is String) {
356 return json;
357 } else {
358 throw mismatch(jsonPath, 'String', json);
359 }
360 }
361
362 /**
363 * Decode a JSON object that is expected to be one of several choices,
364 * where the choices are disambiguated by the contents of the field [field].
365 * [decoders] is a map from each possible string in the field to the decoder
366 * that should be used to decode the JSON object.
367 */
368 Object decodeUnion(String jsonPath, Map json, String field,
369 Map<String, JsonDecoderCallback> decoders) {
370 if (json is Map) {
371 if (!json.containsKey(field)) {
372 throw missingKey(jsonPath, field);
373 }
374 var disambiguatorPath = '$jsonPath[${JSON.encode(field)}]';
375 String disambiguator = decodeString(disambiguatorPath, json[field]);
376 if (!decoders.containsKey(disambiguator)) {
377 throw mismatch(
378 disambiguatorPath, 'One of: ${decoders.keys.toList()}', json);
379 }
380 return decoders[disambiguator](jsonPath, json);
381 } else {
382 throw mismatch(jsonPath, 'Map', json);
383 }
384 }
385
386 /**
387 * Create an exception to throw if the JSON object at [jsonPath] fails to
388 * match the API definition of [expected].
389 */
390 dynamic mismatch(String jsonPath, String expected, [Object actual]);
391
392 /**
393 * Create an exception to throw if the JSON object at [jsonPath] is missing
394 * the key [key].
395 */
396 dynamic missingKey(String jsonPath, String key);
397 }
398
399 /**
400 * JsonDecoder for decoding requests. Errors are reporting by throwing a
401 * [RequestFailure].
402 */
403 class RequestDecoder extends JsonDecoder {
404 /**
405 * The request being deserialized.
406 */
407 final Request _request;
408
409 RequestDecoder(this._request);
410
411 RefactoringKind get refactoringKind {
412 // Refactoring feedback objects should never appear in requests.
413 return null;
414 }
415
416 @override
417 dynamic mismatch(String jsonPath, String expected, [Object actual]) {
418 StringBuffer buffer = new StringBuffer();
419 buffer.write('Expected to be ');
420 buffer.write(expected);
421 if (actual != null) {
422 buffer.write('; found "');
423 buffer.write(JSON.encode(actual));
424 buffer.write('"');
425 }
426 return new RequestFailure(
427 new Response.invalidParameter(_request, jsonPath, buffer.toString()));
428 }
429
430 @override
431 dynamic missingKey(String jsonPath, String key) {
432 return new RequestFailure(new Response.invalidParameter(
433 _request, jsonPath, 'Expected to contain key ${JSON.encode(key)}'));
434 }
435 }
436
437 /**
438 * JsonDecoder for decoding responses from the server. This is intended to be
439 * used only for testing. Errors are reported using bare [Exception] objects.
440 */
441 class ResponseDecoder extends JsonDecoder {
442 final RefactoringKind refactoringKind;
443
444 ResponseDecoder(this.refactoringKind);
445
446 @override
447 dynamic mismatch(String jsonPath, String expected, [Object actual]) {
448 StringBuffer buffer = new StringBuffer();
449 buffer.write('Expected ');
450 buffer.write(expected);
451 if (actual != null) {
452 buffer.write(' found "');
453 buffer.write(JSON.encode(actual));
454 buffer.write('"');
455 }
456 buffer.write(' at ');
457 buffer.write(jsonPath);
458 return new Exception(buffer.toString());
459 }
460
461 @override
462 dynamic missingKey(String jsonPath, String key) {
463 return new Exception('Missing key $key at $jsonPath');
464 }
465 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698