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