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