| 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 |