| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library codegen.protocol; |
| 6 |
| 7 import 'dart:convert'; |
| 8 |
| 9 import 'api.dart'; |
| 10 import 'codegen_tools.dart'; |
| 11 import 'from_html.dart'; |
| 12 import 'implied_types.dart'; |
| 13 import 'to_html.dart'; |
| 14 |
| 15 /** |
| 16 * Container for code that can be used to translate a data type from JSON. |
| 17 */ |
| 18 abstract class FromJsonCode { |
| 19 /** |
| 20 * True if the data type is already in JSON form, so the translation is the |
| 21 * identity function. |
| 22 */ |
| 23 bool get isIdentity; |
| 24 |
| 25 /** |
| 26 * Get the translation code in the form of a closure. |
| 27 */ |
| 28 String get asClosure; |
| 29 |
| 30 /** |
| 31 * Get the translation code in the form of a code snippet, where [jsonPath] |
| 32 * is the variable holding the JSON path, and [json] is the variable holding |
| 33 * the raw JSON. |
| 34 */ |
| 35 String asSnippet(String jsonPath, String json); |
| 36 } |
| 37 |
| 38 /** |
| 39 * Representation of FromJsonCode for a function defined elsewhere. |
| 40 */ |
| 41 class FromJsonFunction extends FromJsonCode { |
| 42 final String asClosure; |
| 43 |
| 44 FromJsonFunction(this.asClosure); |
| 45 |
| 46 @override |
| 47 bool get isIdentity => false; |
| 48 |
| 49 @override |
| 50 String asSnippet(String jsonPath, String json) => |
| 51 '$asClosure($jsonPath, $json)'; |
| 52 } |
| 53 |
| 54 typedef String FromJsonSnippetCallback(String jsonPath, String json); |
| 55 |
| 56 /** |
| 57 * Representation of FromJsonCode for a snippet of inline code. |
| 58 */ |
| 59 class FromJsonSnippet extends FromJsonCode { |
| 60 /** |
| 61 * Callback that can be used to generate the code snippet, once the names |
| 62 * of the [jsonPath] and [json] variables are known. |
| 63 */ |
| 64 final FromJsonSnippetCallback callback; |
| 65 |
| 66 FromJsonSnippet(this.callback); |
| 67 |
| 68 @override |
| 69 bool get isIdentity => false; |
| 70 |
| 71 @override |
| 72 String get asClosure => |
| 73 '(String jsonPath, Object json) => ${callback('jsonPath', 'json')}'; |
| 74 |
| 75 @override |
| 76 String asSnippet(String jsonPath, String json) => callback(jsonPath, json); |
| 77 } |
| 78 |
| 79 /** |
| 80 * Representation of FromJsonCode for the identity transformation. |
| 81 */ |
| 82 class FromJsonIdentity extends FromJsonSnippet { |
| 83 FromJsonIdentity() : super((String jsonPath, String json) => json); |
| 84 |
| 85 @override |
| 86 bool get isIdentity => true; |
| 87 } |
| 88 |
| 89 /** |
| 90 * Container for code that can be used to translate a data type to JSON. |
| 91 */ |
| 92 abstract class ToJsonCode { |
| 93 /** |
| 94 * True if the data type is already in JSON form, so the translation is the |
| 95 * identity function. |
| 96 */ |
| 97 bool get isIdentity; |
| 98 |
| 99 /** |
| 100 * Get the translation code in the form of a closure. |
| 101 */ |
| 102 String get asClosure; |
| 103 |
| 104 /** |
| 105 * Get the translation code in the form of a code snippet, where [value] |
| 106 * is the variable holding the object to be translated. |
| 107 */ |
| 108 String asSnippet(String value); |
| 109 } |
| 110 |
| 111 /** |
| 112 * Representation of ToJsonCode for a function defined elsewhere. |
| 113 */ |
| 114 class ToJsonFunction extends ToJsonCode { |
| 115 final String asClosure; |
| 116 |
| 117 ToJsonFunction(this.asClosure); |
| 118 |
| 119 @override |
| 120 bool get isIdentity => false; |
| 121 |
| 122 @override |
| 123 String asSnippet(String value) => '$asClosure($value)'; |
| 124 } |
| 125 |
| 126 typedef String ToJsonSnippetCallback(String value); |
| 127 |
| 128 /** |
| 129 * Representation of ToJsonCode for a snippet of inline code. |
| 130 */ |
| 131 class ToJsonSnippet extends ToJsonCode { |
| 132 /** |
| 133 * Callback that can be used to generate the code snippet, once the name |
| 134 * of the [value] variable is known. |
| 135 */ |
| 136 final ToJsonSnippetCallback callback; |
| 137 |
| 138 /** |
| 139 * Dart type of the [value] variable. |
| 140 */ |
| 141 final String type; |
| 142 |
| 143 ToJsonSnippet(this.type, this.callback); |
| 144 |
| 145 @override |
| 146 bool get isIdentity => false; |
| 147 |
| 148 @override |
| 149 String get asClosure => '($type value) => ${callback('value')}'; |
| 150 |
| 151 @override |
| 152 String asSnippet(String value) => callback(value); |
| 153 } |
| 154 |
| 155 /** |
| 156 * Representation of FromJsonCode for the identity transformation. |
| 157 */ |
| 158 class ToJsonIdentity extends ToJsonSnippet { |
| 159 ToJsonIdentity(String type) : super(type, (String value) => value); |
| 160 |
| 161 @override |
| 162 bool get isIdentity => true; |
| 163 } |
| 164 |
| 165 /** |
| 166 * Visitor which produces Dart code representing the API. |
| 167 */ |
| 168 class CodegenProtocolVisitor extends HierarchicalApiVisitor with CodeGenerator { |
| 169 /** |
| 170 * Type references in the spec that are named something else in Dart. |
| 171 */ |
| 172 static const Map<String, String> _typeRenames = const { |
| 173 'object': 'Object', |
| 174 }; |
| 175 |
| 176 /** |
| 177 * Visitor used to produce doc comments. |
| 178 */ |
| 179 final ToHtmlVisitor toHtmlVisitor; |
| 180 |
| 181 /** |
| 182 * Types implied by the API. This includes types explicitly named in the |
| 183 * API as well as those implied by the definitions of requests, responses, |
| 184 * notifications, etc. |
| 185 */ |
| 186 final Map<String, ImpliedType> impliedTypes; |
| 187 |
| 188 CodegenProtocolVisitor(Api api) |
| 189 : super(api), |
| 190 toHtmlVisitor = new ToHtmlVisitor(api), |
| 191 impliedTypes = computeImpliedTypes(api); |
| 192 |
| 193 @override |
| 194 visitApi() { |
| 195 outputHeader(); |
| 196 writeln(); |
| 197 writeln('part of protocol2;'); |
| 198 emitClasses(); |
| 199 } |
| 200 |
| 201 /** |
| 202 * Translate each type implied by the API to a class. |
| 203 */ |
| 204 void emitClasses() { |
| 205 for (ImpliedType impliedType in impliedTypes.values) { |
| 206 TypeDecl type = impliedType.type; |
| 207 if (type != null) { |
| 208 String dartTypeName = capitalize(impliedType.camelName); |
| 209 if (type is TypeObject) { |
| 210 writeln(); |
| 211 emitObjectClass(dartTypeName, type, impliedType); |
| 212 } else if (type is TypeEnum) { |
| 213 writeln(); |
| 214 emitEnumClass(dartTypeName, type, impliedType); |
| 215 } |
| 216 } |
| 217 } |
| 218 } |
| 219 |
| 220 /** |
| 221 * Emit the class to encapsulate an object type. |
| 222 */ |
| 223 void emitObjectClass(String className, TypeObject type, ImpliedType |
| 224 impliedType) { |
| 225 docComment(toHtmlVisitor.collectHtml(() { |
| 226 toHtmlVisitor.p(() { |
| 227 toHtmlVisitor.write(impliedType.humanReadableName); |
| 228 }); |
| 229 if (impliedType.type != null) { |
| 230 toHtmlVisitor.showType(null, impliedType.type); |
| 231 } |
| 232 })); |
| 233 writeln('class $className {'); |
| 234 indent(() { |
| 235 for (TypeObjectField field in type.fields) { |
| 236 if (field.value != null) { |
| 237 continue; |
| 238 } |
| 239 docComment(toHtmlVisitor.collectHtml(() { |
| 240 toHtmlVisitor.translateHtml(field.html); |
| 241 })); |
| 242 writeln('final ${dartType(field.type)} ${field.name};'); |
| 243 writeln(); |
| 244 } |
| 245 emitObjectConstructor(type, className); |
| 246 writeln(); |
| 247 emitObjectFromJsonConstructor(className, type, impliedType); |
| 248 writeln(); |
| 249 if (emitConvenienceConstructor(className, impliedType)) { |
| 250 writeln(); |
| 251 } |
| 252 emitToJsonMember(type); |
| 253 writeln(); |
| 254 writeln('@override'); |
| 255 writeln('String toString() => JSON.encode(toJson());'); |
| 256 writeln(); |
| 257 emitObjectEqualsMember(type, className); |
| 258 writeln(); |
| 259 emitObjectHashCode(type); |
| 260 }); |
| 261 writeln('}'); |
| 262 } |
| 263 |
| 264 /** |
| 265 * Emit the constructor for an object class. |
| 266 */ |
| 267 void emitObjectConstructor(TypeObject type, String className) { |
| 268 List<String> args = <String>[]; |
| 269 List<String> optionalArgs = <String>[]; |
| 270 for (TypeObjectField field in type.fields) { |
| 271 if (field.value != null) { |
| 272 continue; |
| 273 } |
| 274 String arg = 'this.${field.name}'; |
| 275 if (field.optional) { |
| 276 optionalArgs.add(arg); |
| 277 } else { |
| 278 args.add(arg); |
| 279 } |
| 280 } |
| 281 if (optionalArgs.isNotEmpty) { |
| 282 args.add('{${optionalArgs.join(', ')}}'); |
| 283 } |
| 284 writeln('$className(${args.join(', ')});'); |
| 285 } |
| 286 |
| 287 /** |
| 288 * Emit the toJson() code for an object class. |
| 289 */ |
| 290 void emitToJsonMember(TypeObject type) { |
| 291 writeln('Map<String, dynamic> toJson() {'); |
| 292 indent(() { |
| 293 writeln('Map<String, dynamic> result = {};'); |
| 294 for (TypeObjectField field in type.fields) { |
| 295 String fieldNameString = literalString(field.name); |
| 296 if (field.value != null) { |
| 297 writeln('result[$fieldNameString] = ${literalString(field.value)};'); |
| 298 continue; |
| 299 } |
| 300 String fieldToJson = toJsonCode(field.type).asSnippet(field.name); |
| 301 String populateField = 'result[$fieldNameString] = $fieldToJson;'; |
| 302 if (field.optional) { |
| 303 writeln('if (${field.name} != null) {'); |
| 304 indent(() { |
| 305 writeln(populateField); |
| 306 }); |
| 307 writeln('}'); |
| 308 } else { |
| 309 writeln(populateField); |
| 310 } |
| 311 } |
| 312 writeln('return result;'); |
| 313 }); |
| 314 writeln('}'); |
| 315 } |
| 316 |
| 317 /** |
| 318 * Emit the operator== code for an object class. |
| 319 */ |
| 320 void emitObjectEqualsMember(TypeObject type, String className) { |
| 321 writeln('@override'); |
| 322 writeln('bool operator==(other) {'); |
| 323 indent(() { |
| 324 writeln('if (other is $className) {'); |
| 325 indent(() { |
| 326 var comparisons = <String>[]; |
| 327 for (TypeObjectField field in type.fields) { |
| 328 if (field.value != null) { |
| 329 continue; |
| 330 } |
| 331 comparisons.add(compareEqualsCode(field.type, field.name, |
| 332 'other.${field.name}')); |
| 333 } |
| 334 if (comparisons.isEmpty) { |
| 335 writeln('return true;'); |
| 336 } else { |
| 337 String concatenated = comparisons.join(' &&\n '); |
| 338 writeln('return $concatenated;'); |
| 339 } |
| 340 }); |
| 341 writeln('}'); |
| 342 writeln('return false;'); |
| 343 }); |
| 344 writeln('}'); |
| 345 } |
| 346 |
| 347 /** |
| 348 * Emit the hashCode getter for an object class. |
| 349 */ |
| 350 void emitObjectHashCode(TypeObject type) { |
| 351 writeln('@override'); |
| 352 writeln('int get hashCode {'); |
| 353 indent(() { |
| 354 writeln('int hash = 0;'); |
| 355 for (TypeObjectField field in type.fields) { |
| 356 String valueToCombine; |
| 357 if (field.value != null) { |
| 358 valueToCombine = field.value.hashCode.toString(); |
| 359 } else { |
| 360 valueToCombine = '${field.name}.hashCode'; |
| 361 } |
| 362 writeln('hash = _JenkinsSmiHash.combine(hash, $valueToCombine);'); |
| 363 } |
| 364 writeln('return _JenkinsSmiHash.finish(hash);'); |
| 365 }); |
| 366 writeln('}'); |
| 367 } |
| 368 |
| 369 /** |
| 370 * Emit a class to encapsulate an enum. |
| 371 */ |
| 372 void emitEnumClass(String className, TypeEnum type, ImpliedType impliedType) { |
| 373 docComment(toHtmlVisitor.collectHtml(() { |
| 374 toHtmlVisitor.p(() { |
| 375 toHtmlVisitor.write(impliedType.humanReadableName); |
| 376 }); |
| 377 if (impliedType.type != null) { |
| 378 toHtmlVisitor.showType(null, impliedType.type); |
| 379 } |
| 380 })); |
| 381 writeln('class $className {'); |
| 382 indent(() { |
| 383 for (TypeEnumValue value in type.values) { |
| 384 docComment(toHtmlVisitor.collectHtml(() { |
| 385 toHtmlVisitor.translateHtml(value.html); |
| 386 })); |
| 387 String valueString = literalString(value.value); |
| 388 writeln( |
| 389 'static const ${value.value} = const $className._($valueString);'); |
| 390 writeln(); |
| 391 } |
| 392 writeln('final String name;'); |
| 393 writeln(); |
| 394 writeln('const $className._(this.name);'); |
| 395 writeln(); |
| 396 emitEnumClassConstructor(className, type); |
| 397 writeln(); |
| 398 emitEnumFromJsonConstructor(className, type, impliedType); |
| 399 writeln(); |
| 400 writeln('@override'); |
| 401 writeln('String toString() => "$className.\$name";'); |
| 402 writeln(); |
| 403 writeln('String toJson() => name;'); |
| 404 }); |
| 405 writeln('}'); |
| 406 } |
| 407 |
| 408 /** |
| 409 * Emit the constructor for an enum class. |
| 410 */ |
| 411 void emitEnumClassConstructor(String className, TypeEnum type) { |
| 412 writeln('factory $className(String name) {'); |
| 413 indent(() { |
| 414 writeln('switch (name) {'); |
| 415 indent(() { |
| 416 for (TypeEnumValue value in type.values) { |
| 417 String valueString = literalString(value.value); |
| 418 writeln('case $valueString:'); |
| 419 indent(() { |
| 420 writeln('return ${value.value};'); |
| 421 }); |
| 422 } |
| 423 }); |
| 424 writeln('}'); |
| 425 writeln(r"throw new Exception('Illegal enum value: $name');"); |
| 426 }); |
| 427 writeln('}'); |
| 428 } |
| 429 |
| 430 /** |
| 431 * Compute the code necessary to convert [type] to JSON. |
| 432 */ |
| 433 ToJsonCode toJsonCode(TypeDecl type) { |
| 434 TypeDecl resolvedType = resolveTypeReferenceChain(type); |
| 435 if (resolvedType is TypeReference) { |
| 436 return new ToJsonIdentity(dartType(type)); |
| 437 } else if (resolvedType is TypeList) { |
| 438 ToJsonCode itemCode = toJsonCode(resolvedType.itemType); |
| 439 if (itemCode.isIdentity) { |
| 440 return new ToJsonIdentity(dartType(type)); |
| 441 } else { |
| 442 return new ToJsonSnippet(dartType(type), (String value) => |
| 443 '$value.map(${itemCode.asClosure})'); |
| 444 } |
| 445 } else if (resolvedType is TypeMap) { |
| 446 ToJsonCode keyCode; |
| 447 if (dartType(resolvedType.keyType) != 'String') { |
| 448 keyCode = toJsonCode(resolvedType.keyType); |
| 449 } else { |
| 450 keyCode = new ToJsonIdentity(dartType(resolvedType.keyType)); |
| 451 } |
| 452 ToJsonCode valueCode = toJsonCode(resolvedType.valueType); |
| 453 if (keyCode.isIdentity && valueCode.isIdentity) { |
| 454 return new ToJsonIdentity(dartType(resolvedType)); |
| 455 } else { |
| 456 return new ToJsonSnippet(dartType(type), (String value) { |
| 457 StringBuffer result = new StringBuffer(); |
| 458 result.write('_mapMap($value'); |
| 459 if (!keyCode.isIdentity) { |
| 460 result.write(', keyCallback: ${keyCode.asClosure}'); |
| 461 } |
| 462 if (!valueCode.isIdentity) { |
| 463 result.write(', valueCallback: ${valueCode.asClosure}'); |
| 464 } |
| 465 result.write(')'); |
| 466 return result.toString(); |
| 467 }); |
| 468 } |
| 469 } else if (resolvedType is TypeUnion) { |
| 470 for (TypeDecl choice in resolvedType.choices) { |
| 471 if (resolveTypeReferenceChain(choice) is! TypeObject) { |
| 472 throw new Exception('Union types must be unions of objects'); |
| 473 } |
| 474 } |
| 475 return new ToJsonSnippet(dartType(type), (String value) => |
| 476 '$value.toJson()'); |
| 477 } else if (resolvedType is TypeObject || resolvedType is TypeEnum) { |
| 478 return new ToJsonSnippet(dartType(type), (String value) => |
| 479 '$value.toJson()'); |
| 480 } else { |
| 481 throw new Exception("Can't convert $resolvedType from JSON"); |
| 482 } |
| 483 } |
| 484 |
| 485 /** |
| 486 * Compute the code necessary to compare two objects for equality. |
| 487 */ |
| 488 String compareEqualsCode(TypeDecl type, String thisVar, String otherVar) { |
| 489 TypeDecl resolvedType = resolveTypeReferenceChain(type); |
| 490 if (resolvedType is TypeReference || resolvedType is TypeEnum || |
| 491 resolvedType is TypeObject || resolvedType is TypeUnion) { |
| 492 return '$thisVar == $otherVar'; |
| 493 } else if (resolvedType is TypeList) { |
| 494 String itemTypeName = dartType(resolvedType.itemType); |
| 495 String subComparison = compareEqualsCode(resolvedType.itemType, 'a', 'b'); |
| 496 String closure = '($itemTypeName a, $itemTypeName b) => $subComparison'; |
| 497 return '_listEqual($thisVar, $otherVar, $closure)'; |
| 498 } else if (resolvedType is TypeMap) { |
| 499 String valueTypeName = dartType(resolvedType.valueType); |
| 500 String subComparison = compareEqualsCode(resolvedType.valueType, 'a', 'b' |
| 501 ); |
| 502 String closure = '($valueTypeName a, $valueTypeName b) => $subComparison'; |
| 503 return '_mapEqual($thisVar, $otherVar, $closure)'; |
| 504 } |
| 505 throw new Exception("Don't know how to compare for equality: $resolvedType" |
| 506 ); |
| 507 } |
| 508 |
| 509 /** |
| 510 * Emit the method for decoding an object from JSON. |
| 511 */ |
| 512 void emitObjectFromJsonConstructor(String className, TypeObject |
| 513 type, ImpliedType impliedType) { |
| 514 String humanReadableNameString = literalString(impliedType.humanReadableName |
| 515 ); |
| 516 writeln( |
| 517 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {' |
| 518 ); |
| 519 indent(() { |
| 520 writeln('if (json is Map) {'); |
| 521 indent(() { |
| 522 List<String> args = <String>[]; |
| 523 List<String> optionalArgs = <String>[]; |
| 524 for (TypeObjectField field in type.fields) { |
| 525 String fieldNameString = literalString(field.name); |
| 526 String fieldAccessor = 'json[$fieldNameString]'; |
| 527 String jsonPath = 'jsonPath + ${literalString('.${field.name}')}'; |
| 528 if (field.value != null) { |
| 529 String valueString = literalString(field.value); |
| 530 writeln('if ($fieldAccessor != $valueString) {'); |
| 531 indent(() { |
| 532 writeln( |
| 533 'throw jsonDecoder.mismatch(jsonPath, "equal " + $valueString)
;'); |
| 534 }); |
| 535 writeln('}'); |
| 536 continue; |
| 537 } |
| 538 if (field.optional) { |
| 539 optionalArgs.add('${field.name}: ${field.name}'); |
| 540 } else { |
| 541 args.add(field.name); |
| 542 } |
| 543 String fieldDartType = dartType(field.type); |
| 544 writeln('$fieldDartType ${field.name};'); |
| 545 writeln('if (json.containsKey($fieldNameString)) {'); |
| 546 indent(() { |
| 547 String toJson = fromJsonCode(field.type).asSnippet(jsonPath, |
| 548 fieldAccessor); |
| 549 writeln('${field.name} = $toJson;'); |
| 550 }); |
| 551 write('}'); |
| 552 if (!field.optional) { |
| 553 writeln(' else {'); |
| 554 indent(() { |
| 555 writeln( |
| 556 "throw jsonDecoder.missingKey(jsonPath, $fieldNameString);"); |
| 557 }); |
| 558 writeln('}'); |
| 559 } else { |
| 560 writeln(); |
| 561 } |
| 562 } |
| 563 args.addAll(optionalArgs); |
| 564 writeln('return new $className(${args.join(', ')});'); |
| 565 }); |
| 566 writeln('} else {'); |
| 567 indent(() { |
| 568 writeln( |
| 569 'throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);'); |
| 570 }); |
| 571 writeln('}'); |
| 572 }); |
| 573 writeln('}'); |
| 574 } |
| 575 |
| 576 /** |
| 577 * Emit the method for decoding an enum from JSON. |
| 578 */ |
| 579 void emitEnumFromJsonConstructor(String className, TypeEnum type, ImpliedType |
| 580 impliedType) { |
| 581 writeln( |
| 582 'factory $className.fromJson(JsonDecoder jsonDecoder, String jsonPath, O
bject json) {' |
| 583 ); |
| 584 indent(() { |
| 585 writeln('if (json is String) {'); |
| 586 indent(() { |
| 587 writeln('try {'); |
| 588 indent(() { |
| 589 writeln('return new $className(json);'); |
| 590 }); |
| 591 writeln('} catch(_) {'); |
| 592 indent(() { |
| 593 writeln('// Fall through'); |
| 594 }); |
| 595 writeln('}'); |
| 596 }); |
| 597 writeln('}'); |
| 598 String humanReadableNameString = literalString( |
| 599 impliedType.humanReadableName); |
| 600 writeln('throw jsonDecoder.mismatch(jsonPath, $humanReadableNameString);' |
| 601 ); |
| 602 }); |
| 603 writeln('}'); |
| 604 } |
| 605 |
| 606 /** |
| 607 * Compute the code necessary to translate [type] from JSON. |
| 608 */ |
| 609 FromJsonCode fromJsonCode(TypeDecl type) { |
| 610 if (type is TypeReference) { |
| 611 TypeDefinition referencedDefinition = api.types[type.typeName]; |
| 612 if (referencedDefinition != null) { |
| 613 TypeDecl referencedType = referencedDefinition.type; |
| 614 if (referencedType is TypeObject || referencedType is TypeEnum) { |
| 615 return new FromJsonSnippet((String jsonPath, String json) => |
| 616 'new ${dartType(type)}.fromJson(jsonDecoder, $jsonPath, $json)'); |
| 617 } else { |
| 618 return fromJsonCode(referencedType); |
| 619 } |
| 620 } else { |
| 621 switch (type.typeName) { |
| 622 case 'String': |
| 623 return new FromJsonFunction('jsonDecoder._decodeString'); |
| 624 case 'bool': |
| 625 return new FromJsonFunction('jsonDecoder._decodeBool'); |
| 626 case 'int': |
| 627 return new FromJsonFunction('jsonDecoder._decodeInt'); |
| 628 case 'object': |
| 629 return new FromJsonIdentity(); |
| 630 default: |
| 631 throw new Exception('Unexpected type name ${type.typeName}'); |
| 632 } |
| 633 } |
| 634 } else if (type is TypeMap) { |
| 635 FromJsonCode keyCode; |
| 636 if (dartType(type.keyType) != 'String') { |
| 637 keyCode = fromJsonCode(type.keyType); |
| 638 } else { |
| 639 keyCode = new FromJsonIdentity(); |
| 640 } |
| 641 FromJsonCode valueCode = fromJsonCode(type.valueType); |
| 642 if (keyCode.isIdentity && valueCode.isIdentity) { |
| 643 return new FromJsonFunction('jsonDecoder._decodeMap'); |
| 644 } else { |
| 645 return new FromJsonSnippet((String jsonPath, String json) { |
| 646 StringBuffer result = new StringBuffer(); |
| 647 result.write('jsonDecoder._decodeMap($jsonPath, $json'); |
| 648 if (!keyCode.isIdentity) { |
| 649 result.write(', keyDecoder: ${keyCode.asClosure}'); |
| 650 } |
| 651 if (!valueCode.isIdentity) { |
| 652 result.write(', valueDecoder: ${valueCode.asClosure}'); |
| 653 } |
| 654 result.write(')'); |
| 655 return result.toString(); |
| 656 }); |
| 657 } |
| 658 } else if (type is TypeList) { |
| 659 FromJsonCode itemCode = fromJsonCode(type.itemType); |
| 660 if (itemCode.isIdentity) { |
| 661 return new FromJsonFunction('jsonDecoder._decodeList'); |
| 662 } else { |
| 663 return new FromJsonSnippet((String jsonPath, String json) => |
| 664 'jsonDecoder._decodeList($jsonPath, $json, ${itemCode.asClosure})'); |
| 665 } |
| 666 } else if (type is TypeUnion) { |
| 667 List<String> decoders = <String>[]; |
| 668 for (TypeDecl choice in type.choices) { |
| 669 TypeDecl resolvedChoice = resolveTypeReferenceChain(choice); |
| 670 if (resolvedChoice is TypeObject) { |
| 671 TypeObjectField field = resolvedChoice.getField(type.field); |
| 672 if (field == null) { |
| 673 throw new Exception( |
| 674 'Each choice in the union needs a field named ${type.field}'); |
| 675 } |
| 676 if (field.value == null) { |
| 677 throw new Exception( |
| 678 'Each choice in the union needs a constant value for the field $
{type.field}'); |
| 679 } |
| 680 String closure = fromJsonCode(choice).asClosure; |
| 681 decoders.add('${literalString(field.value)}: $closure'); |
| 682 } else { |
| 683 throw new Exception('Union types must be unions of objects.'); |
| 684 } |
| 685 } |
| 686 return new FromJsonSnippet((String jsonPath, String json) => |
| 687 'jsonDecoder._decodeUnion($jsonPath, $json, ${literalString(type.field
)}, {${decoders.join(', ')}})' |
| 688 ); |
| 689 } else { |
| 690 throw new Exception("Can't convert $type from JSON"); |
| 691 } |
| 692 } |
| 693 |
| 694 /** |
| 695 * Emit a convenience constructor for decoding a piece of protocol, if |
| 696 * appropriate. Return true if a constructor was emitted. |
| 697 */ |
| 698 bool emitConvenienceConstructor(String className, ImpliedType impliedType) { |
| 699 // The type of object from which this piece of protocol should be decoded. |
| 700 String inputType; |
| 701 // The name of the input object. |
| 702 String inputName; |
| 703 // The field within the input object to decode. |
| 704 String fieldName; |
| 705 // Constructor call to create the JsonDecoder object. |
| 706 String makeDecoder; |
| 707 // Name of the constructor to create. |
| 708 String constructorName; |
| 709 // Extra arguments for the constructor. |
| 710 List<String> extraArgs = <String>[]; |
| 711 switch (impliedType.kind) { |
| 712 case 'requestParams': |
| 713 inputType = 'Request'; |
| 714 inputName = 'request'; |
| 715 fieldName = 'params'; |
| 716 makeDecoder = 'new RequestDecoder(request)'; |
| 717 constructorName = 'fromRequest'; |
| 718 break; |
| 719 case 'requestResult': |
| 720 inputType = 'Response'; |
| 721 inputName = 'response'; |
| 722 fieldName = 'result'; |
| 723 makeDecoder = 'new ResponseDecoder()'; |
| 724 constructorName = 'fromResponse'; |
| 725 break; |
| 726 case 'notificationParams': |
| 727 inputType = 'Notification'; |
| 728 inputName = 'notification'; |
| 729 fieldName = 'params'; |
| 730 makeDecoder = 'new ResponseDecoder()'; |
| 731 constructorName = 'fromNotification'; |
| 732 break; |
| 733 case 'refactoringFeedback': |
| 734 inputType = 'EditGetRefactoringResult'; |
| 735 inputName = 'refactoringResult'; |
| 736 fieldName = 'feedback'; |
| 737 makeDecoder = 'new ResponseDecoder()'; |
| 738 constructorName = 'fromRefactoringResult'; |
| 739 break; |
| 740 case 'refactoringOptions': |
| 741 inputType = 'EditGetRefactoringParams'; |
| 742 inputName = 'refactoringParams'; |
| 743 fieldName = 'options'; |
| 744 makeDecoder = 'new RequestDecoder(request)'; |
| 745 constructorName = 'fromRefactoringParams'; |
| 746 extraArgs.add('Request request'); |
| 747 break; |
| 748 default: |
| 749 return false; |
| 750 } |
| 751 List<String> args = ['$inputType $inputName']; |
| 752 args.addAll(extraArgs); |
| 753 writeln('factory $className.$constructorName(${args.join(', ')}) {'); |
| 754 indent(() { |
| 755 String fieldNameString = literalString(fieldName); |
| 756 writeln('return new $className.fromJson('); |
| 757 writeln(' $makeDecoder, $fieldNameString, $inputName.$fieldName);'); |
| 758 }); |
| 759 writeln('}'); |
| 760 return true; |
| 761 } |
| 762 |
| 763 /** |
| 764 * Create a string literal that evaluates to [s]. |
| 765 */ |
| 766 String literalString(String s) { |
| 767 return JSON.encode(s); |
| 768 } |
| 769 |
| 770 /** |
| 771 * Convert the given [TypeDecl] to a Dart type. |
| 772 */ |
| 773 String dartType(TypeDecl type) { |
| 774 if (type is TypeReference) { |
| 775 String typeName = type.typeName; |
| 776 TypeDefinition referencedDefinition = api.types[typeName]; |
| 777 if (_typeRenames.containsKey(typeName)) { |
| 778 return _typeRenames[typeName]; |
| 779 } |
| 780 if (referencedDefinition == null) { |
| 781 return typeName; |
| 782 } |
| 783 TypeDecl referencedType = referencedDefinition.type; |
| 784 if (referencedType is TypeObject || referencedType is TypeEnum) { |
| 785 return typeName; |
| 786 } |
| 787 return dartType(referencedType); |
| 788 } else if (type is TypeList) { |
| 789 return 'List<${dartType(type.itemType)}>'; |
| 790 } else if (type is TypeMap) { |
| 791 return 'Map<${dartType(type.keyType)}, ${dartType(type.valueType)}>'; |
| 792 } else if (type is TypeUnion) { |
| 793 return 'dynamic'; |
| 794 } else { |
| 795 throw new Exception("Can't convert to a dart type"); |
| 796 } |
| 797 } |
| 798 } |
| 799 |
| 800 final GeneratedFile target = new GeneratedFile( |
| 801 '../../lib/src/generated_protocol.dart', () { |
| 802 CodegenProtocolVisitor visitor = new CodegenProtocolVisitor(readApi()); |
| 803 return visitor.collectCode(visitor.visitApi); |
| 804 }); |
| 805 |
| 806 /** |
| 807 * Translate spec_input.html into protocol_matchers.dart. |
| 808 */ |
| 809 main() { |
| 810 target.generate(); |
| 811 } |
| OLD | NEW |