com.github.fracpete.jshell.JShellPanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jshell-scripting Show documentation
Show all versions of jshell-scripting Show documentation
Allows scripting in Java via JShell.
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* JShellPanel.java
* Copyright (C) 2018 FracPete
*/
package com.github.fracpete.jshell;
import com.github.fracpete.jshell.event.JShellEvent;
import com.github.fracpete.jshell.event.JShellEvent.EventType;
import com.github.fracpete.jshell.event.JShellListener;
import com.github.fracpete.processoutput4j.core.StreamingProcessOutputType;
import com.github.fracpete.processoutput4j.core.StreamingProcessOwner;
import com.github.fracpete.processoutput4j.output.StreamingProcessOutput;
import nz.ac.waikato.cms.core.FileUtils;
import nz.ac.waikato.cms.core.Utils;
import nz.ac.waikato.cms.gui.core.BaseFileChooser;
import nz.ac.waikato.cms.gui.core.BaseFrame;
import nz.ac.waikato.cms.gui.core.BasePanel;
import nz.ac.waikato.cms.gui.core.ExtensionFileFilter;
import nz.ac.waikato.cms.gui.core.GUIHelper;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Panel for performing scripting via jshell. Requires Java 9.
*
* See https://docs.oracle.com/javase/9/jshell/
*
* @author FracPete (fracpete at gmail dot com)
*/
public class JShellPanel
extends BasePanel
implements StreamingProcessOwner {
/** whether scripting is available. */
protected Boolean m_Available;
/** for splitting code and output. */
protected JSplitPane m_SplitPane;
/** the text area for the script. */
protected RSyntaxTextArea m_TextCode;
/** the filechooser for scripts. */
protected BaseFileChooser m_FileChooserScript;
/** the button for loading a script. */
protected JButton m_ButtonScriptLoad;
/** the button for saving as script. */
protected JButton m_ButtonScriptSave;
/** the button for executing a script. */
protected JButton m_ButtonScriptRun;
/** the button for stopping a script. */
protected JButton m_ButtonScriptStop;
/** the filechooser for the output. */
protected BaseFileChooser m_FileChooserOutput;
/** the button for clearing the output. */
protected JButton m_ButtonOutputClear;
/** the button for saving the output. */
protected JButton m_ButtonOutputSave;
/** for the jshell output. */
protected JTextArea m_TextOutput;
/** executes the script. */
protected transient StreamingProcessOutput m_Execution;
/** the listeners that listen for changes. */
protected Set m_JShellListeners;
/**
* Initializes the members.
*/
protected void initialize() {
m_FileChooserScript = new BaseFileChooser();
m_FileChooserScript.addChoosableFileFilter(new ExtensionFileFilter("JShell script", new String[]{"jsh", "jshell"}));
m_FileChooserScript.setAcceptAllFileFilterUsed(true);
m_FileChooserOutput = new BaseFileChooser();
m_FileChooserOutput.addChoosableFileFilter(new ExtensionFileFilter("Text file", "txt"));
m_FileChooserOutput.setAcceptAllFileFilterUsed(true);
m_JShellListeners = new HashSet<>();
}
/**
* Initializes the widgets.
*/
protected void initGUI() {
JPanel panel;
JPanel panelRight;
JPanel panelButtons;
JPanel panelText;
JLabel label;
setLayout(new BorderLayout());
if (!isAvailable()) {
panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
add(panel, BorderLayout.CENTER);
label = new JLabel("jshell executable not found (Java 9+ only) - scripting disabled!");
panel.add(label);
return;
}
m_SplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
m_SplitPane.setOneTouchExpandable(true);
m_SplitPane.setResizeWeight(1.0);
add(m_SplitPane, BorderLayout.CENTER);
// code
panel = new JPanel(new BorderLayout());
m_SplitPane.setTopComponent(panel);
m_TextCode = new RSyntaxTextArea(10, 80);
m_TextCode.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
m_TextCode.setLineWrap(false);
m_TextCode.setAutoIndentEnabled(true);
m_TextCode.setAntiAliasingEnabled(true);
m_TextCode.setCodeFoldingEnabled(true);
m_TextCode.setBracketMatchingEnabled(false);
panel.add(new RTextScrollPane(m_TextCode), BorderLayout.CENTER);
panelText = new JPanel(new FlowLayout(FlowLayout.LEFT));
panel.add(panelText, BorderLayout.NORTH);
label = new JLabel("JShell");
panelText.add(label);
panelRight = new JPanel(new BorderLayout());
panel.add(panelRight, BorderLayout.EAST);
panelButtons = new JPanel(new GridLayout(0, 1));
panelRight.add(panelButtons, BorderLayout.NORTH);
m_ButtonScriptLoad = new JButton(GUIHelper.getIcon("open.gif"));
m_ButtonScriptLoad.setToolTipText("Load script from file");
m_ButtonScriptLoad.addActionListener((ActionEvent e) -> loadScript());
panelButtons.add(m_ButtonScriptLoad);
m_ButtonScriptSave = new JButton(GUIHelper.getIcon("save.gif"));
m_ButtonScriptSave.setToolTipText("Save script to file");
m_ButtonScriptSave.addActionListener((ActionEvent e) -> saveScript());
panelButtons.add(m_ButtonScriptSave);
m_ButtonScriptRun = new JButton(GUIHelper.getIcon("run.gif"));
m_ButtonScriptRun.setToolTipText("Execute script");
m_ButtonScriptRun.addActionListener((ActionEvent e) -> runScript());
panelButtons.add(m_ButtonScriptRun);
m_ButtonScriptStop = new JButton(GUIHelper.getIcon("stop.gif"));
m_ButtonScriptStop.setToolTipText("Stop script");
m_ButtonScriptStop.addActionListener((ActionEvent e) -> stopScript());
panelButtons.add(m_ButtonScriptStop);
// output
panel = new JPanel(new BorderLayout());
m_SplitPane.setBottomComponent(panel);
m_TextOutput = new JTextArea(20, 80);
m_TextOutput.setFont(new Font("monospaced", Font.PLAIN, 10));
panel.add(new JScrollPane(m_TextOutput), BorderLayout.CENTER);
panelText = new JPanel(new FlowLayout(FlowLayout.LEFT));
panel.add(panelText, BorderLayout.NORTH);
label = new JLabel("Output");
panelText.add(label);
panelRight = new JPanel(new BorderLayout());
panel.add(panelRight, BorderLayout.EAST);
panelButtons = new JPanel(new GridLayout(0, 1));
panelRight.add(panelButtons, BorderLayout.NORTH);
m_ButtonOutputClear = new JButton(GUIHelper.getIcon("new.gif"));
m_ButtonOutputClear.setToolTipText("Clear output");
m_ButtonOutputClear.addActionListener((ActionEvent e) -> clearScriptOutput());
panelButtons.add(m_ButtonOutputClear);
m_ButtonOutputSave = new JButton(GUIHelper.getIcon("save.gif"));
m_ButtonOutputSave.setToolTipText("Save output to file");
m_ButtonOutputSave.addActionListener((ActionEvent e) -> saveScriptOutput());
panelButtons.add(m_ButtonOutputSave);
}
/**
* Finishes the initialization.
*/
protected void finishInit() {
updateButtons();
}
/**
* Returns whether a script is currently running.
*
* @return true if a script is running
*/
public boolean isRunning() {
return (m_Execution != null);
}
/**
* Updates the state of the buttons.
*/
protected void updateButtons() {
boolean running;
running = isRunning();
// script
m_ButtonScriptLoad.setEnabled(!running);
m_ButtonScriptSave.setEnabled(!running);
m_ButtonScriptRun.setEnabled(!running && (m_TextCode.getDocument().getLength() > 0));
m_ButtonScriptStop.setEnabled(running);
// output
m_ButtonOutputClear.setEnabled(m_TextOutput.getDocument().getLength() > 0);
m_ButtonOutputSave.setEnabled(m_TextOutput.getDocument().getLength() > 0);
}
/**
* Lets the user select a script to load.
*/
public void loadScript() {
int retVal;
retVal = m_FileChooserScript.showOpenDialog(this);
if (retVal != BaseFileChooser.APPROVE_OPTION)
return;
loadScript(m_FileChooserScript.getSelectedFile());
}
/**
* Loads the specified file.
*
* @param script the script to load
*/
public void loadScript(File script) {
List lines;
try {
lines = Files.readAllLines(script.toPath());
m_TextCode.setText(Utils.flatten(lines, "\n"));
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_LOAD_SUCCESS));
}
catch (Exception e) {
GUIHelper.showErrorMessage(this, "Failed to load script from: " + script, e);
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_LOAD_FAILURE));
}
updateButtons();
}
/**
* Lets the user save the script to a file.
*/
public void saveScript() {
int retVal;
File script;
String msg;
retVal = m_FileChooserScript.showSaveDialog(this);
if (retVal != BaseFileChooser.APPROVE_OPTION)
return;
script = m_FileChooserScript.getSelectedFile();
msg = FileUtils.writeToFileMsg(script.getAbsolutePath(), m_TextCode.getText(), false, null);
if (msg != null) {
GUIHelper.showErrorMessage(this, "Failed to save script to : " + script + "\n" + msg);
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_SAVE_FAILURE));
}
else {
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_SAVE_SUCCESS));
}
updateButtons();
}
/**
* Executes the script.
*/
public void runScript() {
List cmd;
final File tmpFile;
String code;
String msg;
ProcessBuilder builder;
Runnable run;
stopScript();
clearScriptOutput();
// create tmp file name
try {
tmpFile = File.createTempFile("jshell-", ".jsh");
}
catch (Exception e) {
GUIHelper.showErrorMessage(this, "Failed to create temporary file for script!\nCannot execute script!", e);
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_RUN_SETUP_FAILURE));
return;
}
// ensure that "/exit is in code"
code = m_TextCode.getText();
if (!code.toLowerCase().contains("/exit"))
code += "\n/exit\n";
// save script to tmp file
msg = FileUtils.writeToFileMsg(tmpFile.getAbsolutePath(), code, false, null);
if (msg != null) {
tmpFile.delete();
GUIHelper.showErrorMessage(this, "Failed to write script to temporary file: " + tmpFile + "\n" + msg);
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_RUN_SETUP_FAILURE));
return;
}
// build commandline for jshell
cmd = new ArrayList<>();
cmd.add(getExecutable());
cmd.add("--class-path");
cmd.add(System.getProperty("java.class.path"));
cmd.add(tmpFile.getAbsolutePath());
builder = new ProcessBuilder();
builder.command(cmd);
m_Execution = new StreamingProcessOutput(this);
run = new Runnable() {
@Override
public void run() {
try {
m_Execution.monitor(builder);
if (m_Execution.getExitCode() != 0)
notifyJShellListeners(new JShellEvent(JShellPanel.this, EventType.SCRIPT_RUN_FAILURE));
else
notifyJShellListeners(new JShellEvent(JShellPanel.this, EventType.SCRIPT_RUN_SUCCESS));
}
catch (Exception e) {
GUIHelper.showErrorMessage(JShellPanel.this, "Failed to execute script!", e);
notifyJShellListeners(new JShellEvent(JShellPanel.this, EventType.SCRIPT_RUN_FAILURE));
}
notifyJShellListeners(new JShellEvent(JShellPanel.this, EventType.SCRIPT_FINISHED));
m_Execution = null;
tmpFile.delete();
updateButtons();
}
};
new Thread(run).start();
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_RUN));
updateButtons();
}
/**
* Stops a running script.
*/
public void stopScript() {
if (m_Execution != null) {
m_Execution.destroy();
m_Execution = null;
notifyJShellListeners(new JShellEvent(this, EventType.SCRIPT_STOP));
}
updateButtons();
}
/**
* Clears the output of the script.
*/
public void clearScriptOutput() {
m_TextOutput.setText("");
notifyJShellListeners(new JShellEvent(this, EventType.OUTPUT_CLEARED));
updateButtons();
}
/**
* Lets the user save the script output to a file.
*/
public void saveScriptOutput() {
int retVal;
String msg;
retVal = m_FileChooserOutput.showSaveDialog(this);
if (retVal != BaseFileChooser.APPROVE_OPTION)
return;
msg = FileUtils.writeToFileMsg(m_FileChooserOutput.getSelectedFile().getAbsolutePath(), m_TextOutput.getText(), false, null);
if (msg != null) {
GUIHelper.showErrorMessage(this, msg, "Failed saving output");
notifyJShellListeners(new JShellEvent(this, EventType.OUTPUT_SAVE_FAILURE));
}
else {
notifyJShellListeners(new JShellEvent(this, EventType.OUTPUT_SAVE_SUCESS));
}
updateButtons();
}
/**
* Returns the current code.
*
* @return the code
*/
public String getCode() {
return m_TextCode.getText();
}
/**
* Returns the current output.
*
* @return the output
*/
public String getOutput() {
return m_TextOutput.getText();
}
/**
* Returns the jshell executable.
*
* @return the executable path
*/
public String getExecutable() {
String result;
String home;
home = System.getProperty("java.home");
result = home + File.separator + "bin" + File.separator + "jshell";
if (SystemUtils.IS_OS_WINDOWS)
result += ".exe";
return result;
}
/**
* Checks whether jshell executable is available.
*
* @return true if available
*/
public boolean isAvailable() {
if (m_Available == null) {
m_Available = JavaVersion.JAVA_RECENT.atLeast(JavaVersion.JAVA_9)
&& new File(getExecutable()).exists()
&& !System.getProperty("java.class.path").isEmpty();
}
return m_Available;
}
/**
* Returns what output from the process to forward.
*
* @return the output type
*/
public StreamingProcessOutputType getOutputType() {
return StreamingProcessOutputType.BOTH;
}
/**
* Processes the incoming line.
*
* @param line the line to process
* @param stdout whether stdout or stderr
*/
public void processOutput(String line, boolean stdout) {
boolean moveToEnd;
moveToEnd = (m_TextOutput.getDocument().getLength() == m_TextOutput.getCaretPosition());
m_TextOutput.append((stdout ? "[OUT] " : "[ERR] ") + line + "\n");
if (moveToEnd)
m_TextOutput.setCaretPosition(m_TextOutput.getDocument().getLength());
}
/**
* Adds the listener to the internal list.
*
* @param l the listener to add
*/
public void addJShellListener(JShellListener l) {
m_JShellListeners.add(l);
}
/**
* Removes the listener to the internal list.
*
* @param l the listener to remove
*/
public void removeJShellListener(JShellListener l) {
m_JShellListeners.remove(l);
}
/**
* Notifies all the listeners with the specified event.
*
* @param e the event to send
*/
protected synchronized void notifyJShellListeners(JShellEvent e) {
for (JShellListener l: m_JShellListeners)
l.jshellEventOccurred(e);
}
/**
* For testing only.
*
* @param args first argument is interpreted as script
*/
public static void main(String[] args) {
BaseFrame frame;
JShellPanel panel;
panel = new JShellPanel();
panel.addJShellListener((JShellEvent e) -> System.out.println(e.getType()));
frame = new BaseFrame("JShell");
frame.setIconImage(GUIHelper.getIcon("jshell.gif").getImage());
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(panel, BorderLayout.CENTER);
frame.setSize(1200, 900);
frame.setDefaultCloseOperation(BaseFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy