001    package jigcell.compare.views;
002    
003    import java.awt.datatransfer.DataFlavor;
004    import java.awt.datatransfer.Transferable;
005    import java.awt.datatransfer.UnsupportedFlavorException;
006    import java.beans.XMLDecoder;
007    import java.beans.XMLEncoder;
008    import java.io.IOException;
009    import java.io.InputStream;
010    import java.io.PipedInputStream;
011    import java.io.PipedOutputStream;
012    import java.io.Reader;
013    import java.io.StringReader;
014    import java.lang.reflect.InvocationTargetException;
015    import java.util.Arrays;
016    import java.util.ListIterator;
017    import javax.swing.JComponent;
018    import javax.swing.SwingUtilities;
019    import javax.swing.table.TableModel;
020    import jigcell.compare.IDataGenerator;
021    import jigcell.compare.IEditableDataGenerator;
022    import jigcell.compare.ITransferProxy;
023    import jigcell.compare.data.DataGeneratorList;
024    import jigcell.compare.data.IDataGeneratorList;
025    import jigcell.compare.impl.ComparatorDataFlavor;
026    import jigcell.compare.impl.Compare;
027    import jigcell.compare.impl.Config;
028    import jigcell.compare.impl.ExceptionRecorder;
029    import jigcell.compare.impl.Transferer;
030    import jigcell.compare.ui.BasicTable;
031    import jigcell.compare.ui.IClipboardTab;
032    import jigcell.compare.ui.IRowSelectionTab;
033    import jigcell.compare.ui.SeriesDialog;
034    import jigcell.compare.ui.ViewerDialog;
035    
036    /**
037     * A static display for presenting a two-dimensional set of data organized by generators.
038     *
039     * <p>
040     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
041     * </p>
042     *
043     * @author Nicholas Allen
044     */
045    
046    public class SeriesView extends BasicTableView implements IClipboardTab, IRowSelectionTab {
047    
048       /**
049        * Property key for text cutoff value
050        */
051    
052       public final static String CONFIG_TEXTCUTOFF = "SeriesView.textLimit";
053    
054       /**
055        * Display value for data that is not available
056        */
057    
058       protected final static String VALUE_NOTAVAILABLE = "Not available";
059    
060       /**
061        * List of displayed generators
062        */
063    
064       protected IDataGeneratorList generators;
065    
066       /**
067        * Column to cutoff excessively long values at
068        */
069    
070       protected int textCutoff;
071    
072       /**
073        * Data model of the generators
074        */
075    
076       protected SeriesModel model;
077    
078       /**
079        * Converts the internal format of the view to and from a Transferable.
080        */
081    
082       public static class SeriesViewTransferer extends Transferer implements ITransferProxy {
083    
084          /**
085           * Readable name of the exported data flavor
086           */
087    
088          protected final static String NAME_EXPORTEDFLAVOR = "Generator List";
089    
090          /**
091           * Cached exception recorder for this transferer
092           */
093    
094          protected ExceptionRecorder exception;
095    
096          /**
097           * List of displayed generators
098           */
099    
100          protected IDataGeneratorList generators;
101    
102          /**
103           * Model for the data
104           */
105    
106          protected SeriesView.SeriesModel model;
107    
108          static {
109             addFlavor (SeriesViewTransferer.class,
110                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, IDataGeneratorList.class, ComparatorDataFlavor.FLAVOR_LOCALOBJECT));
111             addFlavor (SeriesViewTransferer.class,
112                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, InputStream.class, ComparatorDataFlavor.FLAVOR_XMLSERIALIZEDOBJECT));
113             addFlavor (SeriesViewTransferer.class,
114                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, IDataGeneratorList.class, ComparatorDataFlavor.FLAVOR_SERIALIZEDOBJECT));
115             addFlavor (SeriesViewTransferer.class,
116                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, SeriesView.SeriesModel.class, ComparatorDataFlavor.FLAVOR_LOCALOBJECT));
117             addFlavor (SeriesViewTransferer.class,
118                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, Reader.class, ComparatorDataFlavor.FLAVOR_TEXTHTML));
119             addFlavor (SeriesViewTransferer.class,
120                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, String.class, ComparatorDataFlavor.FLAVOR_TEXTHTML));
121             addFlavor (SeriesViewTransferer.class,
122                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, Reader.class, ComparatorDataFlavor.FLAVOR_TEXTPLAIN));
123             addFlavor (SeriesViewTransferer.class,
124                new ComparatorDataFlavor (NAME_EXPORTEDFLAVOR, String.class, ComparatorDataFlavor.FLAVOR_TEXTPLAIN));
125          }
126    
127          /**
128           * Creates a new transferer proxy.
129           */
130    
131          public SeriesViewTransferer () {
132             super ();
133             exception = new ExceptionRecorder ("Error encountered during transfer.", true, false, true);
134          }
135    
136          /**
137           * Creates a new transferer proxy.
138           *
139           * @param data Data to transfer
140           * @param owner Owner
141           */
142    
143          public SeriesViewTransferer (Object data, Object owner) throws InvocationTargetException {
144             SeriesView view = (SeriesView) owner;
145             if (data == null)
146                generators = new DataGeneratorList ();
147             else if (data instanceof InputStream)
148                generators = (IDataGeneratorList) new XMLDecoder ((InputStream) data, this, exception).readObject ();
149             else if (data instanceof IDataGeneratorList)
150                generators = (IDataGeneratorList) data;
151             else
152                throw new ClassCastException (Compare.getString ("SeriesViewTransferer.unwrapError"));
153             try {
154                model = (SeriesView.SeriesModel) view.model.getClass ().getDeclaredConstructor (new Class [] {view.getClass ()}).newInstance (
155                   new Object [] {view});
156                model.setGenerators (generators);
157             } catch (Exception e) {
158                throw new InvocationTargetException (e, Compare.getString ("SeriesViewTransferer.unwrapError"));
159             }
160          }
161    
162          /**
163           * A transformed version of the transfer data.
164           */
165    
166          public Object getData () {
167             return generators;
168          }
169    
170          public int getSourceActions (JComponent c) {
171             assert SwingUtilities.getAncestorOfClass (SeriesView.class, c) != null;
172             return COPY;
173          }
174    
175          public Object getTransferData (DataFlavor flavor) throws IOException, UnsupportedFlavorException {
176             if (!(flavor instanceof ComparatorDataFlavor))
177                throw new UnsupportedFlavorException (flavor);
178             ComparatorDataFlavor type = (ComparatorDataFlavor) flavor;
179             if (type.isFlavorLocalObjectType () || type.isFlavorSerializedObjectType ()) {
180                Class representation = type.getRepresentationClass ();
181                if (IDataGeneratorList.class.isAssignableFrom (representation))
182                   return generators;
183                if (TableModel.class.isAssignableFrom (representation))
184                   return model;
185                throw new UnsupportedFlavorException (flavor);
186             }
187             if (type.isFlavorXMLSerializedObjectType ()) {
188                if (!PipedInputStream.class.isAssignableFrom (type.getRepresentationClass ()))
189                   throw new UnsupportedFlavorException (flavor);
190                PipedOutputStream out = new PipedOutputStream ();
191                XMLEncoder encoder = new XMLEncoder (out);
192                Compare.updateEncoder (encoder);
193                encoder.setExceptionListener (exception);
194                encoder.writeObject (generators);
195                encoder.close ();
196                if (!exception.hasException ())
197                   return new PipedInputStream (out);
198                throw (IOException) new IOException (Compare.getString ("Transferer.transferError")).initCause (exception.getLastException ());
199             }
200             String text;
201             if (type.isFlavorPlainTextType ())
202                text = createCSVTable (model);
203             else if (type.isFlavorHTMLTextType ())
204                text = createHTMLTable (model);
205             else
206                throw new UnsupportedFlavorException (flavor);
207             Class representation = type.getRepresentationClass ();
208             if (representation == String.class)
209                return text;
210             if (Reader.class.isAssignableFrom (representation))
211                return new StringReader (text);
212             throw new UnsupportedFlavorException (flavor);
213          }
214    
215          protected Transferable createTransferable (JComponent component) {
216             SeriesView view = (SeriesView) SwingUtilities.getAncestorOfClass (SeriesView.class, component);
217             assert view != null;
218             IDataGeneratorList generators = view.generators;
219             IDataGeneratorList list = new DataGeneratorList ();
220             int rows [] = view.getSelectedRows ();
221             Arrays.sort (rows);
222             for (int i = 0, l = rows.length; i < l; i++)
223                list.add (copyGenerator ((IDataGenerator) generators.get (rows [i])));
224             try {
225                return new SeriesViewTransferer (list, view);
226             } catch (InvocationTargetException _e) {
227                view.compare.shellHandleException (Compare.MESSAGE_ERROR, Compare.getString ("SeriesViewTransferer.dragStartError"), _e);
228             }
229             return null;
230          }
231    
232          /**
233           * {@inheritDoc}
234           */
235    
236          protected ExceptionRecorder getExceptionRecorder () {
237             return exception;
238          }
239       }
240    
241       /**
242        * Table model for a SimpleDataSeriesView.
243        */
244    
245       protected class SeriesModel extends BasicTable.BasicTableModel {
246    
247          /**
248           * Comment column
249           */
250    
251          public final Marker COLUMN_COMMENT = new Marker ("SeriesModel.comment", "Comment");
252    
253          /**
254           * Name column
255           */
256    
257          public final Marker COLUMN_NAME = new Marker ("SeriesModel.name", "Name");
258    
259          /**
260           * Value column
261           */
262    
263          public final Marker COLUMN_VALUE = new Marker ("SeriesModel.value", "Value");
264    
265          /**
266           * List of displayed generators
267           */
268    
269          protected IDataGeneratorList generators;
270    
271          /**
272           * Creates a new table model.
273           */
274    
275          public SeriesModel () {
276             super ();
277             setColumnMarkers (new Marker [] {COLUMN_NAME, COLUMN_VALUE, COLUMN_COMMENT});
278             setColumnWeights (new double [] {1.0, 2.0, 2.0});
279             generators = SeriesView.this.generators;
280          }
281    
282          /**
283           * The generators for this model.
284           */
285    
286          public IDataGeneratorList getGenerators () {
287             return generators;
288          }
289    
290          /**
291           * The number of generators.
292           */
293    
294          public int getRowCount () {
295             return generators.size ();
296          }
297    
298          /**
299           * A value in the table.
300           *
301           * @param row Row
302           * @param column Column
303           */
304    
305          public Object getValueAt (int row, int column) {
306             if (column == findColumn (COLUMN_VALUE)) {
307                IDataGenerator entry = (IDataGenerator) generators.get (row);
308                return entry.isCached () ? entry.getElement ().toString () : "Not Available";
309             }
310             if (column == findColumn (COLUMN_NAME))
311                return ((IDataGenerator) generators.get (row)).getName ();
312             if (column == findColumn (COLUMN_COMMENT))
313                return ((IDataGenerator) generators.get (row)).getComment ();
314             return super.getValueAt (row, column);
315          }
316    
317          /**
318           * Sets the generators for this model.
319           *
320           * @param generators Generators
321           */
322    
323          public void setGenerators (IDataGeneratorList generators) {
324             this.generators = generators;
325          }
326    
327          /**
328           * Starts viewing a cell in the table but does not allow editing.
329           *
330           * @param row Row
331           * @param column Column
332           */
333    
334          public void viewCellAt (int row, int column) {
335             IDataGenerator generator = (IDataGenerator) generators.get (row);
336             if (column != findColumn (COLUMN_VALUE) || generator.getElement ().isScalar ())
337                new ViewerDialog (compare, generator.getName (), row, column, model).setVisible (true);
338             else
339                new SeriesDialog (compare, generator, row, column, model).setVisible (true);
340          }
341       }
342    
343       /**
344        * Copies a generator.
345        *
346        * @param generator Generator
347        */
348    
349       protected static IDataGenerator copyGenerator (IDataGenerator generator) {
350          if (generator == null)
351             return null;
352          IDataGenerator _generator = (IDataGenerator) generator.clone ();
353          if (generator instanceof IEditableDataGenerator)
354             ((IEditableDataGenerator) _generator).setElement (generator.getElement ());
355          return _generator;
356       }
357    
358       /**
359        * Creates a new with an integrated model and data store.
360        *
361        * @param compare Comparator backend to interface with
362        * @param configMarker Marker for retrieving configuration information from Comparator backend
363        */
364    
365       public SeriesView (Compare compare, String configMarker) {
366          super (compare, configMarker);
367          clipboard.setTransferClassOption (SeriesViewTransferer.class);
368          clipboard.setOwnerOption (this);
369          clipboard.setFlavorsOption (Transferer.getFlavors (SeriesViewTransferer.class));
370          printer.setTransferClassOption (SeriesViewTransferer.class);
371          printer.setOwnerOption (this);
372       }
373    
374       /**
375        * {@inheritDoc}
376        */
377    
378       public void clipboardCopy () {
379          IDataGeneratorList list = new DataGeneratorList ();
380          for (int i = 0, l = generators.size (); i < l; i++)
381             list.add (copyGenerator ((IDataGenerator) generators.get (i)));
382          clipboardWrite (list, Compare.getString ("SeriesView.copyError"));
383       }
384    
385       /**
386        * {@inheritDoc}
387        */
388    
389       public void clipboardCopy (int rows []) {
390          IDataGeneratorList list = new DataGeneratorList ();
391          Arrays.sort (rows);
392          for (int i = 0, l = rows.length; i < l; i++)
393             list.add (copyGenerator ((IDataGenerator) generators.get (rows [i])));
394          clipboardWrite (list, Compare.getString ("SeriesView.copyError"));
395       }
396    
397       /**
398        * {@inheritDoc}
399        */
400    
401       public void clipboardCut () {
402          throw new UnsupportedOperationException ();
403       }
404    
405       /**
406        * {@inheritDoc}
407        */
408    
409       public void clipboardCut (int rows []) {
410          throw new UnsupportedOperationException ();
411       }
412    
413       /**
414        * {@inheritDoc}
415        */
416    
417       public void clipboardPaste () {
418          throw new UnsupportedOperationException ();
419       }
420    
421       /**
422        * {@inheritDoc}
423        */
424    
425       public int getRowCount () {
426          return generators.size ();
427       }
428    
429       /**
430        * {@inheritDoc}
431        */
432    
433       public Object getRowData (int row) {
434          try {
435             return generators.get (row);
436          } catch (Exception e) {
437             Compare.assertion ("Unable to retrieve generator for row.", e);
438          }
439          return null;
440       }
441    
442       /**
443        * Prints the view.
444        */
445    
446       public void print () {
447          try {
448             if (printer.configure ())
449                printer.write (generators);
450          } catch (Exception e) {
451             compare.shellHandleException (Compare.MESSAGE_ERROR, Compare.getString ("BasicTableView.printError"), e);
452          }
453       }
454    
455       /**
456        * {@inheritDoc}
457        */
458    
459       public void readConfiguration (String state) {
460          super.readConfiguration (state);
461          if (state == STATE_INITIALIZE || state == STATE_RUNNING)
462             textCutoff = (int) Config.convertToInteger (compare.getConfig ().findValue (configMarkers, CONFIG_TEXTCUTOFF), Integer.MAX_VALUE);
463       }
464    
465       /**
466        * A list of generators for the view.
467        */
468    
469       protected IDataGeneratorList createGenerators () {
470          return new DataGeneratorList ();
471       }
472    
473       /**
474        * {@inheritDoc}
475        */
476    
477       protected void createUI () {
478          createUI (new SeriesModel ());
479       }
480    
481       /**
482        * @see jigcell.compare.views.BasicTableView#createUI(jigcell.compare.ui.BasicTable.BasicTableModel)
483        */
484    
485       protected void createUI (SeriesModel model) {
486          super.createUI (model);
487          this.model = model;
488          table.setTransferHandler (new SeriesViewTransferer ());
489          table.setAutoscrolls (true);
490          table.setDragEnabled (true);
491       }
492    
493       /**
494        * {@inheritDoc}
495        */
496    
497       protected boolean findByName (String name, int row, boolean forward) {
498          if (name == null)
499             return false;
500          lastSearchName = name;
501          int length = generators.size ();
502          int index = -1;
503          if (forward) {
504             if (row < -1 || row >= length)
505                return false;
506             for (ListIterator iterator = generators.iterator (row + 1); iterator.hasNext (); )
507                if (((IDataGenerator) iterator.next ()).getName ().indexOf (name) != -1) {
508                   index = iterator.previousIndex ();
509                   break;
510                }
511          } else {
512             if (row < 0 || row > length)
513                return false;
514             for (ListIterator iterator = generators.iterator (row); iterator.hasPrevious (); )
515                if (((IDataGenerator) iterator.previous ()).getName ().indexOf (name) != -1) {
516                   index = iterator.nextIndex ();
517                   break;
518                }
519          }
520          if (index == -1)
521             return false;
522          table.setRowSelectionInterval (index, index);
523          table.scrollRectToVisible (table.getCellRect (index, table.findColumn (model.COLUMN_NAME), true));
524          return true;
525       }
526    
527       /**
528        * {@inheritDoc}
529        */
530    
531       protected void initialize () {
532          super.initialize ();
533          initializeData ();
534       }
535    
536       /**
537        * Initializes data for the view.
538        */
539    
540       protected void initializeData () {
541          generators = createGenerators ();
542          compare.updateResourceMap (IDataGenerator.RESOURCE_GENERATORS, this, generators.asList ());
543          compare.firePropertyChange (IDataGenerator.RESOURCE_GENERATORS);
544       }
545    }