1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are 7 // met: 8 // 9 // * Redistributions of source code must retain the above copyright 10 // notice, this list of conditions and the following disclaimer. 11 // * Redistributions in binary form must reproduce the above 12 // copyright notice, this list of conditions and the following disclaimer 13 // in the documentation and/or other materials provided with the 14 // distribution. 15 // * Neither the name of Google Inc. nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 package com.google.protobuf; 32 33 import static com.google.protobuf.Internal.checkNotNull; 34 35 import com.google.protobuf.Descriptors.Descriptor; 36 import com.google.protobuf.Descriptors.EnumValueDescriptor; 37 import com.google.protobuf.Descriptors.FieldDescriptor; 38 import com.google.protobuf.Descriptors.OneofDescriptor; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.util.Collections; 42 import java.util.List; 43 import java.util.Map; 44 45 /** 46 * An implementation of {@link Message} that can represent arbitrary types, given a {@link 47 * Descriptors.Descriptor}. 48 * 49 * @author kenton@google.com Kenton Varda 50 */ 51 public final class DynamicMessage extends AbstractMessage { 52 private final Descriptor type; 53 private final FieldSet<FieldDescriptor> fields; 54 private final FieldDescriptor[] oneofCases; 55 private final UnknownFieldSet unknownFields; 56 private int memoizedSize = -1; 57 58 /** 59 * Construct a {@code DynamicMessage} using the given {@code FieldSet}. oneofCases stores the 60 * FieldDescriptor for each oneof to indicate which field is set. Caller should make sure the 61 * array is immutable. 62 * 63 * <p>This constructor is package private and will be used in {@code DynamicMutableMessage} to 64 * convert a mutable message to an immutable message. 65 */ DynamicMessage( Descriptor type, FieldSet<FieldDescriptor> fields, FieldDescriptor[] oneofCases, UnknownFieldSet unknownFields)66 DynamicMessage( 67 Descriptor type, 68 FieldSet<FieldDescriptor> fields, 69 FieldDescriptor[] oneofCases, 70 UnknownFieldSet unknownFields) { 71 this.type = type; 72 this.fields = fields; 73 this.oneofCases = oneofCases; 74 this.unknownFields = unknownFields; 75 } 76 77 /** Get a {@code DynamicMessage} representing the default instance of the given type. */ getDefaultInstance(Descriptor type)78 public static DynamicMessage getDefaultInstance(Descriptor type) { 79 int oneofDeclCount = type.toProto().getOneofDeclCount(); 80 FieldDescriptor[] oneofCases = new FieldDescriptor[oneofDeclCount]; 81 return new DynamicMessage( 82 type, 83 FieldSet.<FieldDescriptor>emptySet(), 84 oneofCases, 85 UnknownFieldSet.getDefaultInstance()); 86 } 87 88 89 /** Parse a message of the given type from the given input stream. */ parseFrom(Descriptor type, CodedInputStream input)90 public static DynamicMessage parseFrom(Descriptor type, CodedInputStream input) 91 throws IOException { 92 return newBuilder(type).mergeFrom(input).buildParsed(); 93 } 94 95 /** Parse a message of the given type from the given input stream. */ parseFrom( Descriptor type, CodedInputStream input, ExtensionRegistry extensionRegistry)96 public static DynamicMessage parseFrom( 97 Descriptor type, CodedInputStream input, ExtensionRegistry extensionRegistry) 98 throws IOException { 99 return newBuilder(type).mergeFrom(input, extensionRegistry).buildParsed(); 100 } 101 102 /** Parse {@code data} as a message of the given type and return it. */ parseFrom(Descriptor type, ByteString data)103 public static DynamicMessage parseFrom(Descriptor type, ByteString data) 104 throws InvalidProtocolBufferException { 105 return newBuilder(type).mergeFrom(data).buildParsed(); 106 } 107 108 /** Parse {@code data} as a message of the given type and return it. */ parseFrom( Descriptor type, ByteString data, ExtensionRegistry extensionRegistry)109 public static DynamicMessage parseFrom( 110 Descriptor type, ByteString data, ExtensionRegistry extensionRegistry) 111 throws InvalidProtocolBufferException { 112 return newBuilder(type).mergeFrom(data, extensionRegistry).buildParsed(); 113 } 114 115 /** Parse {@code data} as a message of the given type and return it. */ parseFrom(Descriptor type, byte[] data)116 public static DynamicMessage parseFrom(Descriptor type, byte[] data) 117 throws InvalidProtocolBufferException { 118 return newBuilder(type).mergeFrom(data).buildParsed(); 119 } 120 121 /** Parse {@code data} as a message of the given type and return it. */ parseFrom( Descriptor type, byte[] data, ExtensionRegistry extensionRegistry)122 public static DynamicMessage parseFrom( 123 Descriptor type, byte[] data, ExtensionRegistry extensionRegistry) 124 throws InvalidProtocolBufferException { 125 return newBuilder(type).mergeFrom(data, extensionRegistry).buildParsed(); 126 } 127 128 /** Parse a message of the given type from {@code input} and return it. */ parseFrom(Descriptor type, InputStream input)129 public static DynamicMessage parseFrom(Descriptor type, InputStream input) throws IOException { 130 return newBuilder(type).mergeFrom(input).buildParsed(); 131 } 132 133 /** Parse a message of the given type from {@code input} and return it. */ parseFrom( Descriptor type, InputStream input, ExtensionRegistry extensionRegistry)134 public static DynamicMessage parseFrom( 135 Descriptor type, InputStream input, ExtensionRegistry extensionRegistry) throws IOException { 136 return newBuilder(type).mergeFrom(input, extensionRegistry).buildParsed(); 137 } 138 139 /** Construct a {@link Message.Builder} for the given type. */ newBuilder(Descriptor type)140 public static Builder newBuilder(Descriptor type) { 141 return new Builder(type); 142 } 143 144 /** 145 * Construct a {@link Message.Builder} for a message of the same type as {@code prototype}, and 146 * initialize it with {@code prototype}'s contents. 147 */ newBuilder(Message prototype)148 public static Builder newBuilder(Message prototype) { 149 return new Builder(prototype.getDescriptorForType()).mergeFrom(prototype); 150 } 151 152 // ----------------------------------------------------------------- 153 // Implementation of Message interface. 154 155 @Override getDescriptorForType()156 public Descriptor getDescriptorForType() { 157 return type; 158 } 159 160 @Override getDefaultInstanceForType()161 public DynamicMessage getDefaultInstanceForType() { 162 return getDefaultInstance(type); 163 } 164 165 @Override getAllFields()166 public Map<FieldDescriptor, Object> getAllFields() { 167 return fields.getAllFields(); 168 } 169 170 @Override hasOneof(OneofDescriptor oneof)171 public boolean hasOneof(OneofDescriptor oneof) { 172 verifyOneofContainingType(oneof); 173 FieldDescriptor field = oneofCases[oneof.getIndex()]; 174 if (field == null) { 175 return false; 176 } 177 return true; 178 } 179 180 @Override getOneofFieldDescriptor(OneofDescriptor oneof)181 public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { 182 verifyOneofContainingType(oneof); 183 return oneofCases[oneof.getIndex()]; 184 } 185 186 @Override hasField(FieldDescriptor field)187 public boolean hasField(FieldDescriptor field) { 188 verifyContainingType(field); 189 return fields.hasField(field); 190 } 191 192 @Override getField(FieldDescriptor field)193 public Object getField(FieldDescriptor field) { 194 verifyContainingType(field); 195 Object result = fields.getField(field); 196 if (result == null) { 197 if (field.isRepeated()) { 198 result = Collections.emptyList(); 199 } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 200 result = getDefaultInstance(field.getMessageType()); 201 } else { 202 result = field.getDefaultValue(); 203 } 204 } 205 return result; 206 } 207 208 @Override getRepeatedFieldCount(FieldDescriptor field)209 public int getRepeatedFieldCount(FieldDescriptor field) { 210 verifyContainingType(field); 211 return fields.getRepeatedFieldCount(field); 212 } 213 214 @Override getRepeatedField(FieldDescriptor field, int index)215 public Object getRepeatedField(FieldDescriptor field, int index) { 216 verifyContainingType(field); 217 return fields.getRepeatedField(field, index); 218 } 219 220 @Override getUnknownFields()221 public UnknownFieldSet getUnknownFields() { 222 return unknownFields; 223 } 224 isInitialized(Descriptor type, FieldSet<FieldDescriptor> fields)225 static boolean isInitialized(Descriptor type, FieldSet<FieldDescriptor> fields) { 226 // Check that all required fields are present. 227 for (final FieldDescriptor field : type.getFields()) { 228 if (field.isRequired()) { 229 if (!fields.hasField(field)) { 230 return false; 231 } 232 } 233 } 234 235 // Check that embedded messages are initialized. 236 return fields.isInitialized(); 237 } 238 239 @Override isInitialized()240 public boolean isInitialized() { 241 return isInitialized(type, fields); 242 } 243 244 @Override writeTo(CodedOutputStream output)245 public void writeTo(CodedOutputStream output) throws IOException { 246 if (type.getOptions().getMessageSetWireFormat()) { 247 fields.writeMessageSetTo(output); 248 unknownFields.writeAsMessageSetTo(output); 249 } else { 250 fields.writeTo(output); 251 unknownFields.writeTo(output); 252 } 253 } 254 255 @Override getSerializedSize()256 public int getSerializedSize() { 257 int size = memoizedSize; 258 if (size != -1) return size; 259 260 if (type.getOptions().getMessageSetWireFormat()) { 261 size = fields.getMessageSetSerializedSize(); 262 size += unknownFields.getSerializedSizeAsMessageSet(); 263 } else { 264 size = fields.getSerializedSize(); 265 size += unknownFields.getSerializedSize(); 266 } 267 268 memoizedSize = size; 269 return size; 270 } 271 272 @Override newBuilderForType()273 public Builder newBuilderForType() { 274 return new Builder(type); 275 } 276 277 @Override toBuilder()278 public Builder toBuilder() { 279 return newBuilderForType().mergeFrom(this); 280 } 281 282 @Override getParserForType()283 public Parser<DynamicMessage> getParserForType() { 284 return new AbstractParser<DynamicMessage>() { 285 @Override 286 public DynamicMessage parsePartialFrom( 287 CodedInputStream input, ExtensionRegistryLite extensionRegistry) 288 throws InvalidProtocolBufferException { 289 Builder builder = newBuilder(type); 290 try { 291 builder.mergeFrom(input, extensionRegistry); 292 } catch (InvalidProtocolBufferException e) { 293 throw e.setUnfinishedMessage(builder.buildPartial()); 294 } catch (IOException e) { 295 throw new InvalidProtocolBufferException(e).setUnfinishedMessage(builder.buildPartial()); 296 } 297 return builder.buildPartial(); 298 } 299 }; 300 } 301 302 /** Verifies that the field is a field of this message. */ 303 private void verifyContainingType(FieldDescriptor field) { 304 if (field.getContainingType() != type) { 305 throw new IllegalArgumentException("FieldDescriptor does not match message type."); 306 } 307 } 308 309 /** Verifies that the oneof is an oneof of this message. */ 310 private void verifyOneofContainingType(OneofDescriptor oneof) { 311 if (oneof.getContainingType() != type) { 312 throw new IllegalArgumentException("OneofDescriptor does not match message type."); 313 } 314 } 315 316 // ================================================================= 317 318 /** Builder for {@link DynamicMessage}s. */ 319 public static final class Builder extends AbstractMessage.Builder<Builder> { 320 private final Descriptor type; 321 private FieldSet<FieldDescriptor> fields; 322 private final FieldDescriptor[] oneofCases; 323 private UnknownFieldSet unknownFields; 324 325 /** Construct a {@code Builder} for the given type. */ 326 private Builder(Descriptor type) { 327 this.type = type; 328 this.fields = FieldSet.newFieldSet(); 329 this.unknownFields = UnknownFieldSet.getDefaultInstance(); 330 this.oneofCases = new FieldDescriptor[type.toProto().getOneofDeclCount()]; 331 } 332 333 // --------------------------------------------------------------- 334 // Implementation of Message.Builder interface. 335 336 @Override 337 public Builder clear() { 338 if (fields.isImmutable()) { 339 fields = FieldSet.newFieldSet(); 340 } else { 341 fields.clear(); 342 } 343 unknownFields = UnknownFieldSet.getDefaultInstance(); 344 return this; 345 } 346 347 @Override 348 public Builder mergeFrom(Message other) { 349 if (other instanceof DynamicMessage) { 350 // This should be somewhat faster than calling super.mergeFrom(). 351 DynamicMessage otherDynamicMessage = (DynamicMessage) other; 352 if (otherDynamicMessage.type != type) { 353 throw new IllegalArgumentException( 354 "mergeFrom(Message) can only merge messages of the same type."); 355 } 356 ensureIsMutable(); 357 fields.mergeFrom(otherDynamicMessage.fields); 358 mergeUnknownFields(otherDynamicMessage.unknownFields); 359 for (int i = 0; i < oneofCases.length; i++) { 360 if (oneofCases[i] == null) { 361 oneofCases[i] = otherDynamicMessage.oneofCases[i]; 362 } else { 363 if ((otherDynamicMessage.oneofCases[i] != null) 364 && (oneofCases[i] != otherDynamicMessage.oneofCases[i])) { 365 fields.clearField(oneofCases[i]); 366 oneofCases[i] = otherDynamicMessage.oneofCases[i]; 367 } 368 } 369 } 370 return this; 371 } else { 372 return super.mergeFrom(other); 373 } 374 } 375 376 @Override 377 public DynamicMessage build() { 378 if (!isInitialized()) { 379 throw newUninitializedMessageException( 380 new DynamicMessage( 381 type, 382 fields, 383 java.util.Arrays.copyOf(oneofCases, oneofCases.length), 384 unknownFields)); 385 } 386 return buildPartial(); 387 } 388 389 /** 390 * Helper for DynamicMessage.parseFrom() methods to call. Throws {@link 391 * InvalidProtocolBufferException} instead of {@link UninitializedMessageException}. 392 */ 393 private DynamicMessage buildParsed() throws InvalidProtocolBufferException { 394 if (!isInitialized()) { 395 throw newUninitializedMessageException( 396 new DynamicMessage( 397 type, 398 fields, 399 java.util.Arrays.copyOf(oneofCases, oneofCases.length), 400 unknownFields)) 401 .asInvalidProtocolBufferException(); 402 } 403 return buildPartial(); 404 } 405 406 @Override 407 public DynamicMessage buildPartial() { 408 // Set default values for all fields in a MapEntry. 409 if (type.getOptions().getMapEntry()) { 410 for (FieldDescriptor field : type.getFields()) { 411 if (field.isOptional() && !fields.hasField(field)) { 412 if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 413 fields.setField(field, getDefaultInstance(field.getMessageType())); 414 } else { 415 fields.setField(field, field.getDefaultValue()); 416 } 417 } 418 } 419 } 420 421 fields.makeImmutable(); 422 DynamicMessage result = 423 new DynamicMessage( 424 type, 425 fields, 426 java.util.Arrays.copyOf(oneofCases, oneofCases.length), 427 unknownFields); 428 return result; 429 } 430 431 @Override 432 public Builder clone() { 433 Builder result = new Builder(type); 434 result.fields.mergeFrom(fields); 435 result.mergeUnknownFields(unknownFields); 436 System.arraycopy(oneofCases, 0, result.oneofCases, 0, oneofCases.length); 437 return result; 438 } 439 440 @Override 441 public boolean isInitialized() { 442 return DynamicMessage.isInitialized(type, fields); 443 } 444 445 @Override 446 public Descriptor getDescriptorForType() { 447 return type; 448 } 449 450 @Override 451 public DynamicMessage getDefaultInstanceForType() { 452 return getDefaultInstance(type); 453 } 454 455 @Override 456 public Map<FieldDescriptor, Object> getAllFields() { 457 return fields.getAllFields(); 458 } 459 460 @Override 461 public Builder newBuilderForField(FieldDescriptor field) { 462 verifyContainingType(field); 463 464 if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { 465 throw new IllegalArgumentException( 466 "newBuilderForField is only valid for fields with message type."); 467 } 468 469 return new Builder(field.getMessageType()); 470 } 471 472 @Override 473 public boolean hasOneof(OneofDescriptor oneof) { 474 verifyOneofContainingType(oneof); 475 FieldDescriptor field = oneofCases[oneof.getIndex()]; 476 if (field == null) { 477 return false; 478 } 479 return true; 480 } 481 482 @Override 483 public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) { 484 verifyOneofContainingType(oneof); 485 return oneofCases[oneof.getIndex()]; 486 } 487 488 @Override 489 public Builder clearOneof(OneofDescriptor oneof) { 490 verifyOneofContainingType(oneof); 491 FieldDescriptor field = oneofCases[oneof.getIndex()]; 492 if (field != null) { 493 clearField(field); 494 } 495 return this; 496 } 497 498 @Override 499 public boolean hasField(FieldDescriptor field) { 500 verifyContainingType(field); 501 return fields.hasField(field); 502 } 503 504 @Override 505 public Object getField(FieldDescriptor field) { 506 verifyContainingType(field); 507 Object result = fields.getField(field); 508 if (result == null) { 509 if (field.isRepeated()) { 510 result = Collections.emptyList(); 511 } else if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 512 result = getDefaultInstance(field.getMessageType()); 513 } else { 514 result = field.getDefaultValue(); 515 } 516 } 517 return result; 518 } 519 520 @Override 521 public Builder setField(FieldDescriptor field, Object value) { 522 verifyContainingType(field); 523 ensureIsMutable(); 524 // TODO(xiaofeng): This check should really be put in FieldSet.setField() 525 // where all other such checks are done. However, currently 526 // FieldSet.setField() permits Integer value for enum fields probably 527 // because of some internal features we support. Should figure it out 528 // and move this check to a more appropriate place. 529 if (field.getType() == FieldDescriptor.Type.ENUM) { 530 ensureEnumValueDescriptor(field, value); 531 } 532 OneofDescriptor oneofDescriptor = field.getContainingOneof(); 533 if (oneofDescriptor != null) { 534 int index = oneofDescriptor.getIndex(); 535 FieldDescriptor oldField = oneofCases[index]; 536 if ((oldField != null) && (oldField != field)) { 537 fields.clearField(oldField); 538 } 539 oneofCases[index] = field; 540 } else if (field.getFile().getSyntax() == Descriptors.FileDescriptor.Syntax.PROTO3) { 541 if (!field.isRepeated() 542 && field.getJavaType() != FieldDescriptor.JavaType.MESSAGE 543 && value.equals(field.getDefaultValue())) { 544 // In proto3, setting a field to its default value is equivalent to clearing the field. 545 fields.clearField(field); 546 return this; 547 } 548 } 549 fields.setField(field, value); 550 return this; 551 } 552 553 @Override 554 public Builder clearField(FieldDescriptor field) { 555 verifyContainingType(field); 556 ensureIsMutable(); 557 OneofDescriptor oneofDescriptor = field.getContainingOneof(); 558 if (oneofDescriptor != null) { 559 int index = oneofDescriptor.getIndex(); 560 if (oneofCases[index] == field) { 561 oneofCases[index] = null; 562 } 563 } 564 fields.clearField(field); 565 return this; 566 } 567 568 @Override 569 public int getRepeatedFieldCount(FieldDescriptor field) { 570 verifyContainingType(field); 571 return fields.getRepeatedFieldCount(field); 572 } 573 574 @Override 575 public Object getRepeatedField(FieldDescriptor field, int index) { 576 verifyContainingType(field); 577 return fields.getRepeatedField(field, index); 578 } 579 580 @Override 581 public Builder setRepeatedField(FieldDescriptor field, int index, Object value) { 582 verifyContainingType(field); 583 ensureIsMutable(); 584 fields.setRepeatedField(field, index, value); 585 return this; 586 } 587 588 @Override 589 public Builder addRepeatedField(FieldDescriptor field, Object value) { 590 verifyContainingType(field); 591 ensureIsMutable(); 592 fields.addRepeatedField(field, value); 593 return this; 594 } 595 596 @Override 597 public UnknownFieldSet getUnknownFields() { 598 return unknownFields; 599 } 600 601 @Override 602 public Builder setUnknownFields(UnknownFieldSet unknownFields) { 603 this.unknownFields = unknownFields; 604 return this; 605 } 606 607 @Override 608 public Builder mergeUnknownFields(UnknownFieldSet unknownFields) { 609 this.unknownFields = 610 UnknownFieldSet.newBuilder(this.unknownFields).mergeFrom(unknownFields).build(); 611 return this; 612 } 613 614 /** Verifies that the field is a field of this message. */ 615 private void verifyContainingType(FieldDescriptor field) { 616 if (field.getContainingType() != type) { 617 throw new IllegalArgumentException("FieldDescriptor does not match message type."); 618 } 619 } 620 621 /** Verifies that the oneof is an oneof of this message. */ 622 private void verifyOneofContainingType(OneofDescriptor oneof) { 623 if (oneof.getContainingType() != type) { 624 throw new IllegalArgumentException("OneofDescriptor does not match message type."); 625 } 626 } 627 628 /** Verifies that the value is EnumValueDescriptor and matches Enum Type. */ 629 private void ensureSingularEnumValueDescriptor(FieldDescriptor field, Object value) { 630 checkNotNull(value); 631 if (!(value instanceof EnumValueDescriptor)) { 632 throw new IllegalArgumentException( 633 "DynamicMessage should use EnumValueDescriptor to set Enum Value."); 634 } 635 // TODO(xiaofeng): Re-enable this check after Orgstore is fixed to not 636 // set incorrect EnumValueDescriptors. 637 // EnumDescriptor fieldType = field.getEnumType(); 638 // EnumDescriptor fieldValueType = ((EnumValueDescriptor) value).getType(); 639 // if (fieldType != fieldValueType) { 640 // throw new IllegalArgumentException(String.format( 641 // "EnumDescriptor %s of field doesn't match EnumDescriptor %s of field value", 642 // fieldType.getFullName(), fieldValueType.getFullName())); 643 // } 644 } 645 646 /** Verifies the value for an enum field. */ 647 private void ensureEnumValueDescriptor(FieldDescriptor field, Object value) { 648 if (field.isRepeated()) { 649 for (Object item : (List) value) { 650 ensureSingularEnumValueDescriptor(field, item); 651 } 652 } else { 653 ensureSingularEnumValueDescriptor(field, value); 654 } 655 } 656 657 private void ensureIsMutable() { 658 if (fields.isImmutable()) { 659 fields = fields.clone(); 660 } 661 } 662 663 @Override 664 public com.google.protobuf.Message.Builder getFieldBuilder(FieldDescriptor field) { 665 // TODO(xiangl): need implementation for dynamic message 666 throw new UnsupportedOperationException( 667 "getFieldBuilder() called on a dynamic message type."); 668 } 669 670 @Override 671 public com.google.protobuf.Message.Builder getRepeatedFieldBuilder( 672 FieldDescriptor field, int index) { 673 throw new UnsupportedOperationException( 674 "getRepeatedFieldBuilder() called on a dynamic message type."); 675 } 676 } 677 } 678