1 /*
   2  * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.X11;
  27 
  28 import java.awt.Image;
  29 
  30 import java.awt.datatransfer.DataFlavor;
  31 import java.awt.datatransfer.Transferable;
  32 import java.awt.datatransfer.UnsupportedFlavorException;
  33 
  34 import java.awt.image.BufferedImage;
  35 import java.awt.image.ColorModel;
  36 import java.awt.image.WritableRaster;
  37 
  38 import java.io.BufferedReader;
  39 import java.io.InputStream;
  40 import java.io.InputStreamReader;
  41 import java.io.IOException;
  42 
  43 import java.net.URI;
  44 import java.net.URISyntaxException;
  45 
  46 import java.util.ArrayList;
  47 import java.util.Iterator;
  48 import java.util.List;
  49 
  50 import javax.imageio.ImageIO;
  51 import javax.imageio.ImageTypeSpecifier;
  52 import javax.imageio.ImageWriter;
  53 import javax.imageio.spi.ImageWriterSpi;
  54 
  55 import sun.awt.datatransfer.DataTransferer;
  56 import sun.awt.datatransfer.ToolkitThreadBlockedHandler;
  57 
  58 import java.io.ByteArrayOutputStream;
  59 
  60 /**
  61  * Platform-specific support for the data transfer subsystem.
  62  */
  63 public class XDataTransferer extends DataTransferer {
  64     static final XAtom FILE_NAME_ATOM = XAtom.get("FILE_NAME");
  65     static final XAtom DT_NET_FILE_ATOM = XAtom.get("_DT_NETFILE");
  66     static final XAtom PNG_ATOM = XAtom.get("PNG");
  67     static final XAtom JFIF_ATOM = XAtom.get("JFIF");
  68     static final XAtom TARGETS_ATOM = XAtom.get("TARGETS");
  69     static final XAtom INCR_ATOM = XAtom.get("INCR");
  70     static final XAtom MULTIPLE_ATOM = XAtom.get("MULTIPLE");
  71 
  72     /**
  73      * Singleton constructor
  74      */
  75     private XDataTransferer() {
  76     }
  77 
  78     private static XDataTransferer transferer;
  79 
  80     static XDataTransferer getInstanceImpl() {
  81         synchronized (XDataTransferer.class) {
  82             if (transferer == null) {
  83                 transferer = new XDataTransferer();
  84             }
  85         }
  86         return transferer;
  87     }
  88 
  89     public String getDefaultUnicodeEncoding() {
  90         return "iso-10646-ucs-2";
  91     }
  92 
  93     public boolean isLocaleDependentTextFormat(long format) {
  94         return false;
  95     }
  96 
  97     public boolean isTextFormat(long format) {
  98         return super.isTextFormat(format)
  99             || isMimeFormat(format, "text");
 100     }
 101 
 102     protected String getCharsetForTextFormat(Long lFormat) {
 103         long format = lFormat.longValue();
 104         if (isMimeFormat(format, "text")) {
 105             String nat = getNativeForFormat(format);
 106             DataFlavor df = new DataFlavor(nat, null);
 107             // Ignore the charset parameter of the MIME type if the subtype
 108             // doesn't support charset.
 109             if (!DataTransferer.doesSubtypeSupportCharset(df)) {
 110                 return null;
 111             }
 112             String charset = df.getParameter("charset");
 113             if (charset != null) {
 114                 return charset;
 115             }
 116         }
 117         return super.getCharsetForTextFormat(lFormat);
 118     }
 119 
 120     protected boolean isURIListFormat(long format) {
 121         String nat = getNativeForFormat(format);
 122         if (nat == null) {
 123             return false;
 124         }
 125         try {
 126             DataFlavor df = new DataFlavor(nat);
 127             if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) {
 128                 return true;
 129             }
 130         } catch (Exception e) {
 131             // Not a MIME format.
 132         }
 133         return false;
 134     }
 135 
 136     public boolean isFileFormat(long format) {
 137         return format == FILE_NAME_ATOM.getAtom() ||
 138             format == DT_NET_FILE_ATOM.getAtom();
 139     }
 140 
 141     public boolean isImageFormat(long format) {
 142         return format == PNG_ATOM.getAtom() ||
 143             format == JFIF_ATOM.getAtom() ||
 144             isMimeFormat(format, "image");
 145     }
 146 
 147     protected Long getFormatForNativeAsLong(String str) {
 148         // Just get the atom. If it has already been retrived
 149         // once, we'll get a copy so this should be very fast.
 150         long atom = XAtom.get(str).getAtom();
 151         return Long.valueOf(atom);
 152     }
 153 
 154     protected String getNativeForFormat(long format) {
 155         return getTargetNameForAtom(format);
 156     }
 157 
 158     public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() {
 159         return XToolkitThreadBlockedHandler.getToolkitThreadBlockedHandler();
 160     }
 161 
 162     /**
 163      * Gets an format name for a given format (atom)
 164      */
 165     private String getTargetNameForAtom(long atom) {
 166         return XAtom.get(atom).getName();
 167     }
 168 
 169     protected byte[] imageToPlatformBytes(Image image, long format)
 170       throws IOException {
 171         String mimeType = null;
 172         if (format == PNG_ATOM.getAtom()) {
 173             mimeType = "image/png";
 174         } else if (format == JFIF_ATOM.getAtom()) {
 175             mimeType = "image/jpeg";
 176         } else {
 177             // Check if an image MIME format.
 178             try {
 179                 String nat = getNativeForFormat(format);
 180                 DataFlavor df = new DataFlavor(nat);
 181                 String primaryType = df.getPrimaryType();
 182                 if ("image".equals(primaryType)) {
 183                     mimeType = df.getPrimaryType() + "/" + df.getSubType();
 184                 }
 185             } catch (Exception e) {
 186                 // Not an image MIME format.
 187             }
 188         }
 189         if (mimeType != null) {
 190             return imageToStandardBytes(image, mimeType);
 191         } else {
 192             String nativeFormat = getNativeForFormat(format);
 193             throw new IOException("Translation to " + nativeFormat +
 194                                   " is not supported.");
 195         }
 196     }
 197 
 198     protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList)
 199         throws IOException
 200     {
 201         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 202         for (int i = 0; i < fileList.size(); i++)
 203         {
 204                byte[] bytes = fileList.get(i).getBytes();
 205                if (i != 0) bos.write(0);
 206                bos.write(bytes, 0, bytes.length);
 207         }
 208         return bos;
 209     }
 210 
 211     /**
 212      * Translates either a byte array or an input stream which contain
 213      * platform-specific image data in the given format into an Image.
 214      */
 215     protected Image platformImageBytesOrStreamToImage(InputStream inputStream,
 216                                                       byte[] bytes,
 217                                                       long format)
 218       throws IOException {
 219         String mimeType = null;
 220         if (format == PNG_ATOM.getAtom()) {
 221             mimeType = "image/png";
 222         } else if (format == JFIF_ATOM.getAtom()) {
 223             mimeType = "image/jpeg";
 224         } else {
 225             // Check if an image MIME format.
 226             try {
 227                 String nat = getNativeForFormat(format);
 228                 DataFlavor df = new DataFlavor(nat);
 229                 String primaryType = df.getPrimaryType();
 230                 if ("image".equals(primaryType)) {
 231                     mimeType = df.getPrimaryType() + "/" + df.getSubType();
 232                 }
 233             } catch (Exception e) {
 234                 // Not an image MIME format.
 235             }
 236         }
 237         if (mimeType != null) {
 238             return standardImageBytesOrStreamToImage(inputStream, bytes, mimeType);
 239         } else {
 240             String nativeFormat = getNativeForFormat(format);
 241             throw new IOException("Translation from " + nativeFormat +
 242                                   " is not supported.");
 243         }
 244     }
 245 
 246     protected String[] dragQueryFile(byte[] bytes) {
 247         XToolkit.awtLock();
 248         try {
 249             return XlibWrapper.XTextPropertyToStringList(bytes,
 250                                                          XAtom.get("STRING").getAtom());
 251         } finally {
 252             XToolkit.awtUnlock();
 253         }
 254     }
 255 
 256     protected URI[] dragQueryURIs(InputStream stream,
 257                                   byte[] bytes,
 258                                   long format,
 259                                   Transferable localeTransferable)
 260       throws IOException {
 261 
 262         String charset = null;
 263         if (localeTransferable != null &&
 264             isLocaleDependentTextFormat(format) &&
 265             localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) {
 266             try {
 267                 charset = new String(
 268                     (byte[])localeTransferable.getTransferData(javaTextEncodingFlavor),
 269                     "UTF-8"
 270                 );
 271             } catch (UnsupportedFlavorException cannotHappen) {
 272             }
 273         } else {
 274             charset = getCharsetForTextFormat(format);
 275         }
 276         if (charset == null) {
 277             // Only happens when we have a custom text type.
 278             charset = getDefaultTextCharset();
 279         }
 280 
 281         BufferedReader reader = null;
 282         try {
 283             reader = new BufferedReader(new InputStreamReader(stream, charset));
 284             String line;
 285             ArrayList<URI> uriList = new ArrayList<URI>();
 286             URI uri;
 287             while ((line = reader.readLine()) != null) {
 288                 try {
 289                     uri = new URI(line);
 290                 } catch (URISyntaxException uriSyntaxException) {
 291                     throw new IOException(uriSyntaxException);
 292                 }
 293                 uriList.add(uri);
 294             }
 295             return uriList.toArray(new URI[uriList.size()]);
 296         } finally {
 297             if (reader != null)
 298                 reader.close();
 299         }
 300     }
 301 
 302     /**
 303      * Returns true if and only if the name of the specified format Atom
 304      * constitutes a valid MIME type with the specified primary type.
 305      */
 306     private boolean isMimeFormat(long format, String primaryType) {
 307         String nat = getNativeForFormat(format);
 308 
 309         if (nat == null) {
 310             return false;
 311         }
 312 
 313         try {
 314             DataFlavor df = new DataFlavor(nat);
 315             if (primaryType.equals(df.getPrimaryType())) {
 316                 return true;
 317             }
 318         } catch (Exception e) {
 319             // Not a MIME format.
 320         }
 321 
 322         return false;
 323     }
 324 
 325     /*
 326      * The XDnD protocol prescribes that the Atoms used as targets for data
 327      * transfer should have string names that represent the corresponding MIME
 328      * types.
 329      * To meet this requirement we check if the passed native format constitutes
 330      * a valid MIME and return a list of flavors to which the data in this MIME
 331      * type can be translated by the Data Transfer subsystem.
 332      */
 333     public List getPlatformMappingsForNative(String nat) {
 334         List flavors = new ArrayList();
 335 
 336         if (nat == null) {
 337             return flavors;
 338         }
 339 
 340         DataFlavor df = null;
 341 
 342         try {
 343             df = new DataFlavor(nat);
 344         } catch (Exception e) {
 345             // The string doesn't constitute a valid MIME type.
 346             return flavors;
 347         }
 348 
 349         Object value = df;
 350         final String primaryType = df.getPrimaryType();
 351         final String baseType = primaryType + "/" + df.getSubType();
 352 
 353         // For text formats we map natives to MIME strings instead of data
 354         // flavors to enable dynamic text native-to-flavor mapping generation.
 355         // See SystemFlavorMap.getFlavorsForNative() for details.
 356         if ("text".equals(primaryType)) {
 357             value = primaryType + "/" + df.getSubType();
 358         } else if ("image".equals(primaryType)) {
 359             Iterator readers = ImageIO.getImageReadersByMIMEType(baseType);
 360             if (readers.hasNext()) {
 361                 flavors.add(DataFlavor.imageFlavor);
 362             }
 363         }
 364 
 365         flavors.add(value);
 366 
 367         return flavors;
 368     }
 369 
 370     private static ImageTypeSpecifier defaultSpecifier = null;
 371 
 372     private ImageTypeSpecifier getDefaultImageTypeSpecifier() {
 373         if (defaultSpecifier == null) {
 374             ColorModel model = ColorModel.getRGBdefault();
 375             WritableRaster raster =
 376                 model.createCompatibleWritableRaster(10, 10);
 377 
 378             BufferedImage bufferedImage =
 379                 new BufferedImage(model, raster, model.isAlphaPremultiplied(),
 380                                   null);
 381 
 382             defaultSpecifier = new ImageTypeSpecifier(bufferedImage);
 383         }
 384 
 385         return defaultSpecifier;
 386     }
 387 
 388     /*
 389      * The XDnD protocol prescribes that the Atoms used as targets for data
 390      * transfer should have string names that represent the corresponding MIME
 391      * types.
 392      * To meet this requirement we return a list of formats that represent
 393      * MIME types to which the data in this flavor can be translated by the Data
 394      * Transfer subsystem.
 395      */
 396     public List getPlatformMappingsForFlavor(DataFlavor df) {
 397         List natives = new ArrayList(1);
 398 
 399         if (df == null) {
 400             return natives;
 401         }
 402 
 403         String charset = df.getParameter("charset");
 404         String baseType = df.getPrimaryType() + "/" + df.getSubType();
 405         String mimeType = baseType;
 406 
 407         if (charset != null && DataTransferer.isFlavorCharsetTextType(df)) {
 408             mimeType += ";charset=" + charset;
 409         }
 410 
 411         // Add a mapping to the MIME native whenever the representation class
 412         // doesn't require translation.
 413         if (df.getRepresentationClass() != null &&
 414             (df.isRepresentationClassInputStream() ||
 415              df.isRepresentationClassByteBuffer() ||
 416              byteArrayClass.equals(df.getRepresentationClass()))) {
 417             natives.add(mimeType);
 418         }
 419 
 420         if (DataFlavor.imageFlavor.equals(df)) {
 421             String[] mimeTypes = ImageIO.getWriterMIMETypes();
 422             if (mimeTypes != null) {
 423                 for (int i = 0; i < mimeTypes.length; i++) {
 424                     Iterator writers =
 425                         ImageIO.getImageWritersByMIMEType(mimeTypes[i]);
 426 
 427                     while (writers.hasNext()) {
 428                         ImageWriter imageWriter = (ImageWriter)writers.next();
 429                         ImageWriterSpi writerSpi =
 430                             imageWriter.getOriginatingProvider();
 431 
 432                         if (writerSpi != null &&
 433                             writerSpi.canEncodeImage(getDefaultImageTypeSpecifier())) {
 434                             natives.add(mimeTypes[i]);
 435                             break;
 436                         }
 437                     }
 438                 }
 439             }
 440         } else if (DataTransferer.isFlavorCharsetTextType(df)) {
 441             final Iterator iter = DataTransferer.standardEncodings();
 442 
 443             // stringFlavor is semantically equivalent to the standard
 444             // "text/plain" MIME type.
 445             if (DataFlavor.stringFlavor.equals(df)) {
 446                 baseType = "text/plain";
 447             }
 448 
 449             while (iter.hasNext()) {
 450                 String encoding = (String)iter.next();
 451                 if (!encoding.equals(charset)) {
 452                     natives.add(baseType + ";charset=" + encoding);
 453                 }
 454             }
 455 
 456             // Add a MIME format without specified charset.
 457             if (!natives.contains(baseType)) {
 458                 natives.add(baseType);
 459             }
 460         }
 461 
 462         return natives;
 463     }
 464 }