Hi Damjan,

I have some thoughts (sorry for the delay).

At first, I like your prototype. Thank you for your efforts.

So some ideas and notes are bellow.

1.
The first idea is we should not return an empty List<Files> list. The
better approach would be if we do not publish javaFileListFlavor data
flavor. In that case we have to analyze content of the URI list and
decide whether we can provide all URIs as files. If we cannot, user on
target must not receive javaFileListFlavor data flavor at all. Only
javaURIListFlavor.

2.
Some consideration about JavaDoc

******** DataFlavor.java ***********

+    /**
+     * To transfer a list of files and/or URIs to/from Java (and
+     * the underlying platform) a <code>DataFlavor</code> of this
+     * type/subtype and representation class of
+     * <code>java.util.List</code> is used. Each element of this
+     * list is required/guaranteed to be of type
+     * <code>java.net.URI</code>.
+     * <p>
+     * You should use this flavor instead of javaFileListFlavor,
+     * because some platforms provide only URI lists, not file
+     * lists. On these platforms, if and only if all URIs are
+     * files for the transfer in question, you can use/get
+     * javaFileListFlavor in addition to this flavor, for
+     * backward compatibility.

I think that there is no need to mention javaFileListFlavor here in case
if we are going provide javaURIListFlavor on Windows platform.

3.

**** DataTransferer.java ***************

-    private String getBestCharsetForTextFormat(Long lFormat,
+    protected String getBestCharsetForTextFormat(Long lFormat,

I do not think that changing access level is a good idea here. It is
needed only in single subclass so may be it would be better redesign
this approach.

Thank you,
                  Denis.

Damjan Jovanovic wrote:
> On Mon, Oct 1, 2007 at 7:53 PM, Denis S. Fokin <[EMAIL PROTECTED]> wrote:
>   
>> Hi Damjan,
>>
>>     
>>> Ok I'm sorry for arguing so much. Less talk more code :-).
>>>       
>>  >
>>  > This weekend I started writing code for URIListFlavor, but I quickly
>>  > realized another problem. If you deprecate javaFileListFlavor and use
>>  > URIListFlavor, the problem still exists in the opposite direction:
>>  > when Java is the drag source and a native application the drop target,
>>  > on non-XDnD implementations you still have to do lossy conversion from
>>  > URIs to Files and somehow disregard non-local-file URIs, and possibly
>>  > provide the native application with an empty file list.
>>  >
>>  > Any ideas?
>>  >
>>  >
>>  Currently, AWT supports OLE Drag & Drop protocol on Windows platform,
>>  XDnD and Motif Drag & Drop protocols on UNIX-like platforms.
>>
>>  In case if some application asks our java source about available
>>  formats, we will provide javaFileListFlavor only if among our data files
>>  are presented.
>>
>>  On the basis of a value returned by uri.getScheme() method we could
>>  understand whether we should provide javaFileListFlavor for native
>>  application.
>>
>>  In this case we will not publish native flavors for file list (e.g.
>>  HDROP on windows).
>>
>>  I suppose, the same scenario should works for java intra-jvm transfer.
>>
>>  To summarize, as far as we have all information on drag source we can
>>  publish only data flavors which we really have.
>>
>>  But I could miss out something, so give me know where the problems arise.
>>
>>  Thank you,
>>     Denis.
>>
>>     
>
> Hi again
>
> I've made a patch that works for me (attached). It adds a
> java.awt.datatransfer.DataFlavor.javaURIListFlavor of MIME type
> application/x-java-uri-list;class=java.util.List, and does full
> bidirectional conversion between file lists and URI lists. That is,
> file lists are promoted to URI lists, and URI lists are lossily
> converted to file lists.
>
> If you drag a file list from native into Java, the application sees
> both a URI list and a file list. If you drag in a URI list it sees a
> URI list, and if all URIs are files also a non-empty file list,
> otherwise just an empty file list. If a Java drag source provides a
> file list to native then native sees both a URI list (where
> implemented on native) and a file list, and where Java provides a URI
> list native sees the URI list (where implemented) and the subset of
> files taken from those URIs that are local files (javaFileListFlavor
> already uses similar logic: it just ignores non-String non-File
> entries).
>
> What happens when native doesn't support URI lists and a Java drag
> source provides it? Currently if native doesn't support a URI list
> format, you won't be able to do inter-JVM and Java->native URI list
> transfers, only the local files in those URIs will go through and be
> seen as a file list by the other side. Out of Windows, X11, and Motif,
> in my patch currently only X11 supports them :-(. I haven't been able
> to find proper URI list support in Windows, most of the fancy shell
> clipboard types that can transfer URLs and such, seem to only transfer
> 1 item instead of a list.
>
> Otherwise I've tested dragging files between Java and Gnome's
> Nautilus, in both directions. It works well :-).
>
> Thoughts? Suggestions?
>
> Bye
> Damjan
>   

diff -r 0f955581dc0b src/share/classes/java/awt/datatransfer/DataFlavor.java
--- a/src/share/classes/java/awt/datatransfer/DataFlavor.java	Mon Mar 24 06:33:16 2008 -0700
+++ b/src/share/classes/java/awt/datatransfer/DataFlavor.java	Sat May 03 12:04:38 2008 +0200
@@ -217,8 +217,29 @@ public class DataFlavor implements Exter
      * representation class of <code>java.util.List</code> is used.
      * Each element of the list is required/guaranteed to be of type
      * <code>java.io.File</code>.
+     *
+     * You should generally use javaURIListFlavor instead of this.
      */
     public static final DataFlavor javaFileListFlavor = createConstant("application/x-java-file-list;class=java.util.List", null);
