| 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 codegen.protocol; | 5 library codegen.protocol; |
| 6 | 6 |
| 7 import 'dart:convert'; | 7 import 'dart:convert'; |
| 8 | 8 |
| 9 import 'package:html5lib/dom.dart' as dom; |
| 10 |
| 9 import 'api.dart'; | 11 import 'api.dart'; |
| 10 import 'codegen_dart.dart'; | 12 import 'codegen_dart.dart'; |
| 11 import 'codegen_tools.dart'; | 13 import 'codegen_tools.dart'; |
| 12 import 'from_html.dart'; | 14 import 'from_html.dart'; |
| 13 import 'implied_types.dart'; | 15 import 'implied_types.dart'; |
| 14 import 'to_html.dart'; | 16 import 'to_html.dart'; |
| 15 | 17 |
| 16 import 'package:html5lib/dom.dart' as dom; | |
| 17 | |
| 18 /** | |
| 19 * Container for code that can be used to translate a data type from JSON. | |
| 20 */ | |
| 21 abstract class FromJsonCode { | |
| 22 /** | |
| 23 * True if the data type is already in JSON form, so the translation is the | |
| 24 * identity function. | |
| 25 */ | |
| 26 bool get isIdentity; | |
| 27 | |
| 28 /** | |
| 29 * Get the translation code in the form of a closure. | |
| 30 */ | |
| 31 String get asClosure; | |
| 32 | |
| 33 /** | |
| 34 * Get the translation code in the form of a code snippet, where [jsonPath] | |
| 35 * is the variable holding the JSON path, and [json] is the variable holding | |
| 36 * the raw JSON. | |
| 37 */ | |
| 38 String asSnippet(String jsonPath, String json); | |
| 39 } | |
| 40 | |
| 41 /** | |
| 42 * Representation of FromJsonCode for a function defined elsewhere. | |
| 43 */ | |
| 44 class FromJsonFunction extends FromJsonCode { | |
| 45 final String asClosure; | |
| 46 | |
| 47 FromJsonFunction(this.asClosure); | |
| 48 | |
| 49 @override | |
| 50 bool get isIdentity => false; | |
| 51 | |
| 52 @override | |
| 53 String asSnippet(String jsonPath, String json) => | |
| 54 '$asClosure($jsonPath, $json)'; | |
| 55 } | |
| 56 | |
| 57 typedef String FromJsonSnippetCallback(String jsonPath, String json); | |
| 58 | |
| 59 /** | |
| 60 * Representation of FromJsonCode for a snippet of inline code. | |
| 61 */ | |
| 62 class FromJsonSnippet extends FromJsonCode { | |
| 63 /** | |
| 64 * Callback that can be used to generate the code snippet, once the names | |
| 65 * of the [jsonPath] and [json] variables are known. | |
| 66 */ | |
| 67 final FromJsonSnippetCallback callback; | |
| 68 | |
| 69 FromJsonSnippet(this.callback); | |
| 70 | |
| 71 @override | |
| 72 bool get isIdentity => false; | |
| 73 | |
| 74 @override | |
| 75 String get asClosure => | |
| 76 '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}'; | |
| 77 | |
| 78 @override | |
| 79 String asSnippet(String jsonPath, String json) => callback(jsonPath, json); | |
| 80 } | |
| 81 | |
| 82 /** | |
| 83 * Representation of FromJsonCode for the identity transformation. | |
| 84 */ | |
| 85 class FromJsonIdentity extends FromJsonSnippet { | |
| 86 FromJsonIdentity() : super((String jsonPath, String json) => json); | |
| 87 | |
| 88 @override | |
| 89 bool get isIdentity => true; | |
| 90 } | |
| 91 | |
| 92 /** | |
| 93 * Container for code that can be used to translate a data type to JSON. | |
| 94 */ | |
| 95 abstract class ToJsonCode { | |
| 96 /** | |
| 97 * True if the data type is already in JSON form, so the translation is the | |
| 98 * identity function. | |
| 99 */ | |
| 100 bool get isIdentity; | |
| 101 | |
| 102 /** | |
| 103 * Get the translation code in the form of a closure. | |
| 104 */ | |
| 105 String get asClosure; | |
| 106 | |
| 107 /** | |
| 108 * Get the translation code in the form of a code snippet, where [value] | |
| 109 * is the variable holding the object to be translated. | |
| 110 */ | |
| 111 String asSnippet(String value); | |
| 112 } | |
| 113 | |
| 114 /** | |
| 115 * Representation of ToJsonCode for a function defined elsewhere. | |
| 116 */ | |
| 117 class ToJsonFunction extends ToJsonCode { | |
| 118 final String asClosure; | |
| 119 | |
| 120 ToJsonFunction(this.asClosure); | |
| 121 | |
| 122 @override | |
| 123 bool get isIdentity => false; | |
| 124 | |
| 125 @override | |
| 126 String asSnippet(String value) => '$asClosure($value)'; | |
| 127 } | |
| 128 | |
| 129 typedef String ToJsonSnippetCallback(String value); | |
| 130 | |
| 131 /** | |
| 132 * Representation of ToJsonCode for a snippet of inline code. | |
| 133 */ | |
| 134 class ToJsonSnippet extends ToJsonCode { | |
| 135 /** | |
| 136 * Callback that can be used to generate the code snippet, once the name | |
| 137 * of the [value] variable is known. | |
| 138 */ | |
| 139 final ToJsonSnippetCallback callback; | |
| 140 | |
| 141 /** | |
| 142 * Dart type of the [value] variable. | |
| 143 */ | |
| 144 final String type; | |
| 145 | |
| 146 ToJsonSnippet(this.type, this.callback); | |
| 147 | |
| 148 @override | |
| 149 bool get isIdentity => false; | |
| 150 | |
| 151 @override | |
| 152 String get asClosure => '($type value) => ${callback('value')}'; | |
| 153 | |
| 154 @override | |
| 155 String asSnippet(String value) => callback(value); | |
| 156 } | |
| 157 | |
| 158 /** | |
| 159 * Representation of FromJsonCode for the identity transformation. | |
| 160 */ | |
| 161 class ToJsonIdentity extends ToJsonSnippet { | |
| 162 ToJsonIdentity(String type) : super(type, (String value) => value); | |
| 163 | |
| 164 @override | |
| 165 bool get isIdentity => true; | |
| 166 } | |
| 167 | |
| 168 /** | 18 /** |
| 169 * Special flags that need to be inserted into the declaration of the Element | 19 * Special flags that need to be inserted into the declaration of the Element |
| 170 * class. | 20 * class. |
| 171 */ | 21 */ |
| 172 const Map<String, String> specialElementFlags = const { | 22 const Map<String, String> specialElementFlags = const { |
| 173 'abstract': '0x01', | 23 'abstract': '0x01', |
| 174 'const': '0x02', | 24 'const': '0x02', |
| 175 'final': '0x04', | 25 'final': '0x04', |
| 176 'static': '0x08', | 26 'static': '0x08', |
| 177 'private': '0x10', | 27 'private': '0x10', |
| 178 'deprecated': '0x20' | 28 'deprecated': '0x20' |
| 179 }; | 29 }; |
| 180 | 30 |
| 31 final GeneratedFile target = |
| 32 new GeneratedFile('../../lib/src/generated_protocol.dart', () { |
| 33 CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi()); |
| 34 return visitor.collectCode(visitor.visitApi); |
| 35 }); |
| 36 |
| 37 /** |
| 38 * Translate spec_input.html into protocol_matchers.dart. |
| 39 */ |
| 40 main() { |
| 41 target.generate(); |
| 42 } |
| 43 |
| 181 /** | 44 /** |
| 182 * Callback type used to represent arbitrary code generation. | 45 * Callback type used to represent arbitrary code generation. |
| 183 */ | 46 */ |
| 184 typedef void CodegenCallback(); | 47 typedef void CodegenCallback(); |
| 185 | 48 |
| 49 typedef String FromJsonSnippetCallback(String jsonPath, String json); |
| 50 |
| 51 typedef String ToJsonSnippetCallback(String value); |
| 52 |
| 186 /** | 53 /** |
| 187 * Visitor which produces Dart code representing the API. | 54 * Visitor which produces Dart code representing the API. |
| 188 */ | 55 */ |
| 189 class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator { | 56 class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator { |
| 190 /** | 57 /** |
| 191 * Class members for which the constructor argument should be optional, even | 58 * Class members for which the constructor argument should be optional, even |
| 192 * if the member is not an optional part of the protocol. For list types, | 59 * if the member is not an optional part of the protocol. For list types, |
| 193 * the constructor will default the member to the empty list. | 60 * the constructor will default the member to the empty list. |
| 194 */ | 61 */ |
| 195 static const Map<String, List<String>> _optionalConstructorArguments = const { | 62 static const Map<String, List<String>> _optionalConstructorArguments = const { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 209 * API as well as those implied by the definitions of requests, responses, | 76 * API as well as those implied by the definitions of requests, responses, |
| 210 * notifications, etc. | 77 * notifications, etc. |
| 211 */ | 78 */ |
| 212 final Map<String, ImpliedType> impliedTypes; | 79 final Map<String, ImpliedType> impliedTypes; |
| 213 | 80 |
| 214 CodegenProtocolVisitor(Api api) | 81 CodegenProtocolVisitor(Api api) |
| 215 : super(api), | 82 : super(api), |
| 216 toHtmlVisitor = new ToHtmlVisitor(api), | 83 toHtmlVisitor = new ToHtmlVisitor(api), |
| 217 impliedTypes = computeImpliedTypes(api); | 84 impliedTypes = computeImpliedTypes(api); |
| 218 | 85 |
| 219 @override | 86 /** |
| 220 visitApi() { | 87 * Compute the code necessary to compare two objects for equality. |
| 221 outputHeader(); | 88 */ |
| 222 writeln(); | 89 String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) { |
| 223 writeln('part of protocol;'); | 90 TypeDecl resolvedType = resolveTypeReferenceChain(type); |
| 224 emitClasses(); | 91 if (resolvedType is TypeReference || |
| 92 resolvedType is TypeEnum || |
| 93 resolvedType is TypeObject || |
| 94 resolvedType is TypeUnion) { |
| 95 return '$thisVar == $otherVar'; |
| 96 } else if (resolvedType is TypeList) { |
| 97 String itemTypeName = dartType(resolvedType.itemType); |
| 98 String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b'); |
| 99 String closure = '($itemTypeName a, $itemTypeName b) => $subComparison'; |
| 100 return '_listEqual($thisVar, $otherVar, $closure)'; |
| 101 } else if (resolvedType is TypeMap) { |
| 102 String valueTypeName = dartType(resolvedType.valueType); |
| 103 String subComparison = |
| 104 compareEqualsCode(resolvedType.valueType, 'a', 'b'); |
| 105 String closure = '($valueTypeName a, $valueTypeName b) => $subComparison'; |
| 106 return '_mapEqual($thisVar, $otherVar, $closure)'; |
| 107 } |
| 108 throw new Exception( |
| 109 "Don't know how to compare for equality: $resolvedType"); |
| 225 } | 110 } |
| 226 | 111 |
| 227 /** | 112 /** |
| 228 * Translate each type implied by the API to a class. | 113 * Translate each type implied by the API to a class. |
| 229 */ | 114 */ |
| 230 void emitClasses() { | 115 void emitClasses() { |
| 231 for (ImpliedType impliedType in impliedTypes.values) { | 116 for (ImpliedType impliedType in impliedTypes.values) { |
| 232 TypeDecl type = impliedType.type; | 117 TypeDecl type = impliedType.type; |
| 233 String dartTypeName = capitalize(impliedType.camelName); | 118 String dartTypeName = capitalize(impliedType.camelName); |
| 234 if (type == null) { | 119 if (type == null) { |
| 235 emitEmptyObjectClass(dartTypeName, impliedType); | 120 emitEmptyObjectClass(dartTypeName, impliedType); |
| 236 } else if (type is TypeObject || type == null) { | 121 } else if (type is TypeObject || type == null) { |
| 237 writeln(); | 122 writeln(); |
| 238 emitObjectClass(dartTypeName, type, impliedType); | 123 emitObjectClass(dartTypeName, type, impliedType); |
| 239 } else if (type is TypeEnum) { | 124 } else if (type is TypeEnum) { |
| 240 writeln(); | 125 writeln(); |
| 241 emitEnumClass(dartTypeName, type, impliedType); | 126 emitEnumClass(dartTypeName, type, impliedType); |
| 242 } | 127 } |
| 243 } | 128 } |
| 244 } | 129 } |
| 245 | 130 |
| 246 /** | 131 /** |
| 132 * Emit a convenience constructor for decoding a piece of protocol, if |
| 133 * appropriate. Return true if a constructor was emitted. |
| 134 */ |
| 135 bool emitConvenienceConstructor(String className, ImpliedType impliedType) { |
| 136 // The type of object from which this piece of protocol should be decoded. |
| 137 String inputType; |
| 138 // The name of the input object. |
| 139 String inputName; |
| 140 // The field within the input object to decode. |
| 141 String fieldName; |
| 142 // Constructor call to create the JsonDecoder object. |
| 143 String makeDecoder; |
| 144 // Name of the constructor to create. |
| 145 String constructorName; |
| 146 // Extra arguments for the constructor. |
| 147 List<String> extraArgs = <String>[]; |
| 148 switch (impliedType.kind) { |
| 149 case 'requestParams': |
| 150 inputType = 'Request'; |
| 151 inputName = 'request'; |
| 152 fieldName = '_params'; |
| 153 makeDecoder = 'new RequestDecoder(request)'; |
| 154 constructorName = 'fromRequest'; |
| 155 break; |
| 156 case 'requestResult': |
| 157 inputType = 'Response'; |
| 158 inputName = 'response'; |
| 159 fieldName = '_result'; |
| 160 makeDecoder = |
| 161 'new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id
))'; |
| 162 constructorName = 'fromResponse'; |
| 163 break; |
| 164 case 'notificationParams': |
| 165 inputType = 'Notification'; |
| 166 inputName = 'notification'; |
| 167 fieldName = '_params'; |
| 168 makeDecoder = 'new ResponseDecoder(null)'; |
| 169 constructorName = 'fromNotification'; |
| 170 break; |
| 171 case 'refactoringOptions': |
| 172 inputType = 'EditGetRefactoringParams'; |
| 173 inputName = 'refactoringParams'; |
| 174 fieldName = 'options'; |
| 175 makeDecoder = 'new RequestDecoder(request)'; |
| 176 constructorName = 'fromRefactoringParams'; |
| 177 extraArgs.add('Request request'); |
| 178 break; |
| 179 default: |
| 180 return false; |
| 181 } |
| 182 List<String> args = ['$inputType $inputName']; |
| 183 args.addAll(extraArgs); |
| 184 writeln('factory $className.$constructorName(${args.join(', ')}) {'); |
| 185 indent(() { |
| 186 String fieldNameString = |
| 187 literalString(fieldName.replaceFirst(new RegExp('^_'), '')); |
| 188 if (className == 'EditGetRefactoringParams') { |
| 189 writeln('var params = new $className.fromJson('); |
| 190 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); |
| 191 writeln('REQUEST_ID_REFACTORING_KINDS[request.id] = params.kind;'); |
| 192 writeln('return params;'); |
| 193 } else { |
| 194 writeln('return new $className.fromJson('); |
| 195 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); |
| 196 } |
| 197 }); |
| 198 writeln('}'); |
| 199 return true; |
| 200 } |
| 201 |
| 202 /** |
| 247 * Emit a class representing an data structure that doesn't exist in the | 203 * Emit a class representing an data structure that doesn't exist in the |
| 248 * protocol because it is empty (e.g. the "params" object for a request that | 204 * protocol because it is empty (e.g. the "params" object for a request that |
| 249 * doesn't have any parameters). | 205 * doesn't have any parameters). |
| 250 */ | 206 */ |
| 251 void emitEmptyObjectClass(String className, ImpliedType impliedType) { | 207 void emitEmptyObjectClass(String className, ImpliedType impliedType) { |
| 252 docComment(toHtmlVisitor.collectHtml(() { | 208 docComment(toHtmlVisitor.collectHtml(() { |
| 253 toHtmlVisitor.p(() { | 209 toHtmlVisitor.p(() { |
| 254 toHtmlVisitor.write(impliedType.humanReadableName); | 210 toHtmlVisitor.write(impliedType.humanReadableName); |
| 255 }); | 211 }); |
| 256 })); | 212 })); |
| 257 writeln('class $className {'); | 213 writeln('class $className {'); |
| 258 indent(() { | 214 indent(() { |
| 259 if (emitToRequestMember(impliedType)) { | 215 if (emitToRequestMember(impliedType)) { |
| 260 writeln(); | 216 writeln(); |
| 261 } | 217 } |
| 262 if (emitToResponseMember(impliedType)) { | 218 if (emitToResponseMember(impliedType)) { |
| 263 writeln(); | 219 writeln(); |
| 264 } | 220 } |
| 265 if (emitToNotificationMember(impliedType)) { | 221 if (emitToNotificationMember(impliedType)) { |
| 266 writeln(); | 222 writeln(); |
| 267 } | 223 } |
| 268 emitObjectEqualsMember(null, className); | 224 emitObjectEqualsMember(null, className); |
| 269 writeln(); | 225 writeln(); |
| 270 emitObjectHashCode(null, className); | 226 emitObjectHashCode(null, className); |
| 271 }); | 227 }); |
| 272 writeln('}'); | 228 writeln('}'); |
| 273 } | 229 } |
| 274 | 230 |
| 275 /** | 231 /** |
| 232 * Emit a class to encapsulate an enum. |
| 233 */ |
| 234 void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) { |
| 235 docComment(toHtmlVisitor.collectHtml(() { |
| 236 toHtmlVisitor.p(() { |
| 237 toHtmlVisitor.write(impliedType.humanReadableName); |
| 238 }); |
| 239 if (impliedType.type != null) { |
| 240 toHtmlVisitor.showType(null, impliedType.type); |
| 241 } |
| 242 })); |
| 243 writeln('class $className implements Enum {'); |
| 244 indent(() { |
| 245 if (emitSpecialStaticMembers(className)) { |
| 246 writeln(); |
| 247 } |
| 248 for (TypeEnumValue value in type.values) { |
| 249 docComment(toHtmlVisitor.collectHtml(() { |
| 250 toHtmlVisitor.translateHtml(value.html); |
| 251 })); |
| 252 String valueString = literalString(value.value); |
| 253 writeln( |
| 254 'static const ${value.value} = const $className._($valueString);'); |
| 255 writeln(); |
| 256 } |
| 257 writeln('final String name;'); |
| 258 writeln(); |
| 259 writeln('const $className._(this.name);'); |
| 260 writeln(); |
| 261 emitEnumClassConstructor(className, type); |
| 262 writeln(); |
| 263 emitEnumFromJsonConstructor(className, type, impliedType); |
| 264 writeln(); |
| 265 if (emitSpecialConstructors(className)) { |
| 266 writeln(); |
| 267 } |
| 268 if (emitSpecialGetters(className)) { |
| 269 writeln(); |
| 270 } |
| 271 if (emitSpecialMethods(className)) { |
| 272 writeln(); |
| 273 } |
| 274 writeln('@override'); |
| 275 writeln('String toString() => "$className.\$name";'); |
| 276 writeln(); |
| 277 writeln('String toJson() => name;'); |
| 278 }); |
| 279 writeln('}'); |
| 280 } |
| 281 |
| 282 /** |
| 283 * Emit the constructor for an enum class. |
| 284 */ |
| 285 void emitEnumClassConstructor(String className, TypeEnum type) { |
| 286 writeln('factory $className(String name) {'); |
| 287 indent(() { |
| 288 writeln('switch (name) {'); |
| 289 indent(() { |
| 290 for (TypeEnumValue value in type.values) { |
| 291 String valueString = literalString(value.value); |
| 292 writeln('case $valueString:'); |
| 293 indent(() { |
| 294 writeln('return ${value.value};'); |
| 295 }); |
| 296 } |
| 297 }); |
| 298 writeln('}'); |
| 299 writeln(r"throw new Exception('Illegal enum value: $name');"); |
| 300 }); |
| 301 writeln('}'); |
| 302 } |
| 303 |
| 304 /** |
| 305 * Emit the method for decoding an enum from JSON. |
| 306 */ |
| 307 void emitEnumFromJsonConstructor(String className, TypeEnum type, |
| 308 ImpliedType impliedType) { |
| 309 writeln( |
| 310 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); |
| 311 indent(() { |
| 312 writeln('if (json is String) {'); |
| 313 indent(() { |
| 314 writeln('try {'); |
| 315 indent(() { |
| 316 writeln('return new $className(json);'); |
| 317 }); |
| 318 writeln('} catch(_) {'); |
| 319 indent(() { |
| 320 writeln('// Fall through'); |
| 321 }); |
| 322 writeln('}'); |
| 323 }); |
| 324 writeln('}'); |
| 325 String humanReadableNameString = |
| 326 literalString(impliedType.humanReadableName); |
| 327 writeln( |
| 328 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); |
| 329 }); |
| 330 writeln('}'); |
| 331 } |
| 332 |
| 333 /** |
| 276 * Emit the class to encapsulate an object type. | 334 * Emit the class to encapsulate an object type. |
| 277 */ | 335 */ |
| 278 void emitObjectClass(String className, TypeObject type, | 336 void emitObjectClass(String className, TypeObject type, |
| 279 ImpliedType impliedType) { | 337 ImpliedType impliedType) { |
| 280 docComment(toHtmlVisitor.collectHtml(() { | 338 docComment(toHtmlVisitor.collectHtml(() { |
| 281 toHtmlVisitor.p(() { | 339 toHtmlVisitor.p(() { |
| 282 toHtmlVisitor.write(impliedType.humanReadableName); | 340 toHtmlVisitor.write(impliedType.humanReadableName); |
| 283 }); | 341 }); |
| 284 if (impliedType.type != null) { | 342 if (impliedType.type != null) { |
| 285 toHtmlVisitor.showType(null, impliedType.type); | 343 toHtmlVisitor.showType(null, impliedType.type); |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 338 writeln('String toString() => JSON.encode(toJson());'); | 396 writeln('String toString() => JSON.encode(toJson());'); |
| 339 writeln(); | 397 writeln(); |
| 340 emitObjectEqualsMember(type, className); | 398 emitObjectEqualsMember(type, className); |
| 341 writeln(); | 399 writeln(); |
| 342 emitObjectHashCode(type, className); | 400 emitObjectHashCode(type, className); |
| 343 }); | 401 }); |
| 344 writeln('}'); | 402 writeln('}'); |
| 345 } | 403 } |
| 346 | 404 |
| 347 /** | 405 /** |
| 348 * If the class named [className] requires special static members, emit them | 406 * Emit the constructor for an object class. |
| 349 * and return true. | 407 */ |
| 350 */ | 408 void emitObjectConstructor(TypeObject type, String className) { |
| 351 bool emitSpecialStaticMembers(String className) { | 409 List<String> args = <String>[]; |
| 352 switch (className) { | 410 List<String> optionalArgs = <String>[]; |
| 353 case 'Element': | 411 List<CodegenCallback> extraInitCode = <CodegenCallback>[]; |
| 354 List<String> makeFlagsArgs = <String>[]; | 412 for (TypeObjectField field in type.fields) { |
| 355 List<String> makeFlagsStatements = <String>[]; | 413 if (field.value != null) { |
| 356 specialElementFlags.forEach((String name, String value) { | 414 continue; |
| 357 String flag = 'FLAG_${name.toUpperCase()}'; | 415 } |
| 358 String camelName = camelJoin(['is', name]); | 416 String arg = 'this.${field.name}'; |
| 359 writeln('static const int $flag = $value;'); | 417 if (isOptionalConstructorArg(className, field)) { |
| 360 makeFlagsArgs.add('$camelName: false'); | 418 optionalArgs.add(arg); |
| 361 makeFlagsStatements.add('if ($camelName) flags |= $flag;'); | 419 if (!field.optional) { |
| 362 }); | 420 // Optional constructor arg, but non-optional field. If no arg is |
| 363 writeln(); | 421 // given, the constructor should populate with the empty list. |
| 364 writeln('static int makeFlags({${makeFlagsArgs.join(', ')}}) {'); | 422 TypeDecl fieldType = field.type; |
| 365 indent(() { | 423 if (fieldType is TypeList) { |
| 366 writeln('int flags = 0;'); | 424 extraInitCode.add(() { |
| 367 for (String statement in makeFlagsStatements) { | 425 writeln('if (${field.name} == null) {'); |
| 368 writeln(statement); | 426 indent(() { |
| 369 } | 427 writeln('${field.name} = <${dartType(fieldType.itemType)}>[];'); |
| 370 writeln('return flags;'); | 428 }); |
| 371 }); | 429 writeln('}'); |
| 372 writeln('}'); | 430 }); |
| 373 return true; | 431 } else { |
| 374 case 'SourceEdit': | 432 throw new Exception( |
| 375 docComment( | 433 "Don't know how to create default field value."); |
| 376 [ | 434 } |
| 377 new dom.Text( | 435 } |
| 378 'Get the result of applying a set of ' + | 436 } else { |
| 379 '[edits] to the given [code]. Edits are applied in the
order ' + | 437 args.add(arg); |
| 380 'they appear in [edits].')]); | 438 } |
| 439 } |
| 440 if (optionalArgs.isNotEmpty) { |
| 441 args.add('{${optionalArgs.join(', ')}}'); |
| 442 } |
| 443 write('$className(${args.join(', ')})'); |
| 444 if (extraInitCode.isEmpty) { |
| 445 writeln(';'); |
| 446 } else { |
| 447 writeln(' {'); |
| 448 indent(() { |
| 449 for (CodegenCallback callback in extraInitCode) { |
| 450 callback(); |
| 451 } |
| 452 }); |
| 453 writeln('}'); |
| 454 } |
| 455 } |
| 456 |
| 457 /** |
| 458 * Emit the operator== code for an object class. |
| 459 */ |
| 460 void emitObjectEqualsMember(TypeObject type, String className) { |
| 461 writeln('@override'); |
| 462 writeln('bool operator==(other) {'); |
| 463 indent(() { |
| 464 writeln('if (other is $className) {'); |
| 465 indent(() { |
| 466 var comparisons = <String>[]; |
| 467 if (type != null) { |
| 468 for (TypeObjectField field in type.fields) { |
| 469 if (field.value != null) { |
| 470 continue; |
| 471 } |
| 472 comparisons.add( |
| 473 compareEqualsCode(field.type, field.name, 'other.${field.name}')
); |
| 474 } |
| 475 } |
| 476 if (comparisons.isEmpty) { |
| 477 writeln('return true;'); |
| 478 } else { |
| 479 String concatenated = comparisons.join(' &&\n '); |
| 480 writeln('return $concatenated;'); |
| 481 } |
| 482 }); |
| 483 writeln('}'); |
| 484 writeln('return false;'); |
| 485 }); |
| 486 writeln('}'); |
| 487 } |
| 488 |
| 489 /** |
| 490 * Emit the method for decoding an object from JSON. |
| 491 */ |
| 492 void emitObjectFromJsonConstructor(String className, TypeObject type, |
| 493 ImpliedType impliedType) { |
| 494 String humanReadableNameString = |
| 495 literalString(impliedType.humanReadableName); |
| 496 if (className == 'RefactoringFeedback') { |
| 497 writeln( |
| 498 'factory RefactoringFeedback.fromJson(JsonDecoder jsonDecoder, ' |
| 499 'String jsonPath, Object json, Map responseJson) {'); |
| 500 indent(() { |
| 381 writeln( | 501 writeln( |
| 382 'static String applySequence(String code, Iterable<SourceEdit> edits
) =>'); | 502 'return _refactoringFeedbackFromJson(jsonDecoder, jsonPath, ' |
| 383 writeln(' _applySequence(code, edits);'); | 503 'json, responseJson);'); |
| 384 return true; | 504 }); |
| 385 default: | 505 writeln('}'); |
| 386 return false; | 506 return; |
| 387 } | 507 } |
| 388 } | 508 if (className == 'RefactoringOptions') { |
| 389 | 509 writeln( |
| 390 /** | 510 'factory RefactoringOptions.fromJson(JsonDecoder jsonDecoder, ' |
| 511 'String jsonPath, Object json, RefactoringKind kind) {'); |
| 512 indent(() { |
| 513 writeln( |
| 514 'return _refactoringOptionsFromJson(jsonDecoder, jsonPath, ' 'json,
kind);'); |
| 515 }); |
| 516 writeln('}'); |
| 517 return; |
| 518 } |
| 519 writeln( |
| 520 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); |
| 521 indent(() { |
| 522 writeln('if (json == null) {'); |
| 523 indent(() { |
| 524 writeln('json = {};'); |
| 525 }); |
| 526 writeln('}'); |
| 527 writeln('if (json is Map) {'); |
| 528 indent(() { |
| 529 List<String> args = <String>[]; |
| 530 List<String> optionalArgs = <String>[]; |
| 531 for (TypeObjectField field in type.fields) { |
| 532 String fieldNameString = literalString(field.name); |
| 533 String fieldAccessor = 'json[$fieldNameString]'; |
| 534 String jsonPath = 'jsonPath + ${literalString('.${field.name}')}'; |
| 535 if (field.value != null) { |
| 536 String valueString = literalString(field.value); |
| 537 writeln('if ($fieldAccessor != $valueString) {'); |
| 538 indent(() { |
| 539 writeln( |
| 540 'throw jsonDecoder.mismatch(jsonPath, "equal " + $valueString)
;'); |
| 541 }); |
| 542 writeln('}'); |
| 543 continue; |
| 544 } |
| 545 if (isOptionalConstructorArg(className, field)) { |
| 546 optionalArgs.add('${field.name}: ${field.name}'); |
| 547 } else { |
| 548 args.add(field.name); |
| 549 } |
| 550 TypeDecl fieldType = field.type; |
| 551 String fieldDartType = dartType(fieldType); |
| 552 writeln('$fieldDartType ${field.name};'); |
| 553 writeln('if (json.containsKey($fieldNameString)) {'); |
| 554 indent(() { |
| 555 String fromJson = |
| 556 fromJsonCode(fieldType).asSnippet(jsonPath, fieldAccessor); |
| 557 writeln('${field.name} = $fromJson;'); |
| 558 }); |
| 559 write('}'); |
| 560 if (!field.optional) { |
| 561 writeln(' else {'); |
| 562 indent(() { |
| 563 writeln( |
| 564 "throw jsonDecoder.missingKey(jsonPath, $fieldNameString);"); |
| 565 }); |
| 566 writeln('}'); |
| 567 } else { |
| 568 writeln(); |
| 569 } |
| 570 } |
| 571 args.addAll(optionalArgs); |
| 572 writeln('return new $className(${args.join(', ')});'); |
| 573 }); |
| 574 writeln('} else {'); |
| 575 indent(() { |
| 576 writeln( |
| 577 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); |
| 578 }); |
| 579 writeln('}'); |
| 580 }); |
| 581 writeln('}'); |
| 582 } |
| 583 |
| 584 /** |
| 585 * Emit the hashCode getter for an object class. |
| 586 */ |
| 587 void emitObjectHashCode(TypeObject type, String className) { |
| 588 writeln('@override'); |
| 589 writeln('int get hashCode {'); |
| 590 indent(() { |
| 591 if (type == null) { |
| 592 writeln('return ${className.hashCode};'); |
| 593 } else { |
| 594 writeln('int hash = 0;'); |
| 595 for (TypeObjectField field in type.fields) { |
| 596 String valueToCombine; |
| 597 if (field.value != null) { |
| 598 valueToCombine = field.value.hashCode.toString(); |
| 599 } else { |
| 600 valueToCombine = '${field.name}.hashCode'; |
| 601 } |
| 602 writeln('hash = _JenkinsSmiHash.combine(hash, $valueToCombine);'); |
| 603 } |
| 604 writeln('return _JenkinsSmiHash.finish(hash);'); |
| 605 } |
| 606 }); |
| 607 writeln('}'); |
| 608 } |
| 609 |
| 610 /** |
| 391 * If the class named [className] requires special constructors, emit them | 611 * If the class named [className] requires special constructors, emit them |
| 392 * and return true. | 612 * and return true. |
| 393 */ | 613 */ |
| 394 bool emitSpecialConstructors(String className) { | 614 bool emitSpecialConstructors(String className) { |
| 395 switch (className) { | 615 switch (className) { |
| 396 case 'LinkedEditGroup': | 616 case 'LinkedEditGroup': |
| 397 docComment([new dom.Text('Construct an empty LinkedEditGroup.')]); | 617 docComment([new dom.Text('Construct an empty LinkedEditGroup.')]); |
| 398 writeln( | 618 writeln( |
| 399 'LinkedEditGroup.empty() : this(<Position>[], 0, <LinkedEditSuggesti
on>[]);'); | 619 'LinkedEditGroup.empty() : this(<Position>[], 0, <LinkedEditSuggesti
on>[]);'); |
| 400 return true; | 620 return true; |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 493 docComment([new dom.Text('Adds the given [Edit]s.')]); | 713 docComment([new dom.Text('Adds the given [Edit]s.')]); |
| 494 writeln('void addAll(Iterable<SourceEdit> edits) =>'); | 714 writeln('void addAll(Iterable<SourceEdit> edits) =>'); |
| 495 writeln(' _addAllEditsForSource(this, edits);'); | 715 writeln(' _addAllEditsForSource(this, edits);'); |
| 496 return true; | 716 return true; |
| 497 default: | 717 default: |
| 498 return false; | 718 return false; |
| 499 } | 719 } |
| 500 } | 720 } |
| 501 | 721 |
| 502 /** | 722 /** |
| 503 * Emit the constructor for an object class. | 723 * If the class named [className] requires special static members, emit them |
| 724 * and return true. |
| 504 */ | 725 */ |
| 505 void emitObjectConstructor(TypeObject type, String className) { | 726 bool emitSpecialStaticMembers(String className) { |
| 506 List<String> args = <String>[]; | 727 switch (className) { |
| 507 List<String> optionalArgs = <String>[]; | 728 case 'Element': |
| 508 List<CodegenCallback> extraInitCode = <CodegenCallback>[]; | 729 List<String> makeFlagsArgs = <String>[]; |
| 509 for (TypeObjectField field in type.fields) { | 730 List<String> makeFlagsStatements = <String>[]; |
| 510 if (field.value != null) { | 731 specialElementFlags.forEach((String name, String value) { |
| 511 continue; | 732 String flag = 'FLAG_${name.toUpperCase()}'; |
| 512 } | 733 String camelName = camelJoin(['is', name]); |
| 513 String arg = 'this.${field.name}'; | 734 writeln('static const int $flag = $value;'); |
| 514 if (isOptionalConstructorArg(className, field)) { | 735 makeFlagsArgs.add('$camelName: false'); |
| 515 optionalArgs.add(arg); | 736 makeFlagsStatements.add('if ($camelName) flags |= $flag;'); |
| 516 if (!field.optional) { | 737 }); |
| 517 // Optional constructor arg, but non-optional field. If no arg is | 738 writeln(); |
| 518 // given, the constructor should populate with the empty list. | 739 writeln('static int makeFlags({${makeFlagsArgs.join(', ')}}) {'); |
| 519 TypeDecl fieldType = field.type; | 740 indent(() { |
| 520 if (fieldType is TypeList) { | 741 writeln('int flags = 0;'); |
| 521 extraInitCode.add(() { | 742 for (String statement in makeFlagsStatements) { |
| 522 writeln('if (${field.name} == null) {'); | 743 writeln(statement); |
| 523 indent(() { | |
| 524 writeln('${field.name} = <${dartType(fieldType.itemType)}>[];'); | |
| 525 }); | |
| 526 writeln('}'); | |
| 527 }); | |
| 528 } else { | |
| 529 throw new Exception( | |
| 530 "Don't know how to create default field value."); | |
| 531 } | 744 } |
| 532 } | 745 writeln('return flags;'); |
| 533 } else { | 746 }); |
| 534 args.add(arg); | 747 writeln('}'); |
| 535 } | 748 return true; |
| 536 } | 749 case 'SourceEdit': |
| 537 if (optionalArgs.isNotEmpty) { | 750 docComment( |
| 538 args.add('{${optionalArgs.join(', ')}}'); | 751 [ |
| 539 } | 752 new dom.Text( |
| 540 write('$className(${args.join(', ')})'); | 753 'Get the result of applying a set of ' + |
| 541 if (extraInitCode.isEmpty) { | 754 '[edits] to the given [code]. Edits are applied in the
order ' + |
| 542 writeln(';'); | 755 'they appear in [edits].')]); |
| 543 } else { | 756 writeln( |
| 544 writeln(' {'); | 757 'static String applySequence(String code, Iterable<SourceEdit> edits
) =>'); |
| 545 indent(() { | 758 writeln(' _applySequence(code, edits);'); |
| 546 for (CodegenCallback callback in extraInitCode) { | 759 return true; |
| 547 callback(); | 760 default: |
| 548 } | 761 return false; |
| 549 }); | |
| 550 writeln('}'); | |
| 551 } | 762 } |
| 552 } | 763 } |
| 553 | 764 |
| 554 /** | 765 /** |
| 555 * True if the constructor argument for the given field should be optional. | |
| 556 */ | |
| 557 bool isOptionalConstructorArg(String className, TypeObjectField field) { | |
| 558 if (field.optional) { | |
| 559 return true; | |
| 560 } | |
| 561 List<String> forceOptional = _optionalConstructorArguments[className]; | |
| 562 if (forceOptional != null && forceOptional.contains(field.name)) { | |
| 563 return true; | |
| 564 } | |
| 565 return false; | |
| 566 } | |
| 567 | |
| 568 /** | |
| 569 * Emit the toJson() code for an object class. | 766 * Emit the toJson() code for an object class. |
| 570 */ | 767 */ |
| 571 void emitToJsonMember(TypeObject type) { | 768 void emitToJsonMember(TypeObject type) { |
| 572 writeln('Map<String, dynamic> toJson() {'); | 769 writeln('Map<String, dynamic> toJson() {'); |
| 573 indent(() { | 770 indent(() { |
| 574 writeln('Map<String, dynamic> result = {};'); | 771 writeln('Map<String, dynamic> result = {};'); |
| 575 for (TypeObjectField field in type.fields) { | 772 for (TypeObjectField field in type.fields) { |
| 576 String fieldNameString = literalString(field.name); | 773 String fieldNameString = literalString(field.name); |
| 577 if (field.value != null) { | 774 if (field.value != null) { |
| 578 writeln('result[$fieldNameString] = ${literalString(field.value)};'); | 775 writeln('result[$fieldNameString] = ${literalString(field.value)};'); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 589 } else { | 786 } else { |
| 590 writeln(populateField); | 787 writeln(populateField); |
| 591 } | 788 } |
| 592 } | 789 } |
| 593 writeln('return result;'); | 790 writeln('return result;'); |
| 594 }); | 791 }); |
| 595 writeln('}'); | 792 writeln('}'); |
| 596 } | 793 } |
| 597 | 794 |
| 598 /** | 795 /** |
| 796 * Emit the toNotification() code for a class, if appropriate. Returns true |
| 797 * if code was emitted. |
| 798 */ |
| 799 bool emitToNotificationMember(ImpliedType impliedType) { |
| 800 if (impliedType.kind == 'notificationParams') { |
| 801 writeln('Notification toNotification() {'); |
| 802 indent(() { |
| 803 String eventString = |
| 804 literalString((impliedType.apiNode as Notification).longEvent); |
| 805 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; |
| 806 writeln('return new Notification($eventString, $jsonPart);'); |
| 807 }); |
| 808 writeln('}'); |
| 809 return true; |
| 810 } |
| 811 return false; |
| 812 } |
| 813 |
| 814 /** |
| 599 * Emit the toRequest() code for a class, if appropriate. Returns true if | 815 * Emit the toRequest() code for a class, if appropriate. Returns true if |
| 600 * code was emitted. | 816 * code was emitted. |
| 601 */ | 817 */ |
| 602 bool emitToRequestMember(ImpliedType impliedType) { | 818 bool emitToRequestMember(ImpliedType impliedType) { |
| 603 if (impliedType.kind == 'requestParams') { | 819 if (impliedType.kind == 'requestParams') { |
| 604 writeln('Request toRequest(String id) {'); | 820 writeln('Request toRequest(String id) {'); |
| 605 indent(() { | 821 indent(() { |
| 606 String methodString = | 822 String methodString = |
| 607 literalString((impliedType.apiNode as Request).longMethod); | 823 literalString((impliedType.apiNode as Request).longMethod); |
| 608 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; | 824 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; |
| (...skipping 16 matching lines...) Expand all Loading... |
| 625 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; | 841 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; |
| 626 writeln('return new Response(id, result: $jsonPart);'); | 842 writeln('return new Response(id, result: $jsonPart);'); |
| 627 }); | 843 }); |
| 628 writeln('}'); | 844 writeln('}'); |
| 629 return true; | 845 return true; |
| 630 } | 846 } |
| 631 return false; | 847 return false; |
| 632 } | 848 } |
| 633 | 849 |
| 634 /** | 850 /** |
| 635 * Emit the toNotification() code for a class, if appropriate. Returns true | 851 * Compute the code necessary to translate [type] from JSON. |
| 636 * if code was emitted. | |
| 637 */ | 852 */ |
| 638 bool emitToNotificationMember(ImpliedType impliedType) { | 853 FromJsonCode fromJsonCode(TypeDecl type) { |
| 639 if (impliedType.kind == 'notificationParams') { | 854 if (type is TypeReference) { |
| 640 writeln('Notification toNotification() {'); | 855 TypeDefinition referencedDefinition = api.types[type.typeName]; |
| 641 indent(() { | 856 if (referencedDefinition != null) { |
| 642 String eventString = | 857 TypeDecl referencedType = referencedDefinition.type; |
| 643 literalString((impliedType.apiNode as Notification).longEvent); | 858 if (referencedType is TypeObject || referencedType is TypeEnum) { |
| 644 String jsonPart = impliedType.type != null ? 'toJson()' : 'null'; | 859 return new FromJsonSnippet((String jsonPath, String json) { |
| 645 writeln('return new Notification($eventString, $jsonPart);'); | 860 String typeName = dartType(type); |
| 646 }); | 861 if (typeName == 'RefactoringFeedback') { |
| 647 writeln('}'); | 862 return |
| 863 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, json)'; |
| 864 } else if (typeName == 'RefactoringOptions') { |
| 865 return |
| 866 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, kind)'; |
| 867 } else { |
| 868 return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json)'; |
| 869 } |
| 870 }); |
| 871 } else { |
| 872 return fromJsonCode(referencedType); |
| 873 } |
| 874 } else { |
| 875 switch (type.typeName) { |
| 876 case 'String': |
| 877 return new FromJsonFunction('jsonDecoder._decodeString'); |
| 878 case 'bool': |
| 879 return new FromJsonFunction('jsonDecoder._decodeBool'); |
| 880 case 'int': |
| 881 case 'long': |
| 882 return new FromJsonFunction('jsonDecoder._decodeInt'); |
| 883 case 'object': |
| 884 return new FromJsonIdentity(); |
| 885 default: |
| 886 throw new Exception('Unexpected type name ${type.typeName}'); |
| 887 } |
| 888 } |
| 889 } else if (type is TypeMap) { |
| 890 FromJsonCode keyCode; |
| 891 if (dartType(type.keyType) != 'String') { |
| 892 keyCode = fromJsonCode(type.keyType); |
| 893 } else { |
| 894 keyCode = new FromJsonIdentity(); |
| 895 } |
| 896 FromJsonCode valueCode = fromJsonCode(type.valueType); |
| 897 if (keyCode.isIdentity && valueCode.isIdentity) { |
| 898 return new FromJsonFunction('jsonDecoder._decodeMap'); |
| 899 } else { |
| 900 return new FromJsonSnippet((String jsonPath, String json) { |
| 901 StringBuffer result = new StringBuffer(); |
| 902 result.write('jsonDecoder._decodeMap($jsonPath, $json'); |
| 903 if (!keyCode.isIdentity) { |
| 904 result.write(', keyDecoder: ${keyCode.asClosure}'); |
| 905 } |
| 906 if (!valueCode.isIdentity) { |
| 907 result.write(', valueDecoder: ${valueCode.asClosure}'); |
| 908 } |
| 909 result.write(')'); |
| 910 return result.toString(); |
| 911 }); |
| 912 } |
| 913 } else if (type is TypeList) { |
| 914 FromJsonCode itemCode = fromJsonCode(type.itemType); |
| 915 if (itemCode.isIdentity) { |
| 916 return new FromJsonFunction('jsonDecoder._decodeList'); |
| 917 } else { |
| 918 return new FromJsonSnippet( |
| 919 (String jsonPath, String json) => |
| 920 'jsonDecoder._decodeList($jsonPath, $json, ${itemCode.asClosure}
)'); |
| 921 } |
| 922 } else if (type is TypeUnion) { |
| 923 List<String> decoders = <String>[]; |
| 924 for (TypeDecl choice in type.choices) { |
| 925 TypeDecl resolvedChoice = resolveTypeReferenceChain(choice); |
| 926 if (resolvedChoice is TypeObject) { |
| 927 TypeObjectField field = resolvedChoice.getField(type.field); |
| 928 if (field == null) { |
| 929 throw new Exception( |
| 930 'Each choice in the union needs a field named ${type.field}'); |
| 931 } |
| 932 if (field.value == null) { |
| 933 throw new Exception( |
| 934 'Each choice in the union needs a constant value for the field $
{type.field}'); |
| 935 } |
| 936 String closure = fromJsonCode(choice).asClosure; |
| 937 decoders.add('${literalString(field.value)}: $closure'); |
| 938 } else { |
| 939 throw new Exception('Union types must be unions of objects.'); |
| 940 } |
| 941 } |
| 942 return new FromJsonSnippet( |
| 943 (String jsonPath, String json) => |
| 944 'jsonDecoder._decodeUnion($jsonPath, $json, ${literalString(type.f
ield)}, {${decoders.join(', ')}})'); |
| 945 } else { |
| 946 throw new Exception("Can't convert $type from JSON"); |
| 947 } |
| 948 } |
| 949 |
| 950 /** |
| 951 * True if the constructor argument for the given field should be optional. |
| 952 */ |
| 953 bool isOptionalConstructorArg(String className, TypeObjectField field) { |
| 954 if (field.optional) { |
| 955 return true; |
| 956 } |
| 957 List<String> forceOptional = _optionalConstructorArguments[className]; |
| 958 if (forceOptional != null && forceOptional.contains(field.name)) { |
| 648 return true; | 959 return true; |
| 649 } | 960 } |
| 650 return false; | 961 return false; |
| 651 } | 962 } |
| 652 | 963 |
| 653 /** | 964 /** |
| 654 * Emit the operator== code for an object class. | 965 * Create a string literal that evaluates to [s]. |
| 655 */ | 966 */ |
| 656 void emitObjectEqualsMember(TypeObject type, String className) { | 967 String literalString(String s) { |
| 657 writeln('@override'); | 968 return JSON.encode(s); |
| 658 writeln('bool operator==(other) {'); | |
| 659 indent(() { | |
| 660 writeln('if (other is $className) {'); | |
| 661 indent(() { | |
| 662 var comparisons = <String>[]; | |
| 663 if (type != null) { | |
| 664 for (TypeObjectField field in type.fields) { | |
| 665 if (field.value != null) { | |
| 666 continue; | |
| 667 } | |
| 668 comparisons.add( | |
| 669 compareEqualsCode(field.type, field.name, 'other.${field.name}')
); | |
| 670 } | |
| 671 } | |
| 672 if (comparisons.isEmpty) { | |
| 673 writeln('return true;'); | |
| 674 } else { | |
| 675 String concatenated = comparisons.join(' &&\n '); | |
| 676 writeln('return $concatenated;'); | |
| 677 } | |
| 678 }); | |
| 679 writeln('}'); | |
| 680 writeln('return false;'); | |
| 681 }); | |
| 682 writeln('}'); | |
| 683 } | |
| 684 | |
| 685 /** | |
| 686 * Emit the hashCode getter for an object class. | |
| 687 */ | |
| 688 void emitObjectHashCode(TypeObject type, String className) { | |
| 689 writeln('@override'); | |
| 690 writeln('int get hashCode {'); | |
| 691 indent(() { | |
| 692 if (type == null) { | |
| 693 writeln('return ${className.hashCode};'); | |
| 694 } else { | |
| 695 writeln('int hash = 0;'); | |
| 696 for (TypeObjectField field in type.fields) { | |
| 697 String valueToCombine; | |
| 698 if (field.value != null) { | |
| 699 valueToCombine = field.value.hashCode.toString(); | |
| 700 } else { | |
| 701 valueToCombine = '${field.name}.hashCode'; | |
| 702 } | |
| 703 writeln('hash = _JenkinsSmiHash.combine(hash, $valueToCombine);'); | |
| 704 } | |
| 705 writeln('return _JenkinsSmiHash.finish(hash);'); | |
| 706 } | |
| 707 }); | |
| 708 writeln('}'); | |
| 709 } | |
| 710 | |
| 711 /** | |
| 712 * Emit a class to encapsulate an enum. | |
| 713 */ | |
| 714 void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) { | |
| 715 docComment(toHtmlVisitor.collectHtml(() { | |
| 716 toHtmlVisitor.p(() { | |
| 717 toHtmlVisitor.write(impliedType.humanReadableName); | |
| 718 }); | |
| 719 if (impliedType.type != null) { | |
| 720 toHtmlVisitor.showType(null, impliedType.type); | |
| 721 } | |
| 722 })); | |
| 723 writeln('class $className implements Enum {'); | |
| 724 indent(() { | |
| 725 if (emitSpecialStaticMembers(className)) { | |
| 726 writeln(); | |
| 727 } | |
| 728 for (TypeEnumValue value in type.values) { | |
| 729 docComment(toHtmlVisitor.collectHtml(() { | |
| 730 toHtmlVisitor.translateHtml(value.html); | |
| 731 })); | |
| 732 String valueString = literalString(value.value); | |
| 733 writeln( | |
| 734 'static const ${value.value} = const $className._($valueString);'); | |
| 735 writeln(); | |
| 736 } | |
| 737 writeln('final String name;'); | |
| 738 writeln(); | |
| 739 writeln('const $className._(this.name);'); | |
| 740 writeln(); | |
| 741 emitEnumClassConstructor(className, type); | |
| 742 writeln(); | |
| 743 emitEnumFromJsonConstructor(className, type, impliedType); | |
| 744 writeln(); | |
| 745 if (emitSpecialConstructors(className)) { | |
| 746 writeln(); | |
| 747 } | |
| 748 if (emitSpecialGetters(className)) { | |
| 749 writeln(); | |
| 750 } | |
| 751 if (emitSpecialMethods(className)) { | |
| 752 writeln(); | |
| 753 } | |
| 754 writeln('@override'); | |
| 755 writeln('String toString() => "$className.\$name";'); | |
| 756 writeln(); | |
| 757 writeln('String toJson() => name;'); | |
| 758 }); | |
| 759 writeln('}'); | |
| 760 } | |
| 761 | |
| 762 /** | |
| 763 * Emit the constructor for an enum class. | |
| 764 */ | |
| 765 void emitEnumClassConstructor(String className, TypeEnum type) { | |
| 766 writeln('factory $className(String name) {'); | |
| 767 indent(() { | |
| 768 writeln('switch (name) {'); | |
| 769 indent(() { | |
| 770 for (TypeEnumValue value in type.values) { | |
| 771 String valueString = literalString(value.value); | |
| 772 writeln('case $valueString:'); | |
| 773 indent(() { | |
| 774 writeln('return ${value.value};'); | |
| 775 }); | |
| 776 } | |
| 777 }); | |
| 778 writeln('}'); | |
| 779 writeln(r"throw new Exception('Illegal enum value: $name');"); | |
| 780 }); | |
| 781 writeln('}'); | |
| 782 } | 969 } |
| 783 | 970 |
| 784 /** | 971 /** |
| 785 * Compute the code necessary to convert [type] to JSON. | 972 * Compute the code necessary to convert [type] to JSON. |
| 786 */ | 973 */ |
| 787 ToJsonCode toJsonCode(TypeDecl type) { | 974 ToJsonCode toJsonCode(TypeDecl type) { |
| 788 TypeDecl resolvedType = resolveTypeReferenceChain(type); | 975 TypeDecl resolvedType = resolveTypeReferenceChain(type); |
| 789 if (resolvedType is TypeReference) { | 976 if (resolvedType is TypeReference) { |
| 790 return new ToJsonIdentity(dartType(type)); | 977 return new ToJsonIdentity(dartType(type)); |
| 791 } else if (resolvedType is TypeList) { | 978 } else if (resolvedType is TypeList) { |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 832 (String value) => '$value.toJson()'); | 1019 (String value) => '$value.toJson()'); |
| 833 } else if (resolvedType is TypeObject || resolvedType is TypeEnum) { | 1020 } else if (resolvedType is TypeObject || resolvedType is TypeEnum) { |
| 834 return new ToJsonSnippet( | 1021 return new ToJsonSnippet( |
| 835 dartType(type), | 1022 dartType(type), |
| 836 (String value) => '$value.toJson()'); | 1023 (String value) => '$value.toJson()'); |
| 837 } else { | 1024 } else { |
| 838 throw new Exception("Can't convert $resolvedType from JSON"); | 1025 throw new Exception("Can't convert $resolvedType from JSON"); |
| 839 } | 1026 } |
| 840 } | 1027 } |
| 841 | 1028 |
| 842 /** | 1029 @override |
| 843 * Compute the code necessary to compare two objects for equality. | 1030 visitApi() { |
| 844 */ | 1031 outputHeader(); |
| 845 String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) { | 1032 writeln(); |
| 846 TypeDecl resolvedType = resolveTypeReferenceChain(type); | 1033 writeln('part of protocol;'); |
| 847 if (resolvedType is TypeReference || | 1034 emitClasses(); |
| 848 resolvedType is TypeEnum || | |
| 849 resolvedType is TypeObject || | |
| 850 resolvedType is TypeUnion) { | |
| 851 return '$thisVar == $otherVar'; | |
| 852 } else if (resolvedType is TypeList) { | |
| 853 String itemTypeName = dartType(resolvedType.itemType); | |
| 854 String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b'); | |
| 855 String closure = '($itemTypeName a, $itemTypeName b) => $subComparison'; | |
| 856 return '_listEqual($thisVar, $otherVar, $closure)'; | |
| 857 } else if (resolvedType is TypeMap) { | |
| 858 String valueTypeName = dartType(resolvedType.valueType); | |
| 859 String subComparison = | |
| 860 compareEqualsCode(resolvedType.valueType, 'a', 'b'); | |
| 861 String closure = '($valueTypeName a, $valueTypeName b) => $subComparison'; | |
| 862 return '_mapEqual($thisVar, $otherVar, $closure)'; | |
| 863 } | |
| 864 throw new Exception( | |
| 865 "Don't know how to compare for equality: $resolvedType"); | |
| 866 } | |
| 867 | |
| 868 /** | |
| 869 * Emit the method for decoding an object from JSON. | |
| 870 */ | |
| 871 void emitObjectFromJsonConstructor(String className, TypeObject type, | |
| 872 ImpliedType impliedType) { | |
| 873 String humanReadableNameString = | |
| 874 literalString(impliedType.humanReadableName); | |
| 875 if (className == 'RefactoringFeedback') { | |
| 876 writeln( | |
| 877 'factory RefactoringFeedback.fromJson(JsonDecoder jsonDecoder, ' | |
| 878 'String jsonPath, Object json, Map responseJson) {'); | |
| 879 indent(() { | |
| 880 writeln( | |
| 881 'return _refactoringFeedbackFromJson(jsonDecoder, jsonPath, ' | |
| 882 'json, responseJson);'); | |
| 883 }); | |
| 884 writeln('}'); | |
| 885 return; | |
| 886 } | |
| 887 if (className == 'RefactoringOptions') { | |
| 888 writeln( | |
| 889 'factory RefactoringOptions.fromJson(JsonDecoder jsonDecoder, ' | |
| 890 'String jsonPath, Object json, RefactoringKind kind) {'); | |
| 891 indent(() { | |
| 892 writeln( | |
| 893 'return _refactoringOptionsFromJson(jsonDecoder, jsonPath, ' 'json,
kind);'); | |
| 894 }); | |
| 895 writeln('}'); | |
| 896 return; | |
| 897 } | |
| 898 writeln( | |
| 899 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); | |
| 900 indent(() { | |
| 901 writeln('if (json == null) {'); | |
| 902 indent(() { | |
| 903 writeln('json = {};'); | |
| 904 }); | |
| 905 writeln('}'); | |
| 906 writeln('if (json is Map) {'); | |
| 907 indent(() { | |
| 908 List<String> args = <String>[]; | |
| 909 List<String> optionalArgs = <String>[]; | |
| 910 for (TypeObjectField field in type.fields) { | |
| 911 String fieldNameString = literalString(field.name); | |
| 912 String fieldAccessor = 'json[$fieldNameString]'; | |
| 913 String jsonPath = 'jsonPath + ${literalString('.${field.name}')}'; | |
| 914 if (field.value != null) { | |
| 915 String valueString = literalString(field.value); | |
| 916 writeln('if ($fieldAccessor != $valueString) {'); | |
| 917 indent(() { | |
| 918 writeln( | |
| 919 'throw jsonDecoder.mismatch(jsonPath, "equal " + $valueString)
;'); | |
| 920 }); | |
| 921 writeln('}'); | |
| 922 continue; | |
| 923 } | |
| 924 if (isOptionalConstructorArg(className, field)) { | |
| 925 optionalArgs.add('${field.name}: ${field.name}'); | |
| 926 } else { | |
| 927 args.add(field.name); | |
| 928 } | |
| 929 TypeDecl fieldType = field.type; | |
| 930 String fieldDartType = dartType(fieldType); | |
| 931 writeln('$fieldDartType ${field.name};'); | |
| 932 writeln('if (json.containsKey($fieldNameString)) {'); | |
| 933 indent(() { | |
| 934 String fromJson = | |
| 935 fromJsonCode(fieldType).asSnippet(jsonPath, fieldAccessor); | |
| 936 writeln('${field.name} = $fromJson;'); | |
| 937 }); | |
| 938 write('}'); | |
| 939 if (!field.optional) { | |
| 940 writeln(' else {'); | |
| 941 indent(() { | |
| 942 writeln( | |
| 943 "throw jsonDecoder.missingKey(jsonPath, $fieldNameString);"); | |
| 944 }); | |
| 945 writeln('}'); | |
| 946 } else { | |
| 947 writeln(); | |
| 948 } | |
| 949 } | |
| 950 args.addAll(optionalArgs); | |
| 951 writeln('return new $className(${args.join(', ')});'); | |
| 952 }); | |
| 953 writeln('} else {'); | |
| 954 indent(() { | |
| 955 writeln( | |
| 956 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); | |
| 957 }); | |
| 958 writeln('}'); | |
| 959 }); | |
| 960 writeln('}'); | |
| 961 } | |
| 962 | |
| 963 /** | |
| 964 * Emit the method for decoding an enum from JSON. | |
| 965 */ | |
| 966 void emitEnumFromJsonConstructor(String className, TypeEnum type, | |
| 967 ImpliedType impliedType) { | |
| 968 writeln( | |
| 969 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {'); | |
| 970 indent(() { | |
| 971 writeln('if (json is String) {'); | |
| 972 indent(() { | |
| 973 writeln('try {'); | |
| 974 indent(() { | |
| 975 writeln('return new $className(json);'); | |
| 976 }); | |
| 977 writeln('} catch(_) {'); | |
| 978 indent(() { | |
| 979 writeln('// Fall through'); | |
| 980 }); | |
| 981 writeln('}'); | |
| 982 }); | |
| 983 writeln('}'); | |
| 984 String humanReadableNameString = | |
| 985 literalString(impliedType.humanReadableName); | |
| 986 writeln( | |
| 987 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); | |
| 988 }); | |
| 989 writeln('}'); | |
| 990 } | |
| 991 | |
| 992 /** | |
| 993 * Compute the code necessary to translate [type] from JSON. | |
| 994 */ | |
| 995 FromJsonCode fromJsonCode(TypeDecl type) { | |
| 996 if (type is TypeReference) { | |
| 997 TypeDefinition referencedDefinition = api.types[type.typeName]; | |
| 998 if (referencedDefinition != null) { | |
| 999 TypeDecl referencedType = referencedDefinition.type; | |
| 1000 if (referencedType is TypeObject || referencedType is TypeEnum) { | |
| 1001 return new FromJsonSnippet((String jsonPath, String json) { | |
| 1002 String typeName = dartType(type); | |
| 1003 if (typeName == 'RefactoringFeedback') { | |
| 1004 return | |
| 1005 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, json)'; | |
| 1006 } else if (typeName == 'RefactoringOptions') { | |
| 1007 return | |
| 1008 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json, kind)'; | |
| 1009 } else { | |
| 1010 return 'new $typeName.fromJson(jsonDecoder, $jsonPath, $json)'; | |
| 1011 } | |
| 1012 }); | |
| 1013 } else { | |
| 1014 return fromJsonCode(referencedType); | |
| 1015 } | |
| 1016 } else { | |
| 1017 switch (type.typeName) { | |
| 1018 case 'String': | |
| 1019 return new FromJsonFunction('jsonDecoder._decodeString'); | |
| 1020 case 'bool': | |
| 1021 return new FromJsonFunction('jsonDecoder._decodeBool'); | |
| 1022 case 'int': | |
| 1023 case 'long': | |
| 1024 return new FromJsonFunction('jsonDecoder._decodeInt'); | |
| 1025 case 'object': | |
| 1026 return new FromJsonIdentity(); | |
| 1027 default: | |
| 1028 throw new Exception('Unexpected type name ${type.typeName}'); | |
| 1029 } | |
| 1030 } | |
| 1031 } else if (type is TypeMap) { | |
| 1032 FromJsonCode keyCode; | |
| 1033 if (dartType(type.keyType) != 'String') { | |
| 1034 keyCode = fromJsonCode(type.keyType); | |
| 1035 } else { | |
| 1036 keyCode = new FromJsonIdentity(); | |
| 1037 } | |
| 1038 FromJsonCode valueCode = fromJsonCode(type.valueType); | |
| 1039 if (keyCode.isIdentity && valueCode.isIdentity) { | |
| 1040 return new FromJsonFunction('jsonDecoder._decodeMap'); | |
| 1041 } else { | |
| 1042 return new FromJsonSnippet((String jsonPath, String json) { | |
| 1043 StringBuffer result = new StringBuffer(); | |
| 1044 result.write('jsonDecoder._decodeMap($jsonPath, $json'); | |
| 1045 if (!keyCode.isIdentity) { | |
| 1046 result.write(', keyDecoder: ${keyCode.asClosure}'); | |
| 1047 } | |
| 1048 if (!valueCode.isIdentity) { | |
| 1049 result.write(', valueDecoder: ${valueCode.asClosure}'); | |
| 1050 } | |
| 1051 result.write(')'); | |
| 1052 return result.toString(); | |
| 1053 }); | |
| 1054 } | |
| 1055 } else if (type is TypeList) { | |
| 1056 FromJsonCode itemCode = fromJsonCode(type.itemType); | |
| 1057 if (itemCode.isIdentity) { | |
| 1058 return new FromJsonFunction('jsonDecoder._decodeList'); | |
| 1059 } else { | |
| 1060 return new FromJsonSnippet( | |
| 1061 (String jsonPath, String json) => | |
| 1062 'jsonDecoder._decodeList($jsonPath, $json, ${itemCode.asClosure}
)'); | |
| 1063 } | |
| 1064 } else if (type is TypeUnion) { | |
| 1065 List<String> decoders = <String>[]; | |
| 1066 for (TypeDecl choice in type.choices) { | |
| 1067 TypeDecl resolvedChoice = resolveTypeReferenceChain(choice); | |
| 1068 if (resolvedChoice is TypeObject) { | |
| 1069 TypeObjectField field = resolvedChoice.getField(type.field); | |
| 1070 if (field == null) { | |
| 1071 throw new Exception( | |
| 1072 'Each choice in the union needs a field named ${type.field}'); | |
| 1073 } | |
| 1074 if (field.value == null) { | |
| 1075 throw new Exception( | |
| 1076 'Each choice in the union needs a constant value for the field $
{type.field}'); | |
| 1077 } | |
| 1078 String closure = fromJsonCode(choice).asClosure; | |
| 1079 decoders.add('${literalString(field.value)}: $closure'); | |
| 1080 } else { | |
| 1081 throw new Exception('Union types must be unions of objects.'); | |
| 1082 } | |
| 1083 } | |
| 1084 return new FromJsonSnippet( | |
| 1085 (String jsonPath, String json) => | |
| 1086 'jsonDecoder._decodeUnion($jsonPath, $json, ${literalString(type.f
ield)}, {${decoders.join(', ')}})'); | |
| 1087 } else { | |
| 1088 throw new Exception("Can't convert $type from JSON"); | |
| 1089 } | |
| 1090 } | |
| 1091 | |
| 1092 /** | |
| 1093 * Emit a convenience constructor for decoding a piece of protocol, if | |
| 1094 * appropriate. Return true if a constructor was emitted. | |
| 1095 */ | |
| 1096 bool emitConvenienceConstructor(String className, ImpliedType impliedType) { | |
| 1097 // The type of object from which this piece of protocol should be decoded. | |
| 1098 String inputType; | |
| 1099 // The name of the input object. | |
| 1100 String inputName; | |
| 1101 // The field within the input object to decode. | |
| 1102 String fieldName; | |
| 1103 // Constructor call to create the JsonDecoder object. | |
| 1104 String makeDecoder; | |
| 1105 // Name of the constructor to create. | |
| 1106 String constructorName; | |
| 1107 // Extra arguments for the constructor. | |
| 1108 List<String> extraArgs = <String>[]; | |
| 1109 switch (impliedType.kind) { | |
| 1110 case 'requestParams': | |
| 1111 inputType = 'Request'; | |
| 1112 inputName = 'request'; | |
| 1113 fieldName = '_params'; | |
| 1114 makeDecoder = 'new RequestDecoder(request)'; | |
| 1115 constructorName = 'fromRequest'; | |
| 1116 break; | |
| 1117 case 'requestResult': | |
| 1118 inputType = 'Response'; | |
| 1119 inputName = 'response'; | |
| 1120 fieldName = '_result'; | |
| 1121 makeDecoder = | |
| 1122 'new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id
))'; | |
| 1123 constructorName = 'fromResponse'; | |
| 1124 break; | |
| 1125 case 'notificationParams': | |
| 1126 inputType = 'Notification'; | |
| 1127 inputName = 'notification'; | |
| 1128 fieldName = '_params'; | |
| 1129 makeDecoder = 'new ResponseDecoder(null)'; | |
| 1130 constructorName = 'fromNotification'; | |
| 1131 break; | |
| 1132 case 'refactoringOptions': | |
| 1133 inputType = 'EditGetRefactoringParams'; | |
| 1134 inputName = 'refactoringParams'; | |
| 1135 fieldName = 'options'; | |
| 1136 makeDecoder = 'new RequestDecoder(request)'; | |
| 1137 constructorName = 'fromRefactoringParams'; | |
| 1138 extraArgs.add('Request request'); | |
| 1139 break; | |
| 1140 default: | |
| 1141 return false; | |
| 1142 } | |
| 1143 List<String> args = ['$inputType $inputName']; | |
| 1144 args.addAll(extraArgs); | |
| 1145 writeln('factory $className.$constructorName(${args.join(', ')}) {'); | |
| 1146 indent(() { | |
| 1147 String fieldNameString = | |
| 1148 literalString(fieldName.replaceFirst(new RegExp('^_'), '')); | |
| 1149 if (className == 'EditGetRefactoringParams') { | |
| 1150 writeln('var params = new $className.fromJson('); | |
| 1151 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); | |
| 1152 writeln('REQUEST_ID_REFACTORING_KINDS[request.id] = params.kind;'); | |
| 1153 writeln('return params;'); | |
| 1154 } else { | |
| 1155 writeln('return new $className.fromJson('); | |
| 1156 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); | |
| 1157 } | |
| 1158 }); | |
| 1159 writeln('}'); | |
| 1160 return true; | |
| 1161 } | |
| 1162 | |
| 1163 /** | |
| 1164 * Create a string literal that evaluates to [s]. | |
| 1165 */ | |
| 1166 String literalString(String s) { | |
| 1167 return JSON.encode(s); | |
| 1168 } | 1035 } |
| 1169 } | 1036 } |
| 1170 | 1037 |
| 1171 final GeneratedFile target = | 1038 /** |
| 1172 new GeneratedFile('../../lib/src/generated_protocol.dart', () { | 1039 * Container for code that can be used to translate a data type from JSON. |
| 1173 CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi()); | 1040 */ |
| 1174 return visitor.collectCode(visitor.visitApi); | 1041 abstract class FromJsonCode { |
| 1175 }); | 1042 /** |
| 1043 * Get the translation code in the form of a closure. |
| 1044 */ |
| 1045 String get asClosure; |
| 1046 |
| 1047 /** |
| 1048 * True if the data type is already in JSON form, so the translation is the |
| 1049 * identity function. |
| 1050 */ |
| 1051 bool get isIdentity; |
| 1052 |
| 1053 /** |
| 1054 * Get the translation code in the form of a code snippet, where [jsonPath] |
| 1055 * is the variable holding the JSON path, and [json] is the variable holding |
| 1056 * the raw JSON. |
| 1057 */ |
| 1058 String asSnippet(String jsonPath, String json); |
| 1059 } |
| 1176 | 1060 |
| 1177 /** | 1061 /** |
| 1178 * Translate spec_input.html into protocol_matchers.dart. | 1062 * Representation of FromJsonCode for a function defined elsewhere. |
| 1179 */ | 1063 */ |
| 1180 main() { | 1064 class FromJsonFunction extends FromJsonCode { |
| 1181 target.generate(); | 1065 final String asClosure; |
| 1066 |
| 1067 FromJsonFunction(this.asClosure); |
| 1068 |
| 1069 @override |
| 1070 bool get isIdentity => false; |
| 1071 |
| 1072 @override |
| 1073 String asSnippet(String jsonPath, String json) => |
| 1074 '$asClosure($jsonPath, $json)'; |
| 1182 } | 1075 } |
| 1076 |
| 1077 /** |
| 1078 * Representation of FromJsonCode for the identity transformation. |
| 1079 */ |
| 1080 class FromJsonIdentity extends FromJsonSnippet { |
| 1081 FromJsonIdentity() : super((String jsonPath, String json) => json); |
| 1082 |
| 1083 @override |
| 1084 bool get isIdentity => true; |
| 1085 } |
| 1086 |
| 1087 /** |
| 1088 * Representation of FromJsonCode for a snippet of inline code. |
| 1089 */ |
| 1090 class FromJsonSnippet extends FromJsonCode { |
| 1091 /** |
| 1092 * Callback that can be used to generate the code snippet, once the names |
| 1093 * of the [jsonPath] and [json] variables are known. |
| 1094 */ |
| 1095 final FromJsonSnippetCallback callback; |
| 1096 |
| 1097 FromJsonSnippet(this.callback); |
| 1098 |
| 1099 @override |
| 1100 String get asClosure => |
| 1101 '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}'; |
| 1102 |
| 1103 @override |
| 1104 bool get isIdentity => false; |
| 1105 |
| 1106 @override |
| 1107 String asSnippet(String jsonPath, String json) => callback(jsonPath, json); |
| 1108 } |
| 1109 |
| 1110 /** |
| 1111 * Container for code that can be used to translate a data type to JSON. |
| 1112 */ |
| 1113 abstract class ToJsonCode { |
| 1114 /** |
| 1115 * Get the translation code in the form of a closure. |
| 1116 */ |
| 1117 String get asClosure; |
| 1118 |
| 1119 /** |
| 1120 * True if the data type is already in JSON form, so the translation is the |
| 1121 * identity function. |
| 1122 */ |
| 1123 bool get isIdentity; |
| 1124 |
| 1125 /** |
| 1126 * Get the translation code in the form of a code snippet, where [value] |
| 1127 * is the variable holding the object to be translated. |
| 1128 */ |
| 1129 String asSnippet(String value); |
| 1130 } |
| 1131 |
| 1132 /** |
| 1133 * Representation of ToJsonCode for a function defined elsewhere. |
| 1134 */ |
| 1135 class ToJsonFunction extends ToJsonCode { |
| 1136 final String asClosure; |
| 1137 |
| 1138 ToJsonFunction(this.asClosure); |
| 1139 |
| 1140 @override |
| 1141 bool get isIdentity => false; |
| 1142 |
| 1143 @override |
| 1144 String asSnippet(String value) => '$asClosure($value)'; |
| 1145 } |
| 1146 |
| 1147 /** |
| 1148 * Representation of FromJsonCode for the identity transformation. |
| 1149 */ |
| 1150 class ToJsonIdentity extends ToJsonSnippet { |
| 1151 ToJsonIdentity(String type) : super(type, (String value) => value); |
| 1152 |
| 1153 @override |
| 1154 bool get isIdentity => true; |
| 1155 } |
| 1156 |
| 1157 /** |
| 1158 * Representation of ToJsonCode for a snippet of inline code. |
| 1159 */ |
| 1160 class ToJsonSnippet extends ToJsonCode { |
| 1161 /** |
| 1162 * Callback that can be used to generate the code snippet, once the name |
| 1163 * of the [value] variable is known. |
| 1164 */ |
| 1165 final ToJsonSnippetCallback callback; |
| 1166 |
| 1167 /** |
| 1168 * Dart type of the [value] variable. |
| 1169 */ |
| 1170 final String type; |
| 1171 |
| 1172 ToJsonSnippet(this.type, this.callback); |
| 1173 |
| 1174 @override |
| 1175 String get asClosure => '($type value) => ${callback('value')}'; |
| 1176 |
| 1177 @override |
| 1178 bool get isIdentity => false; |
| 1179 |
| 1180 @override |
| 1181 String asSnippet(String value) => callback(value); |
| 1182 } |
| OLD | NEW |