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