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