001    package jigcell.compare.ui;
002    
003    import java.awt.EventQueue;
004    import java.awt.event.ActionEvent;
005    import java.awt.event.ActionListener;
006    import java.util.ArrayList;
007    import java.util.List;
008    import javax.swing.JButton;
009    import javax.swing.JFileChooser;
010    import javax.swing.JMenu;
011    import javax.swing.JMenuItem;
012    import jigcell.compare.IMultipleDataSource;
013    import jigcell.compare.IPersistableDataSource;
014    import jigcell.compare.IReadableDataSource;
015    import jigcell.compare.IWriteableDataSource;
016    import jigcell.compare.impl.Compare;
017    import jigcell.compare.impl.Config;
018    import jigcell.compare.impl.DataSource;
019    import jigcell.compare.impl.ExceptionRecorder;
020    import jigcell.compare.impl.FileDataSource;
021    import jigcell.compare.impl.ProxyBuilder;
022    
023    /**
024     * A default implementation of a tab providing some basic services.
025     *
026     * <p>
027     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
028     * </p>
029     *
030     * @author Nicholas Allen
031     */
032    
033    public abstract class DataEditorPanelTab extends PanelTab {
034    
035       /**
036        * Beginning of property key for recent source options
037        */
038    
039       public final static String CONFIG_RECENTSOURCE = "DataEditorPanelTab.recentSource";
040    
041       /**
042        * Property key for number of recent sources supported
043        */
044    
045       public final static String CONFIG_RECENTSOURCECOUNT = "DataEditorPanelTab.recentSourceCount";
046    
047       /**
048        * Name of menu containing recent sources to open
049        */
050    
051       protected final static String MENU_OPENRECENT = "Recent Files";
052    
053       /**
054        * Name of default menubar
055        */
056    
057       protected final static String MENUBAR_DEFAULT = "default";
058    
059       /**
060        * General error message for failed load
061        */
062    
063       protected final static String MESSAGE_LOADERROR = "Unable to load: ";
064    
065       /**
066        * Status message when loading
067        */
068    
069       protected final static String MESSAGE_LOADING = "Loading from: ";
070    
071       /**
072        * General error message for failed save
073        */
074    
075       protected final static String MESSAGE_SAVEERROR = "Unable to save: ";
076    
077       /**
078        * File chooser option when opening a file
079        */
080    
081       protected final static String OPTION_CHOOSEROPEN = "Open";
082    
083       /**
084        * File chooser option when saving a file
085        */
086    
087       protected final static String OPTION_CHOOSERSAVE = "Save";
088    
089       /**
090        * Property for storing recent sources within a menu item
091        */
092    
093       protected final static String PROPERTY_RECENTSOURCETARGET = "jigcell_recentsourcetarget";
094    
095       /**
096        * Name of default toolbar
097        */
098    
099       protected final static String TOOLBAR_DEFAULT = "default";
100    
101       /**
102        * The menus that this view is presenting
103        */
104    
105       protected JMenu menus [];
106    
107       /**
108        * Exception handler
109        */
110    
111       protected ExceptionRecorder exception;
112    
113       /**
114        * Provides interface help for the view
115        */
116    
117       protected InterfaceBuilder manager;
118    
119       /**
120        * Menu listing the recently used readable data sources
121        */
122    
123       protected JMenu recentReadablesMenu;
124    
125       /**
126        * Creates menus for the view
127        */
128    
129       protected MenuBuilder menuManager;
130    
131       /**
132        * Lock for temporary read result
133        */
134    
135       protected final Object READ_LOCK = new Object ();
136    
137       /**
138        * Data source the current content should be saved to
139        */
140    
141       private IWriteableDataSource currentSource;
142    
143       /**
144        * Helper class for performing read operations on other threads.
145        */
146    
147       protected class ReadHelper {
148    
149          /**
150           * Data source to read from
151           */
152    
153          protected IReadableDataSource readSource;
154    
155          /**
156           * Temporary read result
157           */
158    
159          protected Object readResult;
160    
161          /**
162           * Creates a new reading helper thread.
163           *
164           * @param readSource Data source to read from
165           */
166    
167          protected ReadHelper (IReadableDataSource readSource) {
168             this.readSource = readSource;
169             new Thread (ProxyBuilder.proxyRunnable (this, "read")).start ();
170          }
171    
172          /**
173           * Sends data read from some external source off for processing.
174           */
175    
176          protected void finish () {
177             try {
178                setCurrentSource (null);
179                readInternal (readResult);
180                setCurrentSource (readSource instanceof IWriteableDataSource ? (IWriteableDataSource) readSource : null);
181             } catch (Exception e) {
182                Compare.throwUncheckedException (e);
183             } finally {
184                readNotify ();
185             }
186          }
187    
188          /**
189           * Reads data from some external source.
190           */
191    
192          protected void read () {
193             ProgressMonitor monitor = new ProgressMonitor (DataEditorPanelTab.this, MESSAGE_LOADING + readSource.getName ());
194             Throwable readError = null;
195             synchronized (READ_LOCK) {
196                try {
197                   try {
198                      readResult = readSource.read ();
199                   } finally {
200                      if (readSource instanceof IMultipleDataSource)
201                         ((IMultipleDataSource) readSource).finish ();
202                   }
203                   if (monitor.isCancelled ())
204                      return;
205                   EventQueue.invokeAndWait (ProxyBuilder.proxyRunnable (this, "finish"));
206                } catch (Exception e) {
207                   readError = e;
208                } finally {
209                   monitor.close ();
210                   if (readError != null)
211                      compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_LOADERROR + readError.getMessage (), readError);
212                }
213             }
214          }
215       }
216    
217       /**
218        * A panel tab with services for reading and writing data.
219        *
220        * @param compare Comparator backend to interface with
221        * @param configMarker Marker for retrieving configuration information from Comparator backend
222        */
223    
224       public DataEditorPanelTab (Compare compare, String configMarker) {
225          super (compare, configMarker);
226          exception = new ExceptionRecorder ("Error encountered during file operation.", true, false, true);
227          setCurrentSource (null);
228       }
229    
230       /**
231        * Adds a recently used data source.  If the source is already in the list, it will be moved to the most recent position.
232        *
233        * @param source Source
234        */
235    
236       public void addRecentSource (IPersistableDataSource source) {
237          int l = (int) getRecentSourceCount ();
238          if (l == 0)
239             return;
240          String location = source.getLocation ();
241          if (location == null)
242             return;
243          String _source = DataSource.persistSource (source);
244          if (_source == null)
245             return;
246          Config config = getConfigForView ();
247          String sources [] = new String [l];
248          sources [0] = _source;
249          int pos = 1;
250          for (int i = 1; i <= l && pos < l; i++) {
251             _source = config.getValue (CONFIG_RECENTSOURCE + i);
252             if (_source == null)
253                break;
254             if (!location.equals (DataSource.unpersistSource (_source).getLocation ()))
255                sources [pos++] = _source;
256          }
257          for (int i = 0; i < pos; i++)
258             config.setValue (CONFIG_RECENTSOURCE + (i + 1), sources [i]);
259          compare.firePropertyChange (Compare.PROPERTY_CONFIG_EDIT);
260       }
261    
262       /**
263        * The current data source to work from.
264        */
265    
266       public IWriteableDataSource getCurrentSource () {
267          return currentSource;
268       }
269    
270       /**
271        * A recently used data source.  If there is no such source, the return value is null.
272        *
273        * @param index The position of the source.  The most recently used source is position 1 and older sources have higher numbers.
274        */
275    
276       public IPersistableDataSource getRecentSource (long index) {
277          assert index > 0 && index <= getRecentSourceCount ();
278          return DataSource.unpersistSource (compare.getConfig ().findValue (configMarkers, CONFIG_RECENTSOURCE + index));
279       }
280    
281       /**
282        * The number of recent data sources that can be remembered.
283        */
284    
285       public long getRecentSourceCount () {
286          return Config.convertToInteger (compare.getConfig ().findValue (configMarkers, CONFIG_RECENTSOURCECOUNT), 0);
287       }
288    
289       /**
290        * Loads the data for the view from some external source.
291        *
292        * @param source Data source to read from
293        */
294    
295       public void load (IReadableDataSource source) {
296          if (source instanceof FileDataSource) {
297             FileDataSource fileSource = (FileDataSource) source;
298             fileSource.setExceptionListenerOption (exception);
299             fileSource.setFileChooserOption (createFileChooser ());
300             fileSource.setFileChooserSelectOption (OPTION_CHOOSEROPEN);
301          }
302          try {
303             if (!source.configure ())
304                return;
305             loadDirect (source);
306          } catch (Exception e) {
307             compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_LOADERROR + e.getMessage (), e);
308          }
309       }
310    
311       /**
312        * Loads the data for the view from some external source without user prompting.
313        *
314        * @param source Data source to read from
315        */
316    
317       public void loadDirect (IReadableDataSource source) {
318          new ReadHelper (source);
319       }
320    
321       /**
322        * {@inheritDoc}
323        */
324    
325       public void readConfiguration (String state) {
326          if (state == STATE_ENDINITIALIZE) {
327             JMenu openMenu = MenuBuilder.findParentMenu (menus, "open", false);
328             if (openMenu == null)
329                return;
330             recentReadablesMenu = MenuBuilder.createMenu (MENU_OPENRECENT, null, null, null);
331             for (int i = 0, l = openMenu.getItemCount (); i < l; i++)
332                if ("open".equals (openMenu.getItem (i).getActionCommand ())) {
333                   openMenu.insert (recentReadablesMenu, i + 1);
334                   break;
335                }
336          } else if (state != STATE_RUNNING)
337             return;
338          if (recentReadablesMenu == null)
339             return;
340          recentReadablesMenu.removeAll ();
341          JMenuItem items [] = createReadableRecentSourceMenuItems ();
342          if (items.length != 0) {
343             recentReadablesMenu.setEnabled (true);
344             for (int i = 0, l = items.length; i < l; i++)
345                recentReadablesMenu.add (items [i]);
346          } else
347             recentReadablesMenu.setEnabled (false);
348       }
349    
350       /**
351        * Saves the data for the view to some external source.
352        *
353        * @param source Data source to write to
354        */
355    
356       public void save (IWriteableDataSource source) {
357          if (source instanceof FileDataSource) {
358             FileDataSource fileSource = (FileDataSource) source;
359             fileSource.setExceptionListenerOption (exception);
360             fileSource.setFileChooserOption (createFileChooser ());
361             fileSource.setFileChooserSelectOption (OPTION_CHOOSERSAVE);
362          }
363          try {
364             if (!source.configure ())
365                return;
366             saveDirect (source);
367          } catch (Exception e) {
368             compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_SAVEERROR + e.getMessage (), e);
369          }
370       }
371    
372       /**
373        * Saves the data for the view to some external source without user prompting.
374        *
375        * @param source Data source to write to
376        */
377    
378       public void saveDirect (IWriteableDataSource source) {
379          try {
380             write (source);
381             setCurrentSource (source);
382          } catch (Exception e) {
383             compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_SAVEERROR + e.getMessage (), e);
384          } finally {
385             if (source instanceof IMultipleDataSource)
386                ((IMultipleDataSource) source).finish ();
387          }
388       }
389    
390       /**
391        * Sets the current data source to work from.
392        *
393        * @param source Data source
394        */
395    
396       public void setCurrentSource (IWriteableDataSource source) {
397          currentSource = source;
398          if (source instanceof IPersistableDataSource)
399             addRecentSource ((IPersistableDataSource) source);
400       }
401    
402       /**
403        * A file chooser specialized for the view.
404        */
405    
406       protected JFileChooser createFileChooser () {
407          return InterfaceBuilder.createFileChooser ();
408       }
409    
410       /**
411        * {@inheritDoc}
412        */
413    
414       protected JMenu [] createMenus () {
415          menus = menuManager.createMenus (MENUBAR_DEFAULT);
416          return menus;
417       }
418    
419       /**
420        * Menu items that correspond to the most recently used readable data sources.  Clicking on the menu item will load the source.
421        */
422    
423       protected JMenuItem [] createReadableRecentSourceMenuItems () {
424          ActionListener listener = ProxyBuilder.proxyActionListener (this, "loadRecentSource");
425          List items = new ArrayList ();
426          for (int i = 1, l = (int) getRecentSourceCount (); i <= l; i++) {
427             IPersistableDataSource source = getRecentSource (i);
428             if (source == null)
429                break;
430             if (!(source instanceof IReadableDataSource))
431                continue;
432             JMenuItem item = MenuBuilder.createMenuItem (source.getLocation (), null, null, null, listener);
433             items.add (item);
434             item.putClientProperty (PROPERTY_RECENTSOURCETARGET, source);
435          }
436          return (JMenuItem []) items.toArray (new JMenuItem [0]);
437       }
438    
439       /**
440        * {@inheritDoc}
441        */
442    
443       protected JButton [] createTools () {
444          return menuManager.createTools (TOOLBAR_DEFAULT);
445       }
446    
447       /**
448        * {@inheritDoc}
449        */
450    
451       protected void initialize () {
452          super.initialize ();
453          manager = new InterfaceBuilder (this, this);
454          menuManager = new MenuBuilder (this, true);
455       }
456    
457       /**
458        * Loads a recent source.
459        *
460        * @param e Action caused by the menu item that indicates the recent source
461        */
462    
463       protected void loadRecentSource (ActionEvent e) {
464          loadDirect ((IReadableDataSource) ((JMenuItem) e.getSource ()).getClientProperty (PROPERTY_RECENTSOURCETARGET));
465       }
466    
467       /**
468        * Processes data that has been read from some external source.
469        *
470        * @param readResult Data
471        */
472    
473       protected void readInternal (Object readResult) throws Exception {}
474    
475       /**
476        * Notifies listeners after a read operation has completed.
477        */
478    
479       protected void readNotify () {}
480    
481       /**
482        * Writes data to some external source.
483        *
484        * @param source Data source to write to
485        */
486    
487       protected void write (IWriteableDataSource source) throws Exception {}
488    }