/*FreeMind - A Program for creating and viewing Mindmaps
*Copyright (C) 2000-2006 Joerg Mueller, Daniel Polansky, Christian Foltin and others.
*
*See COPYING for Details
*
*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, write to the Free Software
*Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Created on 02.09.2006
*/
package plugins.script;
import java.io.File;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import javax.swing.JOptionPane;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.runtime.InvokerHelper;
import freemind.common.OptionalDontShowMeAgainDialog;
import freemind.main.FreeMind;
import freemind.main.FreeMindMain;
import freemind.main.FreeMindSecurityManager;
import freemind.main.Tools;
import freemind.main.Tools.BooleanHolder;
import freemind.modes.MindMapNode;
import freemind.modes.attributes.NodeAttributeTableModel;
import freemind.modes.mindmapmode.MindMapController;
import freemind.modes.mindmapmode.hooks.MindMapHookAdapter;
import groovy.lang.Binding;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
/**
* @author foltin
*
*/
public class ScriptingEngine extends MindMapHookAdapter {
public static final String SCRIPT_PREFIX = "script";
private static final HashMap sScriptCookies = new HashMap();
static java.util.logging.Logger logger;
public interface ErrorHandler {
void gotoLine(int pLineNumber);
}
public void startupMapHook() {
super.startupMapHook();
MindMapNode node = getMindMapController().getMap().getRootNode();
BooleanHolder booleanHolder = new BooleanHolder(false);
// check for installed script:
String scriptLocation = getResourceString("ScriptLocation");
if (scriptLocation != null && scriptLocation.length() != 0) {
performExternalScript(scriptLocation, node, booleanHolder);
return;
}
// start calculation:
getController().getFrame().setWaitingCursor(true);
try {
performScriptOperation(node, booleanHolder);
} finally {
getController().getFrame().setWaitingCursor(false);
}
}
private void performExternalScript(String pScriptLocation,
MindMapNode pNode, BooleanHolder pBooleanHolder) {
// get cookies from base plugin:
ScriptingRegistration reg = (ScriptingRegistration) getPluginBaseClass();
String scriptContent = Tools.getFile(new File(pScriptLocation));
if (scriptContent == null) {
return;
}
executeScript(pNode, pBooleanHolder, scriptContent,
getMindMapController(), new ErrorHandler() {
public void gotoLine(int pLineNumber) {
}
}, System.out, reg.getScriptCookies());
}
private void performScriptOperation(MindMapNode node,
BooleanHolder pAlreadyAScriptExecuted) {
// depth first:
for (Iterator iter = node.childrenUnfolded(); iter.hasNext();) {
MindMapNode element = (MindMapNode) iter.next();
performScriptOperation(element, pAlreadyAScriptExecuted);
}
NodeAttributeTableModel attributes = node.getAttributes();
if (attributes == null)
return;
for (int row = 0; row < attributes.getRowCount(); ++row) {
String attrKey = (String) attributes.getName(row);
String script = (String) attributes.getValue(row);
logger.info("Found key = " + attrKey);
if (attrKey.startsWith(SCRIPT_PREFIX)) {
// get cookies from base plugin:
ScriptingRegistration reg = (ScriptingRegistration) getPluginBaseClass();
boolean result = executeScript(node, pAlreadyAScriptExecuted,
script, getMindMapController(), new ErrorHandler() {
public void gotoLine(int pLineNumber) {
}
}, System.out, reg.getScriptCookies());
if (!result) {
break;
}
}
}
}
public static int findLineNumberInString(String resultString, int lineNumber) {
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(
".*@ line ([0-9]+).*", java.util.regex.Pattern.DOTALL);
Matcher matcher = pattern.matcher(resultString);
if (matcher.matches()) {
lineNumber = Integer.parseInt(matcher.group(1));
}
return lineNumber;
}
/**
* @param node
* @param pAlreadyAScriptExecuted
* @param script
* @param pMindMapController
* @param pScriptCookies
* TODO
* @return true, if further scripts can be executed, false, if the user
* canceled or an error occurred.
*/
static boolean executeScript(MindMapNode node,
BooleanHolder pAlreadyAScriptExecuted, String script,
MindMapController pMindMapController, ErrorHandler pErrorHandler,
PrintStream pOutStream, HashMap pScriptCookies) {
// ask user if first script:
FreeMindMain frame = pMindMapController.getFrame();
if (!pAlreadyAScriptExecuted.getValue()) {
int showResult = new OptionalDontShowMeAgainDialog(
frame.getJFrame(), pMindMapController.getSelectedView(),
"really_execute_script", "confirmation",
pMindMapController,
new OptionalDontShowMeAgainDialog.StandardPropertyHandler(
pMindMapController.getController(),
FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_ASKING),
OptionalDontShowMeAgainDialog.ONLY_OK_SELECTION_IS_STORED)
.show().getResult();
if (showResult != JOptionPane.OK_OPTION) {
return false;
}
}
pAlreadyAScriptExecuted.setValue(true);
Binding binding = new Binding();
binding.setVariable("c", pMindMapController);
binding.setVariable("node", node);
binding.setVariable("cookies", sScriptCookies);
boolean assignResult = false;
String assignTo = null;
if (script.startsWith("=")) {
script = script.substring(1);
assignResult = true;
} else {
int indexOfEquals = script.indexOf('=');
if (indexOfEquals > 0) {
String start = script.substring(0, indexOfEquals);
if (start.matches("[a-zA-Z0-9_]+")) {
assignTo = start;
script = script.substring(indexOfEquals + 1);
assignResult = true;
}
}
}
/*
* get preferences (and store them again after the script execution,
* such that the scripts are not able to change them).
*/
String executeWithoutAsking = frame
.getProperty(FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_ASKING);
String executeWithoutFileRestriction = frame
.getProperty(FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_FILE_RESTRICTION);
String executeWithoutNetworkRestriction = frame
.getProperty(FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_NETWORK_RESTRICTION);
String executeWithoutExecRestriction = frame
.getProperty(FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_EXEC_RESTRICTION);
String signedScriptsWithoutRestriction = frame
.getProperty(FreeMind.RESOURCES_SIGNED_SCRIPT_ARE_TRUSTED);
/* *************** */
/* **Signature ** */
/* *************** */
PrintStream oldOut = System.out;
Object value = null;
GroovyRuntimeException e1 = null;
Throwable e2 = null;
boolean filePerm = Tools
.isPreferenceTrue(executeWithoutFileRestriction);
boolean networkPerm = Tools
.isPreferenceTrue(executeWithoutNetworkRestriction);
boolean execPerm = Tools
.isPreferenceTrue(executeWithoutExecRestriction);
if (Tools.isPreferenceTrue(signedScriptsWithoutRestriction)) {
boolean isSigned = new SignedScriptHandler().isScriptSigned(script,
pOutStream);
if (isSigned) {
filePerm = true;
networkPerm = true;
execPerm = true;
}
}
final ScriptingSecurityManager scriptingSecurityManager = new ScriptingSecurityManager(
filePerm, networkPerm, execPerm);
final FreeMindSecurityManager securityManager = (FreeMindSecurityManager) System
.getSecurityManager();
try {
System.setOut(pOutStream);
// copied from freeplane from
// http://freeplane.bzr.sourceforge.net/bzr/freeplane/freeplane_program/release_branches/1_0_x/annotate/head%3A/freeplane_plugin_script/src/org/freeplane/plugin/script/ScriptingEngine.java
final GroovyShell shell = new GroovyShell(binding) {
/**
* Evaluates some script against the current Binding and returns
* the result
*
* @param in
* the stream reading the script
* @param fileName
* is the logical file name of the script (which is
* used to create the class name of the script)
*/
public Object evaluate(final InputStream in,
final String fileName)
throws CompilationFailedException {
Script script = null;
try {
script = parse(in, fileName);
securityManager
.setFinalSecurityManager(scriptingSecurityManager);
return script.run();
} finally {
if (script != null) {
InvokerHelper.removeClass(script.getClass());
// setting the same security manager the second time
// causes it to be
// removed.
securityManager
.setFinalSecurityManager(scriptingSecurityManager);
}
}
}
};
value = shell.evaluate(script);
} catch (final GroovyRuntimeException e) {
e1 = e;
} catch (final Throwable e) {
e2 = e;
} finally {
System.setOut(oldOut);
/* restore preferences (and assure that the values are unchanged!). */
frame.setProperty(
FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_ASKING,
executeWithoutAsking);
frame.setProperty(
FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_FILE_RESTRICTION,
executeWithoutFileRestriction);
frame.setProperty(
FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_NETWORK_RESTRICTION,
executeWithoutNetworkRestriction);
frame.setProperty(
FreeMind.RESOURCES_EXECUTE_SCRIPTS_WITHOUT_EXEC_RESTRICTION,
executeWithoutExecRestriction);
frame.setProperty(FreeMind.RESOURCES_SIGNED_SCRIPT_ARE_TRUSTED,
signedScriptsWithoutRestriction);
}
/*
* Cover exceptions in normal security context (ie. no problem with
* (log) file writing etc.)
*/
if (e1 != null) {
String resultString = e1.getMessage();
pOutStream.print("message: " + resultString);
ModuleNode module = e1.getModule();
ASTNode astNode = e1.getNode();
int lineNumber = -1;
if (module != null) {
lineNumber = module.getLineNumber();
} else if (astNode != null) {
lineNumber = astNode.getLineNumber();
} else {
lineNumber = findLineNumberInString(resultString, lineNumber);
}
pOutStream.print("Line number: " + lineNumber);
pErrorHandler.gotoLine(lineNumber);
return false;
}
if (e2 != null) {
freemind.main.Resources.getInstance().logException(e2);
pOutStream.print(e2.getMessage());
String cause = ((e2.getCause() != null) ? e2.getCause()
.getMessage() : "");
String message = ((e2.getMessage() != null) ? e2.getMessage() : "");
pMindMapController
.getController()
.errorMessage(
e2.getClass().getName()
+ ": "
+ cause
+ ((cause.length() != 0 && message.length() != 0) ? ", "
: "") + message);
return false;
}
pOutStream.print(frame
.getResourceString("plugins/ScriptEditor/window.Result")
+ value);
if (assignResult && value != null) {
if (assignTo == null) {
pMindMapController.setNodeText(node, value.toString());
} else {
pMindMapController.editAttribute(node, assignTo,
value.toString());
}
}
return true;
}
}