1 // Copyright (c) 2016, the R8 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 package com.android.tools.r8.graph; 5 6 import com.android.tools.r8.dex.DexOutputBuffer; 7 import com.android.tools.r8.dex.FileWriter; 8 import com.android.tools.r8.dex.IndexedItemCollection; 9 import com.android.tools.r8.dex.MixedSectionCollection; 10 import com.android.tools.r8.errors.Unreachable; 11 import com.android.tools.r8.ir.code.ConstNumber; 12 import com.android.tools.r8.ir.code.ConstType; 13 import com.android.tools.r8.ir.code.Instruction; 14 import com.android.tools.r8.ir.code.Value; 15 import com.android.tools.r8.utils.EncodedValueUtils; 16 import java.util.Arrays; 17 18 public abstract class DexValue extends DexItem { 19 20 public static final byte VALUE_BYTE = 0x00; 21 public static final byte VALUE_SHORT = 0x02; 22 public static final byte VALUE_CHAR = 0x03; 23 public static final byte VALUE_INT = 0x04; 24 public static final byte VALUE_LONG = 0x06; 25 public static final byte VALUE_FLOAT = 0x10; 26 public static final byte VALUE_DOUBLE = 0x11; 27 public static final byte VALUE_METHOD_TYPE = 0x15; 28 public static final byte VALUE_METHOD_HANDLE = 0x16; 29 public static final byte VALUE_STRING = 0x17; 30 public static final byte VALUE_TYPE = 0x18; 31 public static final byte VALUE_FIELD = 0x19; 32 public static final byte VALUE_METHOD = 0x1a; 33 public static final byte VALUE_ENUM = 0x1b; 34 public static final byte VALUE_ARRAY = 0x1c; 35 public static final byte VALUE_ANNOTATION = 0x1d; 36 public static final byte VALUE_NULL = 0x1e; 37 public static final byte VALUE_BOOLEAN = 0x1f; 38 writeHeader(byte type, int arg, DexOutputBuffer dest)39 private static void writeHeader(byte type, int arg, DexOutputBuffer dest) { 40 dest.putByte((byte) ((arg << 5) | type)); 41 } 42 43 @Override collectMixedSectionItems(MixedSectionCollection mixedItems)44 void collectMixedSectionItems(MixedSectionCollection mixedItems) { 45 // Should never be visited. 46 assert false; 47 } 48 sort()49 public abstract void sort(); 50 writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)51 public abstract void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping); 52 53 @Override hashCode()54 public abstract int hashCode(); 55 56 @Override equals(Object other)57 public abstract boolean equals(Object other); 58 59 @Override toString()60 public abstract String toString(); 61 defaultForType(DexType type, DexItemFactory factory)62 public static DexValue defaultForType(DexType type, DexItemFactory factory) { 63 if (type == factory.booleanType) { 64 return DexValueBoolean.DEFAULT; 65 } 66 if (type == factory.byteType) { 67 return DexValueByte.DEFAULT; 68 } 69 if (type == factory.charType) { 70 return DexValueChar.DEFAULT; 71 } 72 if (type == factory.shortType) { 73 return DexValueShort.DEFAULT; 74 } 75 if (type == factory.intType) { 76 return DexValueInt.DEFAULT; 77 } 78 if (type == factory.longType) { 79 return DexValueLong.DEFAULT; 80 } 81 if (type == factory.floatType) { 82 return DexValueFloat.DEFAULT; 83 } 84 if (type == factory.doubleType) { 85 return DexValueDouble.DEFAULT; 86 } 87 if (type.isArrayType() || type.isClassType()) { 88 return DexValueNull.NULL; 89 } 90 throw new Unreachable("No default value for unexpected type " + type); 91 } 92 93 // Returns a const instruction for the non default value. asConstInstruction(boolean hasClassInitializer, Value dest)94 public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) { 95 return null; 96 } 97 isDefault(DexType type, DexItemFactory factory)98 public boolean isDefault(DexType type, DexItemFactory factory) { 99 return this == defaultForType(type, factory); 100 } 101 102 static private abstract class SimpleDexValue extends DexValue { 103 104 @Override collectIndexedItems(IndexedItemCollection indexedItems)105 public void collectIndexedItems(IndexedItemCollection indexedItems) { 106 // Intentionally left empty 107 } 108 109 @Override sort()110 public void sort() { 111 // Intentionally empty 112 } 113 writeIntegerTo(byte type, long value, int expected, DexOutputBuffer dest)114 protected static void writeIntegerTo(byte type, long value, int expected, 115 DexOutputBuffer dest) { 116 // Leave space for header. 117 dest.forward(1); 118 int length = dest.putSignedEncodedValue(value, expected); 119 dest.rewind(length + 1); 120 writeHeader(type, length - 1, dest); 121 dest.forward(length); 122 } 123 124 } 125 126 static public class DexValueByte extends SimpleDexValue { 127 128 public static final DexValueByte DEFAULT = new DexValueByte((byte) 0); 129 130 final byte value; 131 DexValueByte(byte value)132 private DexValueByte(byte value) { 133 this.value = value; 134 } 135 create(byte value)136 public static DexValueByte create(byte value) { 137 return value == DEFAULT.value ? DEFAULT : new DexValueByte(value); 138 } 139 140 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)141 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 142 writeHeader(VALUE_BYTE, 0, dest); 143 dest.putSignedEncodedValue(value, 1); 144 } 145 146 @Override hashCode()147 public int hashCode() { 148 return value * 3; 149 } 150 151 @Override equals(Object other)152 public boolean equals(Object other) { 153 if (other == this) { 154 return true; 155 } 156 return other instanceof DexValueByte && value == ((DexValueByte) other).value; 157 } 158 159 @Override toString()160 public String toString() { 161 return "Byte " + value; 162 } 163 164 @Override asConstInstruction(boolean hasClassInitializer, Value dest)165 public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) { 166 return (this == DEFAULT && hasClassInitializer) 167 ? null 168 : new ConstNumber(ConstType.INT, dest, value); 169 } 170 } 171 172 static public class DexValueShort extends SimpleDexValue { 173 174 public static final DexValueShort DEFAULT = new DexValueShort((short) 0); 175 final short value; 176 DexValueShort(short value)177 private DexValueShort(short value) { 178 this.value = value; 179 } 180 create(short value)181 public static DexValueShort create(short value) { 182 return value == DEFAULT.value ? DEFAULT : new DexValueShort(value); 183 } 184 185 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)186 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 187 writeIntegerTo(VALUE_SHORT, value, Short.BYTES, dest); 188 } 189 190 @Override hashCode()191 public int hashCode() { 192 return value * 7; 193 } 194 195 @Override equals(Object other)196 public boolean equals(Object other) { 197 if (other == this) { 198 return true; 199 } 200 return other instanceof DexValueShort && value == ((DexValueShort) other).value; 201 } 202 203 @Override toString()204 public String toString() { 205 return "Short " + value; 206 } 207 208 @Override asConstInstruction(boolean hasClassInitializer, Value dest)209 public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) { 210 return (this == DEFAULT && hasClassInitializer) 211 ? null 212 : new ConstNumber(ConstType.INT, dest, value); 213 } 214 } 215 216 static public class DexValueChar extends SimpleDexValue { 217 218 public static final DexValueChar DEFAULT = new DexValueChar((char) 0); 219 final char value; 220 DexValueChar(char value)221 private DexValueChar(char value) { 222 this.value = value; 223 } 224 create(char value)225 public static DexValueChar create(char value) { 226 return value == DEFAULT.value ? DEFAULT : new DexValueChar(value); 227 } 228 229 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)230 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 231 dest.forward(1); 232 int length = dest.putUnsignedEncodedValue(value, 2); 233 dest.rewind(length + 1); 234 writeHeader(VALUE_CHAR, length - 1, dest); 235 dest.forward(length); 236 } 237 238 @Override hashCode()239 public int hashCode() { 240 return value * 5; 241 } 242 243 @Override equals(Object other)244 public boolean equals(Object other) { 245 if (other == this) { 246 return true; 247 } 248 return other instanceof DexValueChar && value == ((DexValueChar) other).value; 249 } 250 251 @Override toString()252 public String toString() { 253 return "Char " + value; 254 } 255 256 @Override asConstInstruction(boolean hasClassInitializer, Value dest)257 public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) { 258 return (this == DEFAULT && hasClassInitializer) 259 ? null 260 : new ConstNumber(ConstType.INT, dest, value); 261 } 262 } 263 264 static public class DexValueInt extends SimpleDexValue { 265 266 public static final DexValueInt DEFAULT = new DexValueInt(0); 267 public final int value; 268 DexValueInt(int value)269 private DexValueInt(int value) { 270 this.value = value; 271 } 272 create(int value)273 public static DexValueInt create(int value) { 274 return value == DEFAULT.value ? DEFAULT : new DexValueInt(value); 275 } 276 277 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)278 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 279 writeIntegerTo(VALUE_INT, value, Integer.BYTES, dest); 280 } 281 282 @Override hashCode()283 public int hashCode() { 284 return value * 11; 285 } 286 287 @Override equals(Object other)288 public boolean equals(Object other) { 289 if (other == this) { 290 return true; 291 } 292 return other instanceof DexValueInt && value == ((DexValueInt) other).value; 293 } 294 295 @Override toString()296 public String toString() { 297 return "Int " + value; 298 } 299 300 @Override asConstInstruction(boolean hasClassInitializer, Value dest)301 public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) { 302 return (this == DEFAULT && hasClassInitializer) 303 ? null 304 : new ConstNumber(ConstType.INT, dest, value); 305 } 306 } 307 308 static public class DexValueLong extends SimpleDexValue { 309 310 public static final DexValueLong DEFAULT = new DexValueLong(0); 311 final long value; 312 DexValueLong(long value)313 private DexValueLong(long value) { 314 this.value = value; 315 } 316 create(long value)317 public static DexValueLong create(long value) { 318 return value == DEFAULT.value ? DEFAULT : new DexValueLong(value); 319 } 320 321 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)322 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 323 writeIntegerTo(VALUE_LONG, value, Long.BYTES, dest); 324 } 325 326 @Override hashCode()327 public int hashCode() { 328 return (int) value * 13; 329 } 330 331 @Override equals(Object other)332 public boolean equals(Object other) { 333 if (other == this) { 334 return true; 335 } 336 return other instanceof DexValueLong && value == ((DexValueLong) other).value; 337 } 338 339 @Override toString()340 public String toString() { 341 return "Long " + value; 342 } 343 344 @Override asConstInstruction(boolean hasClassInitializer, Value dest)345 public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) { 346 return (this == DEFAULT && hasClassInitializer) 347 ? null 348 : new ConstNumber(ConstType.LONG, dest, value); 349 } 350 } 351 352 static public class DexValueFloat extends SimpleDexValue { 353 354 public static final DexValueFloat DEFAULT = new DexValueFloat(0); 355 final float value; 356 DexValueFloat(float value)357 private DexValueFloat(float value) { 358 this.value = value; 359 } 360 create(float value)361 public static DexValueFloat create(float value) { 362 return Float.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueFloat(value); 363 } 364 365 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)366 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 367 dest.forward(1); 368 int length = EncodedValueUtils.putFloat(dest, value); 369 dest.rewind(length + 1); 370 writeHeader(VALUE_FLOAT, length - 1, dest); 371 dest.forward(length); 372 } 373 374 @Override hashCode()375 public int hashCode() { 376 return (int) value * 19; 377 } 378 379 @Override equals(Object other)380 public boolean equals(Object other) { 381 if (other == this) { 382 return true; 383 } 384 return (other instanceof DexValueFloat) && 385 (Float.compare(value, ((DexValueFloat) other).value) == 0); 386 } 387 388 @Override toString()389 public String toString() { 390 return "Float " + value; 391 } 392 393 } 394 395 static public class DexValueDouble extends SimpleDexValue { 396 397 public static final DexValueDouble DEFAULT = new DexValueDouble(0); 398 399 final double value; 400 DexValueDouble(double value)401 private DexValueDouble(double value) { 402 this.value = value; 403 } 404 create(double value)405 public static DexValueDouble create(double value) { 406 return Double.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueDouble(value); 407 } 408 409 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)410 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 411 dest.forward(1); 412 int length = EncodedValueUtils.putDouble(dest, value); 413 dest.rewind(length + 1); 414 writeHeader(VALUE_DOUBLE, length - 1, dest); 415 dest.forward(length); 416 } 417 418 @Override hashCode()419 public int hashCode() { 420 return (int) value * 29; 421 } 422 423 @Override equals(Object other)424 public boolean equals(Object other) { 425 if (other == this) { 426 return true; 427 } 428 return (other instanceof DexValueDouble) && 429 (Double.compare(value, ((DexValueDouble) other).value) == 0); 430 } 431 432 @Override toString()433 public String toString() { 434 return "Double " + value; 435 } 436 } 437 438 static private abstract class NestedDexValue<T extends IndexedDexItem> extends DexValue { 439 440 public final T value; 441 NestedDexValue(T value)442 private NestedDexValue(T value) { 443 this.value = value; 444 } 445 getValueKind()446 protected abstract byte getValueKind(); 447 448 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)449 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 450 int offset = value.getOffset(mapping); 451 dest.forward(1); 452 int length = dest.putUnsignedEncodedValue(offset, 4); 453 dest.rewind(length + 1); 454 writeHeader(getValueKind(), length - 1, dest); 455 dest.forward(length); 456 } 457 458 @Override collectIndexedItems(IndexedItemCollection indexedItems)459 public void collectIndexedItems(IndexedItemCollection indexedItems) { 460 value.collectIndexedItems(indexedItems); 461 } 462 463 @Override sort()464 public void sort() { 465 // Intentionally empty. 466 } 467 468 @Override hashCode()469 public int hashCode() { 470 return value.hashCode() * 7 + getValueKind(); 471 } 472 473 @Override equals(Object other)474 public boolean equals(Object other) { 475 if (other == this) { 476 return true; 477 } 478 if (other instanceof NestedDexValue) { 479 NestedDexValue<?> that = (NestedDexValue<?>) other; 480 return that.getValueKind() == getValueKind() && that.value.equals(value); 481 } 482 return false; 483 } 484 485 @Override toString()486 public String toString() { 487 return "Item " + getValueKind() + " " + value; 488 } 489 } 490 491 static public class DexValueString extends NestedDexValue<DexString> { 492 DexValueString(DexString value)493 public DexValueString(DexString value) { 494 super(value); 495 } 496 497 @Override getValueKind()498 protected byte getValueKind() { 499 return VALUE_STRING; 500 } 501 } 502 503 static public class DexValueType extends NestedDexValue<DexType> { 504 DexValueType(DexType value)505 public DexValueType(DexType value) { 506 super(value); 507 } 508 509 @Override getValueKind()510 protected byte getValueKind() { 511 return VALUE_TYPE; 512 } 513 514 @Override collectIndexedItems(IndexedItemCollection indexedItems)515 public void collectIndexedItems(IndexedItemCollection indexedItems) { 516 value.collectIndexedItems(indexedItems); 517 } 518 } 519 520 static public class DexValueField extends NestedDexValue<DexField> { 521 DexValueField(DexField value)522 public DexValueField(DexField value) { 523 super(value); 524 } 525 526 @Override getValueKind()527 protected byte getValueKind() { 528 return VALUE_FIELD; 529 } 530 531 @Override collectIndexedItems(IndexedItemCollection indexedItems)532 public void collectIndexedItems(IndexedItemCollection indexedItems) { 533 value.collectIndexedItems(indexedItems); 534 } 535 } 536 537 static public class DexValueMethod extends NestedDexValue<DexMethod> { 538 DexValueMethod(DexMethod value)539 public DexValueMethod(DexMethod value) { 540 super(value); 541 } 542 543 @Override getValueKind()544 protected byte getValueKind() { 545 return VALUE_METHOD; 546 } 547 548 @Override collectIndexedItems(IndexedItemCollection indexedItems)549 public void collectIndexedItems(IndexedItemCollection indexedItems) { 550 value.collectIndexedItems(indexedItems); 551 } 552 } 553 554 static public class DexValueEnum extends NestedDexValue<DexField> { 555 DexValueEnum(DexField value)556 public DexValueEnum(DexField value) { 557 super(value); 558 } 559 560 @Override getValueKind()561 protected byte getValueKind() { 562 return VALUE_ENUM; 563 } 564 565 @Override collectIndexedItems(IndexedItemCollection indexedItems)566 public void collectIndexedItems(IndexedItemCollection indexedItems) { 567 value.collectIndexedItems(indexedItems); 568 } 569 } 570 571 static public class DexValueMethodType extends NestedDexValue<DexProto> { 572 DexValueMethodType(DexProto value)573 public DexValueMethodType(DexProto value) { 574 super(value); 575 } 576 577 @Override getValueKind()578 protected byte getValueKind() { 579 return VALUE_METHOD_TYPE; 580 } 581 582 @Override collectIndexedItems(IndexedItemCollection indexedItems)583 public void collectIndexedItems(IndexedItemCollection indexedItems) { 584 value.collectIndexedItems(indexedItems); 585 } 586 } 587 588 static public class DexValueArray extends DexValue { 589 590 final DexValue[] values; 591 DexValueArray(DexValue[] values)592 public DexValueArray(DexValue[] values) { 593 this.values = values; 594 } 595 596 @Override collectIndexedItems(IndexedItemCollection indexedItems)597 public void collectIndexedItems(IndexedItemCollection indexedItems) { 598 collectAll(indexedItems, values); 599 } 600 601 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)602 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 603 writeHeader(VALUE_ARRAY, 0, dest); 604 dest.putUleb128(values.length); 605 for (DexValue value : values) { 606 value.writeTo(dest, mapping); 607 } 608 } 609 610 @Override sort()611 public void sort() { 612 for (DexValue value : values) { 613 value.sort(); 614 } 615 } 616 617 @Override hashCode()618 public int hashCode() { 619 return Arrays.hashCode(values); 620 } 621 622 @Override equals(Object other)623 public boolean equals(Object other) { 624 if (other == this) { 625 return true; 626 } 627 if (other instanceof DexValueArray) { 628 DexValueArray that = (DexValueArray) other; 629 return Arrays.equals(that.values, values); 630 } 631 return false; 632 } 633 634 @Override toString()635 public String toString() { 636 return "Array " + Arrays.toString(values); 637 } 638 } 639 640 static public class DexValueAnnotation extends DexValue { 641 642 public final DexEncodedAnnotation value; 643 DexValueAnnotation(DexEncodedAnnotation value)644 public DexValueAnnotation(DexEncodedAnnotation value) { 645 this.value = value; 646 } 647 648 @Override collectIndexedItems(IndexedItemCollection indexedItems)649 public void collectIndexedItems(IndexedItemCollection indexedItems) { 650 value.collectIndexedItems(indexedItems); 651 } 652 653 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)654 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 655 writeHeader(VALUE_ANNOTATION, 0, dest); 656 FileWriter.writeEncodedAnnotation(value, dest, mapping); 657 } 658 659 @Override sort()660 public void sort() { 661 value.sort(); 662 } 663 664 @Override hashCode()665 public int hashCode() { 666 return value.hashCode() * 7; 667 } 668 669 @Override equals(Object other)670 public boolean equals(Object other) { 671 if (other == this) { 672 return true; 673 } 674 if (other instanceof DexValueAnnotation) { 675 DexValueAnnotation that = (DexValueAnnotation) other; 676 return that.value.equals(value); 677 } 678 return false; 679 } 680 681 @Override toString()682 public String toString() { 683 return "Annotation " + value; 684 } 685 } 686 687 static public class DexValueNull extends SimpleDexValue { 688 689 public static final DexValue NULL = new DexValueNull(); 690 691 // See DexValueNull.NULL DexValueNull()692 private DexValueNull() { 693 } 694 695 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)696 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 697 writeHeader(VALUE_NULL, 0, dest); 698 } 699 700 @Override hashCode()701 public int hashCode() { 702 return 42; 703 } 704 705 @Override equals(Object other)706 public boolean equals(Object other) { 707 if (other == this) { 708 return true; 709 } 710 return (other instanceof DexValueNull); 711 } 712 713 @Override toString()714 public String toString() { 715 return "Null"; 716 } 717 } 718 719 static public class DexValueBoolean extends SimpleDexValue { 720 721 private static final DexValueBoolean TRUE = new DexValueBoolean(true); 722 private static final DexValueBoolean FALSE = new DexValueBoolean(false); 723 // Use a separate instance for the default value to distinguish it from an explicit false value. 724 private static final DexValueBoolean DEFAULT = new DexValueBoolean(false); 725 726 final boolean value; 727 DexValueBoolean(boolean value)728 private DexValueBoolean(boolean value) { 729 this.value = value; 730 } 731 create(boolean value)732 public static DexValueBoolean create(boolean value) { 733 return value ? TRUE : FALSE; 734 } 735 736 @Override writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping)737 public void writeTo(DexOutputBuffer dest, ObjectToOffsetMapping mapping) { 738 writeHeader(VALUE_BOOLEAN, value ? 1 : 0, dest); 739 } 740 741 @Override hashCode()742 public int hashCode() { 743 return value ? 1234 : 4321; 744 } 745 746 @Override equals(Object other)747 public boolean equals(Object other) { 748 if (other == this) { 749 return true; 750 } 751 return (other instanceof DexValueBoolean) && ((DexValueBoolean) other).value == value; 752 } 753 754 @Override toString()755 public String toString() { 756 return value ? "True" : "False"; 757 } 758 759 @Override asConstInstruction(boolean hasClassInitializer, Value dest)760 public Instruction asConstInstruction(boolean hasClassInitializer, Value dest) { 761 return (this == DEFAULT && hasClassInitializer) 762 ? null 763 : new ConstNumber(ConstType.INT, dest, value ? 1 : 0); 764 } 765 } 766 767 static public class DexValueMethodHandle extends NestedDexValue<DexMethodHandle> { 768 DexValueMethodHandle(DexMethodHandle value)769 public DexValueMethodHandle(DexMethodHandle value) { 770 super(value); 771 } 772 773 @Override getValueKind()774 protected byte getValueKind() { 775 return VALUE_METHOD_HANDLE; 776 } 777 778 @Override collectIndexedItems(IndexedItemCollection indexedItems)779 public void collectIndexedItems(IndexedItemCollection indexedItems) { 780 value.collectIndexedItems(indexedItems); 781 } 782 } 783 } 784