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