PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/bundles/plugins-trunk/Console/console/ConsolePlugin.java

#
Java | 741 lines | 462 code | 89 blank | 190 comment | 105 complexity | 0a33cb452228d4c8285c7345258458cc 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. * ConsolePlugin.java - Console plugin
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 1999, 2004 Slava Pestov
  7. * Portions copyright (C) 1999, 2000 Kevin A. Burton
  8. * Revised 2005, 2010 by Alan Ezust
  9. *
  10. * This program is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU General Public License
  12. * as published by the Free Software Foundation; either version 2
  13. * of the License, or any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program; if not, write to the Free Software
  22. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  23. */
  24. package console;
  25. // {{{ Imports
  26. import java.io.File;
  27. import java.io.IOException;
  28. import java.io.StreamTokenizer;
  29. import java.io.StringReader;
  30. import java.net.URL;
  31. import java.util.Arrays;
  32. import java.util.Comparator;
  33. import java.util.HashMap;
  34. import java.util.TreeMap;
  35. import javax.swing.JOptionPane;
  36. import org.gjt.sp.jedit.*;
  37. import org.gjt.sp.jedit.EditBus.EBHandler;
  38. import org.gjt.sp.jedit.gui.DockableWindowManager;
  39. import org.gjt.sp.jedit.msg.DynamicMenuChanged;
  40. import org.gjt.sp.jedit.msg.PluginUpdate;
  41. import org.gjt.sp.jedit.msg.ViewUpdate;
  42. import org.gjt.sp.util.Log;
  43. import org.gjt.sp.util.StringList;
  44. import console.commando.CommandoCommand;
  45. import console.commando.CommandoToolBar;
  46. import errorlist.DefaultErrorSource;
  47. import projectviewer.action.EditProjectAction;
  48. // }}}
  49. /**
  50. * ConsolePlugin
  51. *
  52. * @version $Id: ConsolePlugin.java 21155 2012-02-20 17:07:14Z ezust $
  53. */
  54. public class ConsolePlugin extends EditPlugin
  55. {
  56. // {{{ Instance and static variables
  57. private static String consoleDirectory;
  58. private static String userCommandDirectory;
  59. private static ActionSet allCommands;
  60. private static ActionSet shellSwitchActions;
  61. static CommandoToolBar toolBar = null;
  62. // }}}
  63. // {{{ static final Members
  64. public static final String MENU = "plugin.console.ConsolePlugin.menu";
  65. public static final String CMD_PATH = "/console/bsh/";
  66. /**
  67. * Return value of {@link #parseLine()} if the text does not match a
  68. * known error pattern.
  69. */
  70. public static final int NO_ERROR = -1;
  71. // }}} static final Members
  72. // {{{ getSystemShell() method
  73. public static SystemShell getSystemShell()
  74. {
  75. return (SystemShell) ServiceManager.getService("console.Shell", "System");
  76. } // }}}
  77. // {{{ getShellSwitchActions()
  78. /**
  79. * @return a dynamically generated list of actions based on which
  80. * console Shells are available.
  81. */
  82. public static ActionSet getShellSwitchActions()
  83. {
  84. return shellSwitchActions;
  85. } // }}}
  86. // {{{ getAllCommands()
  87. /**
  88. @return all commands that are represented as
  89. .xml commando files.
  90. */
  91. public static ActionSet getAllCommands()
  92. {
  93. return allCommands;
  94. }
  95. // }}}
  96. // {{{ start() method
  97. public void start()
  98. {
  99. BeanShell.getNameSpace().addCommandPath(CMD_PATH, getClass());
  100. // systemCommandDirectory = MiscUtilities.constructPath(".",
  101. // "commando");
  102. String settings = jEdit.getSettingsDirectory();
  103. if (settings != null)
  104. {
  105. consoleDirectory = MiscUtilities.constructPath(settings, "console");
  106. userCommandDirectory = MiscUtilities.constructPath(consoleDirectory,
  107. "commando");
  108. File file = new File(userCommandDirectory);
  109. if (!file.exists())
  110. file.mkdirs();
  111. }
  112. allCommands = new ActionSet("Plugin: Console - Commando Commands");
  113. shellSwitchActions = new ActionSet("Plugin: Console - Shell Switchers");
  114. rescanCommands();
  115. CommandoToolBar.init();
  116. EditBus.addToBus(this);
  117. } // }}}
  118. // {{{ parseLine()
  119. /** parseLine()
  120. * Publicly documented class for parsing output of user defined
  121. * programs through the system shell error parser.
  122. *
  123. * @return -1 if no error/warning, or an ErrorType.
  124. * Possible values are:
  125. * @see ErrorSource.ERROR
  126. * @see ErrorSource.WARNING
  127. *
  128. * Although it is possible derived ErrorSources will return custom error codes.
  129. */
  130. public static synchronized int parseLine(View view,
  131. String text, String directory, DefaultErrorSource errorSource)
  132. {
  133. CommandOutputParser parser = getParser(view, directory, errorSource);
  134. return parser.processLine(text, false);
  135. } //}}}
  136. // {{{ stop() method
  137. public void stop()
  138. {
  139. EditBus.removeFromBus(this);
  140. // clean up edit bus
  141. View[] views = jEdit.getViews();
  142. for (int i = 0; i < views.length; i++) {
  143. Console console = getConsole(views[i]);
  144. if (console != null)
  145. console.unload();
  146. }
  147. BeanShell.getNameSpace().addCommandPath(CMD_PATH, getClass());
  148. CommandoToolBar.remove();
  149. jEdit.removeActionSet(allCommands);
  150. jEdit.removeActionSet(shellSwitchActions);
  151. } // }}}
  152. // {{{ handleViewUpdate() method
  153. @EBHandler
  154. public void handleViewUpdate(ViewUpdate vmsg)
  155. {
  156. if (vmsg.getWhat() == ViewUpdate.CREATED)
  157. {
  158. View v = vmsg.getView();
  159. CommandoToolBar.create(v);
  160. }
  161. if (vmsg.getWhat() == ViewUpdate.CLOSED) {
  162. View v = vmsg.getView();
  163. Console c = getConsole(v);
  164. if (c != null) c.unload();
  165. CommandoToolBar.remove(v);
  166. }
  167. }
  168. // }}}
  169. // {{{ handlePluginUpdate() method
  170. @EBHandler
  171. public void handlePluginUpdate(PluginUpdate msg)
  172. {
  173. rescanShells();
  174. }
  175. // }}}
  176. // {{{ getConsoleSettingsDirectory() method
  177. public static String getConsoleSettingsDirectory()
  178. {
  179. return consoleDirectory;
  180. }
  181. // }}}
  182. // {{{ scanDirectory()
  183. /**
  184. * Given a filename, performs translations so that it's a command name
  185. */
  186. public static void scanDirectory(String directory)
  187. {
  188. if (directory != null)
  189. {
  190. File[] files = new File(directory).listFiles();
  191. if (files != null)
  192. {
  193. for (int i = 0; i < files.length; i++)
  194. {
  195. File file = files[i];
  196. String fileName = file.getAbsolutePath();
  197. if (!fileName.endsWith(".xml") || file.isHidden())
  198. continue;
  199. EditAction action = CommandoCommand.create(fileName);
  200. allCommands.addAction(action);
  201. }
  202. }
  203. }
  204. }
  205. // }}}
  206. // {{{ scanJarFile()
  207. public static void scanJarFile()
  208. {
  209. // TODO: scan contents of a resource directory instead of using this property
  210. String defaultCommands = jEdit.getProperty("commando.default");
  211. StringList sl = StringList.split(defaultCommands, " ");
  212. for (String name: sl) {
  213. String key = "commando." + name;
  214. if (allCommands.contains(key)) {
  215. // skip over those that have user overridden versions already loaded
  216. continue;
  217. }
  218. String resourceName = "/console/commands/" + name + ".xml";
  219. // System.out.println ("GetResource: " + resourceName);
  220. URL url = Console.class.getResource(resourceName);
  221. if (url != null)
  222. {
  223. EditAction action = CommandoCommand.create(url);
  224. allCommands.addAction(action);
  225. }
  226. else
  227. {
  228. Log.log(Log.ERROR, "ConsolePlugin", "Unable to access resource: "
  229. + resourceName);
  230. }
  231. }
  232. }
  233. // }}}
  234. // {{{ rescanShells()
  235. static void rescanShells()
  236. {
  237. jEdit.removeActionSet(shellSwitchActions);
  238. shellSwitchActions.removeAllActions();
  239. for (String shell: Shell.getShellNames())
  240. {
  241. EditAction ac1 = new Shell.SwitchAction(shell);
  242. EditAction ac2 = new Shell.ToggleAction(shell);
  243. shellSwitchActions.addAction(ac1);
  244. shellSwitchActions.addAction(ac2);
  245. }
  246. jEdit.addActionSet(shellSwitchActions);
  247. redoKeyboardBindings(shellSwitchActions);
  248. EditBus.send(new DynamicMenuChanged(MENU));
  249. } // }}}
  250. // {{{ rescanCommands()
  251. /** Dynamicly generates two ActionSets, one for Commando commands,
  252. and one for Shells.
  253. Grabs the commando files from the jar file as well as user settings.
  254. */
  255. public static void rescanCommands()
  256. {
  257. /*
  258. if (allCommands.size() > 1)
  259. return; */
  260. jEdit.removeActionSet(allCommands);
  261. allCommands.removeAllActions();
  262. scanDirectory(userCommandDirectory);
  263. scanJarFile();
  264. redoKeyboardBindings(allCommands);
  265. rescanShells();
  266. jEdit.addActionSet(allCommands);
  267. Log.log(Log.DEBUG, ConsolePlugin.class, "Loaded " + allCommands.size()
  268. + " Actions");
  269. } // }}}
  270. // {{{ redoKeyboardBindings
  271. /**
  272. A fix for keyboard bindings that are dynamically generated.
  273. */
  274. static private void redoKeyboardBindings(ActionSet actionSet)
  275. /* Code duplication from jEdit.initKeyBindings() is bad, but
  276. otherwise invoking 'rescan commando directory' will leave
  277. old actions in the input handler
  278. */
  279. {
  280. EditAction[] ea = actionSet.getActions();
  281. for (int i = 0; i < ea.length; ++i)
  282. {
  283. String shortcut1 = jEdit.getProperty(ea[i].getName() + ".shortcut");
  284. if (shortcut1 != null)
  285. jEdit.getInputHandler().addKeyBinding(shortcut1, ea[i]);
  286. String shortcut2 = jEdit.getProperty(ea[i].getName() + ".shortcut2");
  287. if (shortcut2 != null)
  288. jEdit.getInputHandler().addKeyBinding(shortcut2, ea[i]);
  289. }
  290. } // }}}
  291. // {{{ getSwitchActions()
  292. /** @return an array of "Shell Switcher" actions, some that toggle and others
  293. * that just select and focus in the Console dockable.
  294. */
  295. public static EditAction[] getSwitchActions() {
  296. EditAction[] actions = getShellSwitchActions().getActions();
  297. Arrays.sort(actions, new ActionCompare());
  298. return actions;
  299. } // }}}
  300. // {{{ getCommandoCommands() method
  301. /** @return only the visible commando commands as EditActions, in
  302. * a sorted array */
  303. public static EditAction[] getCommandoCommands()
  304. {
  305. String[] names = allCommands.getActionNames();
  306. TreeMap<String, EditAction> actions = new TreeMap<String, EditAction>();
  307. for (String name: names) {
  308. String label=name;
  309. if (label.startsWith("commando.")) {
  310. label = name.substring(9);
  311. }
  312. boolean visible = jEdit.getBooleanProperty("commando.visible." + label, true);
  313. if (visible) {
  314. actions.put(label, allCommands.getAction(name));
  315. }
  316. }
  317. EditAction[] ar = new EditAction[actions.size()];
  318. return actions.values().toArray(ar);
  319. } // }}}
  320. // {{{ compile() method
  321. public static void compile(View view, Buffer buffer)
  322. {
  323. SystemShell systemShell = getSystemShell();
  324. String mode = buffer.getMode().getName();
  325. // Check for a custom compile command
  326. if (jEdit.getBooleanProperty("mode."+mode+".compile.use-custom")) {
  327. String command = jEdit.getProperty("mode."+mode+".compile.custom", "");
  328. view.getDockableWindowManager().showDockableWindow("console");
  329. Console console = (Console) getConsole(view);
  330. Console.ShellState state = console.getShellState(systemShell);
  331. if (buffer.isDirty())
  332. {
  333. Object[] args = { buffer.getName() };
  334. int result = GUIUtilities.confirm(view, "commando.not-saved-compile", args,
  335. JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
  336. if (result == JOptionPane.YES_OPTION)
  337. {
  338. if (!buffer.save(view, null, true))
  339. return;
  340. }
  341. else if (result != JOptionPane.NO_OPTION)
  342. return;
  343. }
  344. systemShell.execute(console, null, state, null, command);
  345. return;
  346. }
  347. // If it got here, run commando as normal
  348. String compiler = buffer.getStringProperty("commando.compile");
  349. if (compiler == null || compiler.length() == 0)
  350. {
  351. GUIUtilities.error(view, "commando.no-compiler", null);
  352. return;
  353. }
  354. CommandoCommand command = (CommandoCommand) allCommands.getAction("commando."
  355. + compiler);
  356. if (command == null)
  357. {
  358. GUIUtilities.error(view, "commando.no-command", new String[] { compiler });
  359. }
  360. else
  361. {
  362. if (buffer.isDirty())
  363. {
  364. Object[] args = { buffer.getName() };
  365. int result = GUIUtilities.confirm(view, "commando.not-saved-compile", args,
  366. JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
  367. if (result == JOptionPane.YES_OPTION)
  368. {
  369. if (!buffer.save(view, null, true))
  370. return;
  371. }
  372. else if (result != JOptionPane.NO_OPTION)
  373. return;
  374. }
  375. command.invoke(view);
  376. }
  377. } // }}}
  378. // {{{ getConsole() static method
  379. static public Console getConsole(View v) {
  380. DockableWindowManager dwm = v.getDockableWindowManager();
  381. return (Console) dwm.getDockable("console");
  382. }
  383. // }}}
  384. // {{{ run() method
  385. public static void run(View view, Buffer buffer)
  386. {
  387. SystemShell systemShell = getSystemShell();
  388. String mode = buffer.getMode().getName();
  389. // Check for a custom compile command
  390. if (jEdit.getBooleanProperty("mode."+mode+".run.use-custom")) {
  391. String command = jEdit.getProperty("mode."+mode+".run.custom", "");
  392. view.getDockableWindowManager().showDockableWindow("console");
  393. Console console = (Console) getConsole(view);
  394. Console.ShellState state = console.getShellState(systemShell);
  395. if (buffer.isDirty())
  396. {
  397. Object[] args = { buffer.getName() };
  398. int result = GUIUtilities.confirm(view, "commando.not-saved-run", args,
  399. JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
  400. if (result == JOptionPane.YES_OPTION)
  401. {
  402. if (!buffer.save(view, null, true))
  403. return;
  404. }
  405. else if (result != JOptionPane.NO_OPTION)
  406. return;
  407. }
  408. systemShell.execute(console, null, state, null, command);
  409. return;
  410. }
  411. // If it got here, run commando as normal
  412. String interpreter = buffer.getStringProperty("commando.run");
  413. if (interpreter == null || interpreter.length() == 0)
  414. {
  415. GUIUtilities.error(view, "commando.no-interpreter", null);
  416. return;
  417. }
  418. CommandoCommand command = (CommandoCommand) allCommands.getAction("commando."
  419. + interpreter);
  420. if (command == null)
  421. {
  422. GUIUtilities.error(view, "commando.no-command",
  423. new String[] { interpreter });
  424. }
  425. else
  426. {
  427. if (buffer.isDirty())
  428. {
  429. Object[] args = { buffer.getName() };
  430. int result = GUIUtilities.confirm(view, "commando.not-saved-run",
  431. args, JOptionPane.YES_NO_CANCEL_OPTION,
  432. JOptionPane.WARNING_MESSAGE);
  433. if (result == JOptionPane.YES_OPTION)
  434. {
  435. if (!buffer.save(view, null, true))
  436. return;
  437. }
  438. else if (result != JOptionPane.NO_OPTION)
  439. return;
  440. }
  441. command.invoke(view);
  442. }
  443. } // }}}
  444. // {{{ compileProject() method
  445. public static void compileProject(View view) {
  446. runProjectCommand(view, "compile");
  447. } // }}}
  448. // {{{ runProject() method
  449. public static void runProject(View view) {
  450. runProjectCommand(view, "run");
  451. } // }}}
  452. // {{{ runProjectCommand() method
  453. private static void runProjectCommand(View view, String prop) {
  454. if (jEdit.getPlugin("projectviewer.ProjectPlugin") == null) {
  455. GUIUtilities.error(view, "console.pv.not-installed", null);
  456. return;
  457. }
  458. projectviewer.vpt.VPTProject project =
  459. projectviewer.ProjectViewer.getActiveProject(view);
  460. if (project == null) {
  461. GUIUtilities.error(view, "console.pv.no-active-project", null);
  462. return;
  463. }
  464. String cmd = project.getProperty("console."+prop);
  465. if (cmd == null) cmd = "";
  466. if (cmd.equals("")) {
  467. // ask the user if they want to define the command now
  468. int res = GUIUtilities.confirm(view, "console.pv.no-command",
  469. new String[] { prop }, JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE);
  470. if (res != JOptionPane.YES_OPTION)
  471. return;
  472. projectviewer.PVActions.pvActionWrapper(
  473. new projectviewer.action.EditProjectAction("pv.commands"), view, true);
  474. cmd = project.getProperty("console."+prop);
  475. if (cmd == null || cmd.trim() == "")
  476. return;
  477. }
  478. // Run the command in the project's root, but then return to
  479. // the original working directory
  480. final SystemShell systemShell = getSystemShell();
  481. view.getDockableWindowManager().showDockableWindow("console");
  482. final Console console = (Console) getConsole(view);
  483. if (jEdit.getBooleanProperty("console.clearBeforeExecute"))
  484. console.clear();
  485. final Console.ShellState state = console.getShellState(systemShell);
  486. console.getOutput().writeAttrs(
  487. ConsolePane.colorAttributes(console.getInfoColor()),
  488. "\n"+cmd+"\n");
  489. systemShell.executeInDir(console, null, state, null, cmd, project.getRootPath());
  490. } // }}}
  491. // {{{ getPackageName() method
  492. /**
  493. * A utility method that returns the name of the package containing the
  494. * current buffer.
  495. * note: these might not be needed anymore as of 4.3pre3
  496. * @param buffer The buffer
  497. */
  498. public static String getPackageName(Buffer buffer)
  499. {
  500. StringReader in = new StringReader(buffer.getText(0, buffer.getLength()));
  501. try
  502. {
  503. StreamTokenizer stok = new StreamTokenizer(in);
  504. // set tokenizer to skip comments
  505. stok.slashStarComments(true);
  506. stok.slashSlashComments(true);
  507. while (stok.nextToken() != StreamTokenizer.TT_EOF)
  508. {
  509. if (stok.sval == null)
  510. continue;
  511. if (stok.sval.equals("package"))
  512. {
  513. stok.nextToken();
  514. in.close();
  515. return stok.sval;
  516. }
  517. else if (stok.sval.equals("class"))
  518. {
  519. in.close();
  520. return null;
  521. }
  522. }
  523. in.close();
  524. }
  525. catch (IOException io)
  526. {
  527. // can't happen
  528. throw new InternalError();
  529. }
  530. return null;
  531. } // }}}
  532. // {{{ getClassName() method
  533. /**
  534. * Returns the name of the specified buffer without the extension,
  535. * appended to the buffer's package name.
  536. * note: this might not be needed with the new JARClassloader
  537. *
  538. * @param buffer
  539. * The buffer
  540. */
  541. public static String getClassName(Buffer buffer)
  542. {
  543. String pkg = getPackageName(buffer);
  544. // TODO: update to use jEdit 5.0 API after 5.0 is released.
  545. // String clazz = MiscUtilities.getBaseName(buffer.getPath());
  546. String clazz = MiscUtilities.getFileNameNoExtension(buffer.getPath());
  547. if (pkg == null)
  548. return clazz;
  549. else
  550. return pkg + '.' + clazz;
  551. } // }}}
  552. // {{{ getPackageRoot() method
  553. /**
  554. * Returns the directory containing the root of the package of the
  555. * current buffer. For example, if the buffer is located in
  556. * <code>/home/slava/Stuff/example/Example.java</code> and contains a
  557. * <code>package example</code> statement, this method will return
  558. * <code>/home/slava/Stuff</code>.
  559. *
  560. * @param buffer
  561. * The buffer
  562. */
  563. public static String getPackageRoot(Buffer buffer)
  564. {
  565. String pkg = getPackageName(buffer);
  566. String path = MiscUtilities.getParentOfPath(buffer.getPath());
  567. if (path.endsWith(File.separator))
  568. path = path.substring(0, path.length() - 1);
  569. if (pkg == null)
  570. return path;
  571. pkg = pkg.replace('.', File.separatorChar);
  572. if (path.endsWith(pkg))
  573. return path.substring(0, path.length() - pkg.length());
  574. else
  575. return path;
  576. } // }}}
  577. // {{{ expandSystemShellVariables() method
  578. /**
  579. * Expands embedded environment variables in the same manner as the
  580. * system shell.
  581. *
  582. * @param view
  583. * The view
  584. * @param text
  585. * The string to expand
  586. */
  587. public static String expandSystemShellVariables(View view, String text)
  588. {
  589. return getSystemShell().expandVariables(view, text);
  590. } // }}}
  591. // {{{ getSystemShellVariableValue() method
  592. /**
  593. * Returns the value of the specified system shell environment variable.
  594. *
  595. * @param view
  596. * The view
  597. * @param var
  598. * The variable name
  599. */
  600. public static String getSystemShellVariableValue(View view, String var)
  601. {
  602. return getSystemShell().getVariableValue(view, var);
  603. } // }}}
  604. // {{{ setSystemShellVariableValue() method
  605. /**
  606. * Sets the value of the specified system shell environment variable.
  607. *
  608. * @param var
  609. * The variable name
  610. * @param value
  611. * The value
  612. */
  613. public static void setSystemShellVariableValue(String var, String value)
  614. {
  615. getSystemShell().getVariables().put(var, value);
  616. } // }}}
  617. // {{{ getUserCommandDirectory()
  618. public static String getUserCommandDirectory()
  619. {
  620. return userCommandDirectory;
  621. }
  622. // }}}
  623. // {{{ private methods
  624. // {{{ getParser()
  625. private static HashMap<View, CommandOutputParser> sm_parsers;
  626. // TODO - make this uniqueify on errorsource - given a new errorsource,
  627. // return a new parser.
  628. private static CommandOutputParser getParser(View v, String dir, DefaultErrorSource es) {
  629. if (sm_parsers == null) {
  630. sm_parsers = new HashMap<View, CommandOutputParser>();
  631. }
  632. CommandOutputParser retval = sm_parsers.get(v);
  633. Console console = ConsolePlugin.getConsole(v);
  634. if (retval == null) {
  635. retval = new CommandOutputParser(v, es, console.getPlainColor());
  636. sm_parsers.put(v, retval);
  637. }
  638. retval.setDirectory(dir);
  639. return retval;
  640. } // }}}
  641. // }}}
  642. // {{{ Inner classes
  643. // {{{ ActionCompare class
  644. static class ActionCompare implements Comparator<EditAction>
  645. {
  646. public int compare(EditAction a1, EditAction a2)
  647. {
  648. return a1.getLabel().compareTo(a2.getLabel());
  649. }
  650. } // }}}
  651. // }}}
  652. } // }}}