updated code for kona libs, not quite working yet.
[feisty_meow.git] / kona / src / org / feistymeow / dragdrop / ListTransferable.java
1 package org.feistymeow.dragdrop;
2
3 import java.awt.datatransfer.DataFlavor;
4 import java.awt.datatransfer.Transferable;
5 import java.awt.datatransfer.UnsupportedFlavorException;
6 import java.io.BufferedReader;
7 import java.io.File;
8 import java.io.IOException;
9 import java.io.Reader;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Vector;
15 import java.util.StringTokenizer;
16 import org.apache.commons.logging.Log;
17 import org.apache.commons.logging.LogFactory;
18
19 /**
20  * Implements a transferable object that understands URI lists as well as java file lists. This is
21  * useful for implementing file drag and drop that will work across different platforms (such as
22  * Gnome on Linux).
23  * 
24  * @author Chris Koeritz
25  * @copyright Copyright (c) 2012-$now By University of Virginia
26  * @license This file is free software; you can modify and redistribute it under the terms of the
27  *          Apache License v2.0: http://www.apache.org/licenses/LICENSE-2.0
28  */
29 @SuppressWarnings("serial")
30 public class ListTransferable extends Vector<Object> implements Transferable
31 {
32     static private Log logger = LogFactory.getLog(ListTransferable.class);
33
34     public ListTransferable()
35     {
36     }
37
38     public ListTransferable(Object initial)
39     {
40         if (initial != null) add(initial);
41     }
42
43     public ListTransferable(List<Object> initial)
44     {
45         if (initial != null) addAll(initial);
46     }
47
48     /**
49      * create a new flavor. this one understands URI lists, such as: file:///home/fred/arf.txt\r\n
50      * file:///etc/inputrc\r\n http://gruntose.com\r\n ...
51      */
52     private static DataFlavor URIListFlavor;
53     static {
54         try {
55             URIListFlavor = new DataFlavor("text/uri-list;class=java.lang.String");
56         } catch (ClassNotFoundException e) {
57             logger.error("should never happen", e);
58         }
59     }
60     private static DataFlavor AltURIListFlavor;
61     static {
62         try {
63             AltURIListFlavor = new DataFlavor("text/uri-list;representationclass=java.lang.String");
64         } catch (ClassNotFoundException e) {
65             logger.error("should never happen", e);
66         }
67     }
68
69     /**
70      * accessors for our special featured flavors of URI lists.
71      */
72     public static DataFlavor getURIListFlavor1()
73     {
74         return URIListFlavor;
75     }
76
77     public static DataFlavor getURIListFlavor2()
78     {
79         return AltURIListFlavor;
80     }
81
82     /**
83      * register the types of transfers that we understand. this is really only the normal java file
84      * list and our new URI list.
85      */
86     protected ArrayList<DataFlavor> FLAVORS = new ArrayList<DataFlavor>(Arrays.asList(
87             DataFlavor.javaFileListFlavor, URIListFlavor, AltURIListFlavor));
88
89     /**
90      * a function that must be overridden by derived classes if they are not initially seeding the
91      * vector of objects that we hold. the caller of this function expects it will populate the
92      * vector held here with usable objects.
93      */
94     public boolean loadDataJustInTime(DataFlavor flavor)
95     {
96         logger.warn("base loadDataJustInTime.  derived class should have implemented this.");
97         return false;
98     }
99
100     /**
101      * using the set of files that we've been handed, we can do transfers using our two supported
102      * flavors.
103      */
104     public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException,
105             java.io.IOException
106     {
107         if (flavor == null) return null;
108         if (size() == 0) {
109             logger.debug("size was zero, so loading data just in time");
110             boolean worked = loadDataJustInTime(flavor);
111             if (!worked || (size() == 0)) {
112                 logger.warn("failed to retrieve data just in time for getTransferData.");
113                 return null;
114             }
115         }
116         // help from workaround at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4899516
117         logger.debug("responding to flavor: " + flavor.toString());
118         if (flavor.equals(DataFlavor.javaFileListFlavor)) {
119             logger.debug("java file list flavor...");
120             List<Object> data = new java.util.ArrayList<Object>();
121             data.addAll(this);
122             return data;
123         } else if (flavor.equals(URIListFlavor) || flavor.equals(AltURIListFlavor)) {
124             logger.debug("uri list flavor...");
125             StringBuilder data = new StringBuilder();
126             Iterator<Object> iter = iterator();
127             while (iter.hasNext()) {
128                 Object x = iter.next();
129                 if (x instanceof File) {
130                     File elem = (File) x;
131                     data.append(elem.toURI() + "\r\n");
132                 } else if (x instanceof String) {
133                     data.append((String) x + "\r\n");
134                 } else {
135                     logger.debug("did not know how to handle type in transfer: " + x.toString());
136                 }
137             }
138             logger.debug("returning URI string: " + data.toString());
139             return data.toString();
140         } else {
141             logger.debug("getTransferData: didn't know how to handle the requested flavor.");
142             throw new UnsupportedFlavorException(flavor);
143         }
144     }
145
146     /**
147      * returns the list of all transfer flavors we understand.
148      */
149     public DataFlavor[] getTransferDataFlavors()
150     {
151         return (DataFlavor[]) FLAVORS.toArray(new DataFlavor[FLAVORS.size()]);
152     }
153
154     /**
155      * reports if a particular flavor is handled here.
156      */
157     public boolean isDataFlavorSupported(DataFlavor flavor)
158     {
159         if (flavor == null) return false;
160         for (int i = 0; i < FLAVORS.size(); i++) {
161             if (flavor.equals((DataFlavor) FLAVORS.get(i))) {
162                 return true;
163             }
164         }
165         logger.debug("failed to find flavor: " + flavor.toString());
166         return false;
167     }
168
169     /**
170      * a helper method that can process transfer data from either a java file list or a URI list.
171      */
172     @SuppressWarnings("unchecked")
173     static public List<Object> extractData(Transferable tran) {
174         if (tran == null) return null;
175         if (tran.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
176             logger.debug("extractData seeing java files flavor.");
177             try {
178                 return (List<Object>) tran.getTransferData(DataFlavor.javaFileListFlavor);
179             } catch (Throwable cause) {
180                 logger.error("extractData caught exception for java file list.", cause);
181                 return null;
182             }
183         } else if (tran.isDataFlavorSupported(ListTransferable.getURIListFlavor1())
184                 || tran.isDataFlavorSupported(ListTransferable.getURIListFlavor2())) {
185             logger.debug("extractData seeing uri list flavor.");
186             try {
187                 return textURIListToFileList((String) tran.getTransferData(getURIListFlavor1()));
188             } catch (Throwable cause) {
189                 logger.error("extractData caught exception for URI list.", cause);
190                 return null;
191             }
192         }
193         logger.error("extractData: Transferable did not support known data flavor.");
194         return null;
195     }
196
197     /**
198      * translates the string in "data" into a list of Files.
199      * 
200      * @param data
201      *            a string formatted with possibly multiple URIs separated by CRLF.
202      * @return a list of the files as java File objects. many thanks to
203      *         http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4899516
204      */
205     public static List<Object> textURIListToFileList(String data)
206     {
207         if (data == null) return null;
208         List<Object> list = new ArrayList<Object>(0);
209         for (StringTokenizer st = new StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
210             String s = st.nextToken();
211             if (s.startsWith("#")) {
212                 // the line is a comment (as per the RFC 2483)
213                 continue;
214             }
215             try {
216                 java.net.URI uri = new java.net.URI(s);
217                 java.io.File file = new java.io.File(uri);
218                 list.add(file);
219             } catch (java.net.URISyntaxException e) {
220                 // this is a malformed URI.
221                 logger.error("Found a malformed URI of: " + data);
222             } catch (IllegalArgumentException e) {
223                 // the URI is not a valid 'file:' URI
224                 logger.error("Found invalid 'file:' URI of: " + data);
225             }
226         }
227         return list;
228     }
229
230     /**
231      * This function will retrieve the file list from a standard file list flavor.
232      */
233     @SuppressWarnings("unchecked")
234     public static List<Object> processStandardFileList(Transferable tran)
235     {
236         if (tran == null) return null;
237         logger.debug("trying java file list flavor.");
238         try {
239             return (List<Object>) tran.getTransferData(DataFlavor.javaFileListFlavor);
240         } catch (Throwable cause) {
241             logger.debug("failed to retrieve transfer data for standard java file list flavor.",
242                     cause);
243         }
244         return new ArrayList<Object>();
245     }
246
247     /**
248      * checks if the transferable is appropriate to try to use as a java Reader.
249      */
250     public static boolean checkReaderFlavor(Transferable tran)
251     {
252         if (tran == null) return false;
253         DataFlavor[] flavors = tran.getTransferDataFlavors();
254         for (int i = 0; i < flavors.length; i++) {
255             if (flavors[i].isRepresentationClassReader())
256                 return true;
257         }
258         return false;
259     }
260
261     /**
262      * Use a Reader to handle an incoming transferable.
263      */
264     public static List<Object> processReaderFlavor(Transferable tran)
265     {
266         if (tran == null) return null;
267         logger.debug("trying URI list flavor.");
268         DataFlavor[] flavors = tran.getTransferDataFlavors();
269         for (int i = 0; i < flavors.length; i++) {
270             if (flavors[i].isRepresentationClassReader()) {
271                 // it looks like we can work with this flavor just fine.
272                 logger.debug("found a reader flavor.");
273                 try {
274                     Reader reader = flavors[i].getReaderForText(tran);
275                     BufferedReader br = new BufferedReader(reader);
276                     return createFileArray(br);
277                 } catch (Throwable cause) {
278                     logger.debug("failed to scan reader for file list.");
279                 }
280             }
281         }
282         return new ArrayList<Object>();
283     }
284
285     private static String ZERO_CHAR_STRING = "" + (char) 0;
286
287     public static List<Object> createFileArray(BufferedReader bReader) {
288         if (bReader == null) return null;
289         try {
290             List<Object> list = new ArrayList<Object>();
291             String line = null;
292             while ((line = bReader.readLine()) != null) {
293                 try {
294                     // kde seems to append a 0 char to the end of the reader
295                     if (ZERO_CHAR_STRING.equals(line))
296                         continue;
297                     File file = new java.io.File(new java.net.URI(line));
298                     list.add(file);
299                 } catch (Exception ex) {
300                     logger.error("Error with " + line + ": " + ex.getMessage());
301                 }
302             }
303
304             return list;
305         } catch (IOException ex) {
306             logger.error("IOException while working on file list");
307         }
308         return new ArrayList<Object>();
309     }
310
311 }