|
What this is
This file is included in the DevDaily.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
Other links
The source code
/*
* BeanShell.java - BeanShell scripting support
* :tabSize=8:indentSize=8:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2000, 2004 Slava Pestov
*
* 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 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.
*/
package org.gjt.sp.jedit;
//{{{ Imports
import bsh.*;
import java.io.*;
import java.lang.ref.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import org.gjt.sp.jedit.io.*;
import org.gjt.sp.jedit.gui.BeanShellErrorDialog;
import org.gjt.sp.jedit.textarea.*;
import org.gjt.sp.util.Log;
//}}}
/**
* BeanShell is jEdit's extension language.
*
* When run from jEdit, BeanShell code has access to the following predefined
* variables:
*
*
* view - the currently active {@link View}.
* editPane - the currently active {@link EditPane}.
* textArea - the edit pane's {@link JEditTextArea}.
* buffer - the edit pane's {@link Buffer}.
* wm - the view's {@link
* org.gjt.sp.jedit.gui.DockableWindowManager}.
* scriptPath - the path name of the currently executing
* BeanShell script.
*
*
* @author Slava Pestov
* @version $Id: BeanShell.java,v 1.44 2004/02/22 20:00:50 spestov Exp $
*/
public class BeanShell
{
private static final String REQUIRED_VERSION = "2.0b1.1-jedit-1";
//{{{ evalSelection() method
/**
* Evaluates the text selected in the specified text area.
* @since jEdit 2.7pre2
*/
public static void evalSelection(View view, JEditTextArea textArea)
{
String command = textArea.getSelectedText();
if(command == null)
{
view.getToolkit().beep();
return;
}
Object returnValue = eval(view,global,command);
if(returnValue != null)
textArea.setSelectedText(returnValue.toString());
} //}}}
//{{{ showEvaluateDialog() method
/**
* Prompts for a BeanShell expression to evaluate.
* @since jEdit 2.7pre2
*/
public static void showEvaluateDialog(View view)
{
String command = GUIUtilities.input(view,"beanshell-eval-input",null);
if(command != null)
{
if(!command.endsWith(";"))
command = command + ";";
int repeat = view.getInputHandler().getRepeatCount();
if(view.getMacroRecorder() != null)
{
view.getMacroRecorder().record(repeat,command);
}
Object returnValue = null;
try
{
for(int i = 0; i < repeat; i++)
{
returnValue = _eval(view,global,command);
}
}
catch(Throwable e)
{
Log.log(Log.ERROR,BeanShell.class,e);
handleException(view,null,e);
}
if(returnValue != null)
{
String[] args = { returnValue.toString() };
GUIUtilities.message(view,"beanshell-eval",args);
}
}
} //}}}
//{{{ showEvaluateLinesDialog() method
/**
* Evaluates the specified script for each selected line.
* @since jEdit 4.0pre1
*/
public static void showEvaluateLinesDialog(View view)
{
String command = GUIUtilities.input(view,"beanshell-eval-line",null);
JEditTextArea textArea = view.getTextArea();
Buffer buffer = view.getBuffer();
if(command == null || command.length() == 0)
return;
Selection[] selection = textArea.getSelection();
if(selection.length == 0)
{
view.getToolkit().beep();
return;
}
if(!command.endsWith(";"))
command = command + ";";
String script = "int[] lines = textArea.getSelectedLines();\n"
+ "for(int i = 0; i < lines.length; i++)\n"
+ "{\n"
+ "line = lines[i];\n"
+ "index = line - lines[0];\n"
+ "start = buffer.getLineStartOffset(line);\n"
+ "end = buffer.getLineEndOffset(line);\n"
+ "text = buffer.getText(start,end - start - 1);\n"
+ "newText = " + command + "\n"
+ "if(newText != null)\n"
+ "{\n"
+ "buffer.remove(start,end - start - 1);\n"
+ "buffer.insert(start,String.valueOf(newText));\n"
+ "}\n"
+ "}\n";
if(view.getMacroRecorder() != null)
view.getMacroRecorder().record(1,script);
try
{
buffer.beginCompoundEdit();
BeanShell.eval(view,global,script);
}
finally
{
buffer.endCompoundEdit();
}
textArea.selectNone();
} //}}}
//{{{ runScript() method
/**
* Runs a BeanShell script. Errors are shown in a dialog box.
*
* If the in parameter is non-null, the script is
* read from that stream; otherwise it is read from the file identified
* by path .
*
* The scriptPath BeanShell variable is set to the path
* name of the script.
*
* @param view The view. Within the script, references to
* buffer , textArea and editPane
* are determined with reference to this parameter.
* @param path The script file's VFS path.
* @param in The reader to read the script from, or null .
* @param ownNamespace If set to false , methods and
* variables defined in the script will be available to all future
* uses of BeanShell; if set to true , they will be lost as
* soon as the script finishes executing. jEdit uses a value of
* false when running startup scripts, and a value of
* true when running all other macros.
*
* @since jEdit 4.0pre7
*/
public static void runScript(View view, String path, Reader in,
boolean ownNamespace)
{
try
{
_runScript(view,path,in,ownNamespace);
}
catch(Throwable e)
{
Log.log(Log.ERROR,BeanShell.class,e);
handleException(view,path,e);
}
} //}}}
//{{{ runScript() method
/**
* Runs a BeanShell script. Errors are shown in a dialog box.
*
* If the in parameter is non-null, the script is
* read from that stream; otherwise it is read from the file identified
* by path .
*
* The scriptPath BeanShell variable is set to the path
* name of the script.
*
* @param view The view. Within the script, references to
* buffer , textArea and editPane
* are determined with reference to this parameter.
* @param path The script file's VFS path.
* @param in The reader to read the script from, or null .
* @param namespace The namespace to run the script in.
*
* @since jEdit 4.2pre5
*/
public static void runScript(View view, String path, Reader in,
NameSpace namespace)
{
try
{
_runScript(view,path,in,namespace);
}
catch(Throwable e)
{
Log.log(Log.ERROR,BeanShell.class,e);
handleException(view,path,e);
}
} //}}}
//{{{ _runScript() method
/**
* Runs a BeanShell script. Errors are passed to the caller.
*
* If the in parameter is non-null, the script is
* read from that stream; otherwise it is read from the file identified
* by path .
*
* The scriptPath BeanShell variable is set to the path
* name of the script.
*
* @param view The view. Within the script, references to
* buffer , textArea and editPane
* are determined with reference to this parameter.
* @param path The script file's VFS path.
* @param in The reader to read the script from, or null .
* @param ownNamespace If set to false , methods and
* variables defined in the script will be available to all future
* uses of BeanShell; if set to true , they will be lost as
* soon as the script finishes executing. jEdit uses a value of
* false when running startup scripts, and a value of
* true when running all other macros.
* @exception Exception instances are thrown when various BeanShell errors
* occur
* @since jEdit 4.0pre7
*/
public static void _runScript(View view, String path, Reader in,
boolean ownNamespace) throws Exception
{
_runScript(view,path,in,ownNamespace
? new NameSpace(global,"script namespace")
: global);
} //}}}
//{{{ _runScript() method
/**
* Runs a BeanShell script. Errors are passed to the caller.
*
* If the in parameter is non-null, the script is
* read from that stream; otherwise it is read from the file identified
* by path .
*
* The scriptPath BeanShell variable is set to the path
* name of the script.
*
* @param view The view. Within the script, references to
* buffer , textArea and editPane
* are determined with reference to this parameter.
* @param path The script file's VFS path.
* @param in The reader to read the script from, or null .
* @param namespace The namespace to run the script in.
* @exception Exception instances are thrown when various BeanShell errors
* occur
* @since jEdit 4.2pre5
*/
public static void _runScript(View view, String path, Reader in,
NameSpace namespace) throws Exception
{
Log.log(Log.MESSAGE,BeanShell.class,"Running script " + path);
Interpreter interp = createInterpreter(namespace);
VFS vfs = null;
Object session = null;
try
{
if(in == null)
{
Buffer buffer = jEdit.getBuffer(path);
vfs = VFSManager.getVFSForPath(path);
session = vfs.createVFSSession(path,view);
if(session == null)
{
// user cancelled???
return;
}
if(buffer != null)
{
if(!buffer.isLoaded())
VFSManager.waitForRequests();
in = new StringReader(buffer.getText(0,
buffer.getLength()));
}
else
{
in = new BufferedReader(new InputStreamReader(
vfs._createInputStream(session,
path,false,view)));
}
}
setupDefaultVariables(namespace,view);
interp.set("scriptPath",path);
running = true;
interp.eval(in,namespace,path);
}
catch(Exception e)
{
unwrapException(e);
}
finally
{
running = false;
if(session != null)
{
try
{
vfs._endVFSSession(session,view);
}
catch(IOException io)
{
Log.log(Log.ERROR,BeanShell.class,io);
GUIUtilities.error(view,"read-error",
new String[] { path, io.toString() });
}
}
try
{
// no need to do this for macros!
if(namespace == global)
{
resetDefaultVariables(namespace);
interp.unset("scriptPath");
}
}
catch(EvalError e)
{
// do nothing
}
}
} //}}}
//{{{ eval() method
/**
* Evaluates the specified BeanShell expression. Errors are reported in
* a dialog box.
* @param view The view. Within the script, references to
* buffer , textArea and editPane
* are determined with reference to this parameter.
* @param namespace The namespace
* @param command The expression
* @since jEdit 4.0pre8
*/
public static Object eval(View view, NameSpace namespace, String command)
{
try
{
return _eval(view,namespace,command);
}
catch(Throwable e)
{
Log.log(Log.ERROR,BeanShell.class,e);
handleException(view,null,e);
}
return null;
} //}}}
//{{{ _eval() method
/**
* Evaluates the specified BeanShell expression. Unlike
* eval() , this method passes any exceptions to the caller.
*
* @param view The view. Within the script, references to
* buffer , textArea and editPane
* are determined with reference to this parameter.
* @param namespace The namespace
* @param command The expression
* @exception Exception instances are thrown when various BeanShell
* errors occur
* @since jEdit 3.2pre7
*/
public static Object _eval(View view, NameSpace namespace, String command)
throws Exception
{
Interpreter interp = createInterpreter(namespace);
try
{
setupDefaultVariables(namespace,view);
if(Debug.BEANSHELL_DEBUG)
Log.log(Log.DEBUG,BeanShell.class,command);
return interp.eval(command);
}
catch(Exception e)
{
unwrapException(e);
// never called
return null;
}
finally
{
try
{
resetDefaultVariables(namespace);
}
catch(UtilEvalError e)
{
// do nothing
}
}
} //}}}
//{{{ cacheBlock() method
/**
* Caches a block of code, returning a handle that can be passed to
* runCachedBlock().
* @param id An identifier. If null, a unique identifier is generated
* @param code The code
* @param namespace If true, the namespace will be set
* @exception Exception instances are thrown when various BeanShell errors
* occur
* @since jEdit 4.1pre1
*/
public static BshMethod cacheBlock(String id, String code, boolean namespace)
throws Exception
{
String name = "__internal_" + id;
// evaluate a method declaration
if(namespace)
{
_eval(null,global,name + "(ns) {\nthis.callstack.set(0,ns);\n" + code + "\n}");
return global.getMethod(name,new Class[] { NameSpace.class });
}
else
{
_eval(null,global,name + "() {\n" + code + "\n}");
return global.getMethod(name,new Class[0]);
}
} //}}}
//{{{ runCachedBlock() method
/**
* Runs a cached block of code in the specified namespace. Faster than
* evaluating the block each time.
* @param method The method instance returned by cacheBlock()
* @param view The view
* @param namespace The namespace to run the code in
* @exception Exception instances are thrown when various BeanShell
* errors occur
* @since jEdit 4.1pre1
*/
public static Object runCachedBlock(BshMethod method, View view,
NameSpace namespace) throws Exception
{
boolean useNamespace;
if(namespace == null)
{
useNamespace = false;
namespace = global;
}
else
useNamespace = true;
try
{
setupDefaultVariables(namespace,view);
Object retVal = method.invoke(useNamespace
? new Object[] { namespace }
: NO_ARGS,
interpForMethods,new CallStack());
if(retVal instanceof Primitive)
{
if(retVal == Primitive.VOID)
return null;
else
return ((Primitive)retVal).getValue();
}
else
return retVal;
}
catch(Exception e)
{
unwrapException(e);
// never called
return null;
}
finally
{
resetDefaultVariables(namespace);
}
} //}}}
//{{{ isScriptRunning() method
/**
* Returns if a BeanShell script or macro is currently running.
* @since jEdit 2.7pre2
*/
public static boolean isScriptRunning()
{
return running;
} //}}}
//{{{ getNameSpace() method
/**
* Returns the global namespace.
* @since jEdit 3.2pre5
*/
public static NameSpace getNameSpace()
{
return global;
} //}}}
//{{{ Deprecated functions
//{{{ runScript() method
/**
* @deprecated The rethrowBshErrors parameter is now
* obsolete; call _runScript() or runScript()
* instead.
*/
public static void runScript(View view, String path,
boolean ownNamespace, boolean rethrowBshErrors)
{
runScript(view,path,null,ownNamespace);
} //}}}
//{{{ runScript() method
/**
* @deprecated The rethrowBshErrors parameter is now
* obsolete; call _runScript() or runScript()
* instead.
*/
public static void runScript(View view, String path, Reader in,
boolean ownNamespace, boolean rethrowBshErrors)
{
runScript(view,path,in,ownNamespace);
} //}}}
//{{{ eval() method
/**
* @deprecated The rethrowBshErrors parameter is now
* obsolete; call _eval() or eval() instead.
*/
public static Object eval(View view, String command,
boolean rethrowBshErrors)
{
return eval(view,global,command);
} //}}}
//{{{ eval() method
/**
* @deprecated The rethrowBshErrors parameter is now
* obsolete; call _eval() or eval() instead.
*/
public static Object eval(View view, NameSpace namespace,
String command, boolean rethrowBshErrors)
{
return eval(view,namespace,command);
} //}}}
//}}}
//{{{ Package-private members
//{{{ init() method
static void init()
{
try
{
NameSpace.class.getMethod("addCommandPath",
new Class[] { String.class, Class.class });
}
catch(Exception e)
{
Log.log(Log.ERROR,BeanShell.class,"You have BeanShell version " + Interpreter.VERSION + " in your CLASSPATH.");
Log.log(Log.ERROR,BeanShell.class,"Please remove it from the CLASSPATH since jEdit can only run with the bundled BeanShell version " + REQUIRED_VERSION);
System.exit(1);
}
classManager = new CustomClassManager();
classManager.setClassLoader(new JARClassLoader());
global = new NameSpace(classManager,
"jEdit embedded BeanShell interpreter");
global.importPackage("org.gjt.sp.jedit");
global.importPackage("org.gjt.sp.jedit.browser");
global.importPackage("org.gjt.sp.jedit.buffer");
global.importPackage("org.gjt.sp.jedit.gui");
global.importPackage("org.gjt.sp.jedit.help");
global.importPackage("org.gjt.sp.jedit.io");
global.importPackage("org.gjt.sp.jedit.menu");
global.importPackage("org.gjt.sp.jedit.msg");
global.importPackage("org.gjt.sp.jedit.options");
global.importPackage("org.gjt.sp.jedit.pluginmgr");
global.importPackage("org.gjt.sp.jedit.print");
global.importPackage("org.gjt.sp.jedit.search");
global.importPackage("org.gjt.sp.jedit.syntax");
global.importPackage("org.gjt.sp.jedit.textarea");
global.importPackage("org.gjt.sp.util");
interpForMethods = createInterpreter(global);
} //}}}
//{{{ resetClassManager() method
/**
* Causes BeanShell internal structures to drop references to cached
* Class instances.
*/
static void resetClassManager()
{
classManager.reset();
} //}}}
//}}}
//{{{ Private members
//{{{ Static variables
private static final Object[] NO_ARGS = new Object[0];
private static CustomClassManager classManager;
private static Interpreter interpForMethods;
private static NameSpace global;
private static boolean running;
//}}}
//{{{ setupDefaultVariables() method
private static void setupDefaultVariables(NameSpace namespace, View view)
throws UtilEvalError
{
if(view != null)
{
EditPane editPane = view.getEditPane();
namespace.setVariable("view",view);
namespace.setVariable("editPane",editPane);
namespace.setVariable("buffer",editPane.getBuffer());
namespace.setVariable("textArea",editPane.getTextArea());
namespace.setVariable("wm",view.getDockableWindowManager());
}
} //}}}
//{{{ resetDefaultVariables() method
private static void resetDefaultVariables(NameSpace namespace)
throws UtilEvalError
{
namespace.setVariable("view",null);
namespace.setVariable("editPane",null);
namespace.setVariable("buffer",null);
namespace.setVariable("textArea",null);
namespace.setVariable("wm",null);
} //}}}
//{{{ unwrapException() method
/**
* This extracts an exception from a 'wrapping' exception, as BeanShell
* sometimes throws. This gives the user a more accurate error traceback
*/
private static void unwrapException(Exception e) throws Exception
{
if(e instanceof TargetError)
{
Throwable t = ((TargetError)e).getTarget();
if(t instanceof Exception)
throw (Exception)t;
else if(t instanceof Error)
throw (Error)t;
}
if(e instanceof InvocationTargetException)
{
Throwable t = ((InvocationTargetException)e).getTargetException();
if(t instanceof Exception)
throw (Exception)t;
else if(t instanceof Error)
throw (Error)t;
}
throw e;
} //}}}
//{{{ handleException() method
private static void handleException(View view, String path, Throwable t)
{
if(t instanceof IOException)
{
VFSManager.error(view,path,"ioerror.read-error",
new String[] { t.toString() });
}
else
new BeanShellErrorDialog(view,t);
} //}}}
//{{{ createInterpreter() method
private static Interpreter createInterpreter(NameSpace nameSpace)
{
return new Interpreter(null,System.out,System.err,false,nameSpace);
} //}}}
//}}}
//{{{ CustomClassManager class
static class CustomClassManager extends BshClassManager
{
private LinkedList listeners = new LinkedList();
private ReferenceQueue refQueue = new ReferenceQueue();
// copy and paste from bsh/classpath/ClassManagerImpl.java...
public synchronized void addListener( Listener l )
{
listeners.add( new WeakReference( l, refQueue) );
// clean up old listeners
Reference deadref;
while ( (deadref = refQueue.poll()) != null )
{
boolean ok = listeners.remove( deadref );
if ( ok )
{
//System.err.println("cleaned up weak ref: "+deadref);
}
else
{
if ( Interpreter.DEBUG ) Interpreter.debug(
"tried to remove non-existent weak ref: "+deadref);
}
}
}
public void removeListener( Listener l )
{
throw new Error("unimplemented");
}
public void reset()
{
classLoaderChanged();
}
protected synchronized void classLoaderChanged()
{
// clear the static caches in BshClassManager
clearCaches();
for (Iterator iter = listeners.iterator();
iter.hasNext(); )
{
WeakReference wr = (WeakReference)
iter.next();
Listener l = (Listener)wr.get();
if ( l == null ) // garbage collected
iter.remove();
else
l.classLoaderChanged();
}
}
} //}}}
}
|