OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 input.transformer; | 5 library input.transformer; |
6 | 6 |
| 7 import 'dart:async'; |
7 import 'dart:convert'; | 8 import 'dart:convert'; |
8 import 'dart:io'; | 9 import 'dart:io'; |
9 | 10 |
10 import 'package:analysis_server/src/constants.dart'; | 11 import 'package:analysis_server/src/constants.dart'; |
11 import 'package:analysis_server/src/protocol.dart'; | 12 import 'package:analysis_server/src/protocol.dart'; |
12 import 'package:analyzer/src/generated/java_engine.dart'; | |
13 import 'package:logging/logging.dart'; | 13 import 'package:logging/logging.dart'; |
14 import 'package:path/path.dart' as path; | 14 import 'package:path/path.dart' as path; |
15 | 15 |
16 import 'instrumentation_input_converter.dart'; | 16 import 'instrumentation_input_converter.dart'; |
17 import 'log_file_input_converter.dart'; | 17 import 'log_file_input_converter.dart'; |
18 import 'operation.dart'; | 18 import 'operation.dart'; |
19 | 19 |
20 /** | 20 /** |
21 * Common input converter superclass for sharing implementation. | 21 * Common input converter superclass for sharing implementation. |
22 */ | 22 */ |
23 abstract class CommonInputConverter extends Converter<String, Operation> { | 23 abstract class CommonInputConverter extends Converter<String, Operation> { |
24 static final ERROR_PREFIX = 'Server responded with an error: '; | 24 static final ERROR_PREFIX = 'Server responded with an error: '; |
25 final Logger logger = new Logger('InstrumentationInputConverter'); | 25 final Logger logger = new Logger('InstrumentationInputConverter'); |
26 final Set<String> eventsSeen = new Set<String>(); | 26 final Set<String> eventsSeen = new Set<String>(); |
27 | 27 |
28 /** | 28 /** |
29 * A mapping from request/response id to expected error message. | 29 * A mapping from request/response id to request json |
| 30 * for those requests for which a response has not been processed. |
30 */ | 31 */ |
31 final Map<String, dynamic> expectedErrors = new Map<String, dynamic>(); | 32 final Map<String, dynamic> requestMap = {}; |
| 33 |
| 34 /** |
| 35 * A mapping from request/response id to a completer |
| 36 * for those requests for which a response has not been processed. |
| 37 * The completer is called with the actual json response |
| 38 * when it becomes available. |
| 39 */ |
| 40 final Map<String, Completer> responseCompleters = {}; |
| 41 |
| 42 /** |
| 43 * A mapping from request/response id to the actual response result |
| 44 * for those responses that have not been processed. |
| 45 */ |
| 46 final Map<String, dynamic> responseMap = {}; |
32 | 47 |
33 /** | 48 /** |
34 * A mapping of current overlay content | 49 * A mapping of current overlay content |
35 * parallel to what is in the analysis server | 50 * parallel to what is in the analysis server |
36 * so that we can update the file system. | 51 * so that we can update the file system. |
37 */ | 52 */ |
38 final Map<String, String> overlays = new Map<String, String>(); | 53 final Map<String, String> overlays = {}; |
39 | 54 |
40 /** | 55 /** |
41 * The prefix used to determine if a request parameter is a file path. | 56 * The prefix used to determine if a request parameter is a file path. |
42 */ | 57 */ |
43 final String rootPrefix = path.rootPrefix(path.current); | 58 final String rootPrefix = path.rootPrefix(path.current); |
44 | 59 |
45 /** | 60 /** |
46 * A mapping of source path prefixes | 61 * A mapping of source path prefixes |
47 * from location where instrumentation or log file was generated | 62 * from location where instrumentation or log file was generated |
48 * to the target location of the source using during performance measurement. | 63 * to the target location of the source using during performance measurement. |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
80 logger.log(Level.INFO, 'Ignored notification: $event\n $json'); | 95 logger.log(Level.INFO, 'Ignored notification: $event\n $json'); |
81 } | 96 } |
82 return null; | 97 return null; |
83 } | 98 } |
84 | 99 |
85 /** | 100 /** |
86 * Return an operation for the request or `null` if none. | 101 * Return an operation for the request or `null` if none. |
87 */ | 102 */ |
88 Operation convertRequest(Map<String, dynamic> origJson) { | 103 Operation convertRequest(Map<String, dynamic> origJson) { |
89 Map<String, dynamic> json = translateSrcPaths(origJson); | 104 Map<String, dynamic> json = translateSrcPaths(origJson); |
| 105 requestMap[json['id']] = json; |
90 String method = json['method']; | 106 String method = json['method']; |
91 // Sanity check operations that modify source | 107 // Sanity check operations that modify source |
92 // to ensure that the operation is on source in temp space | 108 // to ensure that the operation is on source in temp space |
93 if (method == ANALYSIS_UPDATE_CONTENT) { | 109 if (method == ANALYSIS_UPDATE_CONTENT) { |
94 try { | |
95 validateSrcPaths(json); | |
96 } catch (e, s) { | |
97 throw new AnalysisException('invalid src path in update request\n$json', | |
98 new CaughtException(e, s)); | |
99 } | |
100 // Track overlays in parallel with the analysis server | 110 // Track overlays in parallel with the analysis server |
101 // so that when an overlay is removed, the file can be updated on disk | 111 // so that when an overlay is removed, the file can be updated on disk |
102 Request request = new Request.fromJson(json); | 112 Request request = new Request.fromJson(json); |
103 var params = new AnalysisUpdateContentParams.fromRequest(request); | 113 var params = new AnalysisUpdateContentParams.fromRequest(request); |
104 params.files.forEach((String path, change) { | 114 params.files.forEach((String filePath, change) { |
105 if (change is AddContentOverlay) { | 115 if (change is AddContentOverlay) { |
106 String content = change.content; | 116 String content = change.content; |
107 if (content == null) { | 117 if (content == null) { |
108 throw 'expected new overlay content\n$json'; | 118 throw 'expected new overlay content\n$json'; |
109 } | 119 } |
110 overlays[path] = content; | 120 overlays[filePath] = content; |
111 } else if (change is ChangeContentOverlay) { | 121 } else if (change is ChangeContentOverlay) { |
112 String content = overlays[path]; | 122 String content = overlays[filePath]; |
113 if (content == null) { | 123 if (content == null) { |
114 throw 'expected cached overlay content\n$json'; | 124 throw 'expected cached overlay content\n$json'; |
115 } | 125 } |
116 overlays[path] = SourceEdit.applySequence(content, change.edits); | 126 overlays[filePath] = SourceEdit.applySequence(content, change.edits); |
117 } else if (change is RemoveContentOverlay) { | 127 } else if (change is RemoveContentOverlay) { |
118 String content = overlays.remove(path); | 128 String content = overlays.remove(filePath); |
119 if (content == null) { | 129 if (content == null) { |
120 throw 'expected cached overlay content\n$json'; | 130 throw 'expected cached overlay content\n$json'; |
121 } | 131 } |
122 validateSrcPaths(path); | 132 if (!path.isWithin(tmpSrcDirPath, filePath)) { |
123 new File(path).writeAsStringSync(content); | 133 throw 'found path referencing source outside temp space\n$filePath\n
$json'; |
| 134 } |
| 135 new File(filePath).writeAsStringSync(content); |
124 } else { | 136 } else { |
125 throw 'unknown overlay change $change\n$json'; | 137 throw 'unknown overlay change $change\n$json'; |
126 } | 138 } |
127 }); | 139 }); |
128 return new RequestOperation(this, json); | 140 return new RequestOperation(this, json); |
129 } | 141 } |
130 // TODO(danrubel) replace this with code | 142 // Track performance for completion notifications |
| 143 if (method == COMPLETION_GET_SUGGESTIONS) { |
| 144 return new CompletionRequestOperation(this, json); |
| 145 } |
| 146 // TODO(danrubel) replace this with code |
131 // that just forwards the translated request | 147 // that just forwards the translated request |
132 if (method == ANALYSIS_GET_HOVER || | 148 if (method == ANALYSIS_GET_HOVER || |
133 method == ANALYSIS_SET_ANALYSIS_ROOTS || | 149 method == ANALYSIS_SET_ANALYSIS_ROOTS || |
134 method == ANALYSIS_SET_PRIORITY_FILES || | 150 method == ANALYSIS_SET_PRIORITY_FILES || |
135 method == ANALYSIS_SET_SUBSCRIPTIONS || | 151 method == ANALYSIS_SET_SUBSCRIPTIONS || |
136 method == ANALYSIS_UPDATE_OPTIONS || | 152 method == ANALYSIS_UPDATE_OPTIONS || |
137 method == COMPLETION_GET_SUGGESTIONS || | |
138 method == EDIT_GET_ASSISTS || | 153 method == EDIT_GET_ASSISTS || |
139 method == EDIT_GET_AVAILABLE_REFACTORINGS || | 154 method == EDIT_GET_AVAILABLE_REFACTORINGS || |
140 method == EDIT_GET_FIXES || | 155 method == EDIT_GET_FIXES || |
141 method == EDIT_GET_REFACTORING || | 156 method == EDIT_GET_REFACTORING || |
142 method == EDIT_SORT_MEMBERS || | 157 method == EDIT_SORT_MEMBERS || |
143 method == EXECUTION_CREATE_CONTEXT || | 158 method == EXECUTION_CREATE_CONTEXT || |
144 method == EXECUTION_DELETE_CONTEXT || | 159 method == EXECUTION_DELETE_CONTEXT || |
145 method == EXECUTION_MAP_URI || | 160 method == EXECUTION_MAP_URI || |
146 method == EXECUTION_SET_SUBSCRIPTIONS || | 161 method == EXECUTION_SET_SUBSCRIPTIONS || |
147 method == SEARCH_FIND_ELEMENT_REFERENCES || | 162 method == SEARCH_FIND_ELEMENT_REFERENCES || |
| 163 method == SEARCH_FIND_MEMBER_DECLARATIONS || |
148 method == SERVER_GET_VERSION || | 164 method == SERVER_GET_VERSION || |
149 method == SERVER_SET_SUBSCRIPTIONS) { | 165 method == SERVER_SET_SUBSCRIPTIONS) { |
150 return new RequestOperation(this, json); | 166 return new RequestOperation(this, json); |
151 } | 167 } |
152 throw 'unknown request: $method\n $json'; | 168 throw 'unknown request: $method\n $json'; |
153 } | 169 } |
154 | 170 |
155 /** | 171 /** |
156 * Determine if the given request is expected to fail | 172 * Return an operation for the recorded/expected response. |
157 * and log an exception if not. | |
158 */ | 173 */ |
159 void recordErrorResponse(Map<String, dynamic> jsonRequest, exception) { | 174 Operation convertResponse(Map<String, dynamic> json) { |
160 var actualErr; | 175 return new ResponseOperation( |
161 if (exception is UnimplementedError) { | 176 this, requestMap.remove(json['id']), translateSrcPaths(json)); |
162 if (exception.message.startsWith(ERROR_PREFIX)) { | |
163 Map<String, dynamic> jsonResponse = | |
164 JSON.decode(exception.message.substring(ERROR_PREFIX.length)); | |
165 actualErr = jsonResponse['error']; | |
166 } | |
167 } | |
168 String id = jsonRequest['id']; | |
169 if (id != null && actualErr != null) { | |
170 var expectedErr = expectedErrors[id]; | |
171 if (expectedErr != null && actualErr == expectedErr) { | |
172 return; | |
173 } | |
174 // if (jsonRequest['method'] == EDIT_SORT_MEMBERS) { | |
175 // var params = jsonRequest['params']; | |
176 // if (params is Map) { | |
177 // var filePath = params['file']; | |
178 // if (filePath is String) { | |
179 // var content = overlays[filePath]; | |
180 // if (content is String) { | |
181 // logger.log(Level.WARNING, 'sort failed: $filePath\n$content'); | |
182 // } | |
183 // } | |
184 // } | |
185 // } | |
186 } | |
187 logger.log( | |
188 Level.SEVERE, 'Send request failed for $id\n$exception\n$jsonRequest'); | |
189 } | 177 } |
190 | 178 |
191 /** | 179 /** |
192 * Examine recorded responses and record any expected errors. | 180 * Process an error response from the server by either |
| 181 * completing the associated completer in the [responseCompleters] |
| 182 * or stashing it in [responseMap] if no completer exists. |
193 */ | 183 */ |
194 void recordResponse(Map<String, dynamic> json) { | 184 void processErrorResponse(String id, exception) { |
195 var error = json['error']; | 185 var result = exception; |
196 if (error != null) { | 186 if (exception is UnimplementedError) { |
197 String id = json['id']; | 187 if (exception.message.startsWith(ERROR_PREFIX)) { |
198 print('expected error for $id is $error'); | 188 result = JSON.decode(exception.message.substring(ERROR_PREFIX.length)); |
| 189 } |
| 190 } |
| 191 processResponseResult(id, result); |
| 192 } |
| 193 |
| 194 /** |
| 195 * Process the expected response by completing the given completer |
| 196 * with the result if it has alredy been received, |
| 197 * or caching the completer to be completed when the server |
| 198 * returns the associated result. |
| 199 * Return a future that completes when the response is received |
| 200 * or `null` if the response has already been received |
| 201 * and the completer completed. |
| 202 */ |
| 203 Future processExpectedResponse(String id, Completer completer) { |
| 204 if (responseMap.containsKey(id)) { |
| 205 logger.log(Level.INFO, 'processing cached response $id'); |
| 206 completer.complete(responseMap.remove(id)); |
| 207 return null; |
| 208 } else { |
| 209 logger.log(Level.INFO, 'waiting for response $id'); |
| 210 responseCompleters[id] = completer; |
| 211 return completer.future; |
199 } | 212 } |
200 } | 213 } |
201 | 214 |
| 215 /** |
| 216 * Process a success response result from the server by either |
| 217 * completing the associated completer in the [responseCompleters] |
| 218 * or stashing it in [responseMap] if no completer exists. |
| 219 * The response result may be `null`. |
| 220 */ |
| 221 void processResponseResult(String id, result) { |
| 222 Completer completer = responseCompleters[id]; |
| 223 if (completer != null) { |
| 224 logger.log(Level.INFO, 'processing response $id'); |
| 225 completer.complete(result); |
| 226 } else { |
| 227 logger.log(Level.INFO, 'caching response $id'); |
| 228 responseMap[id] = result; |
| 229 } |
| 230 } |
| 231 |
202 /** | 232 /** |
203 * Recursively translate source paths in the specified JSON to reference | 233 * Recursively translate source paths in the specified JSON to reference |
204 * the temporary source used during performance measurement rather than | 234 * the temporary source used during performance measurement rather than |
205 * the original source when the instrumentation or log file was generated. | 235 * the original source when the instrumentation or log file was generated. |
206 */ | 236 */ |
207 translateSrcPaths(json) { | 237 translateSrcPaths(json) { |
208 if (json is String) { | 238 if (json is String) { |
209 String result = json; | 239 String result = json; |
210 srcPathMap.forEach((String oldPrefix, String newPrefix) { | 240 srcPathMap.forEach((String oldPrefix, String newPrefix) { |
211 if (json.startsWith(oldPrefix)) { | 241 if (json.startsWith(oldPrefix)) { |
(...skipping 11 matching lines...) Expand all Loading... |
223 } | 253 } |
224 if (json is Map) { | 254 if (json is Map) { |
225 Map<String, dynamic> result = new Map<String, dynamic>(); | 255 Map<String, dynamic> result = new Map<String, dynamic>(); |
226 json.forEach((String origKey, value) { | 256 json.forEach((String origKey, value) { |
227 result[translateSrcPaths(origKey)] = translateSrcPaths(value); | 257 result[translateSrcPaths(origKey)] = translateSrcPaths(value); |
228 }); | 258 }); |
229 return result; | 259 return result; |
230 } | 260 } |
231 return json; | 261 return json; |
232 } | 262 } |
233 | |
234 /** | |
235 * Recursively verify that the source paths in the specified JSON | |
236 * only reference the temporary source used during performance measurement. | |
237 */ | |
238 void validateSrcPaths(json) { | |
239 if (json is String) { | |
240 if (json != null && | |
241 path.isWithin(rootPrefix, json) && | |
242 !path.isWithin(tmpSrcDirPath, json)) { | |
243 throw 'found path referencing source outside temp space\n$json'; | |
244 } | |
245 } else if (json is List) { | |
246 for (int i = json.length - 1; i >= 0; --i) { | |
247 validateSrcPaths(json[i]); | |
248 } | |
249 } else if (json is Map) { | |
250 json.forEach((String key, value) { | |
251 validateSrcPaths(key); | |
252 validateSrcPaths(value); | |
253 }); | |
254 } | |
255 } | |
256 } | 263 } |
257 | 264 |
258 /** | 265 /** |
259 * [InputConverter] converts an input stream | 266 * [InputConverter] converts an input stream |
260 * into a series of operations to be sent to the analysis server. | 267 * into a series of operations to be sent to the analysis server. |
261 * The input stream can be either an instrumenation or log file. | 268 * The input stream can be either an instrumenation or log file. |
262 */ | 269 */ |
263 class InputConverter extends Converter<String, Operation> { | 270 class InputConverter extends Converter<String, Operation> { |
264 final Logger logger = new Logger('InputConverter'); | 271 final Logger logger = new Logger('InputConverter'); |
265 | 272 |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
342 if (op != null) { | 349 if (op != null) { |
343 outSink.add(op); | 350 outSink.add(op); |
344 } | 351 } |
345 } | 352 } |
346 | 353 |
347 @override | 354 @override |
348 void close() { | 355 void close() { |
349 outSink.close(); | 356 outSink.close(); |
350 } | 357 } |
351 } | 358 } |
OLD | NEW |