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

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

There is a newer version: 1.1.0
Show newest version

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

package org.dellroad.jct.jshell;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

import jdk.jshell.execution.DirectExecutionControl;
import jdk.jshell.execution.LoaderDelegate;
import jdk.jshell.execution.LocalExecutionControl;

/**
 * A {@link LocalExecutionControl} that provides the ability to add thread-local context to the execution of
 * each JShell snippet and its subsequent result decoding into a {@link String}.
 *
 * 

* It is sometimes useful to have the execution of JShell snippets occur within some thread-local context. For example, * a database application might want to open a transaction around each snippet execution. This is somewhat tricky because * JShell's {@link LocalExecutionControl} executes snippets in a different thread from the main command loop thread so, * for example, using {@link LocalExecutionControl#clientCodeEnter clientCodeEnter()} and * {@link LocalExecutionControl#clientCodeLeave clientCodeLeave()} won't work. * *

* In addition, context may also be necessary during the conversion of the snippet result into a {@link String} for display * in the JShell console. This conversion is performed by {@link DirectExecutionControl#valueString} and is normally done * later, in a separate thread. This class performs this conversion earlier, in the same thread that executes the snippet, * while the context is still available. * *

* The methods a subclass can use to bracket snippet execution and {@link String} result decoding are {@link #enterContext} * and {@code #leaveContext leaveContext()}. */ public class LocalContextExecutionControl extends LocalExecutionControl { private static final HashMap SNIPPET_INVOKE_MAP = new HashMap<>(); private static final Method INVOKE_WRAPPER_METHOD; static { try { INVOKE_WRAPPER_METHOD = LocalContextExecutionControl.class.getDeclaredMethod("invokeWrapper"); } catch (NoSuchMethodException e) { throw new RuntimeException("internal error"); } } // Constructor /** * Constructor. * * @param delegate loader delegate */ public LocalContextExecutionControl(LoaderDelegate delegate) { super(delegate); } // LocalExecutionControl // This is a total hack. Our goal is to access this LocalContextExecutionControl from within the thread // that is actually invoking the target method (which is different from the current thread here). The problem // is the target method must be a static method, so we have to stash "this" somewhere. To accomplish that // we invoke invokeWrapper() instead of "method". Our invokeWrapper() will be invoked in some random // thread whose ThreadGroup's parent ThreadGroup is also this current thread's ThreadGroup. We use that // tenuous connection to retrieve the original method given here and also our current "this" instance. @Override protected String invoke(Method method) throws Exception { // Create InvokeInfo info and its lookup key final InvokeInfo invokeInfo = new InvokeInfo(this, method); final ThreadGroup lookupKey = Thread.currentThread().getThreadGroup(); // Wait for any other snippet thread in my ThreadGroup to finish (can there be any?) synchronized (SNIPPET_INVOKE_MAP) { while (SNIPPET_INVOKE_MAP.containsKey(lookupKey)) SNIPPET_INVOKE_MAP.wait(); SNIPPET_INVOKE_MAP.put(lookupKey, invokeInfo); } // Proceed to invoke the snippet try { return super.invoke(INVOKE_WRAPPER_METHOD); } finally { // Clean up in case somehow invokeWrapper() was never actually invoked final InvokeInfo invokeInfo2; synchronized (SNIPPET_INVOKE_MAP) { invokeInfo2 = SNIPPET_INVOKE_MAP.get(lookupKey); if (invokeInfo2 == invokeInfo) // use object equality to ensure it's ours SNIPPET_INVOKE_MAP.remove(lookupKey); } } } // Subclass Hooks /** * Open a new thread-local context for snippet execution. * *

* The implementation in {@link LocalContextExecutionControl} does nothing. */ protected void enterContext() { } /** * Close the thread-local context previously opened by {@link #enterContext} in the current thread. * *

* The implementation in {@link LocalContextExecutionControl} does nothing. * * @param success true if snippet execution was successful, false if an exception occurred */ protected void leaveContext(boolean success) { } // Internal Methods /** * Execute the given snippet in context. * *

* The implementation in {@link LocalContextExecutionControl} uses {@link #enterContext} and * {@link #leaveContext leaveContext()} to bracket the execution of the snippet and * the decoding of its result (via {@link #decodeWithContext decodeWithContext()}). The decoded * result is wrapped in a {@link StringWrapper} to prevent duplicate decoding by JShell. * * @param method static snippet method * @return result from snippet execution */ protected Object invokeWithContext(Method method) throws Throwable { this.enterContext(); boolean success = false; try { Object result = method.invoke(null); if (result != null) result = new StringWrapper(this.decodeWithContext(result)); success = true; return result; } catch (InvocationTargetException e) { throw e.getCause(); } finally { this.leaveContext(success); } } /** * Decode the snippet result into a {@link String} for display in the JShell console. * *

* The implementation in {@link LocalContextExecutionControl} just invokes {@link DirectExecutionControl#valueString}. */ protected String decodeWithContext(Object result) { return DirectExecutionControl.valueString(result); } // Invoke Wrapper /** * Invocation wrapper method. * *

* This method recovers the current instance and the actual method to invoke from our internal secret mapping * and then delegates to {@link #invokeWithContext invokeWithContext()}. * *

* This method is only used internally but is required to be public due to Java access controls. * * @return result from snippet execution */ public static Object invokeWrapper() throws Throwable { // Get the target method info stashed by invoke() final ThreadGroup lookupKey = Thread.currentThread().getThreadGroup().getParent(); final InvokeInfo invokeInfo; synchronized (SNIPPET_INVOKE_MAP) { invokeInfo = SNIPPET_INVOKE_MAP.remove(lookupKey); } if (invokeInfo == null) throw new RuntimeException("internal error: snippet info not found"); // Proceed return invokeInfo.getControl().invokeWithContext(invokeInfo.getMethod()); } // InvokeInfo private static final class InvokeInfo { private final LocalContextExecutionControl control; private final Method method; InvokeInfo(LocalContextExecutionControl control, Method method) { this.control = control; this.method = method; } public LocalContextExecutionControl getControl() { return this.control; } public Method getMethod() { return this.method; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy