1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package runtime.valhalla.valuetypes; 25 26 import java.lang.invoke.*; 27 import java.lang.ref.*; 28 import java.util.concurrent.*; 29 30 import static jdk.test.lib.Asserts.*; 31 import jdk.test.lib.Utils; 32 import sun.hotspot.WhiteBox; 33 34 import jdk.experimental.value.MethodHandleBuilder; 35 36 /** 37 * @test ValueOops 38 * @summary Test embedding oops into Value types 39 * @modules java.base/jdk.experimental.bytecode 40 * java.base/jdk.experimental.value 41 * @library /test/lib 42 * @compile -XDenableValueTypes Person.java 43 * @compile -XDenableValueTypes ValueOops.java 44 * @run main ClassFileInstaller sun.hotspot.WhiteBox 45 * @run driver ClassFileInstaller sun.hotspot.WhiteBox 46 * sun.hotspot.WhiteBox$WhiteBoxPermission 47 * @run main/othervm -Xint -XX:+UseSerialGC -Xmx128m -XX:+EnableValhalla 48 * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 49 * runtime.valhalla.valuetypes.ValueOops 50 * @run main/othervm -Xint -XX:+UseG1GC -Xmx128m -XX:+EnableValhalla 51 * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 52 * runtime.valhalla.valuetypes.ValueOops 100 53 * @run main/othervm -Xint -XX:+UseParallelGC -Xmx128m -XX:+EnableValhalla 54 * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 55 * runtime.valhalla.valuetypes.ValueOops 56 * @run main/othervm -Xcomp -XX:+UseSerialGC -Xmx128m -XX:+EnableValhalla 57 * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 58 * runtime.valhalla.valuetypes.ValueOops 59 * @run main/othervm -Xcomp -XX:+UseG1GC -Xmx128m -XX:+EnableValhalla 60 * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 61 * runtime.valhalla.valuetypes.ValueOops 100 62 * @run main/othervm -Xcomp -XX:+UseParallelGC -Xmx128m -XX:+EnableValhalla 63 * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 64 * runtime.valhalla.valuetypes.ValueOops 65 */ 66 public class ValueOops { 67 68 // Note: -noverify can not be eliminated. Javac Class_Info not using ";Q" 69 70 // Extra debug: -XX:+VerifyOops -XX:+VerifyStack -XX:+VerifyLastFrame -XX:+VerifyBeforeGC -XX:+VerifyAfterGC -XX:+VerifyDuringGC -XX:VerifySubSet=threads,heap 71 // Even more debugging: -XX:+TraceNewOopMapGeneration -Xlog:gc*=info 72 73 static final int NOF_PEOPLE = 1000; // Exercise arrays of this size 74 75 static int MIN_ACTIVE_GC_COUNT = 10; // Run active workload for this number of GC passes 76 77 static int MED_ACTIVE_GC_COUNT = 4; // Medium life span in terms of GC passes 78 79 static final String TEST_STRING1 = "Test String 1"; 80 static final String TEST_STRING2 = "Test String 2"; 81 82 static boolean USE_COMPILER = WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompiler"); 83 84 static MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 85 86 public static void main(String[] args) { 87 if (args.length > 0) { 88 MIN_ACTIVE_GC_COUNT = Integer.parseInt(args[0]); 89 } 90 testClassLoad(); 91 testVvt(); 92 93 if (!USE_COMPILER) { 94 testOopMaps(); 95 } 96 97 // Check we survive GC... 98 testOverGc(); // Exercise root scan / oopMap 99 testActiveGc(); // Brute force 100 } 101 102 /** 103 * Test ClassFileParser can load values with reference fields 104 */ 105 public static void testClassLoad() { 106 String s = Person.class.toString(); 107 new Bar(); 108 new BarWithValue(); 109 s = BarValue.class.toString(); 110 s = ObjectWithObjectValue.class.toString(); 111 s = ObjectWithObjectValues.class.toString(); 112 } 113 114 115 static class Couple { 116 public Person onePerson; 117 public Person otherPerson; 118 } 119 120 static final __ByValue class Composition { 121 public final Person onePerson; 122 public final Person otherPerson; 123 124 private Composition() { 125 this.onePerson = Person.create(0, null, null); 126 this.otherPerson = Person.create(0, null, null); 127 } 128 129 __ValueFactory public static Composition create(Person onePerson, Person otherPerson) { 130 Composition comp = __MakeDefault Composition(); 131 comp.onePerson = onePerson; 132 comp.otherPerson = otherPerson; 133 return comp; 134 } 135 } 136 137 /** 138 * Check value type operations with "Valhalla Value Types" (VVT) 139 */ 140 public static void testVvt() { 141 // Exercise creation, getfield, vreturn with null refs 142 validateDefaultPerson(createDefaultPerson()); 143 144 // anewarray, vaload, vastore 145 int index = 7; 146 Person[] array = new Person[NOF_PEOPLE]; 147 validateDefaultPerson(array[index]); 148 149 // Now with refs... 150 validateIndexedPerson(createIndexedPerson(index), index); 151 array[index] = createIndexedPerson(index); 152 validateIndexedPerson(array[index], index); 153 154 // Check the neighbours 155 validateDefaultPerson(array[index - 1]); 156 validateDefaultPerson(array[index + 1]); 157 158 // getfield/putfield 159 Couple couple = new Couple(); 160 validateDefaultPerson(couple.onePerson); 161 validateDefaultPerson(couple.otherPerson); 162 163 couple.onePerson = createIndexedPerson(index); 164 validateIndexedPerson(couple.onePerson, index); 165 166 Composition composition = Composition.create(couple.onePerson, couple.onePerson); 167 validateIndexedPerson(composition.onePerson, index); 168 validateIndexedPerson(composition.otherPerson, index); 169 } 170 171 /** 172 * Check oop map generation for klass layout and frame... 173 */ 174 public static void testOopMaps() { 175 Object[] objects = WhiteBox.getWhiteBox().getObjectsViaKlassOopMaps(new Couple()); 176 assertTrue(objects.length == 4, "Expected 4 oops"); 177 for (int i = 0; i < objects.length; i++) { 178 assertTrue(objects[i] == null, "not-null"); 179 } 180 181 String fn1 = "Sam"; 182 String ln1 = "Smith"; 183 String fn2 = "Jane"; 184 String ln2 = "Jones"; 185 Couple couple = new Couple(); 186 couple.onePerson = Person.create(0, fn1, ln1); 187 couple.otherPerson = Person.create(1, fn2, ln2); 188 objects = WhiteBox.getWhiteBox().getObjectsViaKlassOopMaps(couple); 189 assertTrue(objects.length == 4, "Expected 4 oops"); 190 assertTrue(objects[0] == fn1, "Bad oop fn1"); 191 assertTrue(objects[1] == ln1, "Bad oop ln1"); 192 assertTrue(objects[2] == fn2, "Bad oop fn2"); 193 assertTrue(objects[3] == ln2, "Bad oop ln2"); 194 195 objects = WhiteBox.getWhiteBox().getObjectsViaOopIterator(couple); 196 assertTrue(objects.length == 4, "Expected 4 oops"); 197 assertTrue(objects[0] == fn1, "Bad oop fn1"); 198 assertTrue(objects[1] == ln1, "Bad oop ln1"); 199 assertTrue(objects[2] == fn2, "Bad oop fn2"); 200 assertTrue(objects[3] == ln2, "Bad oop ln2"); 201 202 // Array.. 203 objects = WhiteBox.getWhiteBox().getObjectsViaOopIterator(createPeople()); 204 assertTrue(objects.length == NOF_PEOPLE * 2, "Unexpected length: " + objects.length); 205 int o = 0; 206 for (int i = 0; i < NOF_PEOPLE; i++) { 207 assertTrue(objects[o++].equals(firstName(i)), "Bad firstName"); 208 assertTrue(objects[o++].equals(lastName(i)), "Bad lastName"); 209 } 210 211 // Sanity check, FixMe need more test cases 212 objects = testFrameOops(couple); 213 assertTrue(objects.length == 5, "Number of frame oops incorrect = " + objects.length); 214 assertTrue(objects[0] == couple, "Bad oop 0"); 215 assertTrue(objects[1] == fn1, "Bad oop 1"); 216 assertTrue(objects[2] == ln1, "Bad oop 2"); 217 assertTrue(objects[3] == TEST_STRING1, "Bad oop 3"); 218 assertTrue(objects[4] == TEST_STRING2, "Bad oop 4"); 219 220 testFrameOopsVBytecodes(); 221 } 222 223 static final String GET_OOP_MAP_NAME = "getOopMap"; 224 static final String GET_OOP_MAP_DESC = "()[Ljava/lang/Object;"; 225 226 public static Object[] getOopMap() { 227 Object[] oopMap = WhiteBox.getWhiteBox().getObjectsViaFrameOopIterator(2); 228 /* Remove this frame (class mirror for this method), and above class mirror */ 229 Object[] trimmedOopMap = new Object[oopMap.length - 2]; 230 System.arraycopy(oopMap, 2, trimmedOopMap, 0, trimmedOopMap.length); 231 return trimmedOopMap; 232 } 233 234 // Expecting Couple couple, Person couple.onePerson, and Person (created here) 235 public static Object[] testFrameOops(Couple couple) { 236 int someId = 89898; 237 Person person = couple.onePerson; 238 assertTrue(person.getId() == 0, "Bad Person"); 239 Person anotherPerson = Person.create(someId, TEST_STRING1, TEST_STRING2); 240 assertTrue(anotherPerson.getId() == someId, "Bad Person"); 241 return getOopMap(); 242 } 243 244 // Debug... 245 static void dumpOopMap(Object[] oopMap) { 246 System.out.println("Oop Map len: " + oopMap.length); 247 for (int i = 0; i < oopMap.length; i++) { 248 System.out.println("[" + i + "] = " + oopMap[i]); 249 } 250 } 251 252 /** 253 * Just some check sanity checks with vdefault, vwithfield, vstore and vload 254 * 255 * Changes to javac slot usage may well break this test 256 */ 257 public static void testFrameOopsVBytecodes() { 258 int nofOopMaps = 4; 259 Object[][] oopMaps = new Object[nofOopMaps][]; 260 String[] inputArgs = new String[] { "aName", "aDescription", "someNotes" }; 261 262 FooValue.testFrameOopsDefault(oopMaps); 263 264 // Test-D0 Slots=R Stack=Q(RRR)RV 265 assertTrue(oopMaps[0].length == 5 && 266 oopMaps[0][1] == null && 267 oopMaps[0][2] == null && 268 oopMaps[0][3] == null, "Test-D0 incorrect"); 269 270 // Test-D1 Slots=R Stack=RV 271 assertTrue(oopMaps[1].length == 2, "Test-D1 incorrect"); 272 273 // Test-D2 Slots=RQ(RRR) Stack=RV 274 assertTrue(oopMaps[2].length == 5 && 275 oopMaps[2][1] == null && 276 oopMaps[2][2] == null && 277 oopMaps[2][3] == null, "Test-D2 incorrect"); 278 279 // Test-D3 Slots=R Stack=Q(RRR)RV 280 assertTrue(oopMaps[3].length == 6 && 281 oopMaps[3][1] == null && 282 oopMaps[3][2] == null && 283 oopMaps[3][3] == null && 284 oopMaps[3][4] == null, "Test-D3 incorrect"); 285 286 // With ref fields... 287 String name = "TestName"; 288 String desc = "TestDesc"; 289 String note = "TestNotes"; 290 FooValue.testFrameOopsRefs(name, desc, note, oopMaps); 291 292 // Test-R0 Slots=RR Stack=Q(RRR)RV 293 assertTrue(oopMaps[0].length == 6 && 294 oopMaps[0][2] == name && 295 oopMaps[0][3] == desc && 296 oopMaps[0][4] == note, "Test-R0 incorrect"); 297 298 /** 299 * TODO: vwithfield from method handle cooked from anonymous class within the value class 300 * even with "MethodHandles.privateLookupIn()" will fail final putfield rules 301 */ 302 } 303 304 /** 305 * Check forcing GC for combination of VT on stack/LVT etc works 306 */ 307 public static void testOverGc() { 308 try { 309 Class<?> vtClass = Person.class; 310 311 doGc(); 312 313 // VT on stack and lvt, null refs, see if GC flies 314 MethodHandle moveValueThroughStackAndLvt = MethodHandleBuilder.loadCode( 315 LOOKUP, 316 "gcOverPerson", 317 MethodType.methodType(vtClass, vtClass), 318 CODE->{ 319 CODE 320 .vload(0) 321 .invokestatic(ValueOops.class, "doGc", "()V", false) // Stack 322 .vstore(0) 323 .invokestatic(ValueOops.class, "doGc", "()V", false) // LVT 324 .vload(0) 325 .vstore(1024) // LVT wide index 326 .vload(1024) 327 .iconst_1() // push a litte further down 328 .invokestatic(ValueOops.class, "doGc", "()V", false) // Stack,LVT 329 .pop() 330 .vreturn(); 331 }); 332 Person person = (Person) moveValueThroughStackAndLvt.invokeExact(createDefaultPerson()); 333 validateDefaultPerson(person); 334 doGc(); 335 336 int index = 4711; 337 person = (Person) moveValueThroughStackAndLvt.invokeExact(createIndexedPerson(index)); 338 validateIndexedPerson(person, index); 339 doGc(); 340 person = createDefaultPerson(); 341 doGc(); 342 } 343 catch (Throwable t) { fail("testOverGc", t); } 344 } 345 346 static void submitNewWork(ForkJoinPool fjPool, int size) { 347 for (int i = 0; i < size; i++) { 348 for (int j = 0; j < 100; j++) { 349 fjPool.execute(ValueOops::testVvt); 350 } 351 } 352 } 353 354 static void sleepNoThrow(long ms) { 355 try { 356 Thread.sleep(ms); 357 } 358 catch (Throwable t) {} 359 } 360 361 /** 362 * Run some workloads with different object/value life times... 363 */ 364 public static void testActiveGc() { 365 try { 366 int nofThreads = 7; 367 int workSize = nofThreads * 10; 368 369 Object longLivedObjects = createLongLived(); 370 Object longLivedPeople = createPeople(); 371 372 Object medLivedObjects = createLongLived(); 373 Object medLivedPeople = createPeople(); 374 375 doGc(); 376 377 ForkJoinPool fjPool = new ForkJoinPool(nofThreads, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); 378 379 // submit work until we see some GC 380 Reference ref = createRef(); 381 submitNewWork(fjPool, workSize); 382 while (ref.get() != null) { 383 if (fjPool.hasQueuedSubmissions()) { 384 sleepNoThrow(1L); 385 } 386 else { 387 workSize *= 2; // Grow the submission size 388 submitNewWork(fjPool, workSize); 389 } 390 } 391 392 // Keep working and actively GC, until MIN_ACTIVE_GC_COUNT 393 int nofActiveGc = 1; 394 ref = createRef(); 395 while (nofActiveGc < MIN_ACTIVE_GC_COUNT) { 396 if (ref.get() == null) { 397 nofActiveGc++; 398 ref = createRef(); 399 if (nofActiveGc % MED_ACTIVE_GC_COUNT == 0) { 400 validateLongLived(medLivedObjects); 401 validatePeople(medLivedPeople); 402 403 medLivedObjects = createLongLived(); 404 medLivedPeople = createPeople(); 405 } 406 } 407 else if (fjPool.hasQueuedSubmissions()) { 408 sleepNoThrow((long) Utils.getRandomInstance().nextInt(1000)); 409 doGc(); 410 } 411 else { 412 submitNewWork(fjPool, workSize); 413 } 414 } 415 fjPool.shutdown(); 416 417 validateLongLived(medLivedObjects); 418 validatePeople(medLivedPeople); 419 medLivedObjects = null; 420 medLivedPeople = null; 421 422 validateLongLived(longLivedObjects); 423 validatePeople(longLivedPeople); 424 425 longLivedObjects = null; 426 longLivedPeople = null; 427 428 doGc(); 429 } 430 catch (Throwable t) { fail("testActiveGc", t); } 431 } 432 433 static final ReferenceQueue<Object> REFQ = new ReferenceQueue<>(); 434 435 public static void doGc() { 436 // Create Reference, wait until it clears... 437 Reference ref = createRef(); 438 while (ref.get() != null) { 439 System.gc(); 440 } 441 } 442 443 static Reference createRef() { 444 return new WeakReference<Object>(new Object(), REFQ); 445 } 446 447 static void validatePerson(Person person, int id, String fn, String ln, boolean equals) { 448 assertTrue(person.id == id); 449 if (equals) { 450 assertTrue(fn.equals(person.getFirstName()), "Invalid field firstName"); 451 assertTrue(ln.equals(person.getLastName()), "Invalid field lastName"); 452 } 453 else { 454 assertTrue(person.getFirstName() == fn, "Invalid field firstName"); 455 assertTrue(person.getLastName() == ln, "Invalid field lastName"); 456 } 457 } 458 459 static Person createIndexedPerson(int i) { 460 return Person.create(i, firstName(i), lastName(i)); 461 } 462 463 static void validateIndexedPerson(Person person, int i) { 464 validatePerson(person, i, firstName(i), lastName(i), true); 465 } 466 467 static Person createDefaultPerson() { 468 return Person.create(0, null, null); 469 } 470 471 static void validateDefaultPerson(Person person) { 472 validatePerson(person, 0, null, null, false); 473 } 474 475 static String firstName(int i) { 476 return "FirstName-" + i; 477 } 478 479 static String lastName(int i) { 480 return "LastName-" + i; 481 } 482 483 static Object createLongLived() throws Throwable { 484 Object[] population = new Object[1]; 485 population[0] = createPeople(); 486 return population; 487 } 488 489 static void validateLongLived(Object pop) throws Throwable { 490 Object[] population = (Object[]) pop; 491 validatePeople(population[0]); 492 } 493 494 static Object createPeople() { 495 int arrayLength = NOF_PEOPLE; 496 Person[] people = new Person[arrayLength]; 497 for (int i = 0; i < arrayLength; i++) { 498 people[i] = createIndexedPerson(i); 499 } 500 return people; 501 } 502 503 static void validatePeople(Object array) { 504 Person[] people = (Person[]) array; 505 int arrayLength = people.length; 506 assertTrue(arrayLength == NOF_PEOPLE); 507 for (int i = 0; i < arrayLength; i++) { 508 validateIndexedPerson(people[i], i); 509 } 510 } 511 512 // Various field layouts...sanity testing, see MVTCombo testing for full-set 513 514 static final __ByValue class ObjectValue { 515 final Object object; 516 517 private ObjectValue(Object object) { 518 this.object = object; 519 } 520 } 521 522 static class ObjectWithObjectValue { 523 ObjectValue value1; 524 Object ref1; 525 } 526 527 static class ObjectWithObjectValues { 528 ObjectValue value1; 529 ObjectValue value2; 530 Object ref1; 531 } 532 533 static class Foo { 534 int id; 535 String name; 536 String description; 537 long timestamp; 538 String notes; 539 } 540 541 static class Bar extends Foo { 542 long extendedId; 543 String moreNotes; 544 int count; 545 String otherStuff; 546 } 547 548 public static final __ByValue class FooValue { 549 public final int id; 550 public final String name; 551 public final String description; 552 public final long timestamp; 553 public final String notes; 554 555 private FooValue() { 556 this.id = 0; 557 this.name = null; 558 this.description = null; 559 this.timestamp = 0L; 560 this.notes = null; 561 } 562 563 __ValueFactory public static FooValue create(int id, String name, String description, long timestamp, String notes) { 564 FooValue f = __MakeDefault FooValue(); 565 f.id = id; 566 f.name = name; 567 f.description = description; 568 f.timestamp = timestamp; 569 f.notes = notes; 570 return f; 571 } 572 573 public static void testFrameOopsDefault(Object[][] oopMaps) { 574 MethodType mt = MethodType.methodType(Void.TYPE, oopMaps.getClass()); 575 int oopMapsSlot = 0; 576 int vtSlot = 1; 577 578 // Slots 1=oopMaps 579 // OopMap Q=RRR (.name .description .someNotes) 580 try { 581 MethodHandleBuilder.loadCode( 582 LOOKUP, "exerciseVBytecodeExprStackWithDefault", mt, 583 CODE->{ 584 CODE 585 .vdefault(FooValue.class) 586 .aload(oopMapsSlot) 587 .iconst_0() // Test-D0 Slots=R Stack=Q(RRR)RV 588 .invokestatic(ValueOops.class, GET_OOP_MAP_NAME, GET_OOP_MAP_DESC, false) 589 .aastore() 590 .pop() 591 .aload(oopMapsSlot) 592 .iconst_1() // Test-D1 Slots=R Stack=RV 593 .invokestatic(ValueOops.class, GET_OOP_MAP_NAME, GET_OOP_MAP_DESC, false) 594 .aastore() 595 .vdefault(FooValue.class) 596 .vstore(vtSlot) 597 .aload(oopMapsSlot) 598 .iconst_2() // Test-D2 Slots=RQ(RRR) Stack=RV 599 .invokestatic(ValueOops.class, GET_OOP_MAP_NAME, GET_OOP_MAP_DESC, false) 600 .aastore() 601 .vload(vtSlot) 602 .aconst_null() 603 .astore(vtSlot) // Storing null over the Q slot won't remove the ref, but should be single null ref 604 .aload(oopMapsSlot) 605 .iconst_3() // Test-D3 Slots=R Stack=Q(RRR)RV 606 .invokestatic(ValueOops.class, GET_OOP_MAP_NAME, GET_OOP_MAP_DESC, false) 607 .aastore() 608 .pop() 609 .return_(); 610 }).invoke(oopMaps); 611 } catch (Throwable t) { fail("exerciseVBytecodeExprStackWithDefault", t); } 612 } 613 614 public static void testFrameOopsRefs(String name, String description, String notes, Object[][] oopMaps) { 615 FooValue f = create(4711, name, description, 9876543231L, notes); 616 FooValue[] fa = new FooValue[] { f }; 617 MethodType mt = MethodType.methodType(Void.TYPE, fa.getClass(), oopMaps.getClass()); 618 int fooArraySlot = 0; 619 int oopMapsSlot = 1; 620 try { 621 MethodHandleBuilder.loadCode(LOOKUP, "exerciseVBytecodeExprStackWithRefs", mt, 622 CODE->{ 623 CODE 624 .aload(fooArraySlot) 625 .iconst_0() 626 .vaload() 627 .aload(oopMapsSlot) 628 .iconst_0() // Test-R0 Slots=RR Stack=Q(RRR)RV 629 .invokestatic(ValueOops.class, GET_OOP_MAP_NAME, GET_OOP_MAP_DESC, false) 630 .aastore() 631 .pop() 632 .return_(); 633 }).invoke(fa, oopMaps); 634 } catch (Throwable t) { fail("exerciseVBytecodeExprStackWithRefs", t); } 635 } 636 } 637 638 static class BarWithValue { 639 FooValue foo; 640 long extendedId; 641 String moreNotes; 642 int count; 643 String otherStuff; 644 } 645 646 static final __ByValue class BarValue { 647 final FooValue foo; 648 final long extendedId; 649 final String moreNotes; 650 final int count; 651 final String otherStuff; 652 653 private BarValue(FooValue foo, long extendedId, String moreNotes, int count, String otherStuff) { 654 this.foo = foo; 655 this.extendedId = extendedId; 656 this.moreNotes = moreNotes; 657 this.count = count; 658 this.otherStuff = otherStuff; 659 } 660 } 661 662 } 663