PageRenderTime 244ms CodeModel.GetById 211ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre4/org/gjt/sp/jedit/BeanShell.java

#
Java | 740 lines | 442 code | 75 blank | 223 comment | 57 complexity | b7b5961fa3bfe0d4fb3d4f1599641626 MD5 | raw file
  1/*
  2 * BeanShell.java - BeanShell scripting support
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2000, 2001, 2002 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit;
 24
 25//{{{ Imports
 26import bsh.*;
 27import java.io.*;
 28import java.lang.ref.*;
 29import java.lang.reflect.InvocationTargetException;
 30import java.util.*;
 31import org.gjt.sp.jedit.io.*;
 32import org.gjt.sp.jedit.gui.BeanShellErrorDialog;
 33import org.gjt.sp.jedit.textarea.*;
 34import org.gjt.sp.util.Log;
 35//}}}
 36
 37/**
 38 * BeanShell is jEdit's extension language.<p>
 39 *
 40 * When run from jEdit, BeanShell code has access to the following predefined
 41 * variables:
 42 *
 43 * <ul>
 44 * <li><code>view</code> - the currently active {@link View}.</li>
 45 * <li><code>editPane</code> - the currently active {@link EditPane}.</li>
 46 * <li><code>textArea</code> - the edit pane's {@link JEditTextArea}.</li>
 47 * <li><code>buffer</code> - the edit pane's {@link Buffer}.</li>
 48 * <li><code>wm</code> - the view's {@link
 49 * org.gjt.sp.jedit.gui.DockableWindowManager}.</li>
 50 * <li><code>scriptPath</code> - the path name of the currently executing
 51 * BeanShell script.</li>
 52 * </ul>
 53 *
 54 * @author Slava Pestov
 55 * @version $Id: BeanShell.java 4787 2003-06-16 05:02:19Z spestov $
 56 */
 57public class BeanShell
 58{
 59	//{{{ evalSelection() method
 60	/**
 61	 * Evaluates the text selected in the specified text area.
 62	 * @since jEdit 2.7pre2
 63	 */
 64	public static void evalSelection(View view, JEditTextArea textArea)
 65	{
 66		String command = textArea.getSelectedText();
 67		if(command == null)
 68		{
 69			view.getToolkit().beep();
 70			return;
 71		}
 72		Object returnValue = eval(view,global,command);
 73		if(returnValue != null)
 74			textArea.setSelectedText(returnValue.toString());
 75	} //}}}
 76
 77	//{{{ showEvaluateDialog() method
 78	/**
 79	 * Prompts for a BeanShell expression to evaluate.
 80	 * @since jEdit 2.7pre2
 81	 */
 82	public static void showEvaluateDialog(View view)
 83	{
 84		String command = GUIUtilities.input(view,"beanshell-eval-input",null);
 85		if(command != null)
 86		{
 87			if(!command.endsWith(";"))
 88				command = command + ";";
 89
 90			int repeat = view.getInputHandler().getRepeatCount();
 91
 92			if(view.getMacroRecorder() != null)
 93			{
 94				view.getMacroRecorder().record(repeat,command);
 95			}
 96
 97			Object returnValue = null;
 98			try
 99			{
100				for(int i = 0; i < repeat; i++)
101				{
102					returnValue = _eval(view,global,command);
103				}
104			}
105			catch(Throwable e)
106			{
107				Log.log(Log.ERROR,BeanShell.class,e);
108
109				handleException(view,null,e);
110			}
111
112			if(returnValue != null)
113			{
114				String[] args = { returnValue.toString() };
115				GUIUtilities.message(view,"beanshell-eval",args);
116			}
117		}
118	} //}}}
119
120	//{{{ showEvaluateLinesDialog() method
121	/**
122	 * Evaluates the specified script for each selected line.
123	 * @since jEdit 4.0pre1
124	 */
125	public static void showEvaluateLinesDialog(View view)
126	{
127		String command = GUIUtilities.input(view,"beanshell-eval-line",null);
128
129		JEditTextArea textArea = view.getTextArea();
130		Buffer buffer = view.getBuffer();
131
132		if(command == null || command.length() == 0)
133			return;
134
135		Selection[] selection = textArea.getSelection();
136		if(selection.length == 0)
137		{
138			view.getToolkit().beep();
139			return;
140		}
141
142		if(!command.endsWith(";"))
143			command = command + ";";
144
145		String script = "int[] lines = textArea.getSelectedLines();\n"
146			+ "for(int i = 0; i < lines.length; i++)\n"
147			+ "{\n"
148				+ "line = lines[i];\n"
149				+ "index = line - lines[0];\n"
150				+ "start = buffer.getLineStartOffset(line);\n"
151				+ "end = buffer.getLineEndOffset(line);\n"
152				+ "text = buffer.getText(start,end - start - 1);\n"
153				+ "newText = " + command + "\n"
154				+ "if(newText != null)\n"
155				+ "{\n"
156					+ "buffer.remove(start,end - start - 1);\n"
157					+ "buffer.insert(start,String.valueOf(newText));\n"
158				+ "}\n"
159			+ "}\n";
160
161		if(view.getMacroRecorder() != null)
162			view.getMacroRecorder().record(1,script);
163
164		try
165		{
166			buffer.beginCompoundEdit();
167
168			BeanShell.eval(view,global,script);
169		}
170		finally
171		{
172			buffer.endCompoundEdit();
173		}
174
175		textArea.selectNone();
176	} //}}}
177
178	//{{{ runScript() method
179	/**
180	 * Runs a BeanShell script. Errors are shown in a dialog box.<p>
181	 *
182	 * If the <code>in</code> parameter is non-null, the script is
183	 * read from that stream; otherwise it is read from the file identified
184	 * by <code>path</code>.<p>
185	 *
186	 * The <code>scriptPath</code> BeanShell variable is set to the path
187	 * name of the script.
188	 *
189	 * @param view The view. Within the script, references to
190	 * <code>buffer</code>, <code>textArea</code> and <code>editPane</code>
191	 * are determined with reference to this parameter.
192	 * @param path The script file's VFS path.
193	 * @param in The reader to read the script from, or <code>null</code>.
194	 * @param ownNamespace If set to <code>false</code>, methods and
195	 * variables defined in the script will be available to all future
196	 * uses of BeanShell; if set to <code>true</code>, they will be lost as
197	 * soon as the script finishes executing. jEdit uses a value of
198	 * <code>false</code> when running startup scripts, and a value of
199	 * <code>true</code> when running all other macros.
200	 *
201	 * @since jEdit 4.0pre7
202	 */
203	public static void runScript(View view, String path, Reader in,
204		boolean ownNamespace)
205	{
206		try
207		{
208			_runScript(view,path,in,ownNamespace);
209		}
210		catch(Throwable e)
211		{
212			Log.log(Log.ERROR,BeanShell.class,e);
213
214			handleException(view,path,e);
215		}
216	} //}}}
217
218	//{{{ _runScript() method
219	/**
220	 * Runs a BeanShell script. Errors are passed to the caller.<p>
221	 *
222	 * If the <code>in</code> parameter is non-null, the script is
223	 * read from that stream; otherwise it is read from the file identified
224	 * by <code>path</code>.<p>
225	 *
226	 * The <code>scriptPath</code> BeanShell variable is set to the path
227	 * name of the script.
228	 *
229	 * @param view The view. Within the script, references to
230	 * <code>buffer</code>, <code>textArea</code> and <code>editPane</code>
231	 * are determined with reference to this parameter.
232	 * @param path The script file's VFS path.
233	 * @param in The reader to read the script from, or <code>null</code>.
234	 * @param ownNamespace If set to <code>false</code>, methods and
235	 * variables defined in the script will be available to all future
236	 * uses of BeanShell; if set to <code>true</code>, they will be lost as
237	 * soon as the script finishes executing. jEdit uses a value of
238	 * <code>false</code> when running startup scripts, and a value of
239	 * <code>true</code> when running all other macros.
240	 * @exception Exception instances are thrown when various BeanShell errors
241	 * occur
242	 * @since jEdit 4.0pre7
243	 */
244	public static void _runScript(View view, String path, Reader in,
245		boolean ownNamespace) throws Exception
246	{
247		Log.log(Log.MESSAGE,BeanShell.class,"Running script " + path);
248
249		NameSpace namespace;
250		if(ownNamespace)
251			namespace = new NameSpace(global,"script namespace");
252		else
253			namespace = global;
254
255		Interpreter interp = createInterpreter(namespace);
256
257		VFS vfs = null;
258		Object session = null;
259
260		try
261		{
262			if(in == null)
263			{
264				Buffer buffer = jEdit.getBuffer(path);
265
266				vfs = VFSManager.getVFSForPath(path);
267				session = vfs.createVFSSession(path,view);
268				if(session == null)
269				{
270					// user cancelled???
271					return;
272				}
273
274				if(buffer != null)
275				{
276					if(!buffer.isLoaded())
277						VFSManager.waitForRequests();
278
279					in = new StringReader(buffer.getText(0,
280						buffer.getLength()));
281				}
282				else
283				{
284					in = new BufferedReader(new InputStreamReader(
285						vfs._createInputStream(session,
286						path,false,view)));
287				}
288			}
289
290			setupDefaultVariables(namespace,view);
291			interp.set("scriptPath",path);
292
293			running = true;
294
295			interp.eval(in,namespace,path);
296		}
297		catch(Exception e)
298		{
299			unwrapException(e);
300		}
301		finally
302		{
303			running = false;
304
305			if(session != null)
306			{
307				try
308				{
309					vfs._endVFSSession(session,view);
310				}
311				catch(IOException io)
312				{
313					Log.log(Log.ERROR,BeanShell.class,io);
314					GUIUtilities.error(view,"read-error",
315						new String[] { path, io.toString() });
316				}
317			}
318
319			try
320			{
321				// no need to do this for macros!
322				if(!ownNamespace)
323				{
324					resetDefaultVariables(namespace);
325					interp.unset("scriptPath");
326				}
327			}
328			catch(EvalError e)
329			{
330				// do nothing
331			}
332		}
333	} //}}}
334
335	//{{{ eval() method
336	/**
337	 * Evaluates the specified BeanShell expression. Errors are reported in
338	 * a dialog box.
339	 * @param view The view. Within the script, references to
340	 * <code>buffer</code>, <code>textArea</code> and <code>editPane</code>
341	 * are determined with reference to this parameter.
342	 * @param namespace The namespace
343	 * @param command The expression
344	 * @since jEdit 4.0pre8
345	 */
346	public static Object eval(View view, NameSpace namespace, String command)
347	{
348		try
349		{
350			return _eval(view,namespace,command);
351		}
352		catch(Throwable e)
353		{
354			Log.log(Log.ERROR,BeanShell.class,e);
355
356			handleException(view,null,e);
357		}
358
359		return null;
360	} //}}}
361
362	//{{{ _eval() method
363	/**
364	 * Evaluates the specified BeanShell expression. Unlike
365	 * <code>eval()</code>, this method passes any exceptions to the caller.
366	 *
367	 * @param view The view. Within the script, references to
368	 * <code>buffer</code>, <code>textArea</code> and <code>editPane</code>
369	 * are determined with reference to this parameter.
370	 * @param namespace The namespace
371	 * @param command The expression
372	 * @exception Exception instances are thrown when various BeanShell
373	 * errors occur
374	 * @since jEdit 3.2pre7
375	 */
376	public static Object _eval(View view, NameSpace namespace, String command)
377		throws Exception
378	{
379		Interpreter interp = createInterpreter(namespace);
380
381		try
382		{
383			setupDefaultVariables(namespace,view);
384			if(Debug.BEANSHELL_DEBUG)
385				Log.log(Log.DEBUG,BeanShell.class,command);
386			return interp.eval(command);
387		}
388		catch(Exception e)
389		{
390			unwrapException(e);
391			// never called
392			return null;
393		}
394		finally
395		{
396			try
397			{
398				resetDefaultVariables(namespace);
399			}
400			catch(UtilEvalError e)
401			{
402				// do nothing
403			}
404		}
405	} //}}}
406
407	//{{{ cacheBlock() method
408	/**
409	 * Caches a block of code, returning a handle that can be passed to
410	 * runCachedBlock().
411	 * @param id An identifier. If null, a unique identifier is generated
412	 * @param code The code
413	 * @param namespace If true, the namespace will be set
414	 * @exception Exception instances are thrown when various BeanShell errors
415	 * occur
416	 * @since jEdit 4.1pre1
417	 */
418	public static BshMethod cacheBlock(String id, String code, boolean namespace)
419		throws Exception
420	{
421		String name = "__internal_" + id;
422
423		// evaluate a method declaration
424		if(namespace)
425		{
426			_eval(null,global,name + "(ns) {\nthis.callstack.set(0,ns);\n" + code + "\n}");
427			return global.getMethod(name,new Class[] { NameSpace.class });
428		}
429		else
430		{
431			_eval(null,global,name + "() {\n" + code + "\n}");
432			return global.getMethod(name,new Class[0]);
433		}
434	} //}}}
435
436	//{{{ runCachedBlock() method
437	/**
438	 * Runs a cached block of code in the specified namespace. Faster than
439	 * evaluating the block each time.
440	 * @param method The method instance returned by cacheBlock()
441	 * @param view The view
442	 * @param namespace The namespace to run the code in
443	 * @exception Exception instances are thrown when various BeanShell
444	 * errors occur
445	 * @since jEdit 4.1pre1
446	 */
447	public static Object runCachedBlock(BshMethod method, View view,
448		NameSpace namespace) throws Exception
449	{
450		boolean useNamespace;
451		if(namespace == null)
452		{
453			useNamespace = false;
454			namespace = global;
455		}
456		else
457			useNamespace = true;
458
459		try
460		{
461			setupDefaultVariables(namespace,view);
462
463			Object retVal = method.invoke(useNamespace
464				? new Object[] { namespace }
465				: NO_ARGS,
466				interpForMethods,new CallStack());
467			if(retVal instanceof Primitive)
468			{
469				if(retVal == Primitive.VOID)
470					return null;
471				else
472					return ((Primitive)retVal).getValue();
473			}
474			else
475				return retVal;
476		}
477		catch(Exception e)
478		{
479			unwrapException(e);
480			// never called
481			return null;
482		}
483		finally
484		{
485			resetDefaultVariables(namespace);
486		}
487	} //}}}
488
489	//{{{ isScriptRunning() method
490	/**
491	 * Returns if a BeanShell script or macro is currently running.
492	 * @since jEdit 2.7pre2
493	 */
494	public static boolean isScriptRunning()
495	{
496		return running;
497	} //}}}
498
499	//{{{ getNameSpace() method
500	/**
501	 * Returns the global namespace.
502	 * @since jEdit 3.2pre5
503	 */
504	public static NameSpace getNameSpace()
505	{
506		return global;
507	} //}}}
508
509	//{{{ Deprecated functions
510
511	//{{{ runScript() method
512	/**
513	 * @deprecated The <code>rethrowBshErrors</code> parameter is now
514	 * obsolete; call <code>_runScript()</code> or <code>runScript()</code>
515	 * instead.
516	 */
517	public static void runScript(View view, String path,
518		boolean ownNamespace, boolean rethrowBshErrors)
519	{
520		runScript(view,path,null,ownNamespace);
521	} //}}}
522
523	//{{{ runScript() method
524	/**
525	 * @deprecated The <code>rethrowBshErrors</code> parameter is now
526	 * obsolete; call <code>_runScript()</code> or <code>runScript()</code>
527	 * instead.
528	 */
529	public static void runScript(View view, String path, Reader in,
530		boolean ownNamespace, boolean rethrowBshErrors)
531	{
532		runScript(view,path,in,ownNamespace);
533	} //}}}
534
535	//{{{ eval() method
536	/**
537	 * @deprecated The <code>rethrowBshErrors</code> parameter is now
538	 * obsolete; call <code>_eval()</code> or <code>eval()</code> instead.
539	 */
540	public static Object eval(View view, String command,
541		boolean rethrowBshErrors)
542	{
543		return eval(view,global,command);
544	} //}}}
545
546	//{{{ eval() method
547	/**
548	 * @deprecated The <code>rethrowBshErrors</code> parameter is now
549	 * obsolete; call <code>_eval()</code> or <code>eval()</code> instead.
550	 */
551	public static Object eval(View view, NameSpace namespace,
552		String command, boolean rethrowBshErrors)
553	{
554		return eval(view,namespace,command);
555	} //}}}
556
557	//}}}
558
559	//{{{ Package-private members
560
561	//{{{ init() method
562	static void init()
563	{
564		classManager = new CustomClassManager();
565		classManager.setClassLoader(new JARClassLoader());
566
567		global = new NameSpace(classManager,
568			"jEdit embedded BeanShell interpreter");
569		global.importPackage("org.gjt.sp.jedit");
570		global.importPackage("org.gjt.sp.jedit.browser");
571		global.importPackage("org.gjt.sp.jedit.buffer");
572		global.importPackage("org.gjt.sp.jedit.gui");
573		global.importPackage("org.gjt.sp.jedit.help");
574		global.importPackage("org.gjt.sp.jedit.io");
575		global.importPackage("org.gjt.sp.jedit.menu");
576		global.importPackage("org.gjt.sp.jedit.msg");
577		global.importPackage("org.gjt.sp.jedit.options");
578		global.importPackage("org.gjt.sp.jedit.pluginmgr");
579		global.importPackage("org.gjt.sp.jedit.print");
580		global.importPackage("org.gjt.sp.jedit.search");
581		global.importPackage("org.gjt.sp.jedit.syntax");
582		global.importPackage("org.gjt.sp.jedit.textarea");
583		global.importPackage("org.gjt.sp.util");
584
585		interpForMethods = createInterpreter(global);
586	} //}}}
587
588	//{{{ resetClassManager() method
589	/**
590	 * Causes BeanShell internal structures to drop references to cached
591	 * Class instances.
592	 */
593	static void resetClassManager()
594	{
595		classManager.reset();
596	} //}}}
597
598	//}}}
599
600	//{{{ Private members
601
602	//{{{ Static variables
603	private static final Object[] NO_ARGS = new Object[0];
604	private static CustomClassManager classManager;
605	private static Interpreter interpForMethods;
606	private static NameSpace global;
607	private static boolean running;
608	//}}}
609
610	//{{{ setupDefaultVariables() method
611	private static void setupDefaultVariables(NameSpace namespace, View view)
612		throws UtilEvalError
613	{
614		if(view != null)
615		{
616			EditPane editPane = view.getEditPane();
617			namespace.setVariable("view",view);
618			namespace.setVariable("editPane",editPane);
619			namespace.setVariable("buffer",editPane.getBuffer());
620			namespace.setVariable("textArea",editPane.getTextArea());
621			namespace.setVariable("wm",view.getDockableWindowManager());
622		}
623	} //}}}
624
625	//{{{ resetDefaultVariables() method
626	private static void resetDefaultVariables(NameSpace namespace)
627		throws UtilEvalError
628	{
629		namespace.setVariable("view",null);
630		namespace.setVariable("editPane",null);
631		namespace.setVariable("buffer",null);
632		namespace.setVariable("textArea",null);
633		namespace.setVariable("wm",null);
634	} //}}}
635
636	//{{{ unwrapException() method
637	/**
638	 * This extracts an exception from a 'wrapping' exception, as BeanShell
639	 * sometimes throws. This gives the user a more accurate error traceback
640	 */
641	private static void unwrapException(Exception e) throws Exception
642	{
643		if(e instanceof TargetError)
644		{
645			Throwable t = ((TargetError)e).getTarget();
646			if(t instanceof Exception)
647				throw (Exception)t;
648			else if(t instanceof Error)
649				throw (Error)t;
650		}
651
652		if(e instanceof InvocationTargetException)
653		{
654			Throwable t = ((InvocationTargetException)e).getTargetException();
655			if(t instanceof Exception)
656				throw (Exception)t;
657			else if(t instanceof Error)
658				throw (Error)t;
659		}
660
661		throw e;
662	} //}}}
663
664	//{{{ handleException() method
665	private static void handleException(View view, String path, Throwable t)
666	{
667		if(t instanceof IOException)
668		{
669			VFSManager.error(view,path,"ioerror.read-error",
670				new String[] { t.toString() });
671		}
672		else
673			new BeanShellErrorDialog(view,t);
674	} //}}}
675
676	//{{{ createInterpreter() method
677	private static Interpreter createInterpreter(NameSpace nameSpace)
678	{
679		return new Interpreter(null,System.out,System.err,false,nameSpace);
680	} //}}}
681
682	//}}}
683
684	//{{{ CustomClassManager class
685	static class CustomClassManager extends BshClassManager
686	{
687		private LinkedList listeners = new LinkedList();
688		private ReferenceQueue refQueue = new ReferenceQueue();
689
690		// copy and paste from bsh/classpath/ClassManagerImpl.java...
691		public synchronized void addListener( Listener l )
692		{
693			listeners.add( new WeakReference( l, refQueue) );
694
695			// clean up old listeners
696			Reference deadref;
697			while ( (deadref = refQueue.poll()) != null )
698			{
699				boolean ok = listeners.remove( deadref );
700				if ( ok )
701				{
702					//System.err.println("cleaned up weak ref: "+deadref);
703				}
704				else
705				{
706					if ( Interpreter.DEBUG ) Interpreter.debug(
707						"tried to remove non-existent weak ref: "+deadref);
708				}
709			}
710		}
711
712		public void removeListener( Listener l )
713		{
714			throw new Error("unimplemented");
715		}
716
717		public void reset()
718		{
719			classLoaderChanged();
720		}
721
722		protected synchronized void classLoaderChanged()
723		{
724			// clear the static caches in BshClassManager
725			clearCaches();
726
727			for (Iterator iter = listeners.iterator();
728				iter.hasNext(); )
729			{
730				WeakReference wr = (WeakReference)
731					iter.next();
732				Listener l = (Listener)wr.get();
733				if ( l == null )  // garbage collected
734					iter.remove();
735				else
736					l.classLoaderChanged();
737			}
738		}
739	} //}}}
740}