/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * 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 http://www.gnu.org/licenses/ */ package org.esa.snap.scripting.visat; import org.esa.snap.rcp.SnapDialogs; import org.esa.snap.scripting.visat.actions.HelpAction; import org.esa.snap.scripting.visat.actions.NewAction; import org.esa.snap.scripting.visat.actions.OpenAction; import org.esa.snap.scripting.visat.actions.RunAction; import org.esa.snap.scripting.visat.actions.SaveAction; import org.esa.snap.scripting.visat.actions.SaveAsAction; import org.esa.snap.scripting.visat.actions.StopAction; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.LineNumberReader; import java.io.PrintWriter; import java.io.Writer; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; // todo (NF) - find out how to: // (1) ... gracefully cancel a running script // (2) ... remove bindings (references) in JavaScript to products, views, etc. in order to avoid memory leaks // (3) ... debug a script // (4) ... trace & undo changes to BEAM made by a script /** * A TopComponent for the scripting console. * @author Norman Fomferra * @author Marco Peters */ @TopComponent.Description( preferredID = "ScriptConsoleTopComponent", iconBase = "tango/16x16/apps/utilities-terminal.png", persistenceType = TopComponent.PERSISTENCE_ALWAYS ) @TopComponent.Registration( mode = "output", // it's one of the standard modes, openAtStartup = false, position = 30) @ActionID(category = "Window", id = "org.esa.snap.scripting.visat.ScriptConsoleTopComponent") @ActionReference(path = "Menu/View/Tool Windows", position = 0) @TopComponent.OpenActionRegistration( displayName = "#CTL_ScriptConsoleTopComponent_Name", preferredID = "ScriptConsoleTopComponent" ) @NbBundle.Messages({ "CTL_ScriptConsoleTopComponent_Name=Script Console", "CTL_ScriptConsoleTopComponent_Description=Execute SNAP scripts using JavaScript (default) or other scripting languages (add-on)."}) public class ScriptConsoleTopComponent extends TopComponent{ // View private Map<String, Action> actionMap; private JTextArea inputTextArea; private JTextArea outputTextArea; private ScriptManager scriptManager; private PrintWriter output; private File file; public ScriptConsoleTopComponent() { this.actionMap = new HashMap<>(); registerAction(new NewAction(this)); registerAction(new OpenAction(this)); registerAction(new SaveAction(this)); registerAction(new SaveAsAction(this)); registerAction(new RunAction(this)); registerAction(new StopAction(this)); registerAction(new HelpAction(this)); inputTextArea = new JTextArea(); // todo - replace by JIDE code editor component (nf) inputTextArea.setWrapStyleWord(false); inputTextArea.setTabSize(4); inputTextArea.setRows(10); inputTextArea.setColumns(80); inputTextArea.setFont(new Font("Courier", Font.PLAIN, 13)); outputTextArea = new JTextArea(); outputTextArea.setWrapStyleWord(false); outputTextArea.setTabSize(4); outputTextArea.setRows(3); outputTextArea.setColumns(80); outputTextArea.setEditable(false); outputTextArea.setBackground(Color.LIGHT_GRAY); outputTextArea.setFont(new Font("Courier", Font.PLAIN, 13)); final JToolBar toolBar = new JToolBar("Script Console"); toolBar.setFloatable(false); toolBar.add(getToolButton(NewAction.ID)); toolBar.add(getToolButton(OpenAction.ID)); toolBar.add(getToolButton(SaveAction.ID)); toolBar.add(getToolButton(SaveAsAction.ID)); toolBar.addSeparator(); toolBar.add(getToolButton(RunAction.ID)); toolBar.add(getToolButton(StopAction.ID)); toolBar.addSeparator(); toolBar.add(getToolButton(HelpAction.ID)); getAction(NewAction.ID).setEnabled(true); getAction(OpenAction.ID).setEnabled(true); getAction(SaveAction.ID).setEnabled(false); getAction(SaveAsAction.ID).setEnabled(false); getAction(RunAction.ID).setEnabled(false); getAction(StopAction.ID).setEnabled(false); getAction(HelpAction.ID).setEnabled(true); inputTextArea.setEditable(false); inputTextArea.setEnabled(false); JScrollPane inputEditorScrollPane = new JScrollPane(inputTextArea); inputEditorScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); inputEditorScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); JScrollPane outputEditorScrollPane = new JScrollPane(outputTextArea); outputEditorScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); outputEditorScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); JSplitPane documentPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT, inputEditorScrollPane, outputEditorScrollPane); documentPanel.setDividerLocation(0.7); documentPanel.setBorder(null); setLayout(new BorderLayout(4, 4)); setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); setPreferredSize(new Dimension(800, 400)); add(toolBar, BorderLayout.NORTH); add(documentPanel, BorderLayout.CENTER); output = new PrintWriter(new ScriptOutput(), true); scriptManager = new ScriptManager(getClass().getClassLoader(), output); updateTitle(); } private void registerAction(Action action) { actionMap.put(action.getValue(Action.ACTION_COMMAND_KEY).toString(), action); } public ScriptManager getScriptManager() { return scriptManager; } public void runScript() { if (scriptManager.getEngine() == null) { showErrorMessage("No script language selected."); return; } final String text = inputTextArea.getText().trim(); if (text.isEmpty()) { return; } outputTextArea.setText(null); enableRun(false); scriptManager.execute(text, new ExecutionObserver()); } public void stopScript() { scriptManager.reset(); getAction(StopAction.ID).setEnabled(false); } public void showErrorMessage(String message) { SnapDialogs.showError("Script Console - Error", message); } public void newScript(ScriptEngineFactory scriptEngineFactory) { inputTextArea.setText(null); outputTextArea.setText(null); ScriptEngine factory = scriptManager.getEngineByFactory(scriptEngineFactory); scriptManager.setEngine(factory); setFile(null); enableRun(true); } private void enableRun(boolean b) { getAction(NewAction.ID).setEnabled(b); getAction(OpenAction.ID).setEnabled(b); getAction(SaveAction.ID).setEnabled(b); getAction(SaveAsAction.ID).setEnabled(b); getAction(RunAction.ID).setEnabled(b); getAction(StopAction.ID).setEnabled(!b); inputTextArea.setEnabled(b); inputTextArea.setEditable(b); } private JButton getToolButton(String actionId) { Action action = getAction(actionId); final JButton button = new JButton(action); button.setText(null); return button; } public Action getAction(String actionId) { return actionMap.get(actionId); } public void openScript(File file) { enableRun(false); // todo - use swing worker try { String fileName = file.getName(); int i = fileName.lastIndexOf('.'); if (i <= 0) { showErrorMessage(MessageFormat.format("Unknown script type ''{0}''.", fileName)); return; } String ext = fileName.substring(i + 1); ScriptEngine scriptEngine = scriptManager.getEngineByExtension(ext); if (scriptEngine == null) { showErrorMessage(MessageFormat.format("Unknown script type ''{0}''.", fileName)); return; } StringBuilder sb = new StringBuilder(); try { try (LineNumberReader reader = new LineNumberReader(new FileReader(file))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); sb.append("\n"); } } } catch (IOException e) { showErrorMessage(MessageFormat.format("I/O error:\n{0}", e.getMessage())); return; } inputTextArea.setText(sb.toString()); scriptManager.setEngine(scriptEngine); setFile(file); } finally { enableRun(true); } } public File getFile() { return file; } private void setFile(File file) { this.file = file; updateTitle(); } private void updateTitle() { ScriptEngine scriptEngine = scriptManager.getEngine(); String titleBase = Bundle.CTL_ScriptConsoleTopComponent_Name(); if (scriptEngine != null) { String languageName = scriptEngine.getFactory().getLanguageName(); if (file != null) { setDisplayName(MessageFormat.format("{0} - [{1}] - [{2}]", titleBase, languageName, file)); } else { setDisplayName(MessageFormat.format("{0} - [{1}] - [unnamed]", titleBase, languageName)); } } else { setDisplayName(titleBase); } } public void saveScriptAs(File file) { enableRun(false); // todo - use swing worker try { try { try (FileWriter writer = new FileWriter(file)) { writer.write(inputTextArea.getText()); } setFile(file); } catch (IOException e) { showErrorMessage(MessageFormat.format("I/O error:\n{0}", e.getMessage())); } } finally { enableRun(true); } } public void saveScript() { saveScriptAs(getFile()); } public class ScriptOutput extends Writer { @Override public void close() { } @Override public void flush() { } @Override public void write(char characters[], int off, int len) { print0(new String(characters, off, len)); } @Override public void write(String str) { print0(str); } ///////////////////////////////////////////////////////////////////// // private private void print0(final String str) { if (SwingUtilities.isEventDispatchThread()) { print1(str); } else { SwingUtilities.invokeLater(() -> print1(str)); } } private void print1(String text) { try { int offset = outputTextArea.getDocument().getEndPosition().getOffset(); outputTextArea.getDocument().insertString(offset, text, null); } catch (BadLocationException e) { // ignore } } } private class ExecutionObserver implements ScriptManager.Observer { @Override public void onSuccess(Object value) { if (value != null) { output.println(String.valueOf(value)); } SwingUtilities.invokeLater(() -> enableRun(true)); } @Override public void onFailure(Throwable throwable) { output.println("Error: " + throwable.getMessage()); throwable.printStackTrace(output); SwingUtilities.invokeLater(() -> enableRun(true)); } } }