org.orbisgis.r.RConsolePanel.java Source code

Java tutorial

Introduction

Here is the source code for org.orbisgis.r.RConsolePanel.java

Source

/*
 * OrbisGIS is an open source GIS application dedicated to research for
 * all geographic information science.
 * 
 * OrbisGIS is distributed under GPL 3 license. It is developped by the "GIS"
 * team of the Lab-STICC laboratory <http://www.lab-sticc.fr/>.
 *
 * Copyright (C) 2007-2014 IRSTV (FR CNRS 2488)
 * Copyright (C) 2015-2016 Lab-STICC CNRS, UMR 6285.
 * 
 * This file is part of OrbisGIS.
 * 
 * OrbisGIS 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.
 * 
 * OrbisGIS 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
 * OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information, please consult: <http://www.orbisgis.org/>
 * or contact directly:
 * info_at_ orbisgis.org
 */
package org.orbisgis.r;

import org.apache.commons.io.FileUtils;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.orbisgis.commons.progress.SwingWorkerPM;
import org.orbisgis.r.icons.RIcon;
import org.orbisgis.rscriptengine.REngineFactory;
import org.orbisgis.sif.UIFactory;
import org.orbisgis.sif.components.OpenFilePanel;
import org.orbisgis.sif.components.actions.ActionCommands;
import org.orbisgis.sif.components.actions.DefaultAction;
import org.orbisgis.sif.components.findReplace.FindReplaceDialog;
import org.orbisgis.sif.docking.DockingPanelParameters;
import org.orbisgis.sif.edition.EditableElement;
import org.orbisgis.sif.edition.EditableElementException;
import org.orbisgis.sif.edition.EditorDockable;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.renjin.sexp.SEXP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;

import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.sql.DataSource;
import javax.swing.*;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.EventHandler;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import org.orbisgis.sif.CommentUtil;

/**
 * Create the R console panel
 *
 * @author Erwan Bocher
 * @author Sylvain PALOMINOS
 */
@Component(service = EditorDockable.class)
public class RConsolePanel extends JPanel implements EditorDockable {

    public static final String EDITOR_NAME = "R";
    private static final I18n I18N = I18nFactory.getI18n(RConsolePanel.class);
    private static final Logger LOGGER = LoggerFactory.getLogger("gui." + RConsolePanel.class);
    private static final Logger LOGGER_POPUP = LoggerFactory.getLogger("gui.popup" + RConsolePanel.class);
    private DockingPanelParameters parameters = new DockingPanelParameters();
    private ActionCommands actions = new ActionCommands();
    private RTextScrollPane centerPanel;
    private RSyntaxTextArea scriptPanel;
    private DefaultAction executeAction;
    private DefaultAction clearAction;
    private DefaultAction saveAction;
    private DefaultAction saveAsAction;
    private DefaultAction findAction;
    private DefaultAction executeSelectedAction;
    private FindReplaceDialog findReplaceDialog;
    private DefaultAction commentAction;
    private int line = 0;
    private int character = 0;
    private static final String MESSAGEBASE = "%d | %d";
    private JLabel statusMessage = new JLabel();
    private ExecutorService executorService;
    private Map<String, Object> variables = new HashMap<>();
    private ScriptEngine engine = null;
    private RElement rElement;

    @Activate
    public void activate() {
        setLayout(new BorderLayout());
        add(getCenterPanel(), BorderLayout.CENTER);
        add(statusMessage, BorderLayout.SOUTH);
        init();
    }

    @Reference
    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public void unsetExecutorService(ExecutorService executorService) {
        this.executorService = null;
    }

    @Reference()
    public void setDataSource(DataSource ds) {
        try {
            variables.put("con", REngineFactory.getConnectionRObject(ds.getConnection().unwrap(Connection.class)));
        } catch (SQLException e) {
            LOGGER.warn(I18N.tr("Unable to get the OrbisGIS JdbcConnection.\nCause : " + e.getMessage()));
        }
    }

    public void unsetDataSource(DataSource ds) {
        variables.remove("con");
    }

    private void execute(SwingWorker swingWorker) {
        if (executorService != null) {
            executorService.execute(swingWorker);
        } else {
            swingWorker.execute();
        }
    }