+
+    /**
+     * To transfer a list of files and/or URIs to/from Java (and
+     * the underlying platform) a <code>DataFlavor</code> of this
+     * type/subtype and representation class of
+     * <code>java.util.List</code> is used. Each element of this
+     * list is required/guaranteed to be of type
+     * <code>java.net.URI</code>.
+     * <p>
+     * You should use this flavor instead of javaFileListFlavor,
+     * because some platforms provide only URI lists, not file
+     * lists. On these platforms, if and only if all URIs are
+     * files for the transfer in question, you can use/get
+     * javaFileListFlavor in addition to this flavor, for
+     * backward compatibility.
+     *
+     * @since 1.7
+     */
+    public static final DataFlavor javaURIListFlavor = createConstant("application/x-java-uri-list;class=java.util.List", null);
 
     /**
      * To transfer a reference to an arbitrary Java object reference that
@@ -1227,6 +1248,20 @@ public class DataFlavor implements Exter
 
    }
 
+   /**
+    * Returns true if the <code>DataFlavor</code> specified represents
+    * a list of URI objects.
+    * @return true is the <code>DataFlavor</code> specified represents
+    *   a List of File objects
+    */
+
+   public boolean isFlavorJavaURIListType() {
+       if (mimeType == null || representationClass == null)
+           return false;
+       return java.util.List.class.isAssignableFrom(representationClass) &&
+              mimeType.match(javaURIListFlavor.mimeType);
+   }
+
     /**
      * Returns whether this <code>DataFlavor</code> is a valid text flavor for
      * this implementation of the Java platform. Only flavors equivalent to
diff -r 0f955581dc0b src/share/classes/sun/awt/datatransfer/DataTransferer.java
--- a/src/share/classes/sun/awt/datatransfer/DataTransferer.java	Mon Mar 24 06:33:16 2008 -0700
+++ b/src/share/classes/sun/awt/datatransfer/DataTransferer.java	Sat May 03 12:04:38 2008 +0200
@@ -50,6 +50,10 @@ import java.io.Reader;
 import java.io.Reader;
 import java.io.SequenceInputStream;
 import java.io.StringReader;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
 
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
@@ -616,6 +620,16 @@ public abstract class DataTransferer {
     public abstract boolean isImageFormat(long format);
 
     /**
+     * Determines whether the DataFlavor corresponding to the specified long
+     * format is DataFlavor.javaURIListFlavor.
+     */
+    public boolean isURIListFormat(long format)
+    {
+        // FIXME: make into an abstract method, with a Windows implementation?
+        return false;
+    }
+
+    /**
      * Returns a Map whose keys are all of the possible formats into which the
      * Transferable's transfer data flavors can be translated. The value of
      * each key is the DataFlavor in which the Transferable's data should be
@@ -683,6 +697,7 @@ public abstract class DataTransferer {
             // case of Serializable
             if (flavor.isFlavorTextType() ||
                 flavor.isFlavorJavaFileListType() ||
+                flavor.isFlavorJavaURIListType() ||
                 DataFlavor.imageFlavor.equals(flavor) ||
                 flavor.isRepresentationClassSerializable() ||
                 flavor.isRepresentationClassInputStream() ||
@@ -786,6 +801,7 @@ public abstract class DataTransferer {
                 // case of Serializable
                 if (flavor.isFlavorTextType() ||
                     flavor.isFlavorJavaFileListType() ||
+                    flavor.isFlavorJavaURIListType() ||
                     DataFlavor.imageFlavor.equals(flavor) ||
                     flavor.isRepresentationClassSerializable() ||
                     flavor.isRepresentationClassInputStream() ||
@@ -862,6 +878,7 @@ public abstract class DataTransferer {
                 // case of Serializable
                 if (flavor.isFlavorTextType() ||
                     flavor.isFlavorJavaFileListType() ||
+                    flavor.isFlavorJavaURIListType() ||
                     DataFlavor.imageFlavor.equals(flavor) ||
                     flavor.isRepresentationClassSerializable() ||
                     flavor.isRepresentationClassInputStream() ||
@@ -948,7 +965,7 @@ public abstract class DataTransferer {
      * clipboard string encoding/decoding, basing on clipboard
      * format and localeTransferable(on decoding, if available)
      */
