Limit how often GC can be run.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / multivm / VMHelpers.java
blobf8ec1aa9a97cc50f07dccf04300f05c0e051f648
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc.squirreljme.plugin.multivm;
12 import cc.squirreljme.plugin.SquirrelJMEPluginConfiguration;
13 import cc.squirreljme.plugin.general.cmake.CMakeBuildTask;
14 import cc.squirreljme.plugin.multivm.ident.SourceTargetClassifier;
15 import cc.squirreljme.plugin.multivm.ident.TargetClassifier;
16 import cc.squirreljme.plugin.swm.JavaMEMidlet;
17 import cc.squirreljme.plugin.util.FileLocation;
18 import cc.squirreljme.plugin.util.TestDetection;
19 import cc.squirreljme.plugin.util.UnassistedLaunchEntry;
20 import java.io.BufferedReader;
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.nio.file.StandardOpenOption;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.LinkedHashMap;
39 import java.util.LinkedHashSet;
40 import java.util.LinkedList;
41 import java.util.List;
42 import java.util.Locale;
43 import java.util.Map;
44 import java.util.Objects;
45 import java.util.Set;
46 import java.util.TreeMap;
47 import java.util.TreeSet;
48 import java.util.concurrent.Callable;
49 import java.util.function.Supplier;
50 import java.util.jar.Attributes;
51 import java.util.jar.Manifest;
52 import java.util.regex.Matcher;
53 import java.util.regex.Pattern;
54 import java.util.stream.Stream;
55 import java.util.zip.ZipEntry;
56 import java.util.zip.ZipInputStream;
57 import java.util.zip.ZipOutputStream;
58 import org.gradle.api.Project;
59 import org.gradle.api.Task;
60 import org.gradle.api.artifacts.Configuration;
61 import org.gradle.api.artifacts.Dependency;
62 import org.gradle.api.artifacts.ProjectDependency;
63 import org.gradle.api.capabilities.Capability;
64 import org.gradle.api.file.FileCollection;
65 import org.gradle.api.provider.Provider;
66 import org.gradle.api.tasks.SourceSet;
67 import org.gradle.jvm.tasks.Jar;
69 /**
70 * Helpers for the multi-VM handlers.
72 * @since 2020/08/07
74 @SuppressWarnings("OverlyComplexClass")
75 public final class VMHelpers
77 /** The class used for single test runs. */
78 public static final String SINGLE_TEST_RUNNER =
79 "net.multiphasicapps.tac.MainSingleRunner";
81 /** Source set name for test fixtures. */
82 public static final String TEST_FIXTURES_SOURCE_SET_NAME =
83 "testFixtures";
85 /** Main configurations. */
86 private static final String[] _MAIN_CONFIGS =
87 new String[]{"api", "implementation"};
89 /** Test configurations. */
90 private static final String[] _TEST_CONFIGS =
91 new String[]{"testImplementation", "testImplementation"};
93 /** Declaration of hyper-test parameters. */
94 private static final String _HYPER_PARAMETERS_KEY =
95 "hyper-parameters";
97 /** Declaration of multi-test parameters. */
98 private static final String _MULTI_PARAMETERS_KEY =
99 "multi-parameters";
101 /* Copy buffer size. */
102 public static final int COPY_BUFFER =
103 4096;
106 * Not used.
108 * @since 2020/08/07
110 private VMHelpers()
115 * Returns a collection of the tests that are available.
117 * @param __project The project to check.
118 * @param __sourceSet The source set for the project.
119 * @return The list of available tests.
120 * @throws NullPointerException On null arguments.
121 * @since 2020/08/07
123 public static Map<String, CandidateTestFiles> availableTests(
124 Project __project, String __sourceSet)
125 throws NullPointerException
127 if (__project == null || __sourceSet == null)
128 throw new NullPointerException("NARG");
130 // Mappings of both source and expected files
131 Set<String> names = new TreeSet<>();
132 Map<String, FileLocation> sources = new HashMap<>();
133 Map<String, FileLocation> secondarySources = new HashMap<>();
134 Map<String, FileLocation> expects = new HashMap<>();
136 // Setup initial set of sources and files for lookup
137 Set<CandidateTestFileSource> everything = new LinkedHashSet<>();
138 everything.add(new CandidateTestFileSource(true,
139 TestDetection.sourceSetFiles(__project, __sourceSet)));
141 // Go through dependencies since we need to know about those test
142 // details and such
143 for (ProjectAndTaskName projectTask : VMHelpers.runClassTasks(
144 __project, new SourceTargetClassifier(__sourceSet, VMType.HOSTED,
145 BangletVariant.NONE, ClutterLevel.DEBUG)))
147 // Only consider actual projects
148 Project subProject = __project.project(projectTask.project);
149 Task subTask = subProject.getTasks().getByName(projectTask.task);
150 if (!(subTask instanceof VMExecutableTask))
151 continue;
153 // Add to be evaluated by everything
154 everything.add(new CandidateTestFileSource(false,
155 TestDetection.sourceSetFiles(subProject,
156 ((VMExecutableTask)subTask).getSourceSet())));
159 // Go through all the potential candidate sources
160 for (CandidateTestFileSource candidateSource : everything)
162 // Scan through every file and match sources and expected tests
163 for (FileLocation file : candidateSource.collection)
165 // If this is a MIME encoded file, normalize the name, so it
166 // does not include the mime extension as that is removed at
167 // JAR build time
168 Path normalized;
169 if ("__mime".equals(VMHelpers.getExtension(
170 file.getRelative())))
171 normalized = VMHelpers.stripExtension(file.getRelative());
172 else
173 normalized = file.getRelative();
175 // Determine the name of the class, used to filter if this is
176 // a valid test or not at a later stage
177 String testName = VMHelpers.pathToString('.',
178 VMHelpers.stripExtension(normalized));
180 // Always add the test now, since this could be a super class
181 // of a test but not something that should be called
182 names.add(testName);
184 // Determine how this file is to be handled
185 switch (VMHelpers.getExtension(normalized))
187 // Executable Classes
188 case "class":
189 case "java":
190 case "j":
191 if (candidateSource.primary)
192 sources.put(testName, file);
193 else
194 secondarySources.put(testName, file);
195 break;
197 // Test expectations
198 case "in":
199 expects.put(testName, file);
200 break;
205 // Setup full set of possible candidates
206 Map<String, CandidateTestFiles> fullSet = new TreeMap<>();
207 for (String testName : names)
209 // Maybe an abstract test?
210 FileLocation source = sources.get(testName);
211 FileLocation secondarySource = secondarySources.get(testName);
212 if (source == null && secondarySource == null)
213 continue;
215 // Store it
216 fullSet.put(testName, new CandidateTestFiles((source != null),
217 (source != null ? source : secondarySource),
218 expects.get(testName)));
221 // Map tests and candidate sets to normal candidates
222 Map<String, CandidateTestFiles> result = new TreeMap<>();
223 for (Map.Entry<String, CandidateTestFiles> entry : fullSet.entrySet())
225 String testName = entry.getKey();
226 CandidateTestFiles candidate = entry.getValue();
228 // Ignore if this does not match the expected name form
229 if (!TestDetection.isTest(testName))
230 continue;
232 // If this is not a primary test, then it does not get to be added
233 // to the test list nor do we need to do any processing for it
234 if (!candidate.primary)
235 continue;
237 // Load in the manifest for the candidate
238 Manifest manifest = VMHelpers.__loadExpectedResults(testName,
239 fullSet);
241 // Replace candidate entry with what is fully available here
242 Map<String, String> expectedValues = new LinkedHashMap<>();
243 for (Map.Entry<Object, Object> value : manifest.getMainAttributes()
244 .entrySet())
245 expectedValues.put(Objects.toString(value.getKey()),
246 Objects.toString(value.getValue()));
247 candidate = new CandidateTestFiles(candidate.primary,
248 candidate.sourceCode, candidate.expectedResult,
249 Collections.unmodifiableMap(expectedValues));
251 // Load the expected results and see if there multi-parameters
252 Collection<String> multiParams = VMHelpers.__parseMultiParams(
253 manifest);
255 // Single test, has no parameters
256 if (multiParams == null || multiParams.isEmpty())
257 result.put(testName, candidate);
259 // Otherwise, signify all the parameters within
260 else
262 for (String multiParam : multiParams)
263 result.put(testName + "@" + multiParam, candidate);
267 return Collections.unmodifiableMap(result);
271 * Returns the cache directory of the project.
273 * @param __project The project to get the cache directory of.
274 * @param __classifier The classifier for the VM.
275 * @return The path provider to the project cache directory.
276 * @throws NullPointerException On null arguments.
277 * @since 2020/08/15
279 public static Provider<Path> cacheDir(Project __project,
280 SourceTargetClassifier __classifier)
281 throws NullPointerException
283 if (__project == null || __classifier == null)
284 throw new NullPointerException("NARG");
286 return __project.provider(() -> __project.getBuildDir().toPath()
287 .resolve("squirreljme").resolve(
288 String.format("vm-%s-%s-%s", __classifier.getSourceSet(),
289 __classifier.getVmType().vmName(VMNameFormat.LOWERCASE),
290 __classifier.getTargetClassifier().getClutterLevel()))
291 .resolve(__classifier.getBangletVariant().properNoun));
295 * Returns the class path as a string.
297 * @param __paths Class paths.
298 * @return The class path as a string.
299 * @throws NullPointerException On null arguments.
300 * @since 2020/08/21
302 public static String classpathAsString(Path... __paths)
303 throws NullPointerException
305 return VMHelpers.classpathAsString(false, __paths);
309 * Returns the class path as a string.
311 * @param __paths Class paths.
312 * @return The class path as a string.
313 * @throws NullPointerException On null arguments.
314 * @since 2020/02/29
316 public static String classpathAsString(Iterable<Path> __paths)
317 throws NullPointerException
319 return VMHelpers.classpathAsString(false, __paths);
323 * Returns the class path as a string.
325 * @param __unix Use UNIX separator and not the system one.
326 * @param __paths Class paths.
327 * @return The class path as a string.
328 * @throws NullPointerException On null arguments.
329 * @since 2023/07/25
331 public static String classpathAsString(boolean __unix, Path... __paths)
332 throws NullPointerException
334 if (__paths == null)
335 throw new NullPointerException("NARG");
337 return VMHelpers.classpathAsString(Arrays.asList(__paths));
341 * Returns the class path as a string.
343 * @param __unix Use UNIX separator and not the system one.
344 * @param __paths Class paths.
345 * @return The class path as a string.
346 * @throws NullPointerException On null arguments.
347 * @since 2023/07/25
349 public static String classpathAsString(boolean __unix,
350 Iterable<Path> __paths)
351 throws NullPointerException
353 if (__paths == null)
354 throw new NullPointerException("NARG");
356 StringBuilder sb = new StringBuilder();
358 char separator = (__unix ? ':' : File.pathSeparatorChar);
359 for (Path path : __paths)
361 if (sb.length() > 0)
362 sb.append(separator);
363 sb.append(path);
366 return sb.toString();
370 * Decodes the classpath string.
372 * @param __string The string to decode.
373 * @return The decoded path.
374 * @throws NullPointerException On null arguments.
375 * @since 2020/10/17
377 public static Path[] classpathDecode(String __string)
378 throws NullPointerException
380 if (__string == null)
381 throw new NullPointerException("NARG");
383 Collection<Path> result = new LinkedList<>();
384 for (String split : __string.split(Pattern.quote(File.pathSeparator)))
385 result.add(Paths.get(split));
387 return result.<Path>toArray(new Path[result.size()]);
391 * Returns all the compact library tasks that the specified project
392 * depends upon.
394 * @param __project The project to get the dependencies from.
395 * @param __sourceSet The source set to base off.
396 * @return The tasks which are part of the compaction dependencies.
397 * @throws NullPointerException On null arguments.
398 * @since 2023/02/02
400 public static Collection<VMCompactLibraryTask> compactLibTaskDepends(
401 Project __project, String __sourceSet)
402 throws NullPointerException
404 if (__project == null || __sourceSet == null)
405 throw new NullPointerException("NARG");
407 // Where does this go?
408 Collection<VMCompactLibraryTask> result = new LinkedHashSet<>();
410 // Are we testing?
411 boolean isTest = SourceSet.TEST_SOURCE_SET_NAME.equals(__sourceSet);
412 boolean isTestFixtures =
413 VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME.equals(__sourceSet);
415 // This is a bit messy but it works for now
416 Collection<ProjectAndTaskName> runTasks =
417 VMHelpers.runClassTasks(__project,
418 SourceTargetClassifier.builder()
419 .sourceSet(__sourceSet)
420 .targetClassifier(TargetClassifier.builder()
421 .bangletVariant(BangletVariant.NONE)
422 .vmType(VMType.SPRINGCOAT)
423 .clutterLevel(ClutterLevel.RELEASE)
424 .build())
425 .build());
426 for (ProjectAndTaskName projectAndTask : runTasks)
428 // Find the referenced project
429 Project subProject = __project.project(projectAndTask.project);
431 // Ignore our own project, if not testing
432 if (__project.equals(subProject) && !isTest && !isTestFixtures)
433 continue;
435 // Check the main source set
436 String checkName = TaskInitialization.task("compactLib",
437 SourceSet.MAIN_SOURCE_SET_NAME);
438 Task maybe = subProject.getTasks().findByName(checkName);
439 if (maybe instanceof VMCompactLibraryTask)
441 VMCompactLibraryTask task = (VMCompactLibraryTask)maybe;
442 result.add(task);
445 // If we are testing, see if we can pull in any test fixtures
446 if (isTest)
448 String check = TaskInitialization.task("compactLib",
449 VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME);
450 Task fixture = subProject.getTasks().findByName(check);
451 if (fixture instanceof VMCompactLibraryTask)
453 VMCompactLibraryTask task = (VMCompactLibraryTask)fixture;
454 result.add(task);
459 return result;
463 * Copies from the input into the output.
465 * @param __in The input.
466 * @param __out The output.
467 * @throws IOException On read/write errors.
468 * @throws NullPointerException On null arguments.
469 * @since 2020/08/15
471 public static void copy(InputStream __in, OutputStream __out)
472 throws IOException, NullPointerException
474 if (__in == null || __out == null)
475 throw new NullPointerException("NARG");
477 byte[] buf = new byte[VMHelpers.COPY_BUFFER];
478 for (;;)
480 int rc = __in.read(buf);
482 if (rc < 0)
483 return;
485 __out.write(buf, 0, rc);
490 * Copies from the input into the output while recompressing the Zip file.
492 * @param __in The input.
493 * @param __out The output.
494 * @throws IOException On read/write errors.
495 * @throws NullPointerException On null arguments.
496 * @since 2024/08/08
498 public static void copyRecompressZip(InputStream __in, OutputStream __out)
499 throws IOException, NullPointerException
501 if (__in == null || __out == null)
502 throw new NullPointerException("NARG");
504 try (ZipInputStream inZip = new ZipInputStream(__in);
505 ZipOutputStream outZip = new ZipOutputStream(__out))
507 // Maximum compression
508 outZip.setMethod(ZipOutputStream.DEFLATED);
509 outZip.setLevel(9);
511 // Recompress each entry
512 for (;;)
514 // Get next entry, if null there are none left
515 ZipEntry entry = inZip.getNextEntry();
516 if (entry == null)
517 break;
519 // Start entry
520 outZip.putNextEntry(new ZipEntry(entry.getName()));
522 // Copy entry data
523 VMHelpers.copy(inZip, outZip);
525 // Finished writing
526 outZip.closeEntry();
529 // Finalize zip
530 outZip.finish();
531 outZip.flush();
534 // Make sure output is flushed
535 __out.flush();
539 * Deletes the given directory tree.
541 * @param __task The task deleting for.
542 * @param __path The path to delete.
543 * @throws NullPointerException On null arguments.
544 * @since 2024/04/08
546 public static void deleteDirTree(Task __task, Path __path)
547 throws NullPointerException
549 if (__task == null || __path == null)
550 throw new NullPointerException("NARG");
552 // Ignore if not a directory
553 Path base = __path.toAbsolutePath().normalize();
554 if (!Files.isDirectory(__path))
555 return;
557 // Collect files to delete
558 Set<Path> deleteFiles = new LinkedHashSet<>();
559 Set<Path> deleteDirs = new LinkedHashSet<>();
561 // Perform the walk to collect files
562 try (Stream<Path> walk = Files.walk(__path))
564 walk.forEach((__it) -> {
565 Path normal = __it.toAbsolutePath().normalize();
567 if (Files.isDirectory(normal))
568 deleteDirs.add(normal);
569 else
570 deleteFiles.add(normal);
573 catch (IOException __e)
575 __e.printStackTrace();
578 // Run through and delete files then directories
579 for (Set<Path> rawByes : Arrays.asList(deleteFiles, deleteDirs))
581 List<Path> byes = new ArrayList<>(rawByes);
582 Collections.reverse(byes);
584 for (Path bye : byes)
586 // Note
587 __task.getLogger().lifecycle(
588 String.format("Cleaning %s...", bye));
590 // Skip out of tree files
591 if (!bye.startsWith(base))
593 __task.getLogger().lifecycle(
594 String.format("%s is out of tree, skipping...", bye));
595 continue;
598 // Perform deletion
601 Files.deleteIfExists(bye);
603 catch (IOException e)
605 e.printStackTrace();
612 * Attempts to find the emulator library so that can be loaded directly
613 * instead of being extracted by each test process, if possible.
615 * @param __anyProject Any project.
616 * @return The path to the emulator library.
617 * @since 2020/12/01
619 @SuppressWarnings("ConstantConditions")
620 public static Path findEmulatorLib(Project __anyProject)
621 throws NullPointerException
623 if (__anyProject == null)
624 throw new NullPointerException("NARG");
626 // We need to look through the emulator base tasks to determine
627 // the library to select
628 Project emuBase = __anyProject.getRootProject()
629 .findProject(":emulators:emulator-base");
631 // Get the CMake Task for this
632 CMakeBuildTask cmake = (CMakeBuildTask)emuBase.getTasks()
633 .getByName("libNativeEmulatorBase");
635 // Use the resultant library
636 return cmake.cmakeOutFile;
640 * Returns the full suite libraries for a given task.
642 * @param __task The task to get.
643 * @return The path for the full suite libraries.
644 * @since 2022/06/13
646 public static Collection<Path> fullSuiteLibraries(Task __task)
647 throws NullPointerException
649 Collection<Path> libPath = new LinkedHashSet<>();
650 for (VMLibraryTask dep : VMHelpers.fullSuiteLibrariesTasks(__task))
651 for (File file : dep.getOutputs().getFiles())
652 libPath.add(file.toPath());
654 return libPath;
658 * Returns the full suite library tasks for a given task.
660 * @param __task The task to get.
661 * @return The path for the full suite libraries.
662 * @since 2024/03/05
664 public static Collection<VMLibraryTask> fullSuiteLibrariesTasks(
665 Task __task)
666 throws NullPointerException
668 // Load executable library tasks from our own VM
669 Collection<VMLibraryTask> result = new LinkedHashSet<>();
670 for (Task dep : __task.getTaskDependencies().getDependencies(__task))
671 if (dep instanceof VMLibraryTask)
672 result.add((VMLibraryTask)dep);
674 return result;
678 * Gets the base name of the file without the extension, if there is one.
680 * @param __path The path to get from.
681 * @return The file base name.
682 * @throws NullPointerException On null arguments.
683 * @since 2023/02/05
685 public static String getBaseName(Path __path)
686 throws NullPointerException
688 if (__path == null)
689 throw new NullPointerException("NARG");
691 // Does this file even have an extension to it?
692 String fileName = __path.getFileName().toString();
693 int ld = fileName.lastIndexOf('.');
694 if (ld < 0)
695 return fileName;
697 // Otherwise extract it
698 return fileName.substring(0, ld);
702 * Gets the extension from the given path.
704 * @param __path The path to get from.
705 * @return The file extension.
706 * @throws NullPointerException On null arguments.
707 * @since 2020/09/06
709 public static String getExtension(Path __path)
710 throws NullPointerException
712 if (__path == null)
713 throw new NullPointerException("NARG");
715 // Does this file even have an extension to it?
716 String fileName = __path.getFileName().toString();
717 int ld = fileName.lastIndexOf('.');
718 if (ld < 0)
719 return "";
721 // Otherwise extract it
722 return fileName.substring(ld + 1);
726 * Returns the current Fossil commit hash.
728 * @param __project The project.
729 * @return The hash or {@code null} if not in a Fossil checkout.
730 * @throws NullPointerException On null arguments.
731 * @since 2024/10/06
733 public static String hashFossil(Project __project)
734 throws NullPointerException
736 if (__project == null)
737 throw new NullPointerException("NARG");
739 Path possible = __project.getRootProject()
740 .getProjectDir().toPath().resolve("manifest.uuid");
741 if (Files.exists(possible))
744 List<String> lines = Files.readAllLines(possible);
745 if (!lines.isEmpty() && lines.get(0) != null)
746 return lines.get(0).trim().toLowerCase(Locale.ROOT);
748 catch (IOException __ignored)
752 // Not in a Fossil checkout
753 return null;
757 * Returns the current Git commit hash.
759 * @param __project The project.
760 * @return The hash or {@code null} if not in a Git checkout.
761 * @throws NullPointerException On null arguments.
762 * @since 2024/10/06
764 public static String hashGit(Project __project)
765 throws NullPointerException
767 if (__project == null)
768 throw new NullPointerException("NARG");
772 // Setup new process
773 ProcessBuilder builder = new ProcessBuilder("git",
774 "rev-parse", "HEAD");
776 builder.redirectOutput(ProcessBuilder.Redirect.PIPE);
777 builder.directory(__project.getRootProject().getProjectDir());
779 // Start and wait for it to complete
780 Process process = builder.start();
781 process.waitFor();
783 // Read in hash
784 try (InputStream in = process.getInputStream();
785 InputStreamReader isr = new InputStreamReader(in);
786 BufferedReader br = new BufferedReader(isr))
788 String maybe = br.readLine();
789 if (maybe != null)
790 return maybe.trim().toLowerCase(Locale.ROOT);
793 catch (IOException|InterruptedException __ignored)
797 return null;
801 * Returns the task that creates the JAR.
803 * @param __project The project to get from.
804 * @param __sourceSet The source set.
805 * @return The jar task used, or {@code null} if not found.
806 * @throws NullPointerException On null arguments.
807 * @since 2020/08/07
809 public static Jar jarTask(Project __project, String __sourceSet)
810 throws NullPointerException
812 if (__project == null || __sourceSet == null)
813 throw new NullPointerException("NARG");
815 switch (__sourceSet)
817 case SourceSet.MAIN_SOURCE_SET_NAME:
818 return (Jar)__project.getTasks().getByName("jar");
820 case SourceSet.TEST_SOURCE_SET_NAME:
821 return (Jar)__project.getTasks().getByName("testJar");
823 case VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME:
824 return (Jar)__project.getTasks()
825 .getByName("testFixturesJar");
827 default:
828 throw new IllegalStateException("Unknown sourceSet: " +
829 __sourceSet);
834 * Returns the main class to execute.
836 * @param __cfg The configuration.
837 * @param __midlet The MIDlet to be run.
838 * @return The main class.
839 * @throws NullPointerException If {@code __cfg} is {@code null}.
840 * @since 2020/03/06
842 public static String mainClass(SquirrelJMEPluginConfiguration __cfg,
843 JavaMEMidlet __midlet)
844 throws NullPointerException
846 if (__cfg == null)
847 throw new NullPointerException("NARG");
849 // We either run the MIDlet or we do not
850 return VMHelpers.mainClass(__midlet, __cfg.mainClass);
854 * Determines the main class to use.
856 * @param __midlet The MIDlet to execute.
857 * @param __mainClass The main class to run.
858 * @return The class for execution.
859 * @throws NullPointerException On null arguments.
860 * @since 2024/07/28
862 public static String mainClass(JavaMEMidlet __midlet, String __mainClass)
863 throws NullPointerException
865 if (__midlet == null && __mainClass == null)
866 throw new NullPointerException("No main class specified.");
868 // We either run the MIDlet or we do not
869 if (__midlet != null)
870 return UnassistedLaunchEntry.MIDLET_MAIN_CLASS;
871 return __mainClass;
875 * Returns the only file with the given extension in the given collection.
877 * @param __collection The collection to get.
878 * @param __ext The extension to get from.
879 * @return The only file in the collection with the extension.
880 * @throws IllegalStateException If there are multiple or no files
881 * available.
882 * @throws NullPointerException On null arguments.
883 * @since 2023/02/05
885 public static Path onlyFile(FileCollection __collection, String __ext)
886 throws IllegalStateException, NullPointerException
888 if (__collection == null || __ext == null)
889 throw new NullPointerException("NARG");
891 // Scan through everything and look for it
892 Path result = null;
893 for (File file : __collection.getFiles())
895 Path path = file.toPath();
897 if (__ext.equals(VMHelpers.getExtension(path)))
899 if (result != null)
900 throw new IllegalStateException(
901 String.format("Multiple .%s in %s",
902 __ext, __collection));
904 result = path;
908 // None found?
909 if (result == null)
910 throw new IllegalStateException(
911 String.format("No .%s in %s", __ext, __collection));
912 return result;
916 * Returns the optional dependencies for a given project.
918 * @param __project The project to get for.
919 * @param __sourceSet The source set to look within.
920 * @return The optional project dependencies or an empty list if unknown.
921 * @throws NullPointerException On null arguments.
922 * @since 2022/09/05
924 public static List<Project> optionalDepends(Project __project,
925 String __sourceSet)
926 throws NullPointerException
928 SquirrelJMEPluginConfiguration config =
929 SquirrelJMEPluginConfiguration.configurationOrNull(__project);
930 if (config == null)
931 return Collections.emptyList();
933 if (__sourceSet.equals(SourceSet.MAIN_SOURCE_SET_NAME))
934 return config.optionalDependencies;
935 else if (__sourceSet.equals(SourceSet.TEST_SOURCE_SET_NAME))
936 return config.optionalDependenciesTest;
937 else if (__sourceSet.equals(VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME))
938 return config.optionalDependenciesTestFixtures;
940 return Collections.emptyList();
944 * Converts the given path to a String using the delimiter.
946 * @param __delim The delimiter.
947 * @param __path The path to convert.
948 * @return The path as a string.
949 * @throws NullPointerException On null arguments.
950 * @since 2020/08/30
952 public static String pathToString(char __delim, Path __path)
953 throws NullPointerException
955 if (__path == null)
956 throw new NullPointerException("NARG");
958 // If the path is of only a single element (or none) then it is just
959 // whatever the string form of the path is
960 int n = __path.getNameCount();
961 if (n <= 1)
962 return __path.toString();
964 // Build together the string
965 StringBuilder sb = new StringBuilder();
966 for (int i = 0; i < n; i++)
968 if (i > 0)
969 sb.append(__delim);
970 sb.append(__path.getName(i).toString());
973 return sb.toString();
977 * Returns the directory where profiler snapshots go.
979 * @param __project The project to get the cache directory of.
980 * @param __classifier The classifier used.
981 * @return The path provider to the profiler snapshot directory.
982 * @throws NullPointerException On null arguments.
983 * @since 2020/09/06
985 public static Provider<Path> profilerDir(Project __project,
986 SourceTargetClassifier __classifier)
987 throws NullPointerException
989 if (__project == null || __classifier == null)
990 throw new NullPointerException("NARG");
992 return __project.provider(() -> VMHelpers.cacheDir(
993 __project, __classifier).get().resolve("nps"));
997 * Returns the internal name via the source set.
999 * @param __project The project.
1000 * @param __sourceSet The source set.
1001 * @return The internal name that is used by SquirrelJME.
1002 * @throws NullPointerException On null arguments.
1003 * @since 2022/08/07
1005 public static String projectInternalNameViaSourceSet(Project __project,
1006 String __sourceSet)
1007 throws NullPointerException
1009 if (__project == null || __sourceSet == null)
1010 throw new NullPointerException("NARG");
1012 // If main project, just use the normal base name
1013 if (__sourceSet.equals(SourceSet.MAIN_SOURCE_SET_NAME))
1014 return __project.getName();
1016 // Otherwise, append the source set
1017 return String.format("%s-%s", __project.getName(),
1018 __sourceSet.toLowerCase(Locale.ROOT));
1022 * Returns the project classpath.
1024 * @param __project The project.
1025 * @return The class path.
1026 * @throws NullPointerException On null arguments.
1027 * @since 2020/02/29
1029 public static Iterable<File> projectRuntimeClasspath(Project __project)
1030 throws NullPointerException
1032 if (__project == null)
1033 throw new NullPointerException("No project specified.");
1035 return __project.getConfigurations().
1036 getByName("runtimeClasspath").getFiles();
1040 * Returns the name of the suite that should be used for the dependency.
1042 * @param __project The project to get for.
1043 * @param __sourceSet The source set used.
1044 * @return The suite name that should be used.
1045 * @throws NullPointerException On null arguments.
1046 * @since 2022/08/07
1048 public static String projectSwmNameViaSourceSet(Project __project,
1049 String __sourceSet)
1050 throws NullPointerException
1052 if (__project == null || __sourceSet == null)
1053 throw new NullPointerException("NARG");
1055 // Need this to get the key
1056 SquirrelJMEPluginConfiguration config =
1057 SquirrelJMEPluginConfiguration.configuration(__project);
1059 // Just uses the set name
1060 if (__sourceSet.equals(SourceSet.MAIN_SOURCE_SET_NAME))
1061 return config.swmName;
1063 // Otherwise, gets prefixed
1064 return TaskInitialization.uppercaseFirst(__sourceSet) + " for " +
1065 config.swmName;
1069 * Reads all the bytes from the stream.
1071 * @param __in The stream to read from.
1072 * @return All the read bytes.
1073 * @throws IOException On read errors.
1074 * @throws NullPointerException On null arguments.
1075 * @since 2020/09/07
1077 public static byte[] readAll(InputStream __in)
1078 throws IOException, NullPointerException
1080 if (__in == null)
1081 throw new NullPointerException("NARG");
1083 try (ByteArrayOutputStream out = new ByteArrayOutputStream(4096))
1085 byte[] buf = new byte[4096];
1086 for (;;)
1088 int rc = __in.read(buf);
1090 if (rc < 0)
1091 return out.toByteArray();
1093 out.write(buf, 0, rc);
1099 * Recompresses the given Zip file.
1101 * @param __zip The ZIP to recompress.
1102 * @throws IOException On read/write errors.
1103 * @throws NullPointerException On null arguments.
1104 * @since 2024/08/08
1106 public static void recompressZip(Path __zip)
1107 throws IOException, NullPointerException
1109 if (__zip == null)
1110 throw new NullPointerException("NARG");
1112 // Load in everything for copy
1113 byte[] result;
1114 byte[] inZip = Files.readAllBytes(__zip);
1115 try (InputStream in = new ByteArrayInputStream(inZip);
1116 ByteArrayOutputStream out = new ByteArrayOutputStream(
1117 inZip.length))
1119 // Perform recompression
1120 VMHelpers.copyRecompressZip(in, out);
1122 // Get resultant output
1123 result = out.toByteArray();
1126 // Replace everything
1127 Files.write(__zip, result,
1128 StandardOpenOption.TRUNCATE_EXISTING,
1129 StandardOpenOption.WRITE,
1130 StandardOpenOption.CREATE);
1134 * Resolves path elements as needed to determine where a file is.
1136 * @param __in The input to resolve.
1137 * @return The path of the given object.
1138 * @throws NullPointerException On null arguments.
1139 * @since 2020/12/27
1141 public static Iterable<Path> resolvePath(Object __in)
1142 throws NullPointerException
1144 if (__in == null)
1145 throw new NullPointerException("NARG");
1147 // Direct file paths
1148 if (__in instanceof Path)
1149 return Collections.singleton((Path)__in);
1150 else if (__in instanceof File)
1151 return Collections.singleton(((File)__in).toPath());
1153 // A produced value
1154 else if (__in instanceof Callable)
1157 return VMHelpers.resolvePath(
1158 ((Callable<?>)__in).call());
1160 catch (Exception e)
1162 if (e instanceof RuntimeException)
1163 throw (RuntimeException)e;
1165 throw new RuntimeException("Could not run Callable.", e);
1168 // A supplied value
1169 else if (__in instanceof Supplier)
1170 return VMHelpers.resolvePath(
1171 ((Supplier<?>)__in).get());
1173 // An iterable sequence
1174 else if (__in instanceof Iterable)
1176 List<Path> result = new ArrayList<>();
1178 // Process each one
1179 for (Object obj : (Iterable<?>)__in)
1180 for (Path sub : VMHelpers.resolvePath(obj))
1181 result.add(sub);
1183 return result;
1186 // Unknown
1187 else
1188 throw new RuntimeException(String.format(
1189 "Unknown input path type %s", __in.getClass()));
1193 * Resolves tasks from the projects and tasks.
1195 * @param <T> The class to resolve as.
1196 * @param __class The class to resolve as.
1197 * @param __project The project to latch onto for lookup.
1198 * @param __in The input project and task names.
1199 * @return An iterable which has the projects resolved.
1200 * @throws NullPointerException On null arguments.
1202 public static <T extends Task> Iterable<T> resolveProjectTasks(
1203 Class<T> __class, Project __project, Iterable<ProjectAndTaskName> __in)
1204 throws NullPointerException
1206 if (__project == null || __in == null)
1207 throw new NullPointerException("NARG");
1209 Collection<T> result = new LinkedList<>();
1211 // Map projects and tasks back into tasks
1212 for (ProjectAndTaskName depend : __in)
1214 T task = __class.cast(__project.project(depend.project)
1215 .getTasks().findByName(depend.task));
1217 if (task != null)
1218 result.add(task);
1221 return Collections.unmodifiableCollection(result);
1225 * Returns the path of the all the JARs which make up the classpath for
1226 * running an executable.
1228 * @param __task The task to get for.
1229 * @param __classifier The classifier used.
1230 * @return An array of paths containing the class path of execution.
1231 * @throws NullPointerException On null arguments.
1232 * @since 2020/11/21
1234 public static Path[] runClassPath(Task __task,
1235 SourceTargetClassifier __classifier)
1237 return VMHelpers.runClassPath(__task, __classifier, false);
1241 * Returns the path of the all the JARs which make up the classpath for
1242 * running an executable.
1244 * @param __task The task to get for.
1245 * @param __classifier The classifier used.
1246 * @param __optional use optional dependencies?
1247 * @return An array of paths containing the class path of execution.
1248 * @throws NullPointerException On null arguments.
1249 * @since 2022/09/05
1251 public static Path[] runClassPath(Task __task,
1252 SourceTargetClassifier __classifier, boolean __optional)
1253 throws NullPointerException
1255 return VMHelpers.runClassPath(__task.getProject(),
1256 __classifier, __optional);
1260 * Returns the path of the all the JARs which make up the classpath for
1261 * running an executable.
1263 * @param __project The project to get for.
1264 * @param __classifier The classifier used.
1265 * @return An array of paths containing the class path of execution.
1266 * @throws NullPointerException On null arguments.
1267 * @since 2020/08/20
1269 public static Path[] runClassPath(Project __project,
1270 SourceTargetClassifier __classifier)
1271 throws NullPointerException
1273 return VMHelpers.runClassPath(__project, __classifier, false);
1277 * Returns the path of the all the JARs which make up the classpath for
1278 * running an executable.
1280 * @param __project The project to get for.
1281 * @param __classifier The classifier used.
1282 * @param __optional use optional dependencies?
1283 * @return An array of paths containing the class path of execution.
1284 * @throws NullPointerException On null arguments.
1285 * @since 2022/09/05
1287 public static Path[] runClassPath(Project __project,
1288 SourceTargetClassifier __classifier, boolean __optional)
1289 throws NullPointerException
1291 if (__project == null || __classifier == null)
1292 throw new NullPointerException("NARG");
1294 // Get tasks that are used for dependency running
1295 Iterable<VMLibraryTask> tasks =
1296 VMHelpers.<VMLibraryTask>resolveProjectTasks(
1297 VMLibraryTask.class, __project, VMHelpers
1298 .runClassTasks(__project, __classifier, __optional));
1300 // Get the outputs of these, as they will be used. Ensure the order is
1301 // kept otherwise execution may be non-deterministic and could break.
1302 Set<Path> classPath = new LinkedHashSet<>();
1303 for (VMLibraryTask vmLib : tasks)
1304 for (File file : vmLib.getOutputs().getFiles().getFiles())
1305 classPath.add(file.toPath());
1307 return classPath.toArray(new Path[classPath.size()]);
1311 * Returns the internal class path specifier to run the given tasks.
1313 * @param __project The project.
1314 * @param __classifier The classifier used.
1315 * @param __optional Include optional JARs?
1316 * @return The class path string.
1317 * @throws NullPointerException On null arguments.
1318 * @since 2023/07/25
1320 public static String runClassPathAsInternalClassPath(
1321 Project __project, SourceTargetClassifier __classifier,
1322 boolean __optional)
1323 throws NullPointerException
1325 // Get tasks that are used for dependency running
1326 Iterable<VMLibraryTask> tasks =
1327 VMHelpers.<VMLibraryTask>resolveProjectTasks(
1328 VMLibraryTask.class, __project, VMHelpers
1329 .runClassTasks(__project, __classifier, __optional));
1331 // Build paths from it
1332 StringBuilder result = new StringBuilder();
1333 for (VMLibraryTask task : tasks)
1335 // Path separator before
1336 if (result.length() > 0)
1337 result.append(":");
1339 // Use Jar name here
1340 result.append(VMHelpers.projectInternalNameViaSourceSet(
1341 task.getProject(), task.getSourceSet()) + ".jar");
1344 return result.toString();
1348 * Returns the task dependencies to get outputs from that would be
1349 * considered a part of the project's class path used at execution time.
1351 * @param __project The task to get from.
1352 * @param __classifier The classifier used.
1353 * @return The direct run dependencies for the task.
1354 * @throws NullPointerException On null arguments.
1355 * @since 2020/08/15
1357 public static Collection<ProjectAndTaskName> runClassTasks(
1358 Project __project, SourceTargetClassifier __classifier)
1359 throws NullPointerException
1361 return VMHelpers.runClassTasks(__project, __classifier,
1362 false, null);
1366 * Returns the task dependencies to get outputs from that would be
1367 * considered a part of the project's class path used at execution time.
1369 * @param __project The task to get from.
1370 * @param __classifier The classifier used.
1371 * @param __optional Include optional dependencies?
1372 * @return The direct run dependencies for the task.
1373 * @throws NullPointerException On null arguments.
1374 * @since 2022/09/05
1376 public static Collection<ProjectAndTaskName> runClassTasks(
1377 Project __project, SourceTargetClassifier __classifier,
1378 boolean __optional)
1379 throws NullPointerException
1381 return VMHelpers.runClassTasks(__project, __classifier, __optional,
1382 null);
1386 * Returns the task dependencies to get outputs from that would be
1387 * considered a part of the project's class path used at execution time.
1389 * @param __project The task to get from.
1390 * @param __classifier The classifier used.
1391 * @param __optional Include optional dependencies?
1392 * @param __did Projects that have been processed.
1393 * @return The direct run dependencies for the task.
1394 * @throws NullPointerException On null arguments.
1395 * @since 2020/08/15
1397 public static Collection<ProjectAndTaskName> runClassTasks(
1398 Project __project, SourceTargetClassifier __classifier,
1399 boolean __optional, Set<ProjectAndTaskName> __did)
1400 throws NullPointerException
1402 if (__project == null || __classifier == null)
1403 throw new NullPointerException("NARG");
1405 // Make sure this is always set
1406 if (__did == null)
1407 __did = new HashSet<>();
1409 // If this was already processed, ignore it
1410 ProjectAndTaskName selfProjectTask = ProjectAndTaskName.of(__project,
1411 TaskInitialization.task("lib",
1412 __classifier.withVmByEmulatedJit()));
1413 if (__did.contains(selfProjectTask))
1414 return Collections.emptySet();
1416 Set<ProjectAndTaskName> result = new LinkedHashSet<>();
1418 // If we are testing then we depend on the main TAC library, otherwise
1419 // we will not be able to do any actual testing
1420 boolean isTesting = __classifier.isTestSourceSet();
1421 if (isTesting)
1423 // Depend on TAC
1424 result.addAll(VMHelpers.runClassTasks(
1425 __project.findProject(":modules:tac"),
1426 __classifier.withSourceSet(SourceSet.MAIN_SOURCE_SET_NAME)
1427 .withVmByEmulatedJit(),
1428 __optional, __did));
1430 // Depend on our main project as we will be testing it
1431 result.addAll(VMHelpers.runClassTasks(__project,
1432 __classifier.withSourceSet(SourceSet.MAIN_SOURCE_SET_NAME)
1433 .withVmByEmulatedJit(),
1434 __optional, __did));
1437 // Go through the configurations to yank in the dependencies as needed
1438 for (String config : (isTesting ? VMHelpers._TEST_CONFIGS :
1439 VMHelpers._MAIN_CONFIGS))
1441 // The configuration may be optional
1442 Configuration foundConfig = __project.getConfigurations()
1443 .findByName(config);
1444 if (foundConfig == null)
1445 continue;
1447 // Handle dependencies
1448 for (Dependency depend : foundConfig.getDependencies())
1450 // Only consider projects
1451 if (!(depend instanceof ProjectDependency))
1452 continue;
1454 ProjectDependency projectDepend = (ProjectDependency)depend;
1455 Project sub = projectDepend.getDependencyProject();
1457 // Only consider SquirrelJME projects
1458 SquirrelJMEPluginConfiguration squirreljmeConf =
1459 SquirrelJMEPluginConfiguration.configurationOrNull(sub);
1460 if (squirreljmeConf == null)
1461 continue;
1463 // Does this depend on test fixtures?
1464 boolean isTestFixture = false;
1465 for (Capability capability :
1466 projectDepend.getRequestedCapabilities())
1467 if (capability.getName()
1468 .equals(sub.getName() + "-test-fixtures"))
1469 isTestFixture = true;
1471 // Otherwise, handle the dependencies
1472 String targetSourceSet = (isTestFixture ?
1473 VMHelpers.TEST_FIXTURES_SOURCE_SET_NAME :
1474 SourceSet.MAIN_SOURCE_SET_NAME);
1475 Collection<ProjectAndTaskName> selected =
1476 VMHelpers.runClassTasks(sub,
1477 __classifier.withSourceSet(targetSourceSet)
1478 .withVmByEmulatedJit(),
1479 __optional, __did);
1481 result.addAll(selected);
1485 // Finally, add our own library for usages
1486 result.add(selfProjectTask);
1488 // Ignore our own project
1489 __did.add(selfProjectTask);
1491 // Include optional dependencies as well, so that they are actually
1492 // used accordingly...
1493 if (__optional)
1494 for (Project optional : VMHelpers.optionalDepends(__project,
1495 __classifier.getSourceSet()))
1496 result.addAll(VMHelpers.runClassTasks(optional,
1497 __classifier.withSourceSet(SourceSet.MAIN_SOURCE_SET_NAME)
1498 .withVmByEmulatedJit(),
1499 true, __did));
1501 // Debug as needed
1502 __project.getLogger().debug("Run Depends: {}", result);
1504 return Collections.unmodifiableCollection(result);
1508 * Returns all the tests to run.
1510 * @param __project The project to check.
1511 * @param __sourceSet The source set to check.
1512 * @return All the tests that should be run.
1513 * @throws NullPointerException On null arguments.
1514 * @since 2020/08/30
1516 public static AvailableTests runningTests(
1517 Project __project, String __sourceSet)
1518 throws NullPointerException
1520 Map<String, CandidateTestFiles> available =
1521 VMHelpers.availableTests(__project, __sourceSet);
1523 // If specifying a single test to run only specify that
1524 String singleTest = System.getProperty(
1525 VMLegacyTestTask.SINGLE_TEST_PROPERTY,
1526 System.getProperty(VMLegacyTestTask.SINGLE_TEST_PROPERTY_B));
1527 if (singleTest != null)
1529 // We need to check every test, since we may have multi-parameters
1530 // to consider
1531 Map<String, CandidateTestFiles> singles = new LinkedHashMap<>();
1532 for (Map.Entry<String, CandidateTestFiles> e : available
1533 .entrySet())
1534 if (VMHelpers.__isSingleTest(e.getKey(), singleTest))
1535 singles.put(e.getKey(), e.getValue());
1537 // If we found at least one test then we can test those, there may
1538 // be multiple ones due to multi-parameters
1539 if (!singles.isEmpty())
1540 return new AvailableTests(
1541 Collections.unmodifiableMap(singles), true);
1543 // If the test has no matching file, then just ignore it
1544 throw new IllegalArgumentException(String.format(
1545 "Could not find test %s, failing.", singleTest));
1548 // Is only valid if there is at least one test
1549 return new AvailableTests(available, false);
1553 * Translates a path to a string.
1555 * @param __name The input string file name.
1556 * @return The resultant path.
1557 * @throws NullPointerException On null arguments.
1558 * @since 2023/09/03
1560 public static Path stringToPath(String __name)
1561 throws NullPointerException
1563 if (__name == null)
1564 throw new NullPointerException("NARG");
1566 return Paths.get("", __name.split(Pattern.quote("/")));
1570 * Strips the extension from the path.
1572 * @param __path The path to strip the extension from.
1573 * @return The input path with the extension stripped.
1574 * @throws NullPointerException On null arguments.
1575 * @since 2020/08/30
1577 public static Path stripExtension(Path __path)
1578 throws NullPointerException
1580 if (__path == null)
1581 throw new NullPointerException("NARG");
1583 // If there is no extension part then nothing has to be done
1584 String fileName = __path.getFileName().toString();
1585 int lastDot = fileName.lastIndexOf('.');
1586 if (lastDot < 0)
1587 return __path;
1589 // The "renamed" file is in the same parent directory
1590 return __path.resolveSibling(fileName.substring(0, lastDot));
1594 * Returns the directory where test results go.
1596 * @param __project The project to get the cache directory of.
1597 * @param __classifier The classifier used.
1598 * @return The path provider to the test result directory.
1599 * @throws NullPointerException On null arguments.
1600 * @since 2020/09/06
1602 public static Provider<Path> testResultXmlDir(Project __project,
1603 SourceTargetClassifier __classifier)
1604 throws NullPointerException
1606 if (__project == null || __classifier == null)
1607 throw new NullPointerException("NARG");
1609 return __project.provider(() -> VMHelpers.cacheDir(__project,
1610 __classifier).get().resolve("junit"));
1614 * Returns the test result XML file name.
1616 * @param __testName The test name.
1617 * @return The name of the XML to use.
1618 * @throws NullPointerException On null arguments.
1619 * @since 2020/09/06
1621 public static String testResultXmlName(String __testName)
1622 throws NullPointerException
1624 if (__testName == null)
1625 throw new NullPointerException("NARG");
1627 // When Gradle normally makes a test, it encodes @ to #40.
1628 return "TEST-" + __testName.replaceAll(Pattern.quote("@"),
1629 Matcher.quoteReplacement("#40")) + ".xml";
1633 * Returns the directory where test results go.
1635 * @param __project The project to get the cache directory of.
1636 * @param __classifier The classifier used.
1637 * @return The path provider to the test result directory.
1638 * @throws NullPointerException On null arguments.
1639 * @since 2020/11/26
1641 public static Provider<Path> testResultsCsvDir(Project __project,
1642 SourceTargetClassifier __classifier)
1643 throws NullPointerException
1645 if (__project == null || __classifier == null)
1646 throw new NullPointerException("NARG");
1648 return __project.provider(() -> VMHelpers.cacheDir(
1649 __project, __classifier).get().resolve("csv"));
1653 * Returns the name for the CSV file.
1655 * @param __project The project this falls under.
1656 * @return The path of the CSV results file.
1657 * @throws NullPointerException On null arguments.
1658 * @since 2020/11/26
1660 public static Path testResultsCsvName(Project __project)
1661 throws NullPointerException
1663 if (__project == null)
1664 throw new NullPointerException("NARG");
1666 return Paths.get("RESULTS-" + __project.getName() + ".csv");
1670 * Returns the unassisted launch entry.
1672 * @param __cfg The configuration to get from.
1673 * @param __midlet The MIDlet to load.
1674 * @return The unassisted launch entry for the given MIDlet.
1675 * @throws NullPointerException If no configuration was specified.
1676 * @since 2021/08/22
1678 public static UnassistedLaunchEntry unassistedLaunch(
1679 SquirrelJMEPluginConfiguration __cfg, JavaMEMidlet __midlet)
1680 throws NullPointerException
1682 if (__cfg == null)
1683 throw new NullPointerException("NARG");
1685 // Starting arguments?
1686 String[] args;
1687 if (__midlet != null)
1688 args = new String[]{__midlet.mainClass};
1689 else
1690 args = new String[0];
1692 return new UnassistedLaunchEntry(
1693 VMHelpers.mainClass(__cfg, __midlet),
1694 args);
1698 * Checks if this is the single test to run, depending if multi-parameters
1699 * are used or not.
1701 * @param __key The key to check.
1702 * @param __singleTest The single test that was requested.
1703 * @return If this is the matching single test.
1704 * @throws NullPointerException On null arguments.
1705 * @since 2020/10/11
1707 private static boolean __isSingleTest(String __key, String __singleTest)
1708 throws NullPointerException
1710 if (__key == null || __singleTest == null)
1711 throw new NullPointerException("NARG");
1713 // If the test does not have a multi-parameter match it exactly.
1714 // However if we requested a specific multi-parameter then match that
1715 // as well.
1716 // Convert slashes to dots as well for binary name usage
1717 int la = __key.indexOf('@');
1718 if (la < 0 || __singleTest.indexOf('@') >= 0)
1719 return __key.equals(__singleTest) ||
1720 __key.equals(__singleTest.replace('/', '.'));
1722 // Only match by the basename, if multi-parameter assume all of them
1723 // But also convert all slashes to dots in the event binary names
1724 // are used.
1725 return __key.substring(0, la).equals(__singleTest) ||
1726 __key.substring(0, la).equals(
1727 __singleTest.replace('/', '.'));
1731 * Loads the expected results for a test.
1733 * @param __testName The test being parsed.
1734 * @param __candidates The candidates available, used for super classes.
1735 * @return The expected results.
1736 * @throws NullPointerException On null arguments.
1737 * @since 2020/10/09
1739 private static Manifest __loadExpectedResults(String __testName,
1740 Map<String, CandidateTestFiles> __candidates)
1741 throws NullPointerException
1743 if (__testName == null || __candidates == null)
1744 throw new NullPointerException("NARG");
1746 // If there is no candidate for this test, always just return a
1747 // blank manifest to be parsed
1748 CandidateTestFiles candidate = __candidates.get(__testName);
1749 if (candidate == null)
1750 return new Manifest();
1752 // Load information gleaned from the source code
1753 __SourceInfo__ info;
1754 try (InputStream in = Files.newInputStream(
1755 candidate.sourceCode.getAbsolute(), StandardOpenOption.READ))
1757 String extension = VMHelpers.getExtension(
1758 candidate.sourceCode.getAbsolute());
1759 if ("class".equals(extension))
1760 info = __SourceInfo__.loadClass(in);
1761 else if ("j".equals(extension))
1762 info = __SourceInfo__.loadJasmin(in);
1763 else
1764 info = __SourceInfo__.loadJava(in);
1766 catch (IOException e)
1768 throw new RuntimeException("Could not parse source: " +
1769 __testName, e);
1772 // Read the current manifest
1773 Manifest over;
1774 if (candidate.expectedResult == null)
1775 over = new Manifest();
1776 else
1777 try (InputStream in = Files.newInputStream(
1778 candidate.expectedResult.getAbsolute(), StandardOpenOption.READ))
1780 over = new Manifest(in);
1782 catch (IOException e)
1784 throw new RuntimeException("Could not parse manifest: " +
1785 __testName, e);
1788 // If there is no super class there is no need to even try reading or
1789 // merging any of them
1790 if (info.superClass == null)
1791 return over;
1793 Attributes overAttr = over.getMainAttributes();
1795 // Load super class information
1796 VMHelpers.__loadExpectedResultsSub(overAttr, info.superClass,
1797 __candidates);
1799 // And interfaces...
1800 for (String implementsClass : info.implementsClasses)
1801 VMHelpers.__loadExpectedResultsSub(overAttr, implementsClass,
1802 __candidates);
1804 return over;
1808 * Loads the expected classes from the result.
1810 * @param __overAttr The attributes to potentially write over.
1811 * @param __class The class to check.
1812 * @param __candidates The candidates for finding the class.
1813 * @since 2022/09/05
1815 private static void __loadExpectedResultsSub(Attributes __overAttr,
1816 String __class, Map<String, CandidateTestFiles> __candidates)
1818 // Load the manifest that belongs to this class if it is possible
1819 Manifest under = VMHelpers.__loadExpectedResults(__class,
1820 __candidates);
1822 // Add the underlying manifest, provided it does not replace anything
1823 // on the higher level
1824 for (Map.Entry<Object, Object> e : under.getMainAttributes()
1825 .entrySet())
1826 __overAttr.putIfAbsent(e.getKey(), e.getValue());
1830 * Parses multi-parameters from test results.
1832 * @param __expected The expected results to parse.
1833 * @return The multi-parameters, if any.
1834 * @throws NullPointerException On null arguments.
1835 * @since 2020/10/09
1837 private static Collection<String> __parseMultiParams(
1838 Manifest __expected)
1839 throws NullPointerException
1841 if (__expected == null)
1842 throw new NullPointerException("NARG");
1844 // Are there hyperparameters?
1845 String hyperIn = __expected.getMainAttributes()
1846 .getValue(VMHelpers._HYPER_PARAMETERS_KEY);
1848 // Get the possible parameter values
1849 String multiIn = __expected.getMainAttributes()
1850 .getValue(VMHelpers._MULTI_PARAMETERS_KEY);
1852 // Do nothing if there is neither
1853 if (hyperIn == null && multiIn == null)
1854 return null;
1856 // Split fields,
1857 String[] hyperSplit = (hyperIn == null ? new String[0] :
1858 hyperIn.trim().split("[ \t]"));
1859 String[] multiSplit = (multiIn == null ? new String[0] :
1860 multiIn.trim().split("[ \t]"));
1862 // Has both parameters
1863 if (hyperSplit.length > 0 && multiSplit.length > 0)
1865 List<String> result = new ArrayList<>(
1866 hyperSplit.length * multiSplit.length);
1868 // Combine every possible variant of this
1869 for (String hyper : hyperSplit)
1870 for (String multi : multiSplit)
1871 result.add(hyper + "@" + multi);
1873 return result;
1876 // Has only one
1877 else if (hyperSplit.length > 0)
1878 return Arrays.asList(hyperSplit);
1879 else if (multiSplit.length > 0)
1880 return Arrays.asList(multiSplit);
1882 // Has nothing
1883 return null;