1 /* 2 * Copyright (c) 2024, 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 import combo.ComboInstance; 25 import combo.ComboParameter; 26 import combo.ComboTask; 27 import combo.ComboTestHelper; 28 29 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.EOFException; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.InvalidClassException; 35 import java.io.NotSerializableException; 36 import java.io.ObjectInputStream; 37 import java.io.ObjectOutputStream; 38 import java.io.ObjectStreamClass; 39 import java.io.OptionalDataException; 40 import java.io.Reader; 41 import java.io.UncheckedIOException; 42 import java.net.URL; 43 import java.net.URLClassLoader; 44 import java.nio.file.Files; 45 import java.nio.file.Path; 46 import java.util.Arrays; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Locale; 51 import java.util.Map; 52 import java.util.Optional; 53 import java.util.Set; 54 import java.util.concurrent.ConcurrentHashMap; 55 import java.util.function.BiFunction; 56 import java.util.function.Predicate; 57 import java.util.stream.Collectors; 58 import java.util.stream.Stream; 59 60 import javax.tools.Diagnostic; 61 import javax.tools.JavaFileObject; 62 63 import static jdk.test.lib.Asserts.assertEquals; 64 import static jdk.test.lib.Asserts.assertTrue; 65 import static jdk.test.lib.Asserts.assertFalse; 66 67 import jdk.test.lib.hexdump.HexPrinter; 68 import jdk.test.lib.hexdump.ObjectStreamPrinter; 69 70 /* 71 * @test 72 * @summary Deserialization Combo tests 73 * @library /test/langtools/tools/javac/lib /test/lib . 74 * @modules jdk.compiler/com.sun.tools.javac.api 75 * jdk.compiler/com.sun.tools.javac.code 76 * jdk.compiler/com.sun.tools.javac.comp 77 * jdk.compiler/com.sun.tools.javac.file 78 * jdk.compiler/com.sun.tools.javac.main 79 * jdk.compiler/com.sun.tools.javac.tree 80 * jdk.compiler/com.sun.tools.javac.util 81 * @build combo.ComboTestHelper SerializedObjectCombo 82 * @run main/othervm --enable-preview SerializedObjectCombo 83 */ 84 85 86 public final class SerializedObjectCombo extends ComboInstance<SerializedObjectCombo> { 87 private static final Map<Path, URLClassLoader> LOADER_FOR_PATH = new ConcurrentHashMap<>(); 88 private static final ParamSet KIND_SET = new ParamSet("KIND", 89 SerializationKind.values()); 90 private static final ParamSet FIELD_SET = new ParamSet("FIELD", 91 2, ArgumentValue.BASIC_VALUES); 92 private static final ParamSet CLASSACCESS_SET = new ParamSet("CLASSACCESS", 93 new ClassAccessKind[]{ClassAccessKind.PUBLIC}); 94 private static final ParamSet SPECIAL_WRITE_METHODS_SET = new ParamSet("SPECIAL_WRITE_METHODS", 95 WriteObjectFragments.values()); 96 private static final ParamSet SPECIAL_READ_METHODS_SET = new ParamSet("SPECIAL_READ_METHODS", 97 ReadObjectFragments.values()); 98 private static final ParamSet EXTERNALIZABLE_METHODS_SET = new ParamSet("EXTERNALIZABLE_METHODS", 99 ExternalizableMethodFragments.values()); 100 private static final ParamSet OBJECT_CONSTRUCTOR_SET = new ParamSet("OBJECT_CONSTRUCTOR", 101 ObjectConstructorFragment.ANNOTATED_OBJECT_CONSTRUCTOR_FRAGMENT, ObjectConstructorFragment.NONE); 102 private static final ParamSet VALUE_SET = new ParamSet("VALUE", 103 ValueKind.values()); 104 private static final ParamSet TESTNAME_EXTENDS_SET = new ParamSet("TESTNAME_EXTENDS", 105 TestNameExtendsFragments.NONE, TestNameExtendsFragments.TESTNAME_EXTENDS_FRAGMENT); 106 private static final ParamSet TOP_ABSTRACT_SET = new ParamSet("TOP_FRAGMENTS", 107 TopFragments.values()); 108 /** 109 * The base template to generate all test classes. 110 * Each substitutable fragment is defined by an Enum of the alternatives. 111 * Giving each a name and an array of ComboParameters with the expansion value. 112 */ 113 private static final String TEST_SOURCE_TEMPLATE = """ 114 import java.io.*; 115 import java.util.*; 116 import jdk.internal.value.DeserializeConstructor; 117 import jdk.internal.MigratedValueClass; 118 119 #{TOP_FRAGMENTS} 120 121 @MigratedValueClass 122 #{CLASSACCESS} #{VALUE} class #{TESTNAME} #{TESTNAME_EXTENDS} #{KIND.IMPLEMENTS} { 123 #{FIELD[0]} f1; 124 #{FIELD[1]} f2; 125 #{FIELD_ADDITIONS} 126 #{CLASSACCESS} #{TESTNAME}() { 127 f1 = #{FIELD[0].RANDOM}; 128 f2 = #{FIELD[1].RANDOM}; 129 #{FIELD_CONSTRUCTOR_ADDITIONS} 130 } 131 #{OBJECT_CONSTRUCTOR} 132 @Override public boolean equals(Object obj) { 133 if (obj instanceof #{TESTNAME} other) { 134 if (#{FIELD[0]}.class.isPrimitive()) { 135 if (f1 != other.f1) return false; 136 } else { 137 if (!Objects.equals(f1, other.f1)) return false; 138 } 139 if (#{FIELD[1]}.class.isPrimitive()) { 140 if (f2 != other.f2) return false; 141 } else { 142 if (!Objects.equals(f2, other.f2)) return false; 143 } 144 return true; 145 } 146 return false; 147 } 148 @Override public String toString() { 149 return "f1: " + String.valueOf(f1) + 150 ", f2: " + String.valueOf(f2) 151 #{FIELD_TOSTRING_ADDITIONS}; 152 } 153 #{KIND.SPECIAL_METHODS} 154 private static final long serialVersionUID = 1L; 155 } 156 """; 157 158 // The unique number to qualify interface names, unique across multiple runs 159 private static int uniqueId = 0; 160 // Compilation errors prevent execution; set/cleared by checkCompile 161 private ComboTask.Result<?> compilationResult = null; 162 // The current set of parameters for the file being compiled and tested 163 private final Set<ComboParameter> currParams = new HashSet<>(); 164 165 private static List<String> focusKeys = null; 166 167 private enum CommandOption { 168 SHOW_SOURCE("--show-source", "show source files"), 169 VERBOSE("--verbose", "show extra information"), 170 SHOW_SERIAL_STREAM("--show-serial", "show and format the serialized stream"), 171 EVERYTHING("--everything", "run all tests"), 172 TRACE("--trace", "set TRACE system property of ObjectInputStream (temp)"), 173 MAX_COMBOS("--max-combo", "maximum number of values for each parameter", CommandOption::parseInt), 174 NO_PRE_FILTER("--no-pre-filter", "disable pre-filter checks"), 175 SELFTEST("--self-test", "run some self tests and exit"), 176 ; 177 private final String option; 178 private final String usage; 179 private final BiFunction<CommandOption, String, Boolean> parseArg; 180 private Optional<Object> value; 181 CommandOption(String option, String usage, BiFunction<CommandOption, String, Boolean> parseArg) { 182 this.option = option; 183 this.usage = usage; 184 this.parseArg = parseArg; 185 this.value = Optional.empty(); 186 } 187 CommandOption(String option, String usage) { 188 this(option, usage, null); 189 } 190 191 /** 192 * Evaluate and parse an array of command line args 193 * @param args array of strings 194 * @return true if parsing succeeded 195 */ 196 static boolean parseOptions(String[] args) { 197 boolean unknownArg = false; 198 for (int i = 0; i < args.length; i++) { 199 String arg = args[i]; 200 Optional<CommandOption> knownOpt = Arrays.stream(CommandOption.values()) 201 .filter(o -> o.option.equals(arg)) 202 .findFirst(); 203 if (knownOpt.isEmpty()) { // Not a recognized option 204 if (arg.startsWith("-")) { 205 System.err.println("Unrecognized option: " + arg); 206 unknownArg = true; 207 } else { 208 // Take the remaining non-option args as selectors of keys to be run 209 String[] keys = Arrays.copyOfRange(args, i, args.length); 210 focusKeys = List.of(keys); 211 } 212 } else { 213 CommandOption option = knownOpt.get(); 214 if (option.parseArg == null) { 215 option.setValue(true); 216 } else { 217 i++; 218 if (i >= args.length || args[i].startsWith("--")) { 219 System.err.println("Missing argument for " + option.option); 220 continue; 221 } 222 option.parseArg.apply(option, args[i]); 223 } 224 } 225 } 226 return !unknownArg; 227 } 228 static void showUsage() { 229 System.out.println(""" 230 Usage: 231 """); 232 Arrays.stream(CommandOption.values()).forEach(o -> System.out.printf(" %-15s: %s\n", o.option, o.usage)); 233 } 234 boolean present() { 235 return value != null && value.isPresent(); 236 } 237 void setValue(Object o) { 238 value = Optional.ofNullable(o); 239 } 240 private static boolean parseInt(CommandOption option, String arg) { 241 try { 242 int count = Integer.parseInt(arg); 243 option.setValue(count); 244 } catch (NumberFormatException nfe) { 245 System.out.println("--max-combo argument not a number: " + arg); 246 } 247 return true; 248 } 249 // Get the int value from the option, defaulting if not valid or present 250 private int getInt(int otherMax) { 251 Object obj = value == null ? otherMax : value.orElseGet(() -> otherMax); 252 return (obj instanceof Integer i) ? i : otherMax; 253 } 254 } 255 256 private static URLClassLoader getLoaderFor(Path path) { 257 return LOADER_FOR_PATH.computeIfAbsent(path, 258 p -> { 259 try { 260 // new URLClassLoader for path 261 Files.createDirectories(p); 262 URL[] urls = {p.toUri().toURL()}; 263 return new URLClassLoader(p.toString(), urls, null); 264 } catch (IOException ioe) { 265 throw new UncheckedIOException(ioe); 266 } 267 }); 268 } 269 270 // Map an array of strings to an array of ComboParameter.Constants. 271 @SuppressWarnings("unchecked") 272 private static ComboParameter.Constant<String>[] paramsForStrings(String... strings) { 273 return Arrays.stream(strings) 274 .map(ComboParameter.Constant::new).toArray(ComboParameter.Constant[]::new); 275 } 276 277 /** 278 * Main to generate combinations and run the tests. 279 * 280 * @param args may contain "--verbose" to show source of every file 281 * @throws Exception In case of failure 282 */ 283 public static void main(String... args) throws Exception { 284 if (!CommandOption.parseOptions(args)) { 285 CommandOption.showUsage(); 286 System.exit(1); 287 } 288 289 Arrays.stream(CommandOption.values()) 290 .filter(o -> o.present()) 291 .forEach( o1 -> System.out.printf(" %15s: %s\n", o1.option, o1.value 292 )); 293 294 if (CommandOption.SELFTEST.present()) { 295 selftest(); 296 return; 297 } 298 299 // Sets of all possible ComboParameters (substitutions) 300 Set<ParamSet> allParams = Set.of( 301 VALUE_SET, 302 KIND_SET, 303 TOP_ABSTRACT_SET, 304 OBJECT_CONSTRUCTOR_SET, 305 TESTNAME_EXTENDS_SET, 306 CLASSACCESS_SET, 307 SPECIAL_READ_METHODS_SET, 308 SPECIAL_WRITE_METHODS_SET, 309 EXTERNALIZABLE_METHODS_SET, 310 FIELD_SET 311 ); 312 313 // Test variations of all code shapes 314 var helper = new ComboTestHelper<SerializedObjectCombo>(); 315 int maxCombos = CommandOption.MAX_COMBOS.getInt(2); 316 317 Set<ParamSet> subSet = CommandOption.EVERYTHING.present() ? allParams 318 : computeSubset(allParams, focusKeys, maxCombos); 319 withDimensions(helper, subSet); 320 if (CommandOption.VERBOSE.present()) { 321 System.out.println("Keys; maximum combinations: " + maxCombos); 322 subSet.stream() 323 .sorted((p, q) -> String.CASE_INSENSITIVE_ORDER.compare(p.key(), q.key())) 324 .forEach(p -> System.out.println(" " + p.key + ": " + Arrays.toString(p.params))); 325 } 326 helper.withFilter(SerializedObjectCombo::filter) 327 .withFailMode(ComboTestHelper.FailMode.FAIL_FAST) 328 .run(SerializedObjectCombo::new); 329 } 330 331 private static void withDimensions(ComboTestHelper<SerializedObjectCombo> helper, Set<ParamSet> subSet) { 332 subSet.forEach(p -> { 333 if (p.count() == 1) 334 helper.withDimension(p.key(), SerializedObjectCombo::saveParameter, p.params()); 335 else 336 helper.withArrayDimension(p.key(), SerializedObjectCombo::saveParameter, p.count(), p.params()); 337 }); 338 } 339 340 // Return a subset of ParamSets with the non-focused ParamSet's truncated to a max number of values 341 private static Set<ParamSet> computeSubset(Set<ParamSet> allParams, List<String> focusKeys, int maxKeys) { 342 if (focusKeys == null || focusKeys.isEmpty()) 343 return allParams; 344 Set<ParamSet> r = allParams.stream().map(p -> 345 (focusKeys.contains(p.key())) ? p 346 : new ParamSet(p.key, p.count(), Arrays.copyOfRange(p.params(), 0, Math.min(p.params().length, maxKeys)))) 347 .collect(Collectors.toUnmodifiableSet()); 348 return r; 349 } 350 351 /** 352 * Print the source files to System out 353 * 354 * @param task the compilation task 355 */ 356 static void showSources(ComboTask task) { 357 task.getSources() 358 .forEach(fo -> { 359 System.out.println("Source: " + fo.getName()); 360 System.out.println(getSource(fo)); 361 }); 362 } 363 364 /** 365 * Return the contents of the source file 366 * 367 * @param fo a file object 368 * @return the contents of the source file 369 */ 370 static String getSource(JavaFileObject fo) { 371 try (Reader reader = fo.openReader(true)) { 372 char[] buf = new char[100000]; 373 var len = reader.read(buf); 374 return new String(buf, 0, len); 375 } catch (IOException ioe) { 376 return "IOException: " + fo.getName() + ", ex: " + ioe.getMessage(); 377 } 378 } 379 380 /** 381 * Dump the serial stream. 382 * 383 * @param bytes the bytes of the stream 384 */ 385 private static void showSerialStream(byte[] bytes) { 386 HexPrinter.simple().dest(System.out).formatter(ObjectStreamPrinter.formatter()).format(bytes); 387 } 388 389 /** 390 * Serialize an object into byte array. 391 */ 392 private static byte[] serialize(Object obj) throws IOException { 393 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 394 try (ObjectOutputStream out = new ObjectOutputStream(bs)) { 395 out.writeObject(obj); 396 } 397 return bs.toByteArray(); 398 } 399 400 /** 401 * Deserialize an object from byte array using the requested classloader. 402 */ 403 private static Object deserialize(byte[] ba, ClassLoader loader) throws IOException, ClassNotFoundException { 404 try (ObjectInputStream in = new LoaderObjectInputStream(new ByteArrayInputStream(ba), loader)) { 405 return in.readObject(); 406 } 407 } 408 409 410 @Override 411 public int id() { 412 return ++uniqueId; 413 } 414 415 private void fail(String msg, Throwable thrown) { 416 super.fail(msg); 417 thrown.printStackTrace(System.out); 418 } 419 420 /** 421 * Save a parameter. 422 * 423 * @param param a ComboParameter 424 */ 425 private void saveParameter(ComboParameter param) { 426 saveParameter(param, 0); 427 } 428 429 /** 430 * Save an indexed parameter. 431 * 432 * @param param a ComboParameter 433 * @param index unused 434 */ 435 private void saveParameter(ComboParameter param, int index) { 436 currParams.add(param); 437 } 438 439 /** 440 * Filter out needless tests (mostly with more variations of arguments than needed). 441 * Usually, these are compile time failures, or code shapes that cannot succeed. 442 * 443 * @return true to run the test, false if not 444 */ 445 boolean filter() { 446 if (!CommandOption.NO_PRE_FILTER.present()) { 447 for (CodeShape shape : CodeShape.values()) { 448 if (shape.test(currParams)) { 449 if (CommandOption.VERBOSE.present()) { 450 System.out.println("IGNORING: " + shape); 451 } 452 return false; 453 } 454 } 455 } 456 if (CommandOption.VERBOSE.present()) { 457 System.out.println("TESTING: "); 458 showParams(); 459 } 460 return true; 461 } 462 463 /** 464 * Generate the source files from the parameters and test a single combination. 465 * Two versions are compiled into different directories and separate class loaders. 466 * They differ only with the addition of a field to the generated class. 467 * Then each class is serialized and deserialized by the other class, 468 * testing simple evolution in the process. 469 * 470 * @throws IOException catch all IOException 471 */ 472 @Override 473 public void doWork() throws IOException { 474 String cp = System.getProperty("test.classes"); 475 String className = "Class_" + this.id(); 476 477 // new URLClassLoader for path 478 final Path firstPath = Path.of(cp, "1st"); 479 URLClassLoader firstLoader = getLoaderFor(firstPath); 480 final Path secondPath = Path.of(cp, "2nd"); 481 URLClassLoader secondLoader = getLoaderFor(secondPath); 482 483 // Create a map of additional constants that are resolved without the combo overhead. 484 final Map<String, ComboParameter.Constant<String>> params = new HashMap<>(); 485 params.put("TESTNAME", new ComboParameter.Constant<>(className)); 486 params.put("SPECIAL_METHODS_SERIALIZABLE", new ComboParameter.Constant<>("#{SPECIAL_READ_METHODS} #{SPECIAL_WRITE_METHODS}")); 487 params.put("SPECIAL_METHODS_EXTERNALIZABLE", new ComboParameter.Constant<>("#{EXTERNALIZABLE_METHODS}")); 488 params.put("FIELD_ADDITIONS", new ComboParameter.Constant<>("")); 489 params.put("FIELD_CONSTRUCTOR_ADDITIONS", new ComboParameter.Constant<>("")); 490 params.put("FIELD_TOSTRING_ADDITIONS", new ComboParameter.Constant<>("")); 491 492 final ComboTask firstTask = generateAndCompile(firstPath, className, params); 493 494 if (firstTask == null) { 495 return; // Skip execution, errors already reported 496 } 497 498 if (CommandOption.EVERYTHING.present()) { 499 params.put("FIELD_ADDITIONS", new ComboParameter.Constant<>("int fExtra;")); 500 params.put("FIELD_CONSTRUCTOR_ADDITIONS", new ComboParameter.Constant<>("this.fExtra = 99;")); 501 params.put("FIELD_TOSTRING_ADDITIONS", new ComboParameter.Constant<>("+ \", fExtra: String.valueOf(fExtra)\"")); 502 final ComboTask secondTask = generateAndCompile(secondPath, className, params); 503 if (secondTask == null) { 504 return; // Skip execution, errors already reported 505 } 506 507 doTestWork(className, firstTask, firstLoader, secondLoader); 508 doTestWork(className, secondTask, secondLoader, firstLoader); 509 } else { 510 doTestWork(className, firstTask, firstLoader, firstLoader); 511 } 512 } 513 514 /** 515 * Test that two versions of the class can be serialized using one version and deserialized 516 * by the other version. 517 * The two classes have the same name and have been compiled into different classloaders. 518 * The original and result objects are compared using .equals if there is only 1 classloader. 519 * If the classloaders are different the `toString()` output for each object is compared loosely. 520 * (One must be the prefix of the other) 521 * 522 * @param className the class name 523 * @param task the task context (for source and parameters to report failures) 524 * @param firstLoader the first classloader 525 * @param secondLoader the second classloader 526 */ 527 private void doTestWork(String className, ComboTask task, ClassLoader firstLoader, ClassLoader secondLoader) { 528 byte[] bytes = null; 529 try { 530 Class<?> tc = Class.forName(className, true, firstLoader); 531 Object testObj = tc.getDeclaredConstructor().newInstance(); 532 bytes = serialize(testObj); 533 if (CommandOption.VERBOSE.present()) { 534 System.out.println("Testing: " + task.getSources()); 535 if (CommandOption.SHOW_SOURCE.present()) { 536 showParams(); 537 showSources(task); 538 } 539 if (CommandOption.SHOW_SERIAL_STREAM.present()) { 540 showSerialStream(bytes); 541 } 542 } 543 544 if (CodeShape.BAD_SO_CONSTRUCTOR.test(currParams)) { 545 // should have thrown ICE due to mismatch between value class and missing constructor 546 System.out.println(CodeShape.BAD_SO_CONSTRUCTOR.explain(currParams)); 547 fail(CodeShape.BAD_SO_CONSTRUCTOR.explain(currParams)); 548 } 549 550 Object actual = deserialize(bytes, secondLoader); 551 if (testObj.getClass().getClassLoader().equals(actual.getClass().getClassLoader())) { 552 assertEquals(testObj, actual, "Round-trip comparison fail using .equals"); 553 } else { 554 // The instances are from different classloaders and can't be compared directly 555 final String s1 = testObj.toString(); 556 final String s2 = actual.toString(); 557 assertTrue(s1.startsWith(s2) || s2.startsWith(s1), 558 "Round-trip comparison fail using toString(): s1: " + s1 + ", s2: " + s2); 559 } 560 } catch (InvalidClassException ice) { 561 for (CodeShape shape : CodeShape.values()){ 562 if (ice.equals(shape.exception)) { 563 if (shape.test(currParams)) { 564 if (CommandOption.VERBOSE.present()) { 565 System.out.println("OK: " + shape.explain(currParams)); 566 } else { 567 // unexpected ICE 568 ice.printStackTrace(System.out); 569 showParams(); 570 showSources(task); 571 if (bytes != null) 572 showSerialStream(bytes); 573 fail(ice.getMessage()); 574 } 575 } 576 } 577 } 578 } catch (EOFException | OptionalDataException eof) { 579 // Ignore if conditions of the source invite EOF 580 if (0 == CodeShape.shapesThrowing(EOFException.class).peek(s -> { 581 // Ignore: Serialized Object to reads custom data but none written 582 if (CommandOption.VERBOSE.present()) { 583 System.out.println("OK: " + s.explain(currParams)); 584 } 585 }).count()) { 586 eof.printStackTrace(System.out); 587 showParams(); 588 showSources(task); 589 showSerialStream(bytes); 590 fail(eof.getMessage(), eof); 591 } 592 } catch (ClassFormatError cfe) { 593 System.out.println(cfe.toString()); 594 } catch (NotSerializableException nse) { 595 if (CodeShape.BAD_EXT_VALUE.test(currParams)) { 596 // Expected Value class that is Externalizable w/o writeReplace 597 } else { 598 // unexpected NSE 599 nse.printStackTrace(System.out); 600 showParams(); 601 showSources(task); 602 fail(nse.getMessage(), nse); 603 } 604 } catch (Throwable ex) { 605 ex.printStackTrace(System.out); 606 showParams(); 607 showSources(task); 608 fail(ex.getMessage()); 609 } 610 } 611 612 // Side effect of error is compilationResult.hasErrors() > 0 613 private ComboTask generateAndCompile(Path path, String className, Map<String, ComboParameter.Constant<String>> params) { 614 ComboTask task = newCompilationTask() 615 .withSourceFromTemplate(className, 616 TEST_SOURCE_TEMPLATE, 617 r -> params.computeIfAbsent(r, s -> new ComboParameter.Constant<>("UNKNOWN_" + s))) 618 .withOption("-d") 619 .withOption(path.toString()) 620 .withOption("--enable-preview") 621 .withOption("--add-modules") 622 .withOption("java.base") 623 .withOption("--add-exports") 624 .withOption("java.base/jdk.internal=ALL-UNNAMED") 625 .withOption("--add-exports") 626 .withOption("java.base/jdk.internal.value=ALL-UNNAMED") 627 .withOption("--source") 628 .withOption(Integer.toString(Runtime.version().feature())); 629 ; 630 task.generate(this::checkCompile); 631 if (compilationResult.hasErrors()) { 632 boolean match = false; 633 for (CodeShape shape : CodeShape.values()){ 634 if (CompileException.class.equals(shape.exception)) { 635 if (shape.test(currParams)) { 636 // shape matches known error 637 if (!uniqueParams.contains(shape)) { 638 System.out.println("// Unique: " + shape); 639 uniqueParams.add(shape); 640 } 641 match = true; 642 } 643 } 644 } 645 if (match) 646 return null; 647 // Unexpected compilation error 648 showDiags(compilationResult); 649 showSources(task); 650 showParams(); 651 fail("Compilation failure"); 652 } 653 return task; 654 } 655 656 private static Set<CodeShape> uniqueParams = new HashSet<>(); 657 658 private String paramToString(ComboParameter param) { 659 String name = param.getClass().getName(); 660 return name.substring(name.indexOf('$') + 1) + "::" + 661 param + ": " + truncate(param.expand(null), 60); 662 } 663 664 private void showParams() { 665 currParams.stream() 666 .sorted((p, q) -> String.CASE_INSENSITIVE_ORDER.compare(paramToString(p), paramToString(q))) 667 .forEach(p -> System.out.println(" " + paramToString(p))); 668 } 669 670 private void showParams(ComboParameter... params) { 671 for (ComboParameter param : params) { 672 System.out.println(">>> " + paramToString(param) + ", present: " 673 + currParams.contains(param)); 674 } 675 } 676 677 private static String truncate(String s, int maxLen) { 678 int nl = s.indexOf("\n"); 679 if (nl >= 0) 680 maxLen = nl; 681 if (maxLen < s.length()) { 682 return s.substring(0, maxLen).concat("..."); 683 } else { 684 return s; 685 } 686 } 687 688 /** 689 * Report any compilation errors. 690 * 691 * @param res the result 692 */ 693 void checkCompile(ComboTask.Result<?> res) { 694 compilationResult = res; 695 } 696 697 void showDiags(ComboTask.Result<?> res) { 698 res.diagnosticsForKind(Diagnostic.Kind.ERROR).forEach(SerializedObjectCombo::showDiag); 699 res.diagnosticsForKind(Diagnostic.Kind.WARNING).forEach(SerializedObjectCombo::showDiag); 700 } 701 702 static void showDiag(Diagnostic<? extends JavaFileObject> diag) { 703 System.out.println(diag.getKind() + ": " + diag.getMessage(Locale.ROOT)); 704 System.out.println("File: " + diag.getSource() + 705 " line: " + diag.getLineNumber() + ", col: " + diag.getColumnNumber()); 706 } 707 708 private static class CodeShapePredicateOp<T> implements Predicate<T> { 709 private final Predicate<T> first; 710 private final Predicate<T> other; 711 private final String op; 712 713 CodeShapePredicateOp(Predicate<T> first, Predicate<T> other, String op) { 714 if ("OR" != op && "AND" != op && "NOT" != op) 715 throw new IllegalArgumentException("unknown op: " + op); 716 this.first = first; 717 this.other = other; 718 this.op = op; 719 } 720 721 @Override 722 public boolean test(T comboParameters) { 723 return switch (op) { 724 case "NOT" -> !first.test(comboParameters); 725 case "OR" -> first.test(comboParameters) || other.test(comboParameters); 726 case "AND" -> first.test(comboParameters) && other.test(comboParameters); 727 default -> throw new IllegalArgumentException("unknown op: " + op); 728 }; 729 } 730 @Override 731 public Predicate<T> and(Predicate<? super T> other) { 732 return new CodeShapePredicateOp(this, other,"AND"); 733 } 734 735 736 @Override 737 public Predicate<T> negate() { 738 return new CodeShapePredicateOp(this, null,"NOT"); 739 } 740 741 @Override 742 public Predicate<T> or(Predicate<? super T> other) { 743 return new CodeShapePredicateOp(this, other,"OR"); 744 } 745 public String toString() { 746 return switch (op) { 747 case "NOT" -> op + " " + first; 748 case "OR" -> "(" + first + " " + op + " " + other + ")"; 749 case "AND" -> "(" + first + " " + op + " " + other + ")"; 750 default -> throw new IllegalArgumentException("unknown op: " + op); 751 }; 752 } 753 } 754 755 interface CodeShapePredicate extends Predicate<Set<ComboParameter>> { 756 @Override 757 default boolean test(Set<ComboParameter> comboParameters) { 758 return comboParameters.contains(this); 759 } 760 761 @Override 762 default Predicate<Set<ComboParameter>> and(Predicate<? super Set<ComboParameter>> other) { 763 return new CodeShapePredicateOp(this, other,"AND"); 764 } 765 766 767 @Override 768 default Predicate<Set<ComboParameter>> negate() { 769 return new CodeShapePredicateOp(this, null,"NOT"); 770 } 771 772 @Override 773 default Predicate<Set<ComboParameter>> or(Predicate<? super Set<ComboParameter>> other) { 774 return new CodeShapePredicateOp(this, other,"OR"); 775 } 776 } 777 778 /** 779 * A set of code shapes that are interesting, usually indicating an error 780 * compile time, or runtime based on the shape of the code and the dependencies between 781 * the code fragments. 782 * The descriptive text may be easier to understand than the boolean expression of the fragments. 783 * They can also be to filter out test cases that would not succeed. 784 * Or can be used after a successful deserialization to check 785 * if an exception should have been thrown. 786 */ 787 private enum CodeShape implements Predicate<Set<ComboParameter>> { 788 BAD_SO_CONSTRUCTOR("Value class does not have a constructor annotated with DeserializeConstructor", 789 InvalidClassException.class, 790 ValueKind.VALUE, 791 ObjectConstructorFragment.ANNOTATED_OBJECT_CONSTRUCTOR_FRAGMENT.negate() 792 ), 793 BAD_EXT_VALUE("Externalizable can not be a value class", 794 CompileException.class, 795 SerializationKind.EXTERNALIZABLE, 796 ValueKind.VALUE), 797 BAD_EXT_METHODS("Externalizable methods but not Externalizable", 798 CompileException.class, 799 ExternalizableMethodFragments.EXTERNALIZABLE_METHODS, 800 SerializationKind.EXTERNALIZABLE.negate()), 801 BAD_EXT_NO_METHODS("Externalizable but no implementation of readExternal or writeExternal", 802 CompileException.class, 803 SerializationKind.EXTERNALIZABLE, 804 ExternalizableMethodFragments.EXTERNALIZABLE_METHODS.negate()), 805 BAD_VALUE_NON_ABSTRACT_SUPER("Can't inherit from non-abstract super or abstract super with fields", 806 CompileException.class, 807 ValueKind.VALUE, 808 TestNameExtendsFragments.TESTNAME_EXTENDS_FRAGMENT, 809 TopFragments.ABSTRACT_NO_FIELDS.negate()), 810 BAD_MISSING_SUPER("Extends TOP_ without TOP_ superclass", 811 CompileException.class, 812 TestNameExtendsFragments.TESTNAME_EXTENDS_FRAGMENT, 813 TopFragments.NONE), 814 BAD_READ_CUSTOM_METHODS("Custom read fragment but no custom write fragment", 815 EOFException.class, 816 ReadObjectFragments.READ_OBJECT_FIELDS_CUSTOM_FRAGMENT 817 .or(ReadObjectFragments.READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT), 818 WriteObjectFragments.WRITE_OBJECT_FIELDS_CUSTOM_FRAGMENT 819 .or(WriteObjectFragments.WRITE_OBJECT_DEFAULT_CUSTOM_FRAGMENT).negate() 820 ), 821 BAD_RW_CUSTOM_METHODS("Custom write fragment but no custom read fragment", 822 null, 823 WriteObjectFragments.WRITE_OBJECT_FIELDS_CUSTOM_FRAGMENT 824 .or(WriteObjectFragments.WRITE_OBJECT_DEFAULT_CUSTOM_FRAGMENT), 825 ReadObjectFragments.READ_OBJECT_FIELDS_CUSTOM_FRAGMENT 826 .or(ReadObjectFragments.READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT).negate()), 827 BAD_VALUE_READOBJECT_METHODS("readObjectXXX(OIS) methods incompatible with Value class", 828 CompileException.class, 829 ReadObjectFragments.READ_OBJECT_FIELDS_FRAGMENT 830 .or(ReadObjectFragments.READ_OBJECT_DEFAULT_FRAGMENT) 831 .or(ReadObjectFragments.READ_OBJECT_FIELDS_CUSTOM_FRAGMENT) 832 .or(ReadObjectFragments.READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT), 833 ValueKind.VALUE), 834 ; 835 836 private final String description; 837 private final Class<? extends Exception> exception; 838 private final List<Predicate<Set<ComboParameter>>> predicates; 839 CodeShape(String desc, Class<? extends Exception> exception, Predicate<Set<ComboParameter>>... predicates) { 840 this.description = desc; 841 this.exception = exception; 842 this.predicates = List.of(predicates); 843 } 844 845 // Return a stream of CodeShapes throwing the exception 846 static Stream<CodeShape> shapesThrowing(Class<?> exception) { 847 return Arrays.stream(values()).filter(s -> exception.equals(s.exception)); 848 849 } 850 851 /** 852 * {@return true if all of the predicates are true in the set of ComboParameters} 853 * @param comboParameters a set of ComboParameters 854 */ 855 @Override 856 public boolean test(Set<ComboParameter> comboParameters) { 857 for (Predicate<Set<ComboParameter>> p : predicates) { 858 if (!p.test(comboParameters)) 859 return false; 860 } 861 return true; 862 } 863 864 /** 865 * {@return a string describing the predicate in relation to a set of parameters} 866 * @param comboParameters a set of active ComboParameters. 867 */ 868 public String explain(Set<ComboParameter> comboParameters) { 869 StringBuffer sbTrue = new StringBuffer(); 870 StringBuffer sbFalse = new StringBuffer(); 871 for (Predicate<Set<ComboParameter>> p : predicates) { 872 ((p.test(comboParameters)) ? sbTrue : sbFalse) 873 .append(p).append(", "); 874 } 875 return description + "\n" +"Missing: " + sbFalse + "\nTrue: " + sbTrue; 876 } 877 public String toString() { 878 return super.toString() + "::" + description + ", params: " + predicates; 879 } 880 } 881 882 /** 883 * TopAbstract Fragments 884 */ 885 enum TopFragments implements ComboParameter, CodeShapePredicate { 886 NONE(""), 887 ABSTRACT_NO_FIELDS(""" 888 @MigratedValueClass 889 abstract #{VALUE} class TOP_#{TESTNAME} implements Serializable { 890 #{CLASSACCESS} TOP_#{TESTNAME}() {} 891 } 892 """), 893 ABSTRACT_ONE_FIELD(""" 894 @MigratedValueClass 895 abstract #{VALUE} class TOP_#{TESTNAME} implements Serializable { 896 private int t1; 897 #{CLASSACCESS} TOP_#{TESTNAME}() { 898 t1 = 1; 899 } 900 } 901 """), 902 NO_FIELDS(""" 903 @MigratedValueClass 904 #{VALUE} class TOP_#{TESTNAME} implements Serializable { 905 #{CLASSACCESS} TOP_#{TESTNAME}() {} 906 } 907 """), 908 ONE_FIELD(""" 909 @MigratedValueClass 910 #{VALUE} class TOP_#{TESTNAME} implements Serializable { 911 private int t1; 912 #{CLASSACCESS} TOP_#{TESTNAME}() { 913 t1 = 1; 914 } 915 } 916 """), 917 ; 918 919 private final String template; 920 921 TopFragments(String template) { 922 this.template = template; 923 } 924 925 @Override 926 public String expand(String optParameter) { 927 return template; 928 } 929 } 930 931 /** 932 * TopAbstract Fragments 933 */ 934 enum TestNameExtendsFragments implements ComboParameter, CodeShapePredicate { 935 NONE(""), 936 TESTNAME_EXTENDS_FRAGMENT("extends TOP_#{TESTNAME}"), 937 ; 938 939 private final String template; 940 941 TestNameExtendsFragments(String template) { 942 this.template = template; 943 } 944 945 @Override 946 public String expand(String optParameter) { 947 return template; 948 } 949 } 950 951 /** 952 * SerializedObjectCustom Fragments 953 */ 954 enum SerializedObjectCustomFragments implements ComboParameter, CodeShapePredicate { 955 NONE(""), 956 ; 957 958 private final String template; 959 960 SerializedObjectCustomFragments(String template) { 961 this.template = template; 962 } 963 964 @Override 965 public String expand(String optParameter) { 966 return template; 967 } 968 } 969 970 /** 971 * ExternalizableMethod Fragments 972 */ 973 enum ExternalizableMethodFragments implements ComboParameter, CodeShapePredicate { 974 NONE(""), 975 EXTERNALIZABLE_METHODS(""" 976 public void writeExternal(ObjectOutput oos) throws IOException { 977 oos.write#{FIELD[0].READFIELD}(f1); 978 oos.write#{FIELD[1].READFIELD}(f2); 979 } 980 981 public void readExternal(ObjectInput ois) throws IOException, ClassNotFoundException { 982 f1 = (#{FIELD[0]})ois.read#{FIELD[0].READFIELD}(); 983 f2 = (#{FIELD[1]})ois.read#{FIELD[1].READFIELD}(); 984 } 985 """), 986 ; 987 988 private final String template; 989 990 ExternalizableMethodFragments(String template) { 991 this.template = template; 992 } 993 994 @Override 995 public String expand(String optParameter) { 996 return template; 997 } 998 } 999 1000 /** 1001 * ObjectConstructorFragment Fragments 1002 */ 1003 enum ObjectConstructorFragment implements ComboParameter, CodeShapePredicate { 1004 NONE(""), 1005 ANNOTATED_OBJECT_CONSTRUCTOR_FRAGMENT(""" 1006 @DeserializeConstructor 1007 #{CLASSACCESS} #{TESTNAME}(#{FIELD[0]} f1, #{FIELD[1]} f2) { 1008 this.f1 = f1; 1009 this.f2 = f2; 1010 #{FIELD_CONSTRUCTOR_ADDITIONS} 1011 } 1012 1013 @DeserializeConstructor 1014 #{CLASSACCESS} #{TESTNAME}(#{FIELD[0]} f1, #{FIELD[1]} f2, int fExtra) { 1015 this.f1 = f1; 1016 this.f2 = f2; 1017 #{FIELD_CONSTRUCTOR_ADDITIONS} 1018 } 1019 """), 1020 1021 ; 1022 1023 private final String template; 1024 1025 ObjectConstructorFragment(String template) { 1026 this.template = template; 1027 } 1028 1029 @Override 1030 public String expand(String optParameter) { 1031 return template; 1032 } 1033 } 1034 1035 /** 1036 * WriteObject templates 1037 */ 1038 enum WriteObjectFragments implements ComboParameter, CodeShapePredicate { 1039 NONE(""), 1040 WRITE_OBJECT_DEFAULT_FRAGMENT(""" 1041 private void writeObject(ObjectOutputStream oos) throws IOException { 1042 oos.defaultWriteObject(); 1043 } 1044 """), 1045 WRITE_OBJECT_FIELDS_FRAGMENT(""" 1046 private void writeObject(ObjectOutputStream oos) throws IOException { 1047 ObjectOutputStream.PutField fields = oos.putFields(); 1048 fields.put("f1", f1); 1049 fields.put("f2", f2); 1050 oos.writeFields(); 1051 } 1052 """), 1053 WRITE_OBJECT_DEFAULT_CUSTOM_FRAGMENT(""" 1054 private void writeObject(ObjectOutputStream oos) throws IOException { 1055 oos.defaultWriteObject(); 1056 // Write custom data 1057 oos.write#{FIELD[0].READFIELD}(#{FIELD[0].DEFAULT}); 1058 oos.write#{FIELD[1].READFIELD}(#{FIELD[1].DEFAULT}); 1059 } 1060 """), 1061 WRITE_OBJECT_FIELDS_CUSTOM_FRAGMENT(""" 1062 private void writeObject(ObjectOutputStream oos) throws IOException { 1063 ObjectOutputStream.PutField fields = oos.putFields(); 1064 fields.put("f1", f1); 1065 fields.put("f2", f2); 1066 oos.writeFields(); 1067 // Write custom data 1068 oos.write#{FIELD[0].READFIELD}(#{FIELD[0].DEFAULT}); 1069 oos.write#{FIELD[1].READFIELD}(#{FIELD[1].DEFAULT}); 1070 } 1071 """), 1072 ; 1073 1074 private final String template; 1075 1076 WriteObjectFragments(String template) { 1077 this.template = template; 1078 } 1079 1080 @Override 1081 public String expand(String optParameter) { 1082 return template; 1083 } 1084 } 1085 1086 /** 1087 * ReadObject templates 1088 */ 1089 enum ReadObjectFragments implements ComboParameter, CodeShapePredicate { 1090 NONE(""), 1091 READ_OBJECT_DEFAULT_FRAGMENT(""" 1092 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 1093 ois.defaultReadObject(); 1094 } 1095 """), 1096 READ_OBJECT_FIELDS_FRAGMENT(""" 1097 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 1098 ObjectInputStream.GetField fields = ois.readFields(); 1099 this.f1 = (#{FIELD[0]})fields.get("f1", #{FIELD[0].DEFAULT}); 1100 this.f2 = (#{FIELD[1]})fields.get("f2", #{FIELD[1].DEFAULT}); 1101 } 1102 """), 1103 READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT(""" 1104 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 1105 ois.defaultReadObject(); 1106 // Read custom data 1107 #{FIELD[0]} d1 = (#{FIELD[0]})ois.read#{FIELD[0].READFIELD}(); 1108 #{FIELD[1]} d2 = (#{FIELD[1]})ois.read#{FIELD[1].READFIELD}(); 1109 assert Objects.equals(#{FIELD[0].DEFAULT}, d1) : "reading custom data1, actual: " + d1; 1110 assert Objects.equals(#{FIELD[1].DEFAULT}, d2) : "reading custom data2, actual: " + d2; 1111 } 1112 """), 1113 READ_OBJECT_FIELDS_CUSTOM_FRAGMENT(""" 1114 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 1115 ObjectInputStream.GetField fields = ois.readFields(); 1116 this.f1 = (#{FIELD[0]})fields.get("f1", #{FIELD[0].DEFAULT}); 1117 this.f2 = (#{FIELD[1]})fields.get("f2", #{FIELD[1].DEFAULT}); 1118 // Read custom data 1119 #{FIELD[0]} d1 = (#{FIELD[0]})ois.read#{FIELD[0].READFIELD}(); 1120 #{FIELD[1]} d2 = (#{FIELD[1]})ois.read#{FIELD[1].READFIELD}(); 1121 assert Objects.equals(#{FIELD[0].DEFAULT}, d1) : "reading custom data1, actual: " + d1; 1122 assert Objects.equals(#{FIELD[1].DEFAULT}, d2) : "reading custom data2, actual: " + d2; 1123 } 1124 """), 1125 ; 1126 1127 private final String template; 1128 1129 ReadObjectFragments(String template) { 1130 this.template = template; 1131 } 1132 1133 @Override 1134 public String expand(String optParameter) { 1135 return template; 1136 } 1137 } 1138 1139 /** 1140 * Value and Identity kinds. 1141 */ 1142 enum ValueKind implements ComboParameter, CodeShapePredicate { 1143 VALUE("value"), 1144 IDENTITY(""), 1145 ; 1146 1147 private final String template; 1148 1149 ValueKind(String template) { 1150 this.template = template; 1151 } 1152 1153 @Override 1154 public String expand(String optParameter) { 1155 return template; 1156 } 1157 } 1158 1159 enum SerializationKind implements ComboParameter, CodeShapePredicate { 1160 SERIALIZABLE("SER", "implements Serializable"), 1161 EXTERNALIZABLE("EXT", "implements Externalizable"), 1162 ; 1163 1164 private final String key; 1165 private final String declaration; 1166 1167 SerializationKind(String key, String declaration) { 1168 this.key = key; 1169 this.declaration = declaration; 1170 } 1171 1172 public String expand(String optParameter) { 1173 return switch (optParameter) { 1174 case null -> key; 1175 case "IMPLEMENTS" -> declaration; 1176 default -> 1177 "#{" + optParameter + "_" + this + "}"; // everything ELSE turn into requested key with suffix 1178 }; 1179 } 1180 } 1181 1182 /** 1183 * Class Access kinds. 1184 */ 1185 enum ClassAccessKind implements ComboParameter, CodeShapePredicate { 1186 PUBLIC("public"), 1187 PACKAGE(""), 1188 ; 1189 1190 private final String classAccessTemplate; 1191 1192 ClassAccessKind(String classAccessTemplate) { 1193 this.classAccessTemplate = classAccessTemplate; 1194 } 1195 1196 @Override 1197 public String expand(String optParameter) { 1198 return classAccessTemplate; 1199 } 1200 } 1201 1202 /** 1203 * Type of arguments to insert in method signatures 1204 */ 1205 enum ArgumentValue implements ComboParameter, CodeShapePredicate { 1206 BOOLEAN("boolean", true), 1207 BYTE("byte", (byte) 127), 1208 CHAR("char", 'Z'), 1209 SHORT("short", (short) 0x7fff), 1210 INT("int", 0x7fffffff), 1211 LONG("long", 0x7fffffffffffffffL), 1212 FLOAT("float", 1.0F), 1213 DOUBLE("double", 1.0d), 1214 STRING("String", "xyz"); 1215 1216 static final ArgumentValue[] BASIC_VALUES = {INT, STRING}; 1217 1218 private final String argumentsValueTemplate; 1219 private final Object value; 1220 1221 ArgumentValue(String argumentsValueTemplate, Object value) { 1222 this.argumentsValueTemplate = argumentsValueTemplate; 1223 this.value = value; 1224 } 1225 1226 @Override 1227 public String expand(String optParameter) { 1228 return switch (optParameter) { 1229 case null -> argumentsValueTemplate; 1230 case "TITLECASE" -> Character.toTitleCase(argumentsValueTemplate.charAt(0)) + 1231 argumentsValueTemplate.substring(1); 1232 case "DEFAULT" -> switch (this) { 1233 case BOOLEAN -> "false"; 1234 case BYTE -> "(byte)-1"; 1235 case CHAR -> "'" + "!" + "'"; 1236 case SHORT -> "(short)-1"; 1237 case INT -> "-1"; 1238 case LONG -> "-1L"; 1239 case FLOAT -> "-1.0f"; 1240 case DOUBLE -> "-1.0d"; 1241 case STRING -> '"' + "n/a" + '"'; 1242 }; 1243 case "READFIELD" -> switch (this) { 1244 case BOOLEAN -> "Boolean"; 1245 case BYTE -> "Byte"; 1246 case CHAR -> "Char"; 1247 case SHORT -> "Short"; 1248 case INT -> "Int"; 1249 case LONG -> "Long"; 1250 case FLOAT -> "Float"; 1251 case DOUBLE -> "Double"; 1252 case STRING -> "Object"; 1253 }; 1254 case "RANDOM" -> switch (this) { // or can be Random 1255 case BOOLEAN -> Boolean.toString(!(boolean) value); 1256 case BYTE -> "(byte)" + value + 1; 1257 case CHAR -> "'" + value + "'"; 1258 case SHORT -> "(short)" + value + 1; 1259 case INT -> "-2"; 1260 case LONG -> "-2L"; 1261 case FLOAT -> (1.0f + (float) value) + "f"; 1262 case DOUBLE -> (1.0d + (float) value) + "d"; 1263 case STRING -> "\"" + value + "!\""; 1264 }; 1265 default -> switch (this) { 1266 case BOOLEAN -> value.toString(); 1267 case BYTE -> "(byte)" + value; 1268 case CHAR -> "'" + value + "'"; 1269 case SHORT -> "(short)" + value; 1270 case INT -> "-1"; 1271 case LONG -> "-1L"; 1272 case FLOAT -> value + "f"; 1273 case DOUBLE -> value + "d"; 1274 case STRING -> '"' + (String) value + '"'; 1275 }; 1276 }; 1277 } 1278 } 1279 1280 /** 1281 * Set of Parameters to fill in template. 1282 * 1283 * @param key the key 1284 * @param params the ComboParameters (one or more) 1285 */ 1286 record ParamSet(String key, int count, ComboParameter... params) { 1287 /** 1288 * Set of parameter strings for fill in template. 1289 * The strings are mapped to CompboParameter.Constants. 1290 * 1291 * @param key the key 1292 * @param strings varargs strings 1293 */ 1294 ParamSet(String key, String... strings) { 1295 this(key, 1, paramsForStrings(strings)); 1296 } 1297 1298 /** 1299 * Set of parameter strings for fill in template. 1300 * The strings are mapped to CompboParameter.Constants. 1301 * 1302 * @param key the key 1303 * @param strings varargs strings 1304 */ 1305 ParamSet(String key, int count, String... strings) { 1306 this(key, count, paramsForStrings(strings)); 1307 } 1308 1309 /** 1310 * Set of parameters for fill in template. 1311 * The strings are mapped to CompboParameter.Constants. 1312 * 1313 * @param key the key 1314 * @param params varargs strings 1315 */ 1316 ParamSet(String key, ComboParameter... params) { 1317 this(key, 1, params); 1318 } 1319 } 1320 1321 /** 1322 * Marks conditions that should match compile time errors 1323 */ 1324 static class CompileException extends RuntimeException { 1325 CompileException(String msg) { 1326 super(msg); 1327 } 1328 } 1329 1330 /** 1331 * Custom ObjectInputStream to be resolve classes from a specific class loader. 1332 */ 1333 private static class LoaderObjectInputStream extends ObjectInputStream { 1334 private final ClassLoader loader; 1335 1336 public LoaderObjectInputStream(InputStream in, ClassLoader loader) throws IOException { 1337 super(in); 1338 this.loader = loader; 1339 } 1340 1341 /** 1342 * Override resolveClass to be resolve classes from the specified loader. 1343 * 1344 * @param desc an instance of class {@code ObjectStreamClass} 1345 * @return the class 1346 * @throws ClassNotFoundException if the class is not found 1347 */ 1348 @Override 1349 protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException { 1350 String name = desc.getName(); 1351 try { 1352 return Class.forName(name, false, loader); 1353 } catch (ClassNotFoundException ex) { 1354 Class<?> cl = Class.forPrimitiveName(name); 1355 if (cl != null) { 1356 return cl; 1357 } else { 1358 throw ex; 1359 } 1360 } 1361 } 1362 } 1363 1364 private abstract class MyCompilationTask extends ComboInstance { 1365 1366 } 1367 private static void selftest() { 1368 Set<ComboParameter> params = Set.of(ValueKind.VALUE, SerializationKind.EXTERNALIZABLE); 1369 assertTrue(ValueKind.VALUE.test(params), "VALUE"); 1370 assertTrue(SerializationKind.EXTERNALIZABLE.test(params), "SerializationKind.EXTERNALIZABLE"); 1371 assertFalse(CodeShape.BAD_EXT_VALUE.test(params)); 1372 } 1373 }