Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: de/hunsicker/jalopy/plugin/AbstractPlugin.java


1   /*
2    * Copyright (c) 2001-2002, Marco Hunsicker. All rights reserved.
3    *
4    * This software is distributable under the BSD license. See the terms of the
5    * BSD license in the documentation provided with this software.
6    */
7   package de.hunsicker.jalopy.plugin;
8   
9   import java.awt.AWTEvent;
10  import java.awt.BorderLayout;
11  import java.awt.Component;
12  import java.awt.Cursor;
13  import java.awt.EventQueue;
14  import java.awt.Frame;
15  import java.awt.Toolkit;
16  import java.awt.event.AWTEventListener;
17  import java.awt.event.KeyEvent;
18  import java.io.File;
19  import java.io.IOException;
20  import java.lang.reflect.InvocationTargetException;
21  import java.text.MessageFormat;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import javax.swing.JComponent;
28  import javax.swing.JDialog;
29  import javax.swing.JFrame;
30  import javax.swing.WindowConstants;
31  
32  import de.hunsicker.io.FileFormat;
33  import de.hunsicker.jalopy.Jalopy;
34  import de.hunsicker.jalopy.language.Position;
35  import de.hunsicker.jalopy.storage.Convention;
36  import de.hunsicker.jalopy.storage.ConventionDefaults;
37  import de.hunsicker.jalopy.storage.ConventionKeys;
38  import de.hunsicker.jalopy.storage.History;
39  import de.hunsicker.jalopy.storage.Loggers;
40  import de.hunsicker.jalopy.swing.ProgressMonitor;
41  import de.hunsicker.jalopy.swing.ProgressPanel;
42  import de.hunsicker.swing.ErrorDialog;
43  import de.hunsicker.swing.util.SwingWorker;
44  import de.hunsicker.util.ChainingRuntimeException;
45  import de.hunsicker.util.ResourceBundleFactory;
46  
47  import org.apache.log4j.ConsoleAppender;
48  import org.apache.log4j.Level;
49  import org.apache.log4j.PatternLayout;
50  
51  
52  //J- needed only as a workaround for a Javadoc bug
53  import java.lang.System;
54  import javax.swing.SwingUtilities;
55  //J+
56  
57  /**
58   * Skeleton implementation of a Jalopy Plug-in for integrated development environments.
59   *
60   * @author <a href="http://jalopy.sf.net/contact.html">Marco Hunsicker</a>
61   * @version $Revision: 1.8 $
62   */
63  public abstract class AbstractPlugin
64  {
65      //~ Static variables/initializers ----------------------------------------------------
66  
67      private static final String EMPTY_STRING = "" /* NOI18N */.intern();
68  
69      /** The default status bar does nothing. */
70      private static final StatusBar DEFAULT_STATUS_BAR = new DummyStatusBar();
71  
72      /** Cursor to display whilst long-running operations. */
73      private static final Cursor WAIT_CURSOR =
74          Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
75  
76      /** The name for ResourceBundle lookup. */
77      private static final String BUNDLE_NAME =
78          "de.hunsicker.jalopy.plugin.Bundle" /* NOI18N */;
79  
80      //~ Instance variables ---------------------------------------------------------------
81  
82      /** The main Jalopy instance. */
83      protected Jalopy jalopy;
84  
85      /** Appender to write messages to. */
86      protected SwingAppender appender;
87      int offset = 0;
88  
89      /** The action that was last performed. */
90      private Action _lastAction;
91  
92      /** Holds the currently running action worker. */
93      private ActionWorker _worker;
94  
95      /** Stores the original glass pane. */
96      private Component _oldGlassPane;
97  
98      /** Pane to display on top of all other components. */
99      private GlassPane _glassPane;
100 
101     /** Used as a monitor to synchronize processes. */
102     private final Object _lock = new Object();
103 
104     /** Progress monitor for long running operations. */
105     private ProgressMonitor _progressMonitor;
106     private final Object[] _args = new Object[3];
107 
108     /** Number of running formatting threads. */
109     private int _threadCount;
110 
111     /** When did the worker thread start? */
112     private long _start;
113 
114     //~ Constructors ---------------------------------------------------------------------
115 
116     /**
117      * Creates a new AbstractPlugin object. Uses a default appender which outputs all
118      * messages to <code>System.out</code>.
119      */
120     public AbstractPlugin()
121     {
122         this(new DefaultAppender());
123     }
124 
125 
126     /**
127      * Creates a new AbstractPlugin object.
128      *
129      * @param appender appender to use for logging; if <code>null</code> all logging
130      *        output goes to <code>System.out</code>.
131      */
132     public AbstractPlugin(SwingAppender appender)
133     {
134         this.appender = appender;
135         initLogging();
136     }
137 
138     //~ Methods --------------------------------------------------------------------------
139 
140     /**
141      * Returns the currently active project.
142      *
143      * @return the active user project.
144      */
145     public abstract Project getActiveProject();
146 
147 
148     /**
149      * Returns the main window of the application.
150      *
151      * @return the main application window.
152      */
153     public abstract Frame getMainWindow();
154 
155 
156     /**
157      * Returns the elapsed execution time of the run.
158      *
159      * @return the elapsed execution time.
160      */
161     public long getElapsed()
162     {
163         return System.currentTimeMillis() - _start;
164     }
165 
166 
167     /**
168      * Returns the action that was performed last.
169      *
170      * @return Last performed action. Returns <code>null</code> if no action was ever
171      *         performed.
172      */
173     public final Action getLastAction()
174     {
175         return _lastAction;
176     }
177 
178 
179     /**
180      * Returns the state info of Plug-in. Use this method to query the state after a run
181      * finished.
182      *
183      * @return The run state.
184      *
185      * @see de.hunsicker.jalopy.Jalopy#getState
186      */
187     public final synchronized Jalopy.State getState()
188     {
189         if (this.jalopy == null)
190         {
191             return Jalopy.State.UNDEFINED;
192         }
193 
194         return this.jalopy.getState();
195     }
196 
197 
198     /**
199      * Determines whether the Plug-in currently processes a request.
200      *
201      * @return <code>true</code> if an action is currently being performed.
202      *
203      * @see #performAction
204      * @see #interrupt
205      * @since 1.0b8
206      */
207     public synchronized boolean isRunning()
208     {
209         return _worker != null;
210     }
211 
212 
213     /**
214      * Returns the active status bar. Override to provide access to the status bar of the
215      * used application.
216      *
217      * @return the active status bar. The default implementation returns a dummy.
218      */
219     public StatusBar getStatusBar()
220     {
221         return DEFAULT_STATUS_BAR;
222     }
223 
224 
225     /**
226      * Called on the event dispatching thread after an action was performed.
227      * 
228      * <p>
229      * Override this method to perform any custom work after the formatting process
230      * finished.
231      * </p>
232      */
233     public void afterEnd()
234     {
235     }
236 
237 
238     /**
239      * Called on the event dispatching thread before an action will be started.
240      * 
241      * <p>
242      * Override this method to perform any custom work before the formatting process
243      * starts.
244      * </p>
245      */
246     public void beforeStart()
247     {
248     }
249 
250 
251     /**
252      * Interrupts the currently performed action, if any.
253      *
254      * @since 1.0b8
255      */
256     public final synchronized void interrupt()
257     {
258         if (_worker != null)
259         {
260             _worker.interrupt();
261         }
262     }
263 
264 
265     /**
266      * Performs the given action.
267      *
268      * @param action action to perform.
269      */
270     public final synchronized void performAction(final AbstractPlugin.Action action)
271     {
272         // clear the message window
273         this.appender.clear();
274 
275         _worker = new ActionWorker(action);
276         _worker.start();
277 
278         _lastAction = action;
279     }
280 
281 
282     /**
283      * Returns the file format to use for writing Java source files.
284      *
285      * @return the file format to use.
286      */
287     protected abstract FileFormat getFileFormat();
288 
289 
290     /**
291      * Returns a Jalopy instance. The instance will be configured according to the
292      * current code convention.
293      *
294      * @return a Jalopy instance.
295      *
296      * @since 1.0b8
297      */
298     protected Jalopy getEngine()
299     {
300         if (this.jalopy == null)
301         {
302             if (_progressMonitor != null)
303             {
304                 _progressMonitor.setText(
305                     ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
306                         "MSG_INITIALIZATION" /* NOI18N */));
307             }
308 
309             getStatusBar().setText(
310                 ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
311                     "MSG_INITIALIZATION" /* NOI18N */));
312             this.jalopy = new Jalopy();
313         }
314 
315         configureJalopy(this.jalopy);
316 
317         return this.jalopy;
318     }
319 
320 
321     /**
322      * Creates a monitor to be used for long-running operations.
323      *
324      * @return the progress monitor to use for long-running operations.
325      */
326     protected ProgressMonitor createProgressMonitor()
327     {
328         return new ProgressMonitorImpl();
329     }
330 
331 
332     /**
333      * Displays an error dialog.
334      *
335      * @param error the throwable which caused the error.
336      * @param parent parent frame of the dialog (used to position the dialog).
337      */
338     protected void displayError(
339         Throwable error,
340         Frame     parent)
341     {
342         ErrorDialog d = ErrorDialog.create(getMainWindow(), error);
343         d.setVisible(true);
344         d.dispose();
345     }
346 
347 
348     /**
349      * Executes the given runnable asynchronously on the AWT event dispatching thread.
350      *
351      * @param operation runnable to be invoked asynchronously on the AWT event
352      *        dispatching thread.
353      *
354      * @see javax.swing.SwingUtilities#invokeLater
355      */
356     protected void executeAsynchron(Runnable operation)
357     {
358         EventQueue.invokeLater(operation);
359     }
360 
361 
362     /**
363      * Executes the given runnable synchronously on the AWT event dispatching thread.
364      *
365      * @param operation runnable to be invoked synchronously on the AWT event dispatching
366      *        thread.
367      *
368      * @throws InterruptedException if another thread has interrupted this thread.
369      * @throws InvocationTargetException if an exception is thrown when running runnable.
370      *
371      * @see javax.swing.SwingUtilities#invokeAndWait
372      */
373     protected void executeSynchron(Runnable operation)
374       throws InterruptedException, InvocationTargetException
375     {
376         EventQueue.invokeAndWait(operation);
377     }
378 
379 
380     /**
381      * Hides the wait cursor.
382      */
383     protected void hideWaitCursor()
384     {
385         if ((_glassPane != null) && _glassPane.isVisible())
386         {
387             // hiding the glass pane restores the normal cursor too
388             _glassPane.setVisible(false);
389 
390             Frame window = getMainWindow();
391 
392             if ((window != null) && window instanceof JFrame)
393             {
394                 JFrame w = (JFrame) window;
395 
396                 // restore the original glass pane
397                 w.getRootPane().setGlassPane(_oldGlassPane);
398             }
399         }
400     }
401 
402 
403     /**
404      * Shows the wait cursor to indicate a long-running operation. Keyboard and mouse
405      * input will be blocked.
406      */
407     protected void showWaitCursor()
408     {
409         final Frame window = getMainWindow();
410 
411         if ((window != null) && window instanceof JFrame)
412         {
413             if (_glassPane == null)
414             {
415                 _glassPane = new GlassPane();
416             }
417 
418             _glassPane.setThread(Thread.currentThread());
419 
420             try
421             {
422                 executeSynchron(
423                     new Runnable()
424                     {
425                         public void run()
426                         {
427                             // make the glass pane visible so that all mouse and key
428                             // actions will be blocked and a wait cursor displayed;
429                             // the pane will be made hidden in the afterEnd() method that
430                             // is always called after the run has finished
431                             JFrame w = (JFrame) window;
432                             _oldGlassPane = w.getRootPane().getGlassPane();
433                             w.getRootPane().setGlassPane(_glassPane);
434                             _glassPane.setCursor(WAIT_CURSOR);
435                             _glassPane.setVisible(true);
436                         }
437                     });
438             }
439             catch (Throwable ignored)
440             {
441                 hideWaitCursor();
442             }
443         }
444     }
445 
446 
447     /**
448      * Configures the given Jalopy instance to meet the current code convention.
449      *
450      * @param jalopy Jalopy instance to configure.
451      */
452     private void configureJalopy(Jalopy jalopy)
453     {
454         Convention settings = Convention.getInstance();
455         int backupLevel =
456             settings.getInt(ConventionKeys.BACKUP_LEVEL, ConventionDefaults.BACKUP_LEVEL);
457         jalopy.setBackup(backupLevel > 0);
458         jalopy.setBackupDirectory(
459             settings.get(
460                 ConventionKeys.BACKUP_DIRECTORY,
461                 Convention.getBackupDirectory().getAbsolutePath()));
462         jalopy.setHistoryPolicy(
463             History.Policy.valueOf(
464                 settings.get(
465                     ConventionKeys.HISTORY_POLICY, ConventionDefaults.HISTORY_POLICY)));
466         jalopy.setHistoryMethod(
467             History.Method.valueOf(
468                 settings.get(
469                     ConventionKeys.HISTORY_METHOD, ConventionDefaults.HISTORY_METHOD)));
470         jalopy.setInspect(
471             settings.getBoolean(ConventionKeys.INSPECTOR, ConventionDefaults.INSPECTOR));
472         jalopy.setBackupLevel(backupLevel);
473         jalopy.setFileFormat(getFileFormat());
474         jalopy.setForce(
475             settings.getBoolean(
476                 ConventionKeys.FORCE_FORMATTING, ConventionDefaults.FORCE_FORMATTING));
477     }
478 
479 
480     /**
481      * Formats the given file. This method performs the actual work.
482      *
483      * @param file Java source file.
484      * @param jalopy Jalopy instance to use.
485      *
486      * @throws IOException if an I/O error occured.
487      * @throws InvocationTargetException if the updating of an editor window failed.
488      */
489     private void format(
490         ProjectFile  file,
491         final Jalopy jalopy)
492       throws IOException, InvocationTargetException
493     {
494         jalopy.setEncoding(file.getEncoding());
495 
496         if (_progressMonitor != null)
497         {
498             _args[0] = file.getName();
499 
500             _progressMonitor.setText(
501                 MessageFormat.format(
502                     ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
503                         "MSG_FORMATTING_FILE" /* NOI18N */), _args));
504 
505             if (_progressMonitor instanceof ProgressMonitorImpl)
506             {
507                 ((ProgressMonitorImpl) _progressMonitor).progressPanel.increaseFiles();
508             }
509         }
510 
511         // only update the file if available
512         if (!file.isReadOnly())
513         {
514             getStatusBar().setText(
515                 ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
516                     "MSG_FORMATTING" /* NOI18N */));
517 
518             if (file.isOpened()) // we're interacting with an editor view
519             {
520                 final Editor editor = file.getEditor();
521 
522                 String content = editor.getText();
523 
524                 if ((content != null) && (content.length() > 0))
525                 {
526                     jalopy.setInput(content, file.getFile().getAbsolutePath());
527 
528                     List annotations = editor.detachAnnotations();
529                     jalopy.getRecognizer().attachAnnotations(annotations);
530 
531                     try
532                     {
533                         executeSynchron(
534                             new Runnable()
535                             {
536                                 public void run()
537                                 {
538                                     jalopy.getRecognizer().setPosition(
539                                         editor.getLine(), editor.getColumn());
540                                 }
541                             });
542                     }
543                     catch (InterruptedException ex)
544                     {
545                         ;
546                     }
547 
548                     final StringBuffer textBuf = new StringBuffer(content.length());
549                     jalopy.setOutput(textBuf);
550                     jalopy.format();
551 
552                     if ((_progressMonitor != null) && _progressMonitor.isCanceled())
553                     {
554                         jalopy.getRecognizer().detachAnnotations();
555 
556                         return;
557                     }
558 
559                     // only update the editor view if no errors showed up
560                     if (getState() != Jalopy.State.ERROR)
561                     {
562                         try
563                         {
564                             executeSynchron(
565                                 new Runnable()
566                                 {
567                                     public void run()
568                                     {
569                                         editor.setText(textBuf.toString());
570                                         editor.attachAnnotations(
571                                             jalopy.getRecognizer().detachAnnotations());
572 
573                                         Position position =
574                                             jalopy.getRecognizer().getPosition();
575                                         editor.setCaretPosition(
576                                             position.getLine(), position.getColumn());
577                                     }
578                                 });
579                         }
580                         catch (InterruptedException ex)
581                         {
582                             ;
583                         }
584                     }
585                 }
586             }
587             else // update the physical file
588             {
589                 File f = file.getFile();
590                 jalopy.setInput(f);
591                 jalopy.setOutput(f);
592                 jalopy.format();
593             }
594         }
595         else
596         {
597             Object[] args = { file };
598             Loggers.IO.l7dlog(Level.INFO, "FILE_READ_ONLY" /* NOI18N */, args, null);
599         }
600     }
601 
602 
603     /**
604      * Formats the given files.
605      *
606      * @param jalopy the Jalopy instance to use for formatting.
607      * @param files list with the files to format.
608      *
609      * @throws IOException if an I/O error occured.
610      * @throws InvocationTargetException if the updating of an editor window failed.
611      */
612     private void formatSeveral(
613         Jalopy     jalopy,
614         Collection files)
615       throws IOException, InvocationTargetException
616     {
617         formatSeveral(jalopy, files, true);
618     }
619 
620 
621     /**
622      * Formats the given files.
623      *
624      * @param jalopy the Jalopy instance to use for formatting.
625      * @param files list with the files to format.
626      * @param checkThreading if <code>true</code> checks whether the user enabled the
627      *        multi-threaded execution and if so, uses multiple threads to perform the
628      *        operation.
629      *
630      * @throws IOException if an I/O error occured.
631      * @throws InvocationTargetException if the updating of an editor window failed.
632      */
633     private void formatSeveral(
634         final Jalopy     jalopy,
635         final Collection files,
636         final boolean    checkThreading)
637       throws IOException, InvocationTargetException
638     {
639         final int size = files.size();
640 
641         if (size > 0)
642         {
643             synchronized (this)
644             {
645                 if (_progressMonitor == null)
646                 {
647                     _progressMonitor = createProgressMonitor();
648 
649                     _progressMonitor.begin(
650                         ((jalopy == null)
651                         ? ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
652                             "MSG_INITIALIZATION" /* NOI18N */)
653                         : EMPTY_STRING), files.size());
654                 }
655             }
656 
657             int numThreads =
658                 Convention.getInstance().getInt(
659                     ConventionKeys.THREAD_COUNT, ConventionDefaults.THREAD_COUNT);
660 
661             if (!checkThreading || (numThreads == 1) || (size == 1))
662             {
663                 for (Iterator i = files.iterator(); i.hasNext();)
664                 {
665                     // the user canceled the program execution
666                     if (_progressMonitor.isCanceled())
667                     {
668                         return;
669                     }
670 
671                     ProjectFile file = (ProjectFile) i.next();
672                     format(file, jalopy);
673 
674                     synchronized (_progressMonitor)
675                     {
676                         _progressMonitor.setProgress(_progressMonitor.getProgress() + 1);
677                     }
678                 }
679             }
680             else
681             {
682                 List workList =
683                     (files instanceof List) ? (List) files
684                                             : new ArrayList(files);
685                 int amount = 1;
686 
687                 if (numThreads < size)
688                 {
689                     amount = size / numThreads;
690                 }
691                 else
692                 {
693                     numThreads = size;
694                 }
695 
696                 // the number of threads to span
697                 _threadCount = numThreads - 1;
698 
699                 for (int i = 0, threshold = numThreads - 1; i < threshold; i++)
700                 {
701                     Jalopy j = new Jalopy();
702                     configureJalopy(j);
703 
704                     Collection part = workList.subList(i * amount, (i + 1) * amount);
705                     new FormatThread(j, part).start();
706                 }
707 
708                 Collection rest = workList.subList((numThreads - 1) * amount, size);
709                 formatSeveral(jalopy, rest, false);
710 
711                 try
712                 {
713                     synchronized (_lock)
714                     {
715                         while (_threadCount > 0)
716                         {
717                             _lock.wait();
718                         }
719                     }
720                 }
721                 catch (InterruptedException ignored)
722                 {
723                     ;
724                 }
725             }
726         }
727     }
728 
729 
730     private synchronized void hideProgressMonitor()
731     {
732         if (_progressMonitor != null)
733         {
734             _progressMonitor.done();
735             _progressMonitor = null;
736         }
737     }
738 
739 
740     /**
741      * Initializes the logging system. This method is called upon the creation of the
742      * object.
743      */
744     private void initLogging()
745     {
746         // if no appender was specified, we output all messages to System.out
747         if (this.appender == null)
748         {
749             this.appender = new DefaultAppender();
750         }
751 
752         Loggers.initialize(this.appender);
753     }
754 
755     //~ Inner Classes --------------------------------------------------------------------
756 
757     /**
758      * Represents an action that can be performed.
759      *
760      * @see AbstractPlugin#performAction
761      * @since 1.0b8
762      */
763     public static final class Action
764     {
765         /** Indicates that no action was ever performed. */
766         public static final Action UNDEFINED = new Action("undefined" /* NOI18N */);
767 
768         /** Format the currently active (opened) file. */
769         public static final Action FORMAT_ACTIVE =
770             new Action("format_active" /* NOI18N */);
771 
772         /** Format all Java Source files of the currently active project. */
773         public static final Action FORMAT_ALL = new Action("format_all" /* NOI18N */);
774 
775         /** Format all currently opened Java source files. */
776         public static final Action FORMAT_OPEN = new Action("format_open" /* NOI18N */);
777 
778         /** Format the selected Java source file(s). */
779         public static final Action FORMAT_SELECTED =
780             new Action("format_selected" /* NOI18N */);
781 
782         /** Parse the currently active (opened) file. */
783         public static final Action PARSE_ACTIVE = new Action("parse_active" /* NOI18N */);
784 
785         /** Parse all Java Source files of the currently active project. */
786         public static final Action PARSE_ALL = new Action("parse_all" /* NOI18N */);
787 
788         /** Parse all currently opened Java source files. */
789         public static final Action PARSE_OPEN = new Action("parse_open" /* NOI18N */);
790 
791         /** Parse the selected Java source file(s). */
792         public static final Action PARSE_SELECTED =
793             new Action("parse_selected" /* NOI18N */);
794 
795         /** Inspect the currently active (opened) file. */
796         public static final Action INSPECT_ACTIVE =
797             new Action("inspect_active" /* NOI18N */);
798 
799         /** Inspect all Java Source files of the currently active project. */
800         public static final Action INSPECT_ALL = new Action("inspect_all" /* NOI18N */);
801 
802         /** Inspect all currently opened Java source files. */
803         public static final Action INSPECT_OPEN = new Action("inspect_open" /* NOI18N */);
804 
805         /** Inspect the selected Java source file(s). */
806         public static final Action INSPECT_SELECTED =
807             new Action("inspect_selected" /* NOI18N */);
808         final String name;
809 
810         private Action(String name)
811         {
812             this.name = name.intern();
813         }
814     }
815 
816 
817     /**
818      * Default appender to use if no custom appender was specified. All output goes to
819      * System.out.
820      */
821     private static class DefaultAppender
822         extends ConsoleAppender
823         implements SwingAppender
824     {
825         /**
826          * DOCUMENT ME!
827          *
828          * @todo overide format() to add stacktraces
829          */
830         public DefaultAppender()
831         {
832             super(new PatternLayout("[%p] %m\n" /* NOI18N */), "System.out" /* NOI18N */);
833         }
834 
835         public void clear()
836         {
837         }
838 
839 
840         public void done()
841         {
842         }
843     }
844 
845 
846     private static final class DummyStatusBar
847         implements StatusBar
848     {
849         public void setText(String text)
850         {
851         }
852     }
853 
854 
855     /**
856      * Worker to perform our actions in a dedicated thread.
857      */
858     private class ActionWorker
859         extends SwingWorker
860     {
861         /** The action to perform. */
862         Action action;
863 
864         /**
865          * Stores the initial position of the caret in case we're formatting an opened
866          * file.
867          */
868         int offset;
869 
870         public ActionWorker(Action action)
871         {
872             this.action = action;
873         }
874 
875         public Object construct()
876         {
877             _start = System.currentTimeMillis();
878 
879             try
880             {
881                 if (this.action == Action.FORMAT_ACTIVE)
882                 {
883                     // wait cursor indicates running operation
884                     showWaitCursor();
885                     beforeStart();
886 
887                     ProjectFile activeFile = getActiveProject().getActiveFile();
888 
889                     //final Editor editor = activeFile.getEditor();
890                     // store the current offset to reposition the caret
891                     // (synchronization needed to make Eclipse happy)
892 
893                     /*executeSynchron(
894                         new Runnable()
895                         {
896                             public void run()
897                             {
898                                 offset = editor.getCaretPosition();
899                             }
900                         });*/
901                     Jalopy jalopy = getEngine();
902                     format(activeFile, jalopy);
903 
904                     // only change if no errors showed up
905                     /*if (getState() != Jalopy.State.ERROR)
906                     {
907                         // move the cursor
908 
909                         **
910                          * @todo this could be improved. Determine the location prior
911                          *       formatting (relative to the next known node) and set
912                          *       the cursor to that position after formatting; quite
913                          *       involved, but possible (and only necessary if in
914                          *       sorting mode)
915                          *
916                         executeSynchron(
917                             new Runnable()
918                             {
919                                 public void run()
920                                 {
921                                     editor.requestFocus();
922 
923                                     if (editor.getLength() > offset)
924                                     {
925                                         editor.setCaretPosition(offset);
926                                     }
927                                 }
928                             });
929                     }*/
930                     jalopy.cleanupBackupDirectory();
931                 }
932                 else if (this.action == Action.FORMAT_ALL)
933                 {
934                     beforeStart();
935 
936                     Jalopy jalopy = getEngine();
937                     formatSeveral(jalopy, getActiveProject().getAllFiles());
938                     jalopy.cleanupBackupDirectory();
939                 }
940                 else if (this.action == Action.FORMAT_SELECTED)
941                 {
942                     beforeStart();
943 
944                     Jalopy jalopy = getEngine();
945                     formatSeveral(jalopy, getActiveProject().getSelectedFiles());
946                     jalopy.cleanupBackupDirectory();
947                 }
948                 else if (this.action == Action.FORMAT_OPEN)
949                 {
950                     beforeStart();
951 
952                     Jalopy jalopy = getEngine();
953                     formatSeveral(jalopy, getActiveProject().getOpenedFiles());
954                     jalopy.cleanupBackupDirectory();
955                 }
956             }
957 
958             /*catch (InterruptedException ex)
959             {
960                 hideProgressMonitor();
961                 notifyAll();
962             }*/
963             catch (Throwable ex)
964             {
965                 hideProgressMonitor();
966                 displayError(ex, getMainWindow());
967                 notifyAll();
968             }
969 
970             return null;
971         }
972 
973 
974         /**
975          * Updates the status bar after a run has finished.
976          *
977          * @see #getStatusBar
978          */
979         public void finished()
980         {
981             try
982             {
983                 if (_lastAction == Action.FORMAT_ACTIVE)
984                 {
985                     hideWaitCursor();
986                 }
987                 else
988                 {
989                     hideProgressMonitor();
990                 }
991 
992                 StatusBar statusBar = getStatusBar();
993 
994                 if (statusBar != null)
995                 {
996                     _args[0] =
997                         ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
998                             (getState() == Jalopy.State.ERROR)
999                             ? "MSG_FORMAT_FAILED" /* NOI18N */
1000                            : "MSG_FORMAT_SUCCEEDED" /* NOI18N */);
1001
1002                    long time = getElapsed();
1003
1004                    if (time > 999)
1005                    {
1006                        time /= 1000;
1007                        _args[2] =
1008                            ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
1009                                (time == 1) ? "MSG_SECOND" /* NOI18N */
1010                                            : "MSG_SECONDS" /* NOI18N */);
1011                    }
1012                    else
1013                    {
1014                        _args[2] =
1015                            ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
1016                                "MSG_MILLI_SECONDS" /* NOI18N */);
1017                    }
1018
1019                    _args[1] = String.valueOf(time);
1020
1021                    statusBar.setText(
1022                        MessageFormat.format(
1023                            ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
1024                                "MSG_FORMAT_FINISHED" /* NOI18N */), _args));
1025                }
1026
1027                AbstractPlugin.this.appender.done();
1028
1029                // call the user hook
1030                AbstractPlugin.this.afterEnd();
1031            }
1032            finally
1033            {
1034                _worker = null;
1035            }
1036        }
1037    }
1038
1039
1040    /**
1041     * Helper to format several files in a dedicated thread.
1042     */
1043    private class FormatThread
1044        extends Thread
1045    {
1046        Collection files; // Collection of <ProjectFile>
1047        Jalopy jalopy;
1048
1049        public FormatThread(
1050            Jalopy     jalopy,
1051            Collection files)
1052        {
1053            this.files = files;
1054            this.jalopy = jalopy;
1055        }
1056
1057        public void run()
1058        {
1059            try
1060            {
1061                formatSeveral(jalopy, files, false);
1062            }
1063            catch (Exception ex)
1064            {
1065                throw new ChainingRuntimeException(ex);
1066            }
1067            finally
1068            {
1069                synchronized (_lock)
1070                {
1071                    _threadCount--;
1072                    _lock.notify();
1073                }
1074            }
1075        }
1076    }
1077
1078
1079    /**
1080     * GlassPane used to block input whilst formatting the currently active file.
1081     */
1082    private class GlassPane
1083        extends JComponent
1084        implements AWTEventListener
1085    {
1086        Thread thread;
1087
1088        public void setThread(Thread thread)
1089        {
1090            this.thread = thread;
1091        }
1092
1093
1094        public void setVisible(boolean visible)
1095        {
1096            super.setVisible(visible);
1097
1098            if (visible)
1099            {
1100                Toolkit.getDefaultToolkit().addAWTEventListener(
1101                    this,
1102                    AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK
1103                    | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.TEXT_EVENT_MASK
1104                    | AWTEvent.INPUT_METHOD_EVENT_MASK);
1105            }
1106            else
1107            {
1108                Toolkit.getDefaultToolkit().removeAWTEventListener(this);
1109            }
1110        }
1111
1112
1113        public void eventDispatched(AWTEvent ev)
1114        {
1115            if (ev instanceof KeyEvent)
1116            {
1117                KeyEvent e = (KeyEvent) ev;
1118
1119                switch (e.getKeyCode())
1120                {
1121                    case KeyEvent.VK_ESCAPE :
1122                        interrupt();
1123
1124                        break;
1125
1126                    case KeyEvent.VK_C :
1127
1128                        if (e.isControlDown())
1129                        {
1130                            interrupt();
1131                        }
1132
1133                        break;
1134
1135                    default :
1136                        e.consume();
1137
1138                        break;
1139                }
1140            }
1141        }
1142    }
1143
1144
1145    /**
1146     * A concrete progress monitor implemenation for Swing-based applications.
1147     */
1148    private final class ProgressMonitorImpl
1149        implements ProgressMonitor
1150    {
1151        JDialog dialog;
1152        ProgressPanel progressPanel;
1153        boolean running;
1154        int progress;
1155
1156        public ProgressMonitorImpl()
1157        {
1158            this.progressPanel = new ProgressPanel();
1159            this.progressPanel.setProgressBarVisible(true);
1160        }
1161
1162        public synchronized void setCanceled(boolean state)
1163        {
1164        }
1165
1166
1167        public synchronized boolean isCanceled()
1168        {
1169            return this.progressPanel.isCanceled();
1170        }
1171
1172
1173        public synchronized void setProgress(int units)
1174        {
1175            if (this.running)
1176            {
1177                this.progress = units;
1178                this.progressPanel.setValue(units);
1179            }
1180        }
1181
1182
1183        public synchronized int getProgress()
1184        {
1185            return this.progress;
1186        }
1187
1188
1189        public synchronized void setText(String text)
1190        {
1191            if (this.running)
1192            {
1193                this.progressPanel.setText(text);
1194            }
1195        }
1196
1197
1198        public synchronized void begin(
1199            String text,
1200            int    units)
1201        {
1202            if (!this.running)
1203            {
1204                dialog =
1205                    new JDialog(
1206                        getMainWindow(),
1207                        ResourceBundleFactory.getBundle(BUNDLE_NAME).getString(
1208                            "TLE_FORMAT_PROGRESS" /* NOI18N */), true);
1209                dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
1210                dialog.getContentPane().add(this.progressPanel, BorderLayout.CENTER);
1211                dialog.pack();
1212                dialog.setLocationRelativeTo(getMainWindow());
1213
1214                this.progressPanel.setText(text);
1215                this.progressPanel.setMaximum(units);
1216
1217                executeAsynchron(
1218                    new Runnable()
1219                    {
1220                        public void run()
1221                        {
1222                            running = true;
1223                            dialog.setVisible(true);
1224                        }
1225                    });
1226            }
1227        }
1228
1229
1230        public synchronized void done()
1231        {
1232            if (this.running)
1233            {
1234                try
1235                {
1236                    executeSynchron(
1237                        new Runnable()
1238                        {
1239                            public void run()
1240                            {
1241                                progressPanel.setValue(progressPanel.getMaximum());
1242                            }
1243                        });
1244                }
1245                catch (Throwable ignored)
1246                {
1247                    ;
1248                }
1249
1250                this.dialog.setVisible(false);
1251                this.running = false;
1252                this.progressPanel.dispose();
1253                this.dialog.dispose();
1254                this.progressPanel = null;
1255            }
1256        }
1257    }
1258}