All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.jct.jshell.LocalContextExecutionControlProvider Maven / Gradle / Ivy


/*
 * Copyright (C) 2023 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.jct.jshell;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import jdk.jshell.execution.LocalExecutionControl;
import jdk.jshell.execution.LocalExecutionControlProvider;
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionControlProvider;
import jdk.jshell.spi.ExecutionEnv;

/**
 * Same as {@link LocalExecutionControlProvider}, but with additional support for handling certain
 * class path and class loading issues.
 *
 * 

* When executing JShell locally, it is often desirable that any classes visible to the thread * that starts JShell should also be visible to any scripts and command inputs given to that JShell * instance. * *

* Unfortunately, this doesn't always happen automatically when using the standard {@code "local"} * execution provided by {@link LocalExecutionControl}. * *

* When executing JShell locally, there are two class path/loading issues to worry about: *

    *
  • When JShell compiles source snippets, what classes are available on the "source class path"? * That is, what class names can you refer to by name in your scripts or snippets? *
  • When a compiled script or snippet is loaded as a Java class file by the execution engine, what * classes are available on the "binary class path" when resolving symbolic references? *
* *

* The standard {@link LocalExecutionControl} requires non-standard classes to be explicitly added * via the {@code --class-path} command line flag. Moreover, the {@link ClassLoader} that is uses delegates * to the system class loader, which means that in certain more complex class loading scenarios (for example, * when running as a servlet in the Tomcat web container), compiled snippets classes will fail to load * due to resolution errors. * *

* This class tries to workaround these issues as follows: *

    *
  • To address the "binary class path", this class uses a {@link ClassLoader} that delegates * to the current thread's context class loader instead of the system class loader. This should * fix linking problems in complex class loading scenarios. *
  • To address the "souce class path", this class introspects the current thread's context class * loader and its parents (recursively), attempting to glean what's on the class path. This works * for any {@link ClassLoader} that are instances of {@link URLClassLoader}. However, Java's standard * application class loader is not, so items on the JVM application class path are missed by this * strategy. To include the application class loader, hacky instrospection relying on illegal accesses * is attempted (failures are silently ignored). To ensure these efforts succeed, the following flag * must be added to JVM startup: {@code --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED}. * Note there is also another ugly workarond, which is to write JShell code that only accesses classes * on the application class path via reflection. *
* *

* To utilize this class, include the flags returned by {@link #generateJShellFlags generateJShellFlags()} * as parameters to JShell startup. */ public class LocalContextExecutionControlProvider implements ExecutionControlProvider { public static final String NAME = "localContext"; // Public Methods /** * Generate a list of JShell tool command line flags to enable use of this class. * * @param loader loader to copy from, or null for the current thread's context class loader * @throws IllegalArgumentException if {@code loader} is null * @return JShell flags to enable the local context execution workaround */ public static List generateJShellFlags(ClassLoader loader) { if (loader == null) loader = Thread.currentThread().getContextClassLoader(); final StringBuilder classPath = new StringBuilder(); for ( ; loader != null; loader = loader.getParent()) { // Extract classpath URLs from this loader final URL[] urls; if (loader instanceof URLClassLoader) urls = ((URLClassLoader)loader).getURLs(); else { try { // Ugly hack; we are trying to do this: "urls = loader.ucp.getURLs();" final Field field = loader.getClass().getDeclaredField("ucp"); field.setAccessible(true); final Object ucp = field.get(loader); final Method method = ucp.getClass().getMethod("getURLs"); urls = (URL[])method.invoke(ucp); } catch (ReflectiveOperationException | SecurityException e) { continue; } } // Pass these URLs to JShell by adding to our "--class-path" flag for (URL url : urls) { final URI uri; try { uri = url.toURI(); } catch (URISyntaxException e) { continue; } final File file; try { file = Paths.get(uri).toFile(); } catch (IllegalArgumentException | FileSystemNotFoundException e) { continue; } if (classPath.length() > 0) classPath.append(':'); classPath.append(file.toString()); } } // Build list of flags final ArrayList list = new ArrayList<>(4); list.add("--execution"); list.add(NAME); if (classPath.length() > 0) { list.add("--class-path"); list.add(classPath.toString()); } // Done return list; } // ExecutionControlProvider @Override public String name() { return NAME; } @Override public Map defaultParameters() { return ExecutionControlProvider.super.defaultParameters(); } @Override public ExecutionControl generate(ExecutionEnv env, Map params) { // Create our class loader final MemoryClassLoader memoryLoader = this.createMemoryClassLoader(); // Set our class loader as the context loader for the current thread. // Note: this action gets undone in JShellShellSession.doExecute(). Thread.currentThread().setContextClassLoader(memoryLoader); // Create our delegate thingie final MemoryLoaderDelegate delegate = this.createMemoryLoaderDelegate(memoryLoader); // Create local ExecutionControl using delegate return new LocalExecutionControl(delegate); } // Subclass Methods protected MemoryLoaderDelegate createMemoryLoaderDelegate(MemoryClassLoader memoryLoader) { return new MemoryLoaderDelegate(memoryLoader); } protected MemoryClassLoader createMemoryClassLoader() { return new MemoryClassLoader(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy