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 }