+package org.feistymeow.dragdrop;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implements a transferable object that understands URI lists as well as java file lists. This is
+ * useful for implementing file drag and drop that will work across different platforms (such as
+ * Gnome on Linux).
+ *
+ * @author Chris Koeritz
+ * @copyright Copyright (c) 2012-$now By University of Virginia
+ * @license This file is free software; you can modify and redistribute it under the terms of the
+ * Apache License v2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+@SuppressWarnings("serial")
+public class ListTransferable extends Vector<Object> implements Transferable
+{
+ static private Log logger = LogFactory.getLog(ListTransferable.class);
+
+ public ListTransferable()
+ {
+ }
+
+ public ListTransferable(Object initial)
+ {
+ if (initial != null) add(initial);
+ }
+
+ public ListTransferable(List<Object> initial)
+ {
+ if (initial != null) addAll(initial);
+ }
+
+ /**
+ * create a new flavor. this one understands URI lists, such as: file:///home/fred/arf.txt\r\n
+ * file:///etc/inputrc\r\n http://gruntose.com\r\n ...
+ */
+ private static DataFlavor URIListFlavor;
+ static {
+ try {
+ URIListFlavor = new DataFlavor("text/uri-list;class=java.lang.String");
+ } catch (ClassNotFoundException e) {
+ logger.error("should never happen", e);
+ }
+ }
+ private static DataFlavor AltURIListFlavor;
+ static {
+ try {
+ AltURIListFlavor = new DataFlavor("text/uri-list;representationclass=java.lang.String");
+ } catch (ClassNotFoundException e) {
+ logger.error("should never happen", e);
+ }
+ }
+
+ /**
+ * accessors for our special featured flavors of URI lists.
+ */
+ public static DataFlavor getURIListFlavor1()
+ {
+ return URIListFlavor;
+ }
+
+ public static DataFlavor getURIListFlavor2()
+ {
+ return AltURIListFlavor;
+ }
+
+ /**
+ * register the types of transfers that we understand. this is really only the normal java file
+ * list and our new URI list.
+ */
+ protected ArrayList<DataFlavor> FLAVORS = new ArrayList<DataFlavor>(Arrays.asList(
+ DataFlavor.javaFileListFlavor, URIListFlavor, AltURIListFlavor));
+
+ /**
+ * a function that must be overridden by derived classes if they are not initially seeding the
+ * vector of objects that we hold. the caller of this function expects it will populate the
+ * vector held here with usable objects.
+ */
+ public boolean loadDataJustInTime(DataFlavor flavor)
+ {
+ logger.warn("base loadDataJustInTime. derived class should have implemented this.");
+ return false;
+ }
+
+ /**
+ * using the set of files that we've been handed, we can do transfers using our two supported
+ * flavors.
+ */
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException,
+ java.io.IOException
+ {
+ if (flavor == null) return null;
+ if (size() == 0) {
+ logger.debug("size was zero, so loading data just in time");
+ boolean worked = loadDataJustInTime(flavor);
+ if (!worked || (size() == 0)) {
+ logger.warn("failed to retrieve data just in time for getTransferData.");
+ return null;
+ }
+ }
+ // help from workaround at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4899516
+ logger.debug("responding to flavor: " + flavor.toString());
+ if (flavor.equals(DataFlavor.javaFileListFlavor)) {
+ logger.debug("java file list flavor...");
+ List<Object> data = new java.util.ArrayList<Object>();
+ data.addAll(this);
+ return data;
+ } else if (flavor.equals(URIListFlavor) || flavor.equals(AltURIListFlavor)) {
+ logger.debug("uri list flavor...");
+ StringBuilder data = new StringBuilder();
+ Iterator<Object> iter = iterator();
+ while (iter.hasNext()) {
+ Object x = iter.next();
+ if (x instanceof File) {
+ File elem = (File) x;
+ data.append(elem.toURI() + "\r\n");
+ } else if (x instanceof String) {
+ data.append((String) x + "\r\n");
+ } else {
+ logger.debug("did not know how to handle type in transfer: " + x.toString());
+ }
+ }
+ logger.debug("returning URI string: " + data.toString());
+ return data.toString();
+ } else {
+ logger.debug("getTransferData: didn't know how to handle the requested flavor.");
+ throw new UnsupportedFlavorException(flavor);
+ }
+ }
+
+ /**
+ * returns the list of all transfer flavors we understand.
+ */
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ return (DataFlavor[]) FLAVORS.toArray(new DataFlavor[FLAVORS.size()]);
+ }
+
+ /**
+ * reports if a particular flavor is handled here.
+ */
+ public boolean isDataFlavorSupported(DataFlavor flavor)
+ {
+ if (flavor == null) return false;
+ for (int i = 0; i < FLAVORS.size(); i++) {
+ if (flavor.equals((DataFlavor) FLAVORS.get(i))) {
+ return true;
+ }
+ }
+ logger.debug("failed to find flavor: " + flavor.toString());
+ return false;
+ }
+
+ /**
+ * a helper method that can process transfer data from either a java file list or a URI list.
+ */
+ @SuppressWarnings("unchecked")
+ static public List<Object> extractData(Transferable tran) {
+ if (tran == null) return null;
+ if (tran.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
+ logger.debug("extractData seeing java files flavor.");
+ try {
+ return (List<Object>) tran.getTransferData(DataFlavor.javaFileListFlavor);
+ } catch (Throwable cause) {
+ logger.error("extractData caught exception for java file list.", cause);
+ return null;
+ }
+ } else if (tran.isDataFlavorSupported(ListTransferable.getURIListFlavor1())
+ || tran.isDataFlavorSupported(ListTransferable.getURIListFlavor2())) {
+ logger.debug("extractData seeing uri list flavor.");
+ try {
+ return textURIListToFileList((String) tran.getTransferData(getURIListFlavor1()));
+ } catch (Throwable cause) {
+ logger.error("extractData caught exception for URI list.", cause);
+ return null;
+ }
+ }
+ logger.error("extractData: Transferable did not support known data flavor.");
+ return null;
+ }
+
+ /**
+ * translates the string in "data" into a list of Files.
+ *
+ * @param data
+ * a string formatted with possibly multiple URIs separated by CRLF.
+ * @return a list of the files as java File objects. many thanks to
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4899516
+ */
+ public static List<Object> textURIListToFileList(String data)
+ {
+ if (data == null) return null;
+ List<Object> list = new ArrayList<Object>(0);
+ for (StringTokenizer st = new StringTokenizer(data, "\r\n"); st.hasMoreTokens();) {
+ String s = st.nextToken();
+ if (s.startsWith("#")) {
+ // the line is a comment (as per the RFC 2483)
+ continue;
+ }
+ try {
+ java.net.URI uri = new java.net.URI(s);
+ java.io.File file = new java.io.File(uri);
+ list.add(file);
+ } catch (java.net.URISyntaxException e) {
+ // this is a malformed URI.
+ logger.error("Found a malformed URI of: " + data);
+ } catch (IllegalArgumentException e) {
+ // the URI is not a valid 'file:' URI
+ logger.error("Found invalid 'file:' URI of: " + data);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * This function will retrieve the file list from a standard file list flavor.
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Object> processStandardFileList(Transferable tran)
+ {
+ if (tran == null) return null;
+ logger.debug("trying java file list flavor.");
+ try {
+ return (List<Object>) tran.getTransferData(DataFlavor.javaFileListFlavor);
+ } catch (Throwable cause) {
+ logger.debug("failed to retrieve transfer data for standard java file list flavor.",
+ cause);
+ }
+ return new ArrayList<Object>();
+ }
+
+ /**
+ * checks if the transferable is appropriate to try to use as a java Reader.
+ */
+ public static boolean checkReaderFlavor(Transferable tran)
+ {
+ if (tran == null) return false;
+ DataFlavor[] flavors = tran.getTransferDataFlavors();
+ for (int i = 0; i < flavors.length; i++) {
+ if (flavors[i].isRepresentationClassReader())
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Use a Reader to handle an incoming transferable.
+ */
+ public static List<Object> processReaderFlavor(Transferable tran)
+ {
+ if (tran == null) return null;
+ logger.debug("trying URI list flavor.");
+ DataFlavor[] flavors = tran.getTransferDataFlavors();
+ for (int i = 0; i < flavors.length; i++) {
+ if (flavors[i].isRepresentationClassReader()) {
+ // it looks like we can work with this flavor just fine.
+ logger.debug("found a reader flavor.");
+ try {
+ Reader reader = flavors[i].getReaderForText(tran);
+ BufferedReader br = new BufferedReader(reader);
+ return createFileArray(br);
+ } catch (Throwable cause) {
+ logger.debug("failed to scan reader for file list.");
+ }
+ }
+ }
+ return new ArrayList<Object>();
+ }
+
+ private static String ZERO_CHAR_STRING = "" + (char) 0;
+
+ public static List<Object> createFileArray(BufferedReader bReader) {
+ if (bReader == null) return null;
+ try {
+ List<Object> list = new ArrayList<Object>();
+ String line = null;
+ while ((line = bReader.readLine()) != null) {
+ try {
+ // kde seems to append a 0 char to the end of the reader
+ if (ZERO_CHAR_STRING.equals(line))
+ continue;
+ File file = new java.io.File(new java.net.URI(line));
+ list.add(file);
+ } catch (Exception ex) {
+ logger.error("Error with " + line + ": " + ex.getMessage());
+ }
+ }
+
+ return list;
+ } catch (IOException ex) {
+ logger.error("IOException while working on file list");
+ }
+ return new ArrayList<Object>();
+ }
+
+}