emulators/emulator-base/src/main/java/cc/squirreljme/emulator/vm/VMFactory.java
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
// Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------
package cc.squirreljme.emulator.vm;
import cc.squirreljme.emulator.profiler.ProfilerSnapshot;
import cc.squirreljme.jdwp.host.JDWPHostFactory;
import cc.squirreljme.jvm.launch.Application;
import cc.squirreljme.jvm.launch.AvailableSuites;
import cc.squirreljme.jvm.launch.SuiteScanner;
import cc.squirreljme.jvm.mle.brackets.JarPackageBracket;
import cc.squirreljme.jvm.suite.EntryPoint;
import cc.squirreljme.jvm.suite.SuiteUtils;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import cc.squirreljme.runtime.cldc.full.SystemPathProvider;
import cc.squirreljme.vm.DataContainerLibrary;
import cc.squirreljme.vm.JarClassLibrary;
import cc.squirreljme.vm.NameOverrideClassLibrary;
import cc.squirreljme.vm.SummerCoatJarLibrary;
import cc.squirreljme.vm.VMClassLibrary;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.jar.Manifest;
/**
* This class is used to initialize virtual machines based on a set of factory
* classes.
*
* This was added because there were many cases where tons of code was
* duplicated to initialize the virtual machine, which were effectively
* copy and pasted to each other so this will remove that.
*
* This uses the service loader to locate factories which are available.
*
* @since 2018/11/17
*/
public abstract class VMFactory
{
/** Default main class when acting standalone. */
private static final String STANDALONE_MAIN_CLASS =
"X-SquirrelJME-Standalone-Main-Class";
/** Default parameter. */
private static final String STANDALONE_PARAMETER =
"X-SquirrelJME-Standalone-Parameter";
/** Default class path when acting standalone. */
private static final String STANDALONE_CLASSPATH =
"X-SquirrelJME-Standalone-Classpath";
/** Standalone library. */
private static final String STANDALONE_LIBRARY =
"X-SquirrelJME-Standalone-Library";
/** Internal JAR directory root. */
private static final String STANDALONE_DIRECTORY =
"X-SquirrelJME-Standalone-Internal-Jar-Root";
/** Internal JAR directory root (debug). */
private static final String STANDALONE_DIRECTORY_DEBUG =
"X-SquirrelJME-Standalone-Internal-Debug-Jar-Root";
/** The separator character. */
private static final char SEPARATOR_CHAR;
/** The name of the VM implementation. */
protected final String name;
static
{
// Determine the path separator character
String sepString = System.getProperty("path.separator");
SEPARATOR_CHAR = (sepString == null || sepString.isEmpty() ? ':' :
sepString.charAt(0));
}
/**
* Initializes the factory.
*
* @param __name The name of the virtual machine.
* @throws NullPointerException On null arguments.
* @since 2018/11/17
*/
public VMFactory(String __name)
throws NullPointerException
{
if (__name == null)
throw new NullPointerException("NARG");
this.name = __name;
}
/**
* Creates the virtual machine using the given parameters.
*
* @param __profiler The profiler snapshot to write to.
* @param __jdwp The debugger to use.
* @param __threadModel The threading model to use.
* @param __suiteManager The suite manager.
* @param __classpath The classpath to initialize with.
* @param __mainClass The main class to start executing.
* @param __sysProps System properties for the running program.
* @param __args Arguments for the running program.
* @return An instance of the virtual machine.
* @throws IllegalArgumentException If an input argument is not valid.
* @throws NullPointerException On null arguments.
* @throws VMException If the virtual machine could not be created.
* @since 2018/11/17
*/
protected abstract VirtualMachine createVM(ProfilerSnapshot __profiler,
JDWPHostFactory __jdwp, VMThreadModel __threadModel,
VMSuiteManager __suiteManager, VMClassLibrary[] __classpath,
String __mainClass, Map<String, String> __sysProps, String[] __args)
throws IllegalArgumentException, NullPointerException, VMException;
/**
* Main entry point for general programs.
*
* @param __args Arguments.
* @since 2020/02/29
*/
public static void main(String... __args)
{
// Default settings
String vmName = "springcoat";
Path snapshotPath = null;
Collection<String> libraries = new LinkedList<>();
Collection<String> suiteClasspath = new LinkedList<>();
Map<String, String> systemProperties = new LinkedHashMap<>();
// There always is a profiler being run, just differs if we save it
ProfilerSnapshot profilerSnapshot = new ProfilerSnapshot();
// Debugging host and port, if enabled
String jdwpHost = null;
int jdwpPort = -1;
boolean internalDebug = false;
// Threading model
VMThreadModel threadModel = VMThreadModel.DEFAULT;
// Load our own META-INF/MANIFEST.MF for some special properties
Manifest metaManifest = null;
try (InputStream in = VMFactory.class
.getResourceAsStream("/META-INF/MANIFEST.MF"))
{
Debugging.debugNote("GOT MANIFEST: %s", in);
if (in != null)
metaManifest = new Manifest(in);
}
catch (IOException e)
{
e.printStackTrace();
}
// Initial trace bits
int initTraceBits = 0;
// Was the -jar switch used?
boolean didJar = false;
String rawJarPath = null;
String rawJarEntry = null;
// Clutter level of the library
String clutterLevel = "release";
// Load in standard system properties
VMFactory.__standardSysProps(systemProperties);
// Load in standard paths
VMFactory.__standardPaths(libraries);
// Command line format is:
// -Xemulator:(vm)
// -Xsnapshot:(path-to-nps)
// -Xentry:id
// -Xlibraries:(class:path:...)
// -Xjdwp:[hostname]:port
// -Xthread:(single|coop|multi|smt)
// -Dsysprop=value
// -classpath (class:path:...)
// -Xclutter:(release|debug)
// -Xtrace=(flag|...)
// Optionally `-jar`
// Main-class
// Arguments...
Deque<String> queue = new LinkedList<>(Arrays.<String>asList(__args));
while (!queue.isEmpty())
{
// End of our items?
String item = queue.peekFirst();
if (item == null || item.isEmpty() || item.charAt(0) != '-')
break;
// Eat it up
queue.removeFirst();
// Thread model
if (item.startsWith("-Xthread:"))
threadModel = VMThreadModel.of(
item.substring("-Xthread:".length()));
// JDWP Usage
else if (item.startsWith("-Xjdwp:"))
{
String hostPort = item.substring("-Xjdwp:".length());
// Figure the hostname/port split
int lastCol = hostPort.lastIndexOf(':');
if (lastCol < 0)
throw new IllegalArgumentException(String.format(
"Expected %s to be like -Xjdwp:[hostname]:port.",
item));
// Split hostname and port
jdwpHost = hostPort.substring(0, lastCol);
jdwpPort = Integer.parseInt(
hostPort.substring(lastCol + 1));
}
// Direct debugger usage
else if (item.startsWith("-Xdebug"))
{
// Just set this flag
internalDebug = true;
}
// Select a VM
else if (item.startsWith("-Xemulator:"))
vmName = item.substring("-Xemulator:".length());
// VisualVM Snapshot Dump path
else if (item.startsWith("-Xsnapshot:"))
snapshotPath = Paths.get(
item.substring("-Xsnapshot:".length()));
// System property
else if (item.startsWith("-D"))
{
int equalDx = item.indexOf('=');
if (equalDx < 0)
systemProperties.put(item.substring(2), "");
else
systemProperties.put(item.substring(2, equalDx),
item.substring(equalDx + 1));
}
// Libraries to make available to the virtual machine
else if (item.startsWith("-Xlibraries:"))
{
for (String entry : VMFactory.__unSeparateClassPath(
item.substring(item.indexOf(':') + 1)))
VMFactory.__addPaths(libraries, entry);
}
// Jar entry point selection
else if (item.startsWith("-Xentry:"))
{
rawJarEntry = item.substring("-Xentry:".length());
}
// Initial trace options
else if (item.startsWith("-Xtrace:"))
{
initTraceBits = VMTraceFlagTracker.parseBits(
item.substring("-Xtrace:".length()));
}
// Clutter level to use
else if (item.startsWith("-Xclutter:"))
clutterLevel = item.substring("-Xclutter:".length());
// JARs to load
else if (item.equals("-classpath") || item.equals("-cp"))
{
// Get argument attached to this
String strings = queue.pollFirst();
if (strings == null)
throw new IllegalArgumentException("Classpath missing.");
// Extract path elements
for (String entry : VMFactory.__unSeparateClassPath(strings))
VMFactory.__addPaths(suiteClasspath, entry);
}
// Direct Jar launch
else if (item.equals("-jar"))
{
// Get Jar attached to this
String string = queue.pollFirst();
if (string == null)
throw new IllegalArgumentException(
"Jar argument missing.");
// We use this Jar
rawJarPath = string;
// We stop everything and just parse everything else as a Jar
// directly...
didJar = true;
break;
}
// Unknown
else
throw new IllegalArgumentException(String.format(
"Unknown command line switch: %s", item));
}
// These options are mutually exclusive...
if (internalDebug && (jdwpHost != null || jdwpPort >= 1))
throw new IllegalArgumentException(
"-Xdebug and -Xjdwp are mutually exclusive.");
// Main program arguments
List<String> mainArgs = new LinkedList<>();
// Default built in libraries, if available?
if (metaManifest != null)
{
String defLib = metaManifest.getMainAttributes().getValue(
VMFactory.STANDALONE_LIBRARY);
if (defLib != null && !defLib.isEmpty())
for (String entry : VMFactory.__unSeparateClassPath(defLib))
VMFactory.__addPaths(libraries, entry);
}
// Did not do -jar, so do normal command line parse
String mainClass;
if (!didJar)
{
// Main class is here
mainClass = queue.pollFirst();
if (mainClass == null || mainClass.isEmpty())
{
// Try from the manifest
if (metaManifest != null)
mainClass = metaManifest.getMainAttributes().getValue(
VMFactory.STANDALONE_MAIN_CLASS);
// Still failed?
if (mainClass == null || mainClass.isEmpty())
throw new IllegalArgumentException(
"No main class specified.");
// Default class path for launching
String defCp = metaManifest.getMainAttributes().getValue(
VMFactory.STANDALONE_CLASSPATH);
if (defCp != null && !defCp.isEmpty())
for (String entry : VMFactory.__unSeparateClassPath(defCp))
VMFactory.__addPaths(suiteClasspath, entry);
// Default parameter?
String defParam = metaManifest.getMainAttributes().getValue(
VMFactory.STANDALONE_PARAMETER);
if (defParam != null && !defParam.isEmpty())
mainArgs.add(defParam);
}
}
else
{
// Make sure this exists in the library path
if (!libraries.contains(rawJarPath))
libraries.add(rawJarPath);
mainClass = null;
}
// Fill in the rest with the main argument calls
while (!queue.isEmpty())
mainArgs.add(queue.removeFirst());
// Implicitly include all the classes specified as part of suites
// to be part of the library path
libraries.addAll(suiteClasspath);
// Standalone directory, if one is passed through the JAR?
ResourceBasedSuiteManager standaloneDir = null;
if (metaManifest != null)
{
String prefix;
if (Objects.equals("debug", clutterLevel))
prefix = metaManifest.getMainAttributes()
.getValue(VMFactory.STANDALONE_DIRECTORY_DEBUG);
else
prefix = metaManifest.getMainAttributes()
.getValue(VMFactory.STANDALONE_DIRECTORY);
// If it exists, use it!
if (prefix != null)
standaloneDir = new ResourceBasedSuiteManager(
VMFactory.class, prefix);
}
// Found Jar library?
VMClassLibrary jarLib = null;
// Determine any suites that are available in the suite library
Map<String, VMClassLibrary> suites = new LinkedHashMap<>();
for (String library : libraries)
{
Path path = Paths.get(library);
// Do not add the same libraries multiple times
String normalName = VMFactory.__normalizeName(
path.getFileName().toString());
if (suites.containsKey(normalName))
continue;
// Note it
Debugging.debugNote("Registering %s (%s)",
normalName, path);
// Is there a built-in resource based for this JAR itself?
VMClassLibrary place;
if (standaloneDir != null &&
standaloneDir.loadLibrary(normalName) != null)
place = standaloneDir.loadLibrary(normalName);
// Treat SQCs special in that they have a specific resource for
// their ROM data
else if (SummerCoatJarLibrary.isSqc(path))
place = new SummerCoatJarLibrary(path);
else if (JarClassLibrary.isJar(path))
place = JarClassLibrary.of(path);
// Is just normalized data
else
place = new DataContainerLibrary(path);
// Place in the class library, but make sure the name matches
// the normalized name of the JAR
VMClassLibrary target =
new NameOverrideClassLibrary(place, normalName);
suites.put(normalName, target);
// Is this a Jar we are launching?
if (rawJarPath != null)
if (rawJarPath.equals(normalName) ||
rawJarPath.equals(library))
jarLib = target;
}
// Go through the class path and normalize the names so that it finds
// the correct JAR files
Collection<String> classpath = new LinkedList<>();
for (String classItem : suiteClasspath)
classpath.add(VMFactory.__normalizeName(
Paths.get(classItem).getFileName().toString()));
// Now that we loaded in all the libraries we can do the resolution
// for the -jar switch
if (didJar)
{
// Initialize fake shelf
FakeJarPackageShelf fakeShelf = new FakeJarPackageShelf(suites);
// No original launching Jar found?
if (jarLib == null)
throw new IllegalArgumentException(
"Could not find the original Jar?");
// Map to a fake jar
JarPackageBracket fakeJar = new FakeJarPackageBracket(jarLib);
// Setup suite scanner to use our fake suite list and combined
// libraries accordingly, scan all suites to get available
// applications we can potentially launch
SuiteScanner scanner = new SuiteScanner(false, fakeShelf);
AvailableSuites available = scanner.scanSuites();
// Find applications for our Jar
Application[] apps = available.findApplications(fakeJar);
if (apps == null || apps.length == 0)
throw new IllegalArgumentException("Found no applications " +
"within jar: " + rawJarPath);
// Debug note them
for (int i = 0, n = apps.length; i < n; i++)
Debugging.debugNote("Application %d: %s",
i, apps[i].entryPoint());
// Which index are we launching?
int launchIndex;
if (rawJarEntry == null)
launchIndex = 0;
else
{
// Mappable to integer?
try
{
launchIndex = Integer.parseInt(rawJarEntry);
}
catch (NumberFormatException ignored)
{
launchIndex = 0;
for (int i = 0, n = apps.length; i < n; i++)
{
Application app = apps[i];
if (rawJarEntry.equals(
app.entryPoint().name()) || rawJarEntry.equals(
app.entryPoint().entryPoint()))
{
launchIndex = i;
break;
}
}
}
}
// Fill in launch information accordingly
Application app = apps[launchIndex];
EntryPoint appEntry = app.entryPoint();
// There might need to be a helper for this
mainClass = app.loaderEntryClass();
if (mainClass == null)
mainClass = appEntry.entryPoint();
// Extract any needed system properties
Map<String, String> wantProps = app.loaderSystemProperties();
if (wantProps != null)
systemProperties.putAll(wantProps);
// Do we need special loader arguments to pass before this, so
// it can correctly launch?
String[] loaderArgs = app.loaderEntryArgs();
if (loaderArgs != null && loaderArgs.length > 0)
mainArgs.addAll(0, Arrays.asList(loaderArgs));
// Need to use the classpath to run the jar with
classpath.clear();
for (JarPackageBracket jar : app.classPath())
{
// Get the original path
String path = fakeShelf.libraryPath(jar);
// Debug
Debugging.debugNote("Adding into classpath: %s",
path);
// Add it
classpath.add(path);
}
}
// Run the VM, but always make sure we can
int exitCode = -1;
try
{
// Debug
Debugging.debugNote("Starting virtual machine (in %s)...",
mainClass);
Debugging.debugNote("Args: %s", Arrays.asList(__args));
// Run the VM
VirtualMachine vm = VMFactory.mainVm(vmName,
profilerSnapshot,
(internalDebug ? VMFactory.__setupJdwpInternal() :
(jdwpPort >= 1 ?
VMFactory.__setupJdwp(jdwpHost, jdwpPort) : null)),
threadModel,
new ArraySuiteManager(suites.values()),
classpath.<String>toArray(new String[classpath.size()]),
mainClass,
systemProperties,
mainArgs.<String>toArray(new String[mainArgs.size()]));
// Set global trace bits for the VM
if (VMTraceFlagTracker.GLOBAL_TRACING_BITS != 0)
vm.setTraceBits(true,
VMTraceFlagTracker.GLOBAL_TRACING_BITS);
// Set trace bits for the VM
if (initTraceBits != 0)
vm.setTraceBits(true,
initTraceBits);
// Run the virtual machine until it exits, but do not exit yet
// because we want the snapshot to be created
exitCode = vm.runVm();
}
// Always write the snapshot file
finally
{
if (snapshotPath != null)
{
// Create directory where it goes
try
{
Files.createDirectories(snapshotPath.getParent());
}
catch (IOException e)
{
// Ignore
}
// Write file
try (OutputStream out = Files.newOutputStream(snapshotPath,
StandardOpenOption.WRITE, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING))
{
profilerSnapshot.writeTo(out);
}
catch (IOException e)
{
// Ignore
}
}
}
// Exit with the exit code the VM gave us back
System.exit(exitCode);
}
/**
* Main entry point for the virtual machine using the given properties.
*
* @param __vm The name of the virtual machine to use, if {@code null}
* then this is automatically determined.
* @param __ps The profiler snapshot to use.
* @param __jdwp The debugger to use.
* @param __threadModel The threading model.
* @param __sm The suite manager used.
* @param __cp The starting class path.
* @param __bootcl The booting class, if {@code null} then {@code __bootid}
* is used instead.
* @param __sprops System properties to pass to the target VM.
* @param __args Arguments to the program which is running.
* @return The created virtual machine.
* @throws IllegalArgumentException If neither a boot class or boot ID
* were specified.
* @throws NullPointerException On null arguments.
* @throws VMException If the virtual machine failed to initialize.
* @since 2018/11/17
*/
public static VirtualMachine mainVm(String __vm, ProfilerSnapshot __ps,
JDWPHostFactory __jdwp, VMThreadModel __threadModel, VMSuiteManager __sm,
String[] __cp,
String __bootcl, Map<String, String> __sprops, String... __args)
throws IllegalArgumentException, NullPointerException, VMException
{
if (__bootcl == null || __sm == null || __cp == null)
throw new NullPointerException("NARG");
// Always exists
__args = (__args == null ? new String[0] : __args.clone());
__sprops = (__sprops == null ? new HashMap<String, String>() :
new HashMap<String, String>(__sprops));
// If not specified, check the system property that specifies the VM
if (__vm == null)
try
{
// {@squirreljme.property cc.squirreljme.vm.name Specifies the
// name of the virtual machine to use.}
__vm = VMFactory.__getProperty(__sprops,
"cc.squirreljme.vm.name");
}
catch (SecurityException e)
{
}
// If none specified, default to SpringCoat
if (__vm == null)
__vm = "springcoat";
// Determine the virtual machine to use
VMFactory factory = null;
for (VMFactory f : ServiceLoader.<VMFactory>load(VMFactory.class))
{
// If no name was specified then use the first one, otherwise
// use the one which matches the name
if (f.name.equalsIgnoreCase(__vm))
{
factory = f;
break;
}
}
/* {@squirreljme.error AK03 The specified virtual machine does not
exist. (The virtual machine name)} */
if (factory == null)
throw new VMException("AK03 " + __vm);
// Always make a profiler snapshot exist
if (__ps == null)
__ps = new ProfilerSnapshot();
// Go through the suites and load the classpath that we are using
int numlibs = __cp.length;
VMClassLibrary[] classpath = new VMClassLibrary[numlibs];
for (int i = 0; i < numlibs; i++)
{
VMClassLibrary lib = __sm.loadLibrary(__cp[i]);
if (lib == null)
throw new IllegalArgumentException(String.format(
"Library %s not in classpath!", __cp[i]));
classpath[i] = lib;
}
// Create the virtual machine now that everything is available
return factory.createVM(__ps, __jdwp, __threadModel, __sm, classpath,
__bootcl, __sprops, __args);
}
/**
* Adds paths to the collection of files for the classpath usage.
*
* @param __files The target files.
* @param __path The path to evaluate.
* @throws NullPointerException On null arguments.
* @since 2020/04/19
*/
@SuppressWarnings("SingleCharacterStartsWith")
private static void __addPaths(Collection<String> __files, String __path)
throws NullPointerException
{
if (__files == null || __path == null)
throw new NullPointerException("NARG");
// Add directly if not a wildcard
if (!__path.endsWith("*") && !__path.startsWith("wildcard="))
{
__files.add(__path);
return;
}
// Try multiple different wildcard types
String basePath;
if (__path.startsWith("wildcard="))
basePath = __path.substring("wildcard=".length());
else if (__path.endsWith("*.*"))
basePath = __path.substring(0, __path.length() - 3);
else if (__path.endsWith("**"))
basePath = __path.substring(0, __path.length() - 2);
else
basePath = __path.substring(0, __path.length() - 1);
// Realize it
VMFactory.__addPathsWildcard(__files, basePath);
}
/**
* Adds wildcard directory.
*
* @param __files The files to place into.
* @param __basePath The base path.
* @since 2024/02/25
*/
private static void __addPathsWildcard(Collection<String> __files,
String __basePath)
throws NullPointerException
{
if (__files == null || __basePath == null)
throw new NullPointerException("NARG");
VMFactory.__addPathsWildcard(__files, Paths.get(__basePath));
}
/**
* Adds wildcard directory.
*
* @param __files The files to place into.
* @param __basePath The base path.
* @since 2024/02/25
*/
private static void __addPathsWildcard(Collection<String> __files,
Path __basePath)
throws NullPointerException
{
if (__files == null || __basePath == null)
throw new NullPointerException("NARG");
try
{
// Ignore if not a directory
if (!Files.isDirectory(__basePath))
return;
Files.walkFileTree(__basePath,
new HashSet<FileVisitOption>(
Arrays.asList(FileVisitOption.FOLLOW_LINKS)),
64,
new __JarWalker__(__files));
}
catch (IOException __e)
{
__e.printStackTrace();
}
}
/**
* Tries to get a property from a passed map otherwise reads from the
* system properties used.
*
* @param __props The properties to check first.
* @param __key The key to get.
* @return The value for the given key, {@code null} means it is not set.
* @throws NullPointerException On null arguments.
* @since 2019/01/23
*/
private static String __getProperty(Map<String, String> __props,
String __key)
throws NullPointerException
{
return VMFactory.__getProperty(__props, __key, null);
}
/**
* Tries to get a property from a passed map otherwise reads from the
* system properties used.
*
* @param __props The properties to check first.
* @param __key The key to get.
* @param __def Default value.
* @return The value for the given key, {@code __def} means it is not set.
* @throws NullPointerException On null arguments.
* @since 2019/01/23
*/
private static String __getProperty(Map<String, String> __props,
String __key, String __def)
throws NullPointerException
{
if (__props == null || __key == null)
throw new NullPointerException("NARG");
// Grab from these properties first
String rv = __props.get(__key);
if (rv != null)
return rv;
// Otherwise try a property instead
try
{
rv = System.getProperty(__key);
if (rv != null)
return rv;
return __def;
}
catch (SecurityException e)
{
return __def;
}
}
/**
* Maps class libraries to names.
*
* @param __libs The values to map to name.
* @return Class libraries to strings.
* @throws NullPointerException On null arguments.
* @since 2020/05/28
*/
private static Collection<String> __libraryNames(
Iterable<VMClassLibrary> __libs)
throws NullPointerException
{
if (__libs == null)
throw new NullPointerException("NARG");
Collection<String> rv = new LinkedList<>();
for (VMClassLibrary lib : __libs)
rv.add(lib.name());
return rv;
}
/**
* Normalizes the name of the library.
*
* @param __name The name of the JAR.
* @return The normalized name.
* @throws NullPointerException On null arguments.
* @since 2020/03/01
*/
private static String __normalizeName(String __name)
throws NullPointerException
{
if (__name == null)
throw new NullPointerException("NARG");
// Not a known extension or normalized type
if (!SuiteUtils.isAny(__name))
return __name;
// Get the base name of the JAR or SQC
int lastDot = __name.lastIndexOf('.');
String ext = __name.substring(lastDot + 1);
__name = __name.substring(0, lastDot);
// Chop down potential foo"-0.4.0" from the end
for (int n = __name.length(), i = n - 1; i >= 0; i--)
{
char c = __name.charAt(i);
// Still potentially a version bit
if (c == '.' || c == '-' || (c >= '0' && c <= '9'))
__name = __name.substring(0, i);
// Do not need
else
break;
}
// Use this name
return __name + "." + ext;
}
/**
* Sets up JDWP stream for connection.
*
* @param __host The hostname to use, if {@code null} this will be
* a server.
* @param __port The port to listen on.
* @since 2021/03/08
*/
private static JDWPHostFactory __setupJdwp(String __host, int __port)
{
// Listening?
if (__host == null)
{
throw Debugging.todo();
}
// Try opening the socket
Socket socket = null;
try
{
// Create socket
Debugging.debugNote("Waiting for connection with %s:%d...",
__host, __port);
if (__host == null || __host.isEmpty())
socket = new ServerSocket(__port).accept();
else
socket = new Socket(__host, __port);
// Use factory to create it
return new JDWPHostFactory(socket.getInputStream(),
socket.getOutputStream());
}
// Could not open the socket?
catch (IOException e)
{
// Close the socket or try to
if (socket != null)
try
{
socket.close();
}
catch (IOException f)
{
e.addSuppressed(f);
}
throw new RuntimeException(String.format(
"Could not open JDWP socket: %s:%d", __host, __port), e);
}
}
/**
* Sets up an internal JDWP based debugger that is built into SquirrelJME.
*
* @return The factory for creating the buffer.
* @since 2024/01/19
*/
private static JDWPHostFactory __setupJdwpInternal()
{
// Look for service for it
for (VMDebuggerService service :
ServiceLoader.load(VMDebuggerService.class))
{
return service.jdwpFactory();
}
// Not found, does nothing
return null;
}
/**
* Load in standard paths.
*
* @param __libraries The libraries to load into.
* @since 2024/02/25
*/
private static void __standardPaths(Collection<String> __libraries)
{
// Class path to the environment?
String classPath = System.getenv("SQUIRRELJME_CLASSPATH");
if (classPath != null)
for (String path : VMFactory.__unSeparateClassPath(classPath))
VMFactory.__addPathsWildcard(__libraries, path);
// Java Home Directory?
String rawJavaHome = System.getenv("SQUIRRELJME_JAVA_HOME");
if (rawJavaHome != null)
{
Path javaHome = Paths.get(rawJavaHome);
VMFactory.__addPathsWildcard(__libraries,
javaHome.resolve("lib"));
VMFactory.__addPathsWildcard(__libraries,
javaHome.resolve("jre").resolve("lib"));
}
// Standard data libraries?
SystemPathProvider paths = SystemPathProvider.provider();
Path dataPath = paths.data();
if (dataPath != null)
VMFactory.__addPathsWildcard(__libraries,
dataPath.resolve("lib"));
}
/**
* Loads standard system properties from the environment and
* configuration.
*
* @param __sysProps The system properties to load into.
* @throws NullPointerException On null arguments.
* @since 2024/02/25
*/
private static void __standardSysProps(
Map<String, String> __sysProps)
throws NullPointerException
{
if (__sysProps == null)
throw new NullPointerException("NARG");
SystemPathProvider paths = SystemPathProvider.provider();
// Configuration file, if it exists?
Path configDir = paths.config();
if (configDir != null)
{
Path configFile = configDir.resolve(
"squirreljme.properties");
if (Files.exists(configFile))
try
{
for (String line : Files.readAllLines(configFile))
{
// Comment?
line = line.trim();
if (line.isEmpty() || line.startsWith("#"))
continue;
// Add property?
int eq = line.indexOf('=');
if (eq > 0)
__sysProps.put(line.substring(0, eq).trim(),
line.substring(eq + 1).trim());
}
}
catch (IOException __e)
{
__e.printStackTrace();
}
}
// Extra Java VM options
String javaOpts = System.getenv("SQUIRRELJME_JAVA_OPTS");
if (javaOpts != null)
try
{
// Setup tokenizer
StreamTokenizer tokenizer = new StreamTokenizer(
new StringReader(javaOpts));
tokenizer.resetSyntax();
tokenizer.quoteChar('\"');
tokenizer.quoteChar('\'');
tokenizer.wordChars('.', '.');
tokenizer.wordChars('-', '-');
tokenizer.wordChars('_', '_');
tokenizer.wordChars('a', 'z');
tokenizer.wordChars('A', 'Z');
tokenizer.wordChars('0', '9');
// Handle all tokens
String key = null;
String val = null;
boolean wantKey = true;
boolean wantVal = false;
for (;;)
{
// Read in more tokens
int token = tokenizer.nextToken();
if (token == StreamTokenizer.TT_EOF)
break;
// Token string?
if (tokenizer.sval != null)
{
if (wantKey && tokenizer.sval.startsWith("-D"))
key = tokenizer.sval.substring(2);
else if (wantVal)
{
// Add in key
val = tokenizer.sval;
__sysProps.put(key, val);
// Clear
key = null;
val = null;
// Reset
wantKey = true;
wantVal = false;
}
}
else if (token == '=')
{
if (wantKey)
{
wantKey = false;
wantVal = true;
}
}
}
}
catch (IOException __e)
{
__e.printStackTrace();
}
}
/**
* Merges path entries for the classpath.
*
* @param __in The input string.
* @return The un-separated string.
* @since 2022/06/13
*/
private static String[] __unSeparateClassPath(String __in)
{
List<String> result = new ArrayList<>();
// Extract path elements
for (int i = 0, n = __in.length(); i < n; i++)
{
// Get location of the next colon
int dx = __in.indexOf(VMFactory.SEPARATOR_CHAR, i);
if (dx < 0)
dx = n;
// Add
result.add(__in.substring(i, dx));
// Go to next colon
i = dx;
}
return result.<String>toArray(new String[result.size()]);
}
}