Hi

Here's the second attempt.

On Wed, Jun 4, 2008 at 7:05 PM, Denis S. Fokin <[EMAIL PROTECTED]> wrote:
> 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.

Ok, I've added code to not publish a file list format to native when
Java is the drag source, and the Java URI list can't be losslessly
converted to a local file list. But, that means we have to call
Transferable.getTransferData() more than once during the same transfer
because it's now also called before the transfer has been accepted by
the drop target, maybe that should be documented.

It's not clear whether the opposite conversion (native URI list ->
Java file list) can be done safely; we'd have to request the data from
native at least once before the transfer has been accepted and I'm not
sure all platforms and applications support/like that. Currently
you'll get an empty file list.


> 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.

I've taken that paragraph out.

The documentation corrections Alla mailed me have also been put in.


> 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.

I've left it as private and inlined the contents of that method where
it's needed.

> Thank you,
>                  Denis.

Thank you
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 Jun 14 17:21:49 2008 +0200
@@ -217,8 +217,20 @@ 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>.
+     *
+     * Use the [EMAIL PROTECTED] javaURIListFlavor} class instead.
      */
     public static final DataFlavor javaFileListFlavor = createConstant("application/x-java-file-list;class=java.util.List", null);
+
+    /**
+     * A [EMAIL PROTECTED] DataFlavor} of this type (subtype) and representation
+     * class of the [EMAIL PROTECTED] java.util.List} class are used to transfer a list of files
+     * or URIs to Java and the underlying platform. Each element of this
+     * list is required to be of the [EMAIL PROTECTED] java.net.URI} type.
+     *
+     * @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 +1239,20 @@ public class DataFlavor implements Exter
 
    }
 
+   /**
+    * Returns [EMAIL PROTECTED] true} if the specified [EMAIL PROTECTED] DataFlavor} represents
+    * a list of URI objects.
+    * @return [EMAIL PROTECTED] true} if the specified [EMAIL PROTECTED] DataFlavor} represents
+    *   a [EMAIL PROTECTED] List} of [EMAIL PROTECTED] URI} 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 Jun 14 17:21:49 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;
@@ -615,6 +619,17 @@ public abstract class DataTransferer {
      */
     public abstract boolean isImageFormat(long format);
 
+
+    /**
+     * Determines whether the @{code DataFlavor} corresponding to the specified long
+     * format is @{code 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
@@ -630,7 +645,41 @@ public abstract class DataTransferer {
         if (flavors == null) {
             return new TreeMap();
         }
-        return getFormatsForFlavors(flavors, map);
+        SortedMap formatMap = getFormatsForFlavors(flavors, map);
+        try {
+            final String myHostname = InetAddress.getLocalHost().getHostName();
+            // Only publish native file lists for our URI lists if all URIs are files
+            for (Iterator iter = formatMap.keySet().iterator(); iter.hasNext(); ) {
+                long lFormat = ((Long) iter.next()).longValue();
+                if (isFileFormat(lFormat)) {
+                    DataFlavor flavor = (DataFlavor) formatMap.get(lFormat);
+                    if (flavor.isFlavorJavaURIListType()) {
+                        List uriList = (List) contents.getTransferData(flavor);
+                        boolean canConvert = true;
+                        for (Object o : uriList) {
+                            if (!(o instanceof URI)) {
+                                canConvert = false;
+                                break;
+                            }
+                            URI uri = (URI) o;
+                            if (!uri.getScheme().equals("file")) {
+                                String host = uri.getHost();
+                                if (host != null || !host.equals("localhost") ||
+                                    !host.equals(myHostname)) {
+                                    canConvert = false;
+                                    break;
+                                }
+                            }
+                        }
+                        if (!canConvert)
+                            iter.remove();
+                    }
+                }
+            }
+        } catch (Exception exception) {
+            // Swallow, and return the best-known mapping
+        }
+        return formatMap;
     }
 
     /**
@@ -683,6 +732,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 +836,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 +913,7 @@ public abstract class DataTransferer {
                 // case of Serializable
                 if (flavor.isFlavorTextType() ||
                     flavor.isFlavorJavaFileListType() ||
+                    flavor.isFlavorJavaURIListType() ||
                     DataFlavor.imageFlavor.equals(flavor) ||
                     flavor.isRepresentationClassSerializable() ||
                     flavor.isRepresentationClassInputStream() ||
@@ -971,6 +1023,68 @@ public abstract class DataTransferer {
             charset = getDefaultTextCharset();
         }
         return charset;
+    }
+
+    /**
+     * Translate a transferable object 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 +1393,38 @@ 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)) {
-            if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
+        } else if (isFileFormat(format)) {
+            if (!DataFlavor.javaFileListFlavor.equals(flavor) &&
+                !DataFlavor.javaURIListFlavor.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 +1434,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 +1546,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 +1984,19 @@ search:
      * Decodes a byte array into a set of String filenames.
      */
     protected abstract String[] dragQueryFile(byte[] bytes);
+
+    /**
+     * Decodes 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 Jun 14 17:21:49 2008 +0200
@@ -28,13 +28,20 @@ import java.awt.Image;
 import java.awt.Image;
 
 import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
 
 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 +113,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 +235,53 @@ 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 = null;
+        if (localeTransferable != null &&
+            isLocaleDependentTextFormat(format) &&
+            localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor))
+        {
+            try {
+                charset = new String(
+                    (byte[])localeTransferable.getTransferData(javaTextEncodingFlavor),
+                    "UTF-8"
+                );
+            } catch (UnsupportedFlavorException cannotHappen) {
+            }
+        } else {
+            charset = getCharsetForTextFormat(format);
+        }
+        if (charset == null) {
+            // Only happens when we have a custom text type.
+            charset = getDefaultTextCharset();
+        }
+
+        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 Jun 14 17:21:49 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 Jun 14 17:21:49 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