org.feistymeow.dragdrop.ListTransferable.java Source code

Java tutorial

Introduction

Here is the source code for org.feistymeow.dragdrop.ListTransferable.java

Source

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>();
    }

}