001    package jigcell.compare.impl;
002    
003    import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
004    import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
005    import EDU.oswego.cs.dl.util.concurrent.Sync;
006    import java.awt.datatransfer.DataFlavor;
007    import java.awt.datatransfer.Transferable;
008    import java.awt.datatransfer.UnsupportedFlavorException;
009    import java.beans.XMLEncoder;
010    import java.io.File;
011    import java.io.IOException;
012    import java.io.PipedInputStream;
013    import java.io.PipedOutputStream;
014    import java.util.ArrayList;
015    import java.util.HashMap;
016    import java.util.HashSet;
017    import java.util.Iterator;
018    import java.util.LinkedHashSet;
019    import java.util.LinkedList;
020    import java.util.List;
021    import java.util.ListIterator;
022    import java.util.Map;
023    import java.util.Set;
024    import javax.swing.TransferHandler;
025    import javax.swing.table.TableModel;
026    
027    /**
028     * A helper class for transfering data between the Comparator and other applications.
029     *
030     * <p>
031     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
032     * </p>
033     *
034     * @author Nicholas Allen
035     */
036    
037    public abstract class Transferer extends TransferHandler implements Transferable {
038    
039       /**
040        * Footer for CSV data
041        */
042    
043       protected final static String CSV_FOOTER = "";
044    
045       /**
046        * Line postpender for CSV data
047        */
048    
049       protected final static String CSV_LINEPOSTPEND = "\"\n";
050    
051       /**
052        * Line prepender for CSV data
053        */
054    
055       protected final static String CSV_LINEPREPEND = "\"";
056    
057       /**
058        * Item separator for CSV data
059        */
060    
061       protected final static String CSV_LINESEPARATOR = "\",\"";
062    
063       /**
064        * Footer for HTML data
065        */
066    
067       protected final static String HTML_FOOTER = "</table>";
068    
069       /**
070        * Header for HTML data
071        */
072    
073       protected final static String HTML_HEADPREPEND = "<table border=\"1\" width=\"100%\"><tr><th>";
074    
075       /**
076        * Header postpender for HTML data
077        */
078    
079       protected final static String HTML_HEADPOSTPEND = "</th></tr>";
080    
081       /**
082        * Header separator for HTML data
083        */
084    
085       protected final static String HTML_HEADSEPARATOR = "</th><th>";
086    
087       /**
088        * Line postpender for HTML data
089        */
090    
091       protected final static String HTML_LINEPOSTPEND = "</td></tr>";
092    
093       /**
094        * Line prepender for HTML data
095        */
096    
097       protected final static String HTML_LINEPREPEND = "<tr><td>";
098    
099       /**
100        * Item separator for HTML data
101        */
102    
103       protected final static String HTML_LINESEPARATOR = "</td><td>";
104    
105       /**
106        * System data extensions map
107        */
108    
109       private final static Map extensions = new HashMap ();
110    
111       /**
112        * System data flavors map
113        */
114    
115       private final static Map flavors = new HashMap ();
116    
117       /**
118        * Lock for manipulating MIME type information
119        */
120    
121       private final static ReadWriteLock MIME_LOCK = new ReentrantWriterPreferenceReadWriteLock ();
122    
123       /**
124        * Adds a mapping between a data flavor and a preferred serialization extension.
125        *
126        * @param flavor Data flavor
127        * @param extension Extension
128        */
129    
130       public static void addExtension (ComparatorDataFlavor flavor, String extension) {
131          if (flavor == null || extension == null)
132             throw new IllegalArgumentException ();
133          Sync sync = MIME_LOCK.writeLock ();
134          Compare.acquireSync (sync);
135          try {
136             LinkedHashSet set = (LinkedHashSet) extensions.get (flavor);
137             if (set == null) {
138                set = new LinkedHashSet ();
139                extensions.put (flavor, set);
140             }
141             set.add (extension);
142          } finally {
143             sync.release ();
144          }
145       }
146    
147       /**
148        * Adds a mapping between a class and a data flavor.
149        *
150        * @param clazz Class
151        * @param flavor Data flavor
152        */
153    
154       public static void addFlavor (Class clazz, ComparatorDataFlavor flavor) {
155          if (clazz == null || flavor == null)
156             throw new IllegalArgumentException ();
157          Sync sync = MIME_LOCK.writeLock ();
158          Compare.acquireSync (sync);
159          try {
160             LinkedHashSet set = (LinkedHashSet) flavors.get (clazz);
161             if (set == null) {
162                set = new LinkedHashSet ();
163                flavors.put (clazz, set);
164             }
165             set.add (flavor);
166          } finally {
167             sync.release ();
168          }
169       }
170    
171       /**
172        * The expected classes given a data flavor as a List[Class].
173        *
174        * @param flavor Data flavor
175        */
176    
177       public static List getExpectedClasses (ComparatorDataFlavor flavor) {
178          List classes = new ArrayList ();
179          if (flavor == null)
180             return classes;
181          Sync sync = MIME_LOCK.readLock ();
182          Compare.acquireSync (sync);
183          try {
184             for (Iterator iterator = flavors.entrySet ().iterator (); iterator.hasNext (); ) {
185                Map.Entry entry = (Map.Entry) iterator.next ();
186                for (Iterator _iterator = ((Set) entry.getValue ()).iterator (); _iterator.hasNext (); )
187                   if (flavor.equals ((ComparatorDataFlavor) _iterator.next ())) {
188                      classes.add (entry.getKey ());
189                      break;
190                   }
191             }
192          } finally {
193             sync.release ();
194          }
195          if (classes.isEmpty ())
196             classes.add (Object.class);
197          return classes;
198       }
199    
200       /**
201        * The expected classes given a file as a List[Class].
202        *
203        * @param file File
204        */
205    
206       public static List getExpectedClasses (File file) {
207          return getExpectedClasses (file == null ? null : file.getName ());
208       }
209    
210       /**
211        * The expected classes given a file name as a List[Class].
212        *
213        * @param name File name
214        */
215    
216       public static List getExpectedClasses (String name) {
217          List classes = new ArrayList ();
218          if (name == null)
219             return classes;
220          Set used = new HashSet ();
221          Sync sync = MIME_LOCK.readLock ();
222          Compare.acquireSync (sync);
223          try {
224             for (Iterator iterator = extensions.entrySet ().iterator (); iterator.hasNext (); ) {
225                Map.Entry entry = (Map.Entry) iterator.next ();
226                for (Iterator _iterator = ((Set) entry.getValue ()).iterator (); _iterator.hasNext (); )
227                   if (name.endsWith ((String) _iterator.next ())) {
228                      used.addAll (getExpectedClasses ((ComparatorDataFlavor) entry.getKey ()));
229                      break;
230                   }
231             }
232          } finally {
233             sync.release ();
234          }
235          classes.addAll (used);
236          if (classes.isEmpty ())
237             classes.add (Object.class);
238          return classes;
239       }
240    
241       /**
242        * The associated flavors for any class in the supporting hierarchy as a List[ComparatorDataFlavor].
243        *
244        * @param clazz Class
245        */
246    
247       public static List getFlavors (Class clazz) {
248          List found = new ArrayList ();
249          if (clazz == null)
250             return found;
251          Set usedClasses = new HashSet ();
252          Set usedFlavors = new HashSet ();
253          List classes = new LinkedList ();
254          if (usedClasses.add (clazz))
255             classes.add (clazz);
256          Sync sync = MIME_LOCK.readLock ();
257          Compare.acquireSync (sync);
258          try {
259             for (ListIterator iterator = classes.listIterator (); iterator.hasNext (); ) {
260                Class support = (Class) iterator.next ();
261                Set flavorSet = (Set) flavors.get (support);
262                if (flavorSet != null)
263                   for (Iterator _iterator = flavorSet.iterator (); _iterator.hasNext (); )
264                      usedFlavors.add (_iterator.next ());
265                Class supports [] = support.getInterfaces ();
266                for (int i = 0, l = supports.length; i < l; i++) {
267                   Class _support = supports [i];
268                   if (usedClasses.add (_support))
269                      iterator.add (_support);
270                }
271                Class _support = support.getSuperclass ();
272                if (usedClasses.add (_support))
273                   iterator.add (_support);
274             }
275          } finally {
276             sync.release ();
277          }
278          found.addAll (usedFlavors);
279          return found;
280       }
281    
282       /**
283        * The associated flavors for any class in the supporting hierarchy of an instance as a List[ComparatorDataFlavor].
284        *
285        * @param instance Instance
286        */
287    
288       public static List getFlavors (Transferable instance) {
289          return getFlavors (instance == null ? null : instance.getClass ());
290       }
291    
292       /**
293        * The preferred serialization extensions for a given class as a List[String].
294        *
295        * @param clazz Class
296        */
297    
298       public static List getPreferredExtensions (Class clazz) {
299          List found = new ArrayList ();
300          if (clazz == null)
301             return found;
302          Sync sync = MIME_LOCK.readLock ();
303          Compare.acquireSync (sync);
304          Set used = new HashSet ();
305          try {
306             Set set = (Set) flavors.get (clazz);
307             if (set == null)
308                return found;
309             for (Iterator iterator = set.iterator (); iterator.hasNext (); ) {
310                Set _set = (Set) extensions.get (iterator.next ());
311                if (_set != null)
312                   for (Iterator _iterator = _set.iterator (); _iterator.hasNext (); )
313                      used.add (iterator.next ());
314             }
315          } finally {
316             sync.release ();
317          }
318          found.addAll (used);
319          return found;
320       }
321    
322       /**
323        * The preferred serialization extensions for a given instance as a List[String].
324        *
325        * @param instance Instance
326        */
327    
328       public static List getPreferredExtensions (Transferable instance) {
329          return getPreferredExtensions (instance == null ? null : instance.getClass ());
330       }
331    
332       /**
333        * The type that will be returned from a transfer operation or null if the transfer will fail.
334        *
335        * @param flavors List of flavors to try
336        * @param transferable Transferable
337        */
338    
339       public static Class getTransferClass (List flavors, Transferable transferable) {
340          ComparatorDataFlavor flavor = getTransferFlavor (flavors, transferable);
341          return flavor == null ? null : flavor.getRepresentationClass ();
342       }
343    
344       /**
345        * The type that will be returned from a transfer operation or null if the transfer will fail.
346        *
347        * @param flavors List of flavors to try
348        * @param transferable Transferable
349        */
350    
351       public static ComparatorDataFlavor getTransferFlavor (List flavors, Transferable transferable) {
352          for (Iterator iterator = flavors.iterator (); iterator.hasNext (); ) {
353             ComparatorDataFlavor flavor = (ComparatorDataFlavor) iterator.next ();
354             if (transferable.isDataFlavorSupported (flavor))
355                return flavor;
356          }
357          return null;
358       }
359    
360       /**
361        * The first successful transfer for a given list of flavors.
362        *
363        * @param flavors List of flavors to try
364        * @param transferable Transferable
365        */
366    
367       public static Object transfer (List flavors, Transferable transferable) {
368          ComparatorDataFlavor flavor = getTransferFlavor (flavors, transferable);
369          try {
370             return flavor == null ? null : transferable.getTransferData (flavor);
371          } catch (Exception e) {
372             Compare.assertion (Compare.getString ("Transferer.transferError") , e);
373             return null;
374          }
375       }
376    
377       protected static String createCSVTable (TableModel model) {
378          return createFormattedTable (model, CSV_LINEPREPEND, CSV_LINESEPARATOR, CSV_LINEPOSTPEND, CSV_LINEPREPEND, CSV_LINESEPARATOR,
379             CSV_LINEPOSTPEND, CSV_FOOTER);
380       }
381    
382       /**
383        * Creates a formatted version of the table model contents.
384        *
385        * @param model Table model
386        * @param headPrepend Text to start header
387        * @param headSeparator Text to separator header entries
388        * @param headPostpend Text to finish header
389        * @param linePrepend Text to start line
390        * @param lineSeparator Text to separate line entries
391        * @param linePostpend Text to finish line
392        * @param footer Text to finish document
393        */
394    
395       protected static String createFormattedTable (TableModel model, String headPrepend, String headSeparator, String headPostpend,
396          String linePrepend, String lineSeparator, String linePostpend, String footer) {
397          StringBuffer buffer = new StringBuffer (headPrepend);
398          int columns = model.getColumnCount ();
399          if (columns == 0)
400             return buffer.append (headPostpend).append (footer).toString ();
401          buffer.append (model.getColumnName (0));
402          for (int column = 1; column < columns; column++)
403             buffer.append (headSeparator).append (model.getColumnName (column));
404          buffer.append (headPostpend);
405          for (int row = 0, rows = model.getRowCount (); row < rows; row++) {
406             Object value = model.getValueAt (row, 0);
407             buffer.append (linePrepend).append (value == null ? "" : value);
408             for (int column = 1; column < columns; column++) {
409                value = model.getValueAt (row, column);
410                buffer.append (lineSeparator).append (value == null ? "" : value);
411             }
412             buffer.append (linePostpend);
413          }
414          return buffer.append (footer).toString ();
415       }
416    
417       protected static String createHTMLTable (TableModel model) {
418          return createFormattedTable (model, HTML_HEADPREPEND, HTML_HEADSEPARATOR, HTML_HEADPOSTPEND, HTML_LINEPREPEND, HTML_LINESEPARATOR,
419             HTML_LINEPOSTPEND, HTML_FOOTER);
420       }
421    
422       /**
423        * A transferable object for the given data flavor.
424        *
425        * @param flavor Flavor
426        */
427    
428       public Object getTransferData (DataFlavor flavor) throws UnsupportedFlavorException, IOException {
429          if (!(flavor instanceof ComparatorDataFlavor))
430             throw new UnsupportedFlavorException (flavor);
431          ComparatorDataFlavor type = (ComparatorDataFlavor) flavor;
432          if (type.isFlavorLocalObjectType () || type.isFlavorSerializedObjectType ())
433             if (type.getRepresentationClass ().isAssignableFrom (getClass ()))
434                return this;
435             else
436                throw new UnsupportedFlavorException (flavor);
437          if (type.isFlavorXMLSerializedObjectType ())
438             if (type.getRepresentationClass ().isAssignableFrom (PipedInputStream.class)) {
439                ExceptionRecorder exception = getExceptionRecorder ();
440                exception.clear ();
441                PipedOutputStream out = new PipedOutputStream ();
442                XMLEncoder encoder = new XMLEncoder (out);
443                Compare.updateEncoder (encoder);
444                encoder.setExceptionListener (exception);
445                encoder.writeObject (this);
446                encoder.close ();
447                if (exception.hasException ())
448                   throw (IOException) new IOException (Compare.getString ("Transferer.xmlEncodingError")).initCause (exception.getLastException ());
449                return new PipedInputStream (out);
450             }
451          throw new UnsupportedFlavorException (flavor);
452       }
453    
454       /**
455        * The data flavors this transferable supports.
456        */
457    
458       public DataFlavor [] getTransferDataFlavors () {
459          return (DataFlavor []) getFlavors (this).toArray (new DataFlavor [0]);
460       }
461    
462       /**
463        * Whether this transferable supports a particular data flavor.
464        *
465        * @param flavor Flavor
466        */
467    
468       public boolean isDataFlavorSupported (DataFlavor flavor) {
469          if (!(flavor instanceof ComparatorDataFlavor))
470             return false;
471          Class clazz = flavor.getRepresentationClass ();
472          String primaryType = ((ComparatorDataFlavor) flavor).getPrimaryType ();
473          for (Iterator iterator = getFlavors (this).iterator (); iterator.hasNext (); ) {
474             ComparatorDataFlavor type = (ComparatorDataFlavor) iterator.next ();
475             if (type.getPrimaryType ().equals (primaryType) && type.getRepresentationClass ().isAssignableFrom (clazz))
476                return true;
477          }
478          return false;
479       }
480    
481       /**
482        * Creates a new data transferer.
483        */
484    
485       protected Transferer () {}
486    
487       /**
488        * The exception recorder this transferer should use.  The recorder must be one that saves the last exception caught.
489        */
490    
491       protected ExceptionRecorder getExceptionRecorder () {
492          return new ExceptionRecorder (Compare.getString ("Transferer.transferError") , true, false, true);
493       }
494    }