    /**
     * Init the groovy panel with all docking parameters and set the necessary
     * properties to the console shell
     */
    private void init() {
        parameters.setName(EDITOR_NAME);
        parameters.setTitle(I18N.tr("R"));
        parameters.setTitleIcon(new ImageIcon(RConsolePanel.class.getResource("icon.png")));
        parameters.setDockActions(getActions().getActions());
        this.setEditableElement(new RElement());
    }

    /**
     * Get the action manager.
     *
     * @return ActionCommands instance.
     */
    public ActionCommands getActions() {
        return actions;
    }

    /**
     * The main panel to write and execute a groovy script.
     *
     * @return
     */
    private RTextScrollPane getCenterPanel() {
        if (centerPanel == null) {
            initActions();
            scriptPanel = new RSyntaxTextArea();
            scriptPanel.setLineWrap(true);
            scriptPanel
                    .addCaretListener(EventHandler.create(CaretListener.class, this, "onScriptPanelCaretUpdate"));
            scriptPanel.getDocument().addDocumentListener(
                    EventHandler.create(DocumentListener.class, this, "onUserSelectionChange"));
            scriptPanel.clearParsers();
            scriptPanel.setTabsEmulated(true);
            actions.setAccelerators(scriptPanel);
            // Actions will be set on the scriptPanel PopupMenu
            scriptPanel.getPopupMenu().addSeparator();
            actions.registerContainer(scriptPanel.getPopupMenu());
            centerPanel = new RTextScrollPane(scriptPanel);
            onUserSelectionChange();

        }
        return centerPanel;
    }

    /**
     * Create actions instances
     *
     * Each action is put in the Popup menu and the tool bar Their shortcuts are
     * registered also in the editor
     */
    private void initActions() {
        //Execute action
        executeAction = new DefaultAction(RConsoleActions.A_EXECUTE, I18N.tr("Execute"),
                I18N.tr("Execute the R script"), RIcon.getIcon("execute"),
                EventHandler.create(ActionListener.class, this, "onExecute"),
                KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK));
        actions.addAction(executeAction);