-    private String getBestCharsetForTextFormat(Long lFormat,
+    protected String getBestCharsetForTextFormat(Long lFormat,
         Transferable localeTransferable) throws IOException
     {
         String charset = null;
@@ -971,6 +988,68 @@ public abstract class DataTransferer {
             charset = getDefaultTextCharset();
         }
         return charset;
+    }
+
+    /**
+     * Translate a transferable to a URI list. The default implementation
+     * provides a text/uri-list MIME type in the target encoding.
+     */
+    protected void translateTransferableToURIList(Object obj,
+                                                  long format,
+                                                  ByteArrayOutputStream outStream) throws IOException
+    {
+        String nat = getNativeForFormat(format);
+        String targetCharset = "UTF-8";
+        if (nat != null) {
+            try {
+                if ((targetCharset = new DataFlavor(nat).getParameter("charset")) == null)
+                    targetCharset = "UTF-8";
+            } catch (ClassNotFoundException cnfe) {
+                throw new IOException(cnfe);
+            }
+        }
+        final List list = (List)obj;
+        int nItems = 0;
+        for (Object o : list) {
+             if (o instanceof String || o instanceof File ||
+                 o instanceof URI)
+                 nItems++;
+        }
+        final String[] items = new String[nItems];
+
+        try {
+            AccessController.doPrivileged(new PrivilegedExceptionAction() {
+                public Object run() throws IOException {
+                    try {
+                        int j = 0;
+                        for (Object o : list) {
+                            if (o instanceof URI) {
+                                items[j++] = ((URI)o).toString();
+                            } else if (o instanceof File) {
+                                // Many implementations are fussy about the number of slashes
+                                items[j++] = new URI("file:///" + ((File)o).getAbsolutePath()).toString();
+                            } else if (o instanceof String) {
+                                items[j++] = new URI((String)o).toString();
+                            }
+                        }
+                    } catch (URISyntaxException uriSyntaxException) {
+                        throw new IOException(uriSyntaxException);
+                    }
+                    return null;
+                }
+            });
+        } catch (PrivilegedActionException pae) {
+            throw new IOException(pae.getMessage());
+        }
+
+        byte[] bytes = items[0].getBytes(targetCharset);
+        outStream.write(bytes, 0, bytes.length);
+        byte[] eoln = "\r\n".getBytes(targetCharset);
+        for (int i = 1; i < items.length; i++) {
+            outStream.write(eoln, 0, eoln.length);
+            bytes = items[i].getBytes(targetCharset);
+            outStream.write(bytes, 0, bytes.length);
+        }
     }
 
     /**
@@ -1279,18 +1358,37 @@ search:
 
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 
+        // Target data is a URI list. Source data must be a
+        // j.u.List of j.i.File, j.l.String, or j.n.URI objects.
+        if (isURIListFormat(format)) {
+            if (!DataFlavor.javaFileListFlavor.equals(flavor) &&
+                !DataFlavor.javaURIListFlavor.equals(flavor)) {
+                throw new IOException("data translation failed");
+            }
+            translateTransferableToURIList(obj, format, bos);
+
         // Target data is a file list. Source data must be a
         // java.util.List which contains java.io.File or String instances.
-        if (isFileFormat(format)) {
+        } else if (isFileFormat(format)) {
             if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
                 throw new IOException("data translation failed");
             }
             final List list = (List)obj;
             int nFiles = 0;
+            final String myHostname = InetAddress.getLocalHost().getHostName();
             for (int i = 0; i < list.size(); i++) {
                 Object o = list.get(i);
                 if (o instanceof File || o instanceof String) {
                     nFiles++;
+                } else if (o instanceof URI) {
+                    URI uri = (URI) o;
+                    if (uri.getScheme().equals("file")) {
+                         String host = uri.getHost();
+                         if (host == null || host.equals("localhost") ||
+                             host.equals(myHostname)) {
+                             nFiles++;
+                         }
+                    }
                 }
             }
             final String[] files = new String[nFiles];
@@ -1300,7 +1398,16 @@ search:
                     public Object run() throws IOException {
                         for (int i = 0, j = 0; i < list.size(); i++) {
                             Object o = list.get(i);
-                            if (o instanceof File) {
+                            if (o instanceof URI) {
+                                URI uri = (URI) o;
+                                if (uri.getScheme().equals("file")) {
+                                    String host = uri.getHost();
+                                    if (host == null || host.equals("localhost") ||
+                                        host.equals(myHostname)) {
+                                        files[j++] = new File(uri.getPath()).getCanonicalPath();
+                                    }
+                                }
+                            } else if (o instanceof File) {
                                 files[j++] = ((File)o).getCanonicalPath();
                             } else if (o instanceof String) {
                                 files[j++] = (String)o;
@@ -1403,32 +1510,61 @@ search:
             str = new ByteArrayInputStream(bytes);
         }
 
+        // Source data is a URI list. Parse it into List<URI> or
+        // List<File> depending on the desired format.
+        if (isURIListFormat(format)) {
+            URI uris[] = dragQueryURIs(str, bytes, format, localeTransferable);
+            if (DataFlavor.javaURIListFlavor.equals(flavor)) {
+                return Arrays.asList(uris);
+            } else if (DataFlavor.javaFileListFlavor.equals(flavor)) {
+                ArrayList<File> files = new ArrayList<File>();
+                String myHostname = InetAddress.getLocalHost().getHostName();
+                for (URI uri : uris) {
+                    // All file must be local, or we have to return
+                    // an empty list (can't have missing files)
+                    if (!uri.getScheme().equals("file")) {
+                        return new ArrayList<File>();
+                    }
+                    String host = uri.getHost();
+                    if (host != null && !host.equals("localhost") &&
+                        !host.equals(myHostname)) {
+                        return new ArrayList<File>();
+                    }
+                    files.add(new File(uri.getPath()));
+                }
+                return files;
+            } else {
+                throw new IOException("data translation failed");
+            }
         // Source data is a file list. Use the dragQueryFile native function to
         // do most of the decoding. Then wrap File objects around the String
         // filenames and return a List.
-        if (isFileFormat(format)) {
-            if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
-                throw new IOException("data translation failed");
-            }
+        } else if (isFileFormat(format)) {
             if (bytes == null) {
                 bytes = inputStreamToByteArray(str);
             }
             String[] filenames = dragQueryFile(bytes);
+            str.close();
             if (filenames == null) {
-                str.close();
                 return null;
             }
 
-            // Convert the strings to File objects
-            File[] files = new File[filenames.length];
-            for (int i = 0; i < filenames.length; i++) {
-                files[i] = new File(filenames[i]);
+            if (DataFlavor.javaFileListFlavor.equals(flavor)) {
+                // Convert the strings to File objects
+                File[] files = new File[filenames.length];
+                for (int i = 0; i < filenames.length; i++) {
+                    files[i] = new File(filenames[i]);
+                }
+                return Arrays.asList(files);
+            } else if (DataFlavor.javaURIListFlavor.equals(flavor)) {
+                URI[] uris = new URI[filenames.length];
+                for (int i = 0; i < filenames.length; i++) {
+                    uris[i] = new File(filenames[i]).toURI();
+                }
+                return Arrays.asList(uris);
+            } else {
+                throw new IOException("data translation failed");
             }
-            str.close();
-
-            // Turn the list of Files into a List and return
-            return Arrays.asList(files);
-
         // Target data is a String. Strip terminating NUL bytes. Decode bytes
         // into characters. Search-and-replace EOLN.
         } else if (String.class.equals(flavor.getRepresentationClass()) &&
@@ -1812,6 +1948,19 @@ search:
      * Decodes a byte array into a set of String filenames.
      */
     protected abstract String[] dragQueryFile(byte[] bytes);
+
+    /**
+     * Decode URIs from either a byte array or a stream.
+     */
+    protected URI[] dragQueryURIs(InputStream stream,
+                                  byte[] bytes,
+                                  long format,
+                                  Transferable localeTransferable)
+      throws IOException
+    {
+        throw new IOException(
+            new UnsupportedOperationException("not implemented on this platform"));
+    }
 
     /**
      * Translates either a byte array or an input stream which contain
diff -r 0f955581dc0b src/solaris/classes/sun/awt/X11/XDataTransferer.java
--- a/src/solaris/classes/sun/awt/X11/XDataTransferer.java	Mon Mar 24 06:33:16 2008 -0700
+++ b/src/solaris/classes/sun/awt/X11/XDataTransferer.java	Sat May 03 12:04:38 2008 +0200
@@ -28,13 +28,19 @@ import java.awt.Image;
 import java.awt.Image;
 
 import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
 
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.WritableRaster;
 
+import java.io.BufferedReader;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.IOException;
+
+import java.net.URI;
+import java.net.URISyntaxException;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -106,6 +112,22 @@ public class XDataTransferer extends Dat
             }
         }
         return super.getCharsetForTextFormat(lFormat);
+    }
+
+    public boolean isURIListFormat(long format) {
+        String nat = getNativeForFormat(format);
+        if (nat == null) {
+            return false;
+        }
+        try {
+            DataFlavor df = new DataFlavor(nat);
+            if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) {
+                return true;
+            }
+        } catch (Exception e) {
+            // Not a MIME format.
+        }
+        return false;
     }
 
     public boolean isFileFormat(long format) {
@@ -212,6 +234,33 @@ public class XDataTransferer extends Dat
                                                          XAtom.get("STRING").getAtom());
         } finally {
             XToolkit.awtUnlock();
+        }
+    }
+
+    protected URI[] dragQueryURIs(InputStream stream,
+                                  byte[] bytes,
+                                  long format,
+                                  Transferable localeTransferable)
+      throws IOException {
+        String charset = getBestCharsetForTextFormat(format, localeTransferable);
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(stream, charset));
+            String line;
+            ArrayList<URI> uriList = new ArrayList<URI>();
+            URI uri;
+            while ((line = reader.readLine()) != null) {
+                try {
+                    uri = new URI(line);
+                } catch (URISyntaxException uriSyntaxException) {
+                    throw new IOException(uriSyntaxException);
+                }
+                uriList.add(uri);
+            }
+            return uriList.toArray(new URI[uriList.size()]);
+        } finally {
+            if (reader != null)
+                reader.close();
         }
     }
 
diff -r 0f955581dc0b src/solaris/lib/flavormap.properties
--- a/src/solaris/lib/flavormap.properties	Mon Mar 24 06:33:16 2008 -0700
+++ b/src/solaris/lib/flavormap.properties	Sat May 03 12:04:38 2008 +0200
@@ -73,5 +73,8 @@ TEXT=text/plain;eoln="\n";terminators=0
 TEXT=text/plain;eoln="\n";terminators=0
 STRING=text/plain;charset=iso8859-1;eoln="\n";terminators=0
 FILE_NAME=application/x-java-file-list;class=java.util.List
+text/uri-list=application/x-java-uri-list;class=java.util.List
+FILE_NAME=application/x-java-uri-list;class=java.util.List
+text/uri-list=application/x-java-file-list;class=java.util.List
 PNG=image/x-java-image;class=java.awt.Image
 JFIF=image/x-java-image;class=java.awt.Image
diff -r 0f955581dc0b src/windows/lib/flavormap.properties
--- a/src/windows/lib/flavormap.properties	Mon Mar 24 06:33:16 2008 -0700
+++ b/src/windows/lib/flavormap.properties	Sat May 03 12:04:38 2008 +0200
@@ -64,6 +64,7 @@ HTML\ Format=text/html;charset=utf-8;eol
 HTML\ Format=text/html;charset=utf-8;eoln="\r\n";terminators=1
 Rich\ Text\ Format=text/rtf
 HDROP=application/x-java-file-list;class=java.util.List
+HDROP=application/x-java-uri-list;class=java.util.List
 PNG=image/x-java-image;class=java.awt.Image
 JFIF=image/x-java-image;class=java.awt.Image
 DIB=image/x-java-image;class=java.awt.Image

Reply via email to