JShell improvements
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 9 May 2023 09:45:23 +0000 (11:45 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 9 May 2023 09:45:23 +0000 (11:45 +0200)
org.argeo.cms.jshell/bnd.bnd
org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsExecutionControl.java [new file with mode: 0644]
org.argeo.cms.jshell/src/org/argeo/cms/jshell/JShellClient.java
org.argeo.cms.jshell/src/org/argeo/cms/jshell/SocketPipeMirror.java
org.argeo.cms.jshell/src/org/argeo/internal/cms/jshell/osgi/OsgiExecutionControlProvider.java

index e575175a82b9f93f193eaa1b5fa049fe95b768b6..5f4b47a6205220df41e28c82eac5602cd2080520 100644 (file)
@@ -1,3 +1,5 @@
+Main-Class: org.argeo.cms.jshell.JShellClient
+
 Import-Package: \
 org.osgi.framework.namespace, \
 *
diff --git a/org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsExecutionControl.java b/org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsExecutionControl.java
new file mode 100644 (file)
index 0000000..5cd575c
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.cms.jshell;
+
+import jdk.jshell.execution.DirectExecutionControl;
+import jdk.jshell.execution.LoaderDelegate;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionEnv;
+
+/** Custom {@link ExecutionControl}. */
+public class CmsExecutionControl extends DirectExecutionControl {
+       private final ExecutionEnv executionEnv;
+
+       public CmsExecutionControl(ExecutionEnv executionEnv, LoaderDelegate loaderDelegate) {
+               super(loaderDelegate);
+               this.executionEnv = executionEnv;
+
+       }
+
+       @Override
+       protected void clientCodeEnter() throws InternalException {
+               super.clientCodeEnter();
+       }
+
+       @Override
+       protected void clientCodeLeave() throws InternalException {
+               super.clientCodeLeave();
+               executionEnv.userOut().flush();
+       }
+
+}
index f6846f196c0192204e54acc5e3e0ec895461c9bc..87e88b77fe2d994014b488b84f5927e143ac4f1a 100644 (file)
@@ -16,6 +16,7 @@ import java.lang.management.ManagementFactory;
 import java.net.StandardSocketOptions;
 import java.net.UnixDomainSocketAddress;
 import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
 import java.nio.channels.Channels;
 import java.nio.channels.ClosedByInterruptException;
 import java.nio.channels.ReadableByteChannel;
@@ -30,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+/** A JShell client to a local CMS node. */
 public class JShellClient {
        private final static Logger logger = System.getLogger(JShellClient.class.getName());
 
@@ -97,7 +99,7 @@ public class JShellClient {
                        ctl.setOutputStream(System.err);
 
                        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-                               System.out.println("\nShutting down...");
+//                             System.out.println("\nShutting down...");
                                toOriginalTerminal();
                                std.shutdown();
                                ctl.shutdown();
@@ -258,7 +260,6 @@ public class JShellClient {
                                if (benchmark)
                                        System.err.println(ManagementFactory.getRuntimeMXBean().getUptime());
                                StringBuilder sb = new StringBuilder();
-//                             sb.append("/set feedback silent\n");
                                if (!scriptArgs.isEmpty()) {
                                        // additional arguments as $1, $2, etc.
                                        for (String arg : scriptArgs)
@@ -267,30 +268,15 @@ public class JShellClient {
                                if (sb.length() > 0)
                                        writeLine(sb);
 
-                               ByteBuffer buffer = ByteBuffer.allocate(1024);
                                try (BufferedReader reader = Files.newBufferedReader(script)) {
                                        String line;
                                        lines: while ((line = reader.readLine()) != null) {
                                                if (line.startsWith("#"))
                                                        continue lines;
-                                               buffer.put((line + "\n").getBytes(UTF_8));
-                                               buffer.flip();
-                                               channel.write(buffer);
-                                               buffer.rewind();
+                                               writeLine(line);
                                        }
                                }
 
-//                             ByteBuffer buffer = ByteBuffer.allocate(1024);
-//                             try (SeekableByteChannel scriptChannel = Files.newByteChannel(script, StandardOpenOption.READ)) {
-//                                     while (channel.isConnected()) {
-//                                             if (scriptChannel.read(buffer) < 0)
-//                                                     break;
-//                                             buffer.flip();
-//                                             channel.write(buffer);
-//                                             buffer.rewind();
-//                                     }
-//                             }
-
                                // exit
                                if (channel.isConnected())
                                        writeLine("/exit");
@@ -299,6 +285,7 @@ public class JShellClient {
                        }
                }
 
+               /** Not optimal, but performance is not critical here. */
                private void writeLine(Object obj) throws IOException {
                        channel.write(ByteBuffer.wrap((obj + "\n").getBytes(UTF_8)));
                }
@@ -346,6 +333,8 @@ class SocketPipeSource {
                                }
                        } catch (ClosedByInterruptException e) {
                                // silent
+                       } catch (AsynchronousCloseException e) {
+                               // silent
                        } catch (IOException e) {
                                e.printStackTrace();
                        }
@@ -366,8 +355,11 @@ class SocketPipeSource {
                                try {
                                        ByteBuffer buffer = ByteBuffer.allocate(inBufferSize);
                                        while (channel.isConnected()) {
-                                               if (inChannel.read(buffer) < 0)
+                                               if (inChannel.read(buffer) < 0) {
+                                                       System.err.println("in EOF");
+                                                       channel.shutdownOutput();
                                                        break;
+                                               }
 //                     int b = (int) buffer.get(0);
 //                     if (b == 0x1B) {
 //                             System.out.println("Ctrl+C");
@@ -423,9 +415,9 @@ class SocketPipeSource {
                } catch (IOException e) {
                        e.printStackTrace();
                }
-               if (inChannel != null)
-                       forwardThread.interrupt();
-               readThread.interrupt();
+//             if (inChannel != null)
+//                     forwardThread.interrupt();
+//             readThread.interrupt();
        }
 
        public void setInputStream(InputStream in) {
index 7a2332bed1a38c6dbb202a349b5888493fe3db59..1e6c9deabac78a289b5dc8ec8f57047970609b6c 100644 (file)
@@ -36,15 +36,16 @@ class SocketPipeMirror implements Closeable {
                        try {
                                ByteBuffer buffer = ByteBuffer.allocate(1024);
                                while (!readInThread.isInterrupted() && channel.isConnected()) {
-                                       if (channel.read(buffer) < 0)
+                                       if (channel.read(buffer) < 0) {
+                                               in.close();
                                                break;
+                                       }
                                        buffer.flip();
                                        inPipe.sink().write(buffer);
                                        buffer.rewind();
                                }
                        } catch (AsynchronousCloseException e) {
                                // ignore
-                               // TODO make it cleaner
                        } catch (IOException e) {
                                e.printStackTrace();
                        }
@@ -56,8 +57,10 @@ class SocketPipeMirror implements Closeable {
                        try {
                                ByteBuffer buffer = ByteBuffer.allocate(1024);
                                while (!writeOutThread.isInterrupted() && channel.isConnected()) {
-                                       if (outPipe.source().read(buffer) < 0)
+                                       if (outPipe.source().read(buffer) < 0) {
+                                               out.close();
                                                break;
+                                       }
                                        buffer.flip();
                                        channel.write(buffer);
                                        buffer.rewind();
index 80acd55517e9af81e2b7a990584603458877397c..a98326c309e952ede6bcb38f50cbeb52188bfbf0 100644 (file)
@@ -19,6 +19,7 @@ import java.util.TreeMap;
 import java.util.TreeSet;
 
 import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.jshell.CmsExecutionControl;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
@@ -28,7 +29,6 @@ import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.BundleWire;
 import org.osgi.framework.wiring.BundleWiring;
 
-import jdk.jshell.execution.DirectExecutionControl;
 import jdk.jshell.spi.ExecutionControl;
 import jdk.jshell.spi.ExecutionControlProvider;
 import jdk.jshell.spi.ExecutionEnv;
@@ -53,24 +53,15 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
 
        @Override
        public ExecutionControl generate(ExecutionEnv env, Map<String, String> parameters) throws Throwable {
-               // TODO find a better way to get a default bundle context
-               // NOTE: the related default bundle has to be started
-
-//             String symbolicName = parameters.get(BUNDLE_PARAMETER);
-//             Bundle fromBundle = getBundleFromSn(symbolicName);
-
                Long bundleId = Long.parseLong(parameters.get(BUNDLE_PARAMETER));
                Bundle fromBundle = getBundleFromId(bundleId);
 
                BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
                ClassLoader fromBundleClassLoader = fromBundleWiring.getClassLoader();
 
-               // use the bundle classloade as context classloader
-               Thread.currentThread().setContextClassLoader(fromBundleClassLoader);
-
-               ExecutionControl executionControl = new DirectExecutionControl(
+               ExecutionControl executionControl = new CmsExecutionControl(env,
                                new WrappingLoaderDelegate(env, fromBundleClassLoader));
-               log.debug("JShell from " + fromBundle.getSymbolicName() + "_" + fromBundle.getVersion() + " ["
+               log.trace(() -> "JShell from " + fromBundle.getSymbolicName() + "_" + fromBundle.getVersion() + " ["
                                + fromBundle.getBundleId() + "]");
                return executionControl;
        }
@@ -123,7 +114,9 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                        String std = """
                                        import jdk.jshell.spi.ExecutionEnv;
 
-                                       InputStream STDIN = new Supplier<InputStream>() {
+                                       /** Redirected standard IO. */
+                                       public class Std {
+                                               final static InputStream in = new Supplier<InputStream>() {
 
                                                        @Override
                                                        public InputStream get() {
@@ -131,7 +124,7 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                                        }
 
                                                }.get();
-                                       PrintStream STDOUT = new Supplier<PrintStream>() {
+                                               final static PrintStream out = new Supplier<PrintStream>() {
 
                                                        @Override
                                                        public PrintStream get() {
@@ -139,7 +132,7 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                                        }
 
                                                }.get();
-                                       PrintStream STDERR = new Supplier<PrintStream>() {
+                                               final static PrintStream err = new Supplier<PrintStream>() {
 
                                                        @Override
                                                        public PrintStream get() {
@@ -147,7 +140,9 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                                        }
 
                                                }.get();
-                                                                               """;
+
+                                       }
+                                       """;
                        writer.write(std);
                } catch (IOException e) {
                        throw new RuntimeException("Cannot writer bundle startup script to " + bundleStartupScript, e);