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 }