        //Execute Selected SQL
        executeSelectedAction = new DefaultAction(RConsoleActions.A_EXECUTE_SELECTION, I18N.tr("Execute selected"),
                I18N.tr("Run selected code"), RIcon.getIcon("execute_selection"),
                EventHandler.create(ActionListener.class, this, "onExecuteSelected"),
                KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.ALT_DOWN_MASK)).setLogicalGroup("custom")
                        .setAfter(RConsoleActions.A_EXECUTE);
        actions.addAction(executeSelectedAction);

        //Clear action
        clearAction = new DefaultAction(RConsoleActions.A_CLEAR, I18N.tr("Clear"),
                I18N.tr("Erase the content of the editor"), RIcon.getIcon("erase"),
                EventHandler.create(ActionListener.class, this, "onClear"), null);
        actions.addAction(clearAction);

        //Open action
        actions.addAction(
                new DefaultAction(RConsoleActions.A_OPEN, I18N.tr("Open"), I18N.tr("Load a file in this editor"),
                        RIcon.getIcon("open"), EventHandler.create(ActionListener.class, this, "onOpenFile"),
                        KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)));
        //Save
        saveAction = new DefaultAction(RConsoleActions.A_SAVE, I18N.tr("Save"),
                I18N.tr("Save the editor content into a file"), RIcon.getIcon("save"),
                EventHandler.create(ActionListener.class, this, "onSaveFile"),
                KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
        actions.addAction(saveAction);
        // Save As
        saveAsAction = new DefaultAction(RConsoleActions.A_SAVE, I18N.tr("Save As"),
                I18N.tr("Save the editor content into a new file"), RIcon.getIcon("page_white_save"),
                EventHandler.create(ActionListener.class, this, "onSaveAsNewFile"),
                KeyStroke.getKeyStroke("ctrl maj s"));
        actions.addAction(saveAsAction);
        //Find action
        findAction = new DefaultAction(RConsoleActions.A_SEARCH, I18N.tr("Search.."),
                I18N.tr("Search text in the document"), RIcon.getIcon("find"),
                EventHandler.create(ActionListener.class, this, "openFindReplaceDialog"),
                KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK))
                        .addStroke(KeyStroke.getKeyStroke(KeyEvent.VK_H, InputEvent.CTRL_DOWN_MASK));
        actions.addAction(findAction);

        // Comment/Uncomment
        commentAction = new DefaultAction(RConsoleActions.A_COMMENT, I18N.tr("(Un)comment"),
                I18N.tr("(Un)comment the selected text"), null,
                EventHandler.create(ActionListener.class, this, "onComment"), KeyStroke.getKeyStroke("alt C"))
                        .setLogicalGroup("format");
        actions.addAction(commentAction);

    }

    /**
     * Clear the content of the console
     */
    public void onClear() {
        if (scriptPanel.getDocument().getLength() != 0) {
            int answer = JOptionPane.showConfirmDialog(this,
                    I18N.tr("Do you want to clear the contents of the console?"), I18N.tr("Clear script"),
                    JOptionPane.YES_NO_OPTION);
            if (answer == JOptionPane.YES_OPTION) {
                scriptPanel.setText("");
            }
        }
    }

    /**
     * Open a dialog that let the user to select a file and save the content
     * of the R editor into this file.
     */
    public void onSaveAsNewFile() {
        File oldDocPath = rElement.getDocumentPath();
        rElement.setDocumentPath(null);
        onSaveFile();
        if (rElement.getDocumentPath() == null && oldDocPath != null) {
            // If canceled
            rElement.setDocumentPath(oldDocPath);
        }
    }

    /**
     * Open a dialog that let the user select a file and save the content of the
     * R editor into this file.
     */
    public void onSaveFile() {
        try {
            rElement.save();
        } catch (EditableElementException ex) {
            LOGGER.error(ex.getLocalizedMessage(), ex);
        }
        if (!rElement.isModified()) {
            LOGGER.info(I18N.tr("The file has been saved."));
        }
    }

    /**
     * Open a dialog that let the user select a file and add or replace the
     * content of the R editor.
     */
    public void onOpenFile() {
        final OpenFilePanel inFilePanel = new OpenFilePanel("rConsoleInFile", I18N.tr("Open script"));
        inFilePanel.addFilter("R", I18N.tr("R Script (*.R)"));
        inFilePanel.loadState();
        if (UIFactory.showDialog(inFilePanel)) {
            int answer = JOptionPane.NO_OPTION;
            if (scriptPanel.getDocument().getLength() > 0) {
                answer = JOptionPane.showConfirmDialog(this,
                        I18N.tr("Do you want to clear all before loading the file ?"), I18N.tr("Open file"),
                        JOptionPane.YES_NO_CANCEL_OPTION);
            }
            String text;
            try {
                text = FileUtils.readFileToString(inFilePanel.getSelectedFile());
            } catch (IOException e1) {
                LOGGER.error(I18N.tr("Cannot write the script."), e1);
                return;
            }

            if (answer == JOptionPane.YES_OPTION) {
                scriptPanel.setText(text);
            } else if (answer == JOptionPane.NO_OPTION) {
                scriptPanel.append(text);
            }
        }
    }

    /**
     * Update the row:column label
     */
    public void onScriptPanelCaretUpdate() {
        line = scriptPanel.getCaretLineNumber() + 1;
        character = scriptPanel.getCaretOffsetFromLineStart();
        statusMessage.setText(String.format(MESSAGEBASE, line, character));
    }

    /**
     * Change the status of the button when the console is empty or not.
     */
    public void onUserSelectionChange() {
        String text = scriptPanel.getText().trim();
        if (text.isEmpty()) {
            executeAction.setEnabled(false);
            executeSelectedAction.setEnabled(false);
            clearAction.setEnabled(false);
            saveAction.setEnabled(false);
            findAction.setEnabled(false);
            commentAction.setEnabled(false);
        } else {
            executeAction.setEnabled(true);
            executeSelectedAction.setEnabled(true);
            clearAction.setEnabled(true);
            saveAction.setEnabled(true);
            findAction.setEnabled(true);
            commentAction.setEnabled(true);
        }
    }

    /**
     * User click on execute script button
     */
    public void onExecute() {
        if (executeAction.isEnabled()) {
            String text = scriptPanel.getText().trim();
            if (engine == null) {
                engine = REngineFactory.createRScriptEngine();
            }
            RJob rJob = new RJob(text, executeAction, variables, engine);
            execute(rJob);
        }
    }

    /**
     * Execute the selected R code
     */
    public void onExecuteSelected() {
        if (executeSelectedAction.isEnabled()) {
            String selected = scriptPanel.getSelectedText();
            if (engine == null) {
                engine = REngineFactory.createRScriptEngine();
            }
            if (selected != null) {
                RJob rJob = new RJob(selected, executeSelectedAction, variables, engine);
                execute(rJob);
            }
        }
    }

    /**
     * (Un)comment the selected text.
     */
    public void onComment() {
        CommentUtil.commentOrUncommentR(scriptPanel);
    }

    @Override
    public DockingPanelParameters getDockingParameters() {
        return parameters;
    }

    @Override
    public JComponent getComponent() {
        return this;
    }

    /**
     * Open one instanceof the find replace dialog
     */
    public void openFindReplaceDialog() {
        if (findReplaceDialog == null) {
            findReplaceDialog = new FindReplaceDialog(scriptPanel, UIFactory.getMainFrame());
        }
        findReplaceDialog.setAlwaysOnTop(true);
        findReplaceDialog.setVisible(true);
    }

    @Override
    public boolean match(EditableElement editableElement) {
        return false;
    }

    @Override
    public EditableElement getEditableElement() {
        return rElement;
    }

    @Override
    public void setEditableElement(EditableElement editableElement) {
        if (editableElement instanceof RElement) {
            this.rElement = (RElement) editableElement;
            rElement.setDocument(scriptPanel);
            rElement.addPropertyChangeListener(RElement.PROP_DOCUMENT_PATH,
                    EventHandler.create(PropertyChangeListener.class, this, "onPathChanged"));
            onPathChanged();
            LoadScript loadScript = new LoadScript(rElement);
            if (executorService != null) {
                executorService.execute(loadScript);
            } else {
                loadScript.execute();
            }
        }
    }

    public void onPathChanged() {
        if (!rElement.getDocumentPathString().isEmpty()) {
            getDockingParameters().setTitle(rElement.getDocumentPath().getName());
        }
    }

    /**
     * Execute the provided script in R
     */
    private static class RJob extends SwingWorkerPM {

        private String script;
        private Action executeAction;
        private Map<String, Object> variables;
        private ScriptEngine engine;
        private long startScript;
        private Thread thread;

        public RJob(String script, Action executeAction, Map<String, Object> variables, ScriptEngine engine) {
            this.script = script;
            this.executeAction = executeAction;
            this.variables = variables;
            this.engine = engine;
            setTaskName(I18N.tr("Execute R script"));
        }

        @Override
        protected Object doInBackground() throws Exception {
            executeAction.setEnabled(false);
            //give to the engine some variables
            for (Map.Entry<String, Object> entry : variables.entrySet()) {
                engine.put(entry.getKey(), entry.getValue());
            }
            // check if the engine has loaded correctly:
            if (engine == null) {
                LOGGER.error(I18N.tr("Renjin Script Engine not found on the classpath."));
            } else {
                startScript = System.currentTimeMillis();
                Runnable scriptRun = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            engine.eval(script);
                        } catch (Exception e) {
                            LOGGER.error(I18N.tr("Cannot execute this R script.\nCause : " + e.getMessage()));
                        } finally {
                            executeAction.setEnabled(true);
                        }
                    }
                };
                thread = new Thread(scriptRun);
                thread.start();
                thread.join();
            }
            return null;
        }

        @Override
        protected void done() {
            super.done();
            LOGGER.info(I18N.tr("R script executed in {0} seconds",
                    (System.currentTimeMillis() - startScript) / 1000.));
        }

        @Override
        public void cancel() {
            super.cancel();
            thread.interrupt();
            try {
                thread.join(500);
            } catch (InterruptedException ignored) {
            }
            if (thread.isAlive()) {
                LOGGER.info(I18N.tr("Forcing script to cancel."));
                //TODO find a good way to kill the script thread
                thread.stop();
            }
            LOGGER.info(I18N.tr("Script canceled."));
            executeAction.setEnabled(true);
        }
    }
}