jdk/src/java.desktop/unix/classes/sun/awt/X11/XDataTransferer.java
changeset 25859 3317bb8137f4
parent 24538 25bf8153fbfe
child 26037 508779ce6619
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/unix/classes/sun/awt/X11/XDataTransferer.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.awt.X11;
+
+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;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriter;
+import javax.imageio.spi.ImageWriterSpi;
+
+import sun.awt.datatransfer.DataTransferer;
+import sun.awt.datatransfer.ToolkitThreadBlockedHandler;
+
+import java.io.ByteArrayOutputStream;
+import java.util.stream.Stream;
+
+/**
+ * Platform-specific support for the data transfer subsystem.
+ */
+public class XDataTransferer extends DataTransferer {
+    static final XAtom FILE_NAME_ATOM = XAtom.get("FILE_NAME");
+    static final XAtom DT_NET_FILE_ATOM = XAtom.get("_DT_NETFILE");
+    static final XAtom PNG_ATOM = XAtom.get("PNG");
+    static final XAtom JFIF_ATOM = XAtom.get("JFIF");
+    static final XAtom TARGETS_ATOM = XAtom.get("TARGETS");
+    static final XAtom INCR_ATOM = XAtom.get("INCR");
+    static final XAtom MULTIPLE_ATOM = XAtom.get("MULTIPLE");
+
+    /**
+     * Singleton constructor
+     */
+    private XDataTransferer() {
+    }
+
+    private static XDataTransferer transferer;
+
+    static synchronized XDataTransferer getInstanceImpl() {
+        if (transferer == null) {
+            transferer = new XDataTransferer();
+        }
+        return transferer;
+    }
+
+    public String getDefaultUnicodeEncoding() {
+        return "iso-10646-ucs-2";
+    }
+
+    public boolean isLocaleDependentTextFormat(long format) {
+        return false;
+    }
+
+    public boolean isTextFormat(long format) {
+        return super.isTextFormat(format)
+            || isMimeFormat(format, "text");
+    }
+
+    protected String getCharsetForTextFormat(Long lFormat) {
+        long format = lFormat.longValue();
+        if (isMimeFormat(format, "text")) {
+            String nat = getNativeForFormat(format);
+            DataFlavor df = new DataFlavor(nat, null);
+            // Ignore the charset parameter of the MIME type if the subtype
+            // doesn't support charset.
+            if (!DataTransferer.doesSubtypeSupportCharset(df)) {
+                return null;
+            }
+            String charset = df.getParameter("charset");
+            if (charset != null) {
+                return charset;
+            }
+        }
+        return super.getCharsetForTextFormat(lFormat);
+    }
+
+    protected 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) {
+        return format == FILE_NAME_ATOM.getAtom() ||
+            format == DT_NET_FILE_ATOM.getAtom();
+    }
+
+    public boolean isImageFormat(long format) {
+        return format == PNG_ATOM.getAtom() ||
+            format == JFIF_ATOM.getAtom() ||
+            isMimeFormat(format, "image");
+    }
+
+    protected Long getFormatForNativeAsLong(String str) {
+        // Just get the atom. If it has already been retrived
+        // once, we'll get a copy so this should be very fast.
+        long atom = XAtom.get(str).getAtom();
+        return Long.valueOf(atom);
+    }
+
+    protected String getNativeForFormat(long format) {
+        return getTargetNameForAtom(format);
+    }
+
+    public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() {
+        return XToolkitThreadBlockedHandler.getToolkitThreadBlockedHandler();
+    }
+
+    /**
+     * Gets an format name for a given format (atom)
+     */
+    private String getTargetNameForAtom(long atom) {
+        return XAtom.get(atom).getName();
+    }
+
+    protected byte[] imageToPlatformBytes(Image image, long format)
+      throws IOException {
+        String mimeType = null;
+        if (format == PNG_ATOM.getAtom()) {
+            mimeType = "image/png";
+        } else if (format == JFIF_ATOM.getAtom()) {
+            mimeType = "image/jpeg";
+        } else {
+            // Check if an image MIME format.
+            try {
+                String nat = getNativeForFormat(format);
+                DataFlavor df = new DataFlavor(nat);
+                String primaryType = df.getPrimaryType();
+                if ("image".equals(primaryType)) {
+                    mimeType = df.getPrimaryType() + "/" + df.getSubType();
+                }
+            } catch (Exception e) {
+                // Not an image MIME format.
+            }
+        }
+        if (mimeType != null) {
+            return imageToStandardBytes(image, mimeType);
+        } else {
+            String nativeFormat = getNativeForFormat(format);
+            throw new IOException("Translation to " + nativeFormat +
+                                  " is not supported.");
+        }
+    }
+
+    protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList)
+        throws IOException
+    {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        for (int i = 0; i < fileList.size(); i++)
+        {
+               byte[] bytes = fileList.get(i).getBytes();
+               if (i != 0) bos.write(0);
+               bos.write(bytes, 0, bytes.length);
+        }
+        return bos;
+    }
+
+    /**
+     * Translates either a byte array or an input stream which contain
+     * platform-specific image data in the given format into an Image.
+     */
+    protected Image platformImageBytesToImage(
+        byte[] bytes, long format) throws IOException
+    {
+        String mimeType = null;
+        if (format == PNG_ATOM.getAtom()) {
+            mimeType = "image/png";
+        } else if (format == JFIF_ATOM.getAtom()) {
+            mimeType = "image/jpeg";
+        } else {
+            // Check if an image MIME format.
+            try {
+                String nat = getNativeForFormat(format);
+                DataFlavor df = new DataFlavor(nat);
+                String primaryType = df.getPrimaryType();
+                if ("image".equals(primaryType)) {
+                    mimeType = df.getPrimaryType() + "/" + df.getSubType();
+                }
+            } catch (Exception e) {
+                // Not an image MIME format.
+            }
+        }
+        if (mimeType != null) {
+            return standardImageBytesToImage(bytes, mimeType);
+        } else {
+            String nativeFormat = getNativeForFormat(format);
+            throw new IOException("Translation from " + nativeFormat +
+                                  " is not supported.");
+        }
+    }
+
+    @Override
+    protected String[] dragQueryFile(byte[] bytes) {
+        XToolkit.awtLock();
+        try {
+            return XlibWrapper.XTextPropertyToStringList(bytes,
+                                                         XAtom.get("STRING").getAtom());
+        } finally {
+            XToolkit.awtUnlock();
+        }
+    }
+
+    @Override
+    protected URI[] dragQueryURIs(InputStream stream,
+                                  long format,
+                                  Transferable localeTransferable)
+      throws IOException {
+
+        String charset = getBestCharsetForTextFormat(format, localeTransferable);
+        try (InputStreamReader isr = new InputStreamReader(stream, charset);
+             BufferedReader reader = new BufferedReader(isr)) {
+            String line;
+            ArrayList<URI> uriList = new ArrayList<>();
+            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()]);
+        }
+    }
+
+    /**
+     * Returns true if and only if the name of the specified format Atom
+     * constitutes a valid MIME type with the specified primary type.
+     */
+    private boolean isMimeFormat(long format, String primaryType) {
+        String nat = getNativeForFormat(format);
+
+        if (nat == null) {
+            return false;
+        }
+
+        try {
+            DataFlavor df = new DataFlavor(nat);
+            if (primaryType.equals(df.getPrimaryType())) {
+                return true;
+            }
+        } catch (Exception e) {
+            // Not a MIME format.
+        }
+
+        return false;
+    }
+
+    /*
+     * The XDnD protocol prescribes that the Atoms used as targets for data
+     * transfer should have string names that represent the corresponding MIME
+     * types.
+     * To meet this requirement we check if the passed native format constitutes
+     * a valid MIME and return a list of flavors to which the data in this MIME
+     * type can be translated by the Data Transfer subsystem.
+     */
+    @Override
+    public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
+        LinkedHashSet<DataFlavor> flavors = new LinkedHashSet<>();
+
+        if (nat == null) {
+            return flavors;
+        }
+
+        DataFlavor df = null;
+
+        try {
+            df = new DataFlavor(nat);
+        } catch (Exception e) {
+            // The string doesn't constitute a valid MIME type.
+            return flavors;
+        }
+
+        DataFlavor value = df;
+        final String primaryType = df.getPrimaryType();
+        final String baseType = primaryType + "/" + df.getSubType();
+
+        // For text formats we map natives to MIME strings instead of data
+        // flavors to enable dynamic text native-to-flavor mapping generation.
+        // See SystemFlavorMap.getFlavorsForNative() for details.
+        if ("image".equals(primaryType)) {
+            Iterator<ImageReader> readers = ImageIO.getImageReadersByMIMEType(baseType);
+            if (readers.hasNext()) {
+                flavors.add(DataFlavor.imageFlavor);
+            }
+        }
+
+        flavors.add(value);
+
+        return flavors;
+    }
+
+    private static ImageTypeSpecifier defaultSpecifier = null;
+
+    private ImageTypeSpecifier getDefaultImageTypeSpecifier() {
+        if (defaultSpecifier == null) {
+            ColorModel model = ColorModel.getRGBdefault();
+            WritableRaster raster =
+                model.createCompatibleWritableRaster(10, 10);
+
+            BufferedImage bufferedImage =
+                new BufferedImage(model, raster, model.isAlphaPremultiplied(),
+                                  null);
+
+            defaultSpecifier = new ImageTypeSpecifier(bufferedImage);
+        }
+
+        return defaultSpecifier;
+    }
+
+    /*
+     * The XDnD protocol prescribes that the Atoms used as targets for data
+     * transfer should have string names that represent the corresponding MIME
+     * types.
+     * To meet this requirement we return a list of formats that represent
+     * MIME types to which the data in this flavor can be translated by the Data
+     * Transfer subsystem.
+     */
+    @Override
+    public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
+        LinkedHashSet<String> natives = new LinkedHashSet<>(1);
+
+        if (df == null) {
+            return natives;
+        }
+
+        String charset = df.getParameter("charset");
+        String baseType = df.getPrimaryType() + "/" + df.getSubType();
+        String mimeType = baseType;
+
+        if (charset != null && DataTransferer.isFlavorCharsetTextType(df)) {
+            mimeType += ";charset=" + charset;
+        }
+
+        // Add a mapping to the MIME native whenever the representation class
+        // doesn't require translation.
+        if (df.getRepresentationClass() != null &&
+            (df.isRepresentationClassInputStream() ||
+             df.isRepresentationClassByteBuffer() ||
+             byte[].class.equals(df.getRepresentationClass()))) {
+            natives.add(mimeType);
+        }
+
+        if (DataFlavor.imageFlavor.equals(df)) {
+            String[] mimeTypes = ImageIO.getWriterMIMETypes();
+            if (mimeTypes != null) {
+                for (String mime : mimeTypes) {
+                    Iterator<ImageWriter> writers = ImageIO.getImageWritersByMIMEType(mime);
+                    while (writers.hasNext()) {
+                        ImageWriter imageWriter = writers.next();
+                        ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();
+
+                        if (writerSpi != null &&
+                                writerSpi.canEncodeImage(getDefaultImageTypeSpecifier())) {
+                            natives.add(mime);
+                            break;
+                        }
+                    }
+                }
+            }
+        } else if (DataTransferer.isFlavorCharsetTextType(df)) {
+            // stringFlavor is semantically equivalent to the standard
+            // "text/plain" MIME type.
+            if (DataFlavor.stringFlavor.equals(df)) {
+                baseType = "text/plain";
+            }
+
+            for (String encoding : DataTransferer.standardEncodings()) {
+                if (!encoding.equals(charset)) {
+                    natives.add(baseType + ";charset=" + encoding);
+                }
+            }
+
+            // Add a MIME format without specified charset.
+            if (!natives.contains(baseType)) {
+                natives.add(baseType);
+            }
+        }
+
+        return natives;
+    }
+}