org.freeplane.plugin.script.GenericScript.java Source code

Java tutorial

Introduction

Here is the source code for org.freeplane.plugin.script.GenericScript.java

Source

/*
 *  Freeplane - mind map editor
 *  Copyright (C) 2012 Dimitry
 *
 *  This file author is Dimitry
 *
 *  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 2 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.freeplane.plugin.script;

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

import org.apache.commons.io.FilenameUtils;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.util.FileUtils;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.mode.Controller;
import org.freeplane.main.application.FreeplaneSecurityManager;
import org.freeplane.plugin.script.proxy.ProxyFactory;

/**
 * Implements scripting via JSR233 implementation for all other languages except Groovy.
 */
public class GenericScript implements IScript {
    public static final class ScriptSource {
        private final File file;
        private final String script;
        private String cachedFileContent;

        public ScriptSource(String script) {
            this.script = script;
            this.file = null;
        }

        public ScriptSource(File file) {
            this.script = null;
            this.file = file;
            this.cachedFileContent = slurpFile(file);
        }

        public boolean isFile() {
            return file != null;
        }

        public String rereadFile() {
            cachedFileContent = slurpFile(file);
            return cachedFileContent;
        }

        public String getScript() {
            return isFile() ? cachedFileContent : script;
        }
    }

    final private ScriptSource scriptSource;
    private final ScriptingPermissions specificPermissions;
    private CompiledScript compiledScript;
    private Throwable errorsInScript;
    private IFreeplaneScriptErrorHandler errorHandler;
    private PrintStream outStream;
    private ScriptContext scriptContext;
    private final static Object scriptEngineManagerMutex = new Object();
    private static ScriptEngineManager scriptEngineManager;
    private static URLClassLoader classLoader;
    private final ScriptEngine engine;
    private boolean compilationEnabled = true;
    private CompileTimeStrategy compileTimeStrategy;

    private GenericScript(ScriptSource scriptSource, ScriptEngine engine, ScriptingPermissions permissions) {
        this.scriptSource = scriptSource;
        this.specificPermissions = permissions;
        this.engine = engine;
        compiledScript = null;
        errorsInScript = null;
        errorHandler = ScriptResources.IGNORING_SCRIPT_ERROR_HANDLER;
        outStream = System.out;
        scriptContext = null;
    }

    public GenericScript(String script, ScriptEngine engine, ScriptingPermissions permissions) {
        this(new ScriptSource(script), engine, permissions);
        compileTimeStrategy = new CompileTimeStrategy(null);
    }

    public GenericScript(String script, String scriptEngineName, ScriptingPermissions permissions) {
        this(script, findScriptEngine(scriptEngineName), permissions);
    }

    public GenericScript(File scriptFile, ScriptingPermissions permissions) {
        this(new ScriptSource(scriptFile), findScriptEngine(scriptFile), permissions);
        engine.put(ScriptEngine.FILENAME, scriptFile.toString());
        compilationEnabled = !disableScriptCompilation(scriptFile);
        compileTimeStrategy = new CompileTimeStrategy(scriptFile);
    }

    private static String slurpFile(File scriptFile) {
        try {
            return FileUtils.slurpFile(scriptFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public IScript setErrorHandler(IFreeplaneScriptErrorHandler pErrorHandler) {
        this.errorHandler = pErrorHandler;
        return this;
    }

    @Override
    public IScript setOutStream(PrintStream outStream) {
        this.outStream = outStream;
        return this;
    }

    @Override
    public IScript setScriptContext(ScriptContext scriptContext) {
        this.scriptContext = scriptContext;
        return this;
    }

    @Override
    public Object getScript() {
        return scriptSource;
    }

    @Override
    public Object execute(final NodeModel node) {
        try {
            if (errorsInScript != null && compileTimeStrategy.canUseOldCompiledScript()) {
                throw new ExecuteScriptException(errorsInScript.getMessage(), errorsInScript);
            }
            final ScriptingSecurityManager scriptingSecurityManager = createScriptingSecurityManager();
            final ScriptingPermissions originalScriptingPermissions = new ScriptingPermissions(
                    ResourceController.getResourceController().getProperties());
            final FreeplaneSecurityManager securityManager = (FreeplaneSecurityManager) System.getSecurityManager();
            final boolean needToSetFinalSecurityManager = securityManager.needToSetFinalSecurityManager();
            final PrintStream oldOut = System.out;
            try {
                final SimpleScriptContext context = createScriptContext(node);
                if (compilationEnabled && engine instanceof Compilable) {
                    compileAndCache((Compilable) engine);
                    if (needToSetFinalSecurityManager)
                        securityManager.setFinalSecurityManager(scriptingSecurityManager);
                    System.setOut(outStream);
                    return compiledScript.eval(context);
                } else {
                    if (needToSetFinalSecurityManager)
                        securityManager.setFinalSecurityManager(scriptingSecurityManager);
                    System.setOut(outStream);
                    return engine.eval(scriptSource.getScript(), context);
                }
            } finally {
                System.setOut(oldOut);
                if (needToSetFinalSecurityManager && securityManager.hasFinalSecurityManager())
                    securityManager.removeFinalSecurityManager(scriptingSecurityManager);
                /* restore preferences (and assure that the values are unchanged!). */
                originalScriptingPermissions.restorePermissions();
            }
        } catch (final ScriptException e) {
            handleScriptRuntimeException(e);
            // :fixme: This throw is only reached, if handleScriptRuntimeException
            // does not raise an exception. Should it be here at all?
            // And if: Shouldn't it raise an ExecuteScriptException?
            throw new RuntimeException(e);
        } catch (final Throwable e) {
            if (Controller.getCurrentController().getSelection() != null)
                Controller.getCurrentModeController().getMapController().select(node);
            throw new ExecuteScriptException(e.getMessage(), e);
        }
    }

    private ScriptingSecurityManager createScriptingSecurityManager() {
        return new ScriptSecurity(scriptSource, specificPermissions, outStream).getScriptingSecurityManager();
    }

    private boolean disableScriptCompilation(File scriptFile) {
        return FilenameUtils.isExtension(scriptFile.getName(),
                ScriptResources.SCRIPT_COMPILATION_DISABLED_EXTENSIONS);
    }

    private SimpleScriptContext createScriptContext(final NodeModel node) {
        final SimpleScriptContext context = new SimpleScriptContext();
        final OutputStreamWriter outWriter = new OutputStreamWriter(outStream);
        context.setWriter(outWriter);
        context.setErrorWriter(outWriter);
        context.setBindings(createBinding(node), javax.script.ScriptContext.ENGINE_SCOPE);
        return context;
    }

    private Bindings createBinding(final NodeModel node) {
        final Bindings binding = engine.createBindings();
        binding.put("c", ProxyFactory.createController(scriptContext));
        binding.put("node", ProxyFactory.createNode(node, scriptContext));
        binding.putAll(ScriptingConfiguration.getStaticProperties());
        return binding;
    }

    static ScriptEngineManager getScriptEngineManager() {
        synchronized (scriptEngineManagerMutex) {
            if (scriptEngineManager == null) {
                final ClassLoader classLoader = createClassLoader();
                scriptEngineManager = new ScriptEngineManager(classLoader);
            }
            return scriptEngineManager;
        }
    }

    private static ClassLoader createClassLoader() {
        if (classLoader == null) {
            final List<String> classpath = ScriptResources.getClasspath();
            final List<URL> urls = new ArrayList<URL>();
            for (String path : classpath) {
                urls.add(pathToUrl(path));
            }
            classLoader = URLClassLoader.newInstance(urls.toArray(new URL[urls.size()]),
                    GenericScript.class.getClassLoader());
        }
        return classLoader;
    }

    private static URL pathToUrl(String path) {
        try {
            return new File(path).toURI().toURL();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void compileAndCache(Compilable engine) throws Throwable {
        if (compileTimeStrategy.canUseOldCompiledScript())
            return;
        compiledScript = null;
        errorsInScript = null;
        try {
            scriptSource.rereadFile();
            compileTimeStrategy.scriptCompileStart();
            compiledScript = engine.compile(scriptSource.getScript());
            compileTimeStrategy.scriptCompiled();
        } catch (Throwable e) {
            errorsInScript = e;
            throw e;
        }
    }

    private static ScriptEngine findScriptEngine(String scriptEngineName) {
        final ScriptEngineManager manager = getScriptEngineManager();
        return checkNotNull(manager.getEngineByName(scriptEngineName), "name", scriptEngineName);
    }

    private static ScriptEngine findScriptEngine(File scriptFile) {
        final ScriptEngineManager manager = getScriptEngineManager();
        final String extension = FilenameUtils.getExtension(scriptFile.getName());
        return checkNotNull(manager.getEngineByExtension(extension), "extension", extension);
    }

    private static ScriptEngine checkNotNull(final ScriptEngine motor, String what, String detail) {
        if (motor == null)
            throw new RuntimeException("can't load script engine by " + what + ": " + detail);
        return motor;
    }

    private void handleScriptRuntimeException(final ScriptException e) {
        outStream.print("message: " + e.getMessage());
        int lineNumber = e.getLineNumber();
        outStream.print("Line number: " + lineNumber);
        errorHandler.gotoLine(lineNumber);
        throw new ExecuteScriptException(e.getMessage() + " at line " + lineNumber,
                // The ScriptException should have a cause. Use
                // that, it is what we want to know.
                (e.getCause() == null) ? e : e.getCause());
    }

    @Override
    public boolean permissionsEquals(ScriptingPermissions permissions) {
        if (this.specificPermissions == null)
            return this.specificPermissions == permissions;
        else
            return this.specificPermissions.equals(permissions);
    }
}