1 /* 2 * Copyright (c) 2003, 2014, 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.ImageReader; 52 import javax.imageio.ImageTypeSpecifier; 53 import javax.imageio.ImageWriter; 54 import javax.imageio.spi.ImageWriterSpi; 55 56 import sun.awt.datatransfer.DataTransferer; 57 import sun.awt.datatransfer.ToolkitThreadBlockedHandler; 58 59 import java.io.ByteArrayOutputStream; 60 import java.util.stream.Stream; 61 62 /** 63 * Platform-specific support for the data transfer subsystem. 64 */ 65 public class XDataTransferer extends DataTransferer { 66 static final XAtom FILE_NAME_ATOM = XAtom.get("FILE_NAME"); 67 static final XAtom DT_NET_FILE_ATOM = XAtom.get("_DT_NETFILE"); 68 static final XAtom PNG_ATOM = XAtom.get("PNG"); 69 static final XAtom JFIF_ATOM = XAtom.get("JFIF"); 70 static final XAtom TARGETS_ATOM = XAtom.get("TARGETS"); 71 static final XAtom INCR_ATOM = XAtom.get("INCR"); 72 static final XAtom MULTIPLE_ATOM = XAtom.get("MULTIPLE"); 73 74 /** 75 * Singleton constructor 76 */ 77 private XDataTransferer() { 78 } 79 80 private static XDataTransferer transferer; 81 82 static synchronized XDataTransferer getInstanceImpl() { 83 if (transferer == null) { 84 transferer = new XDataTransferer(); 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 platformImageBytesToImage( 216 byte[] bytes, long format) throws IOException 217 { 218 String mimeType = null; 219 if (format == PNG_ATOM.getAtom()) { 220 mimeType = "image/png"; 221 } else if (format == JFIF_ATOM.getAtom()) { 222 mimeType = "image/jpeg"; 223 } else { 224 // Check if an image MIME format. 225 try { 226 String nat = getNativeForFormat(format); 227 DataFlavor df = new DataFlavor(nat); 228 String primaryType = df.getPrimaryType(); 229 if ("image".equals(primaryType)) { 230 mimeType = df.getPrimaryType() + "/" + df.getSubType(); 231 } 232 } catch (Exception e) { 233 // Not an image MIME format. 234 } 235 } 236 if (mimeType != null) { 237 return standardImageBytesToImage(bytes, mimeType); 238 } else { 239 String nativeFormat = getNativeForFormat(format); 240 throw new IOException("Translation from " + nativeFormat + 241 " is not supported."); 242 } 243 } 244 245 @Override 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 @Override 257 protected URI[] dragQueryURIs(InputStream stream, 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<>(); 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 @Override 334 public List<DataFlavor> getPlatformMappingsForNative(String nat) { 335 List<DataFlavor> flavors = new ArrayList<>(); 336 337 if (nat == null) { 338 return flavors; 339 } 340 341 DataFlavor df = null; 342 343 try { 344 df = new DataFlavor(nat); 345 } catch (Exception e) { 346 // The string doesn't constitute a valid MIME type. 347 return flavors; 348 } 349 350 DataFlavor value = df; 351 final String primaryType = df.getPrimaryType(); 352 final String baseType = primaryType + "/" + df.getSubType(); 353 354 // For text formats we map natives to MIME strings instead of data 355 // flavors to enable dynamic text native-to-flavor mapping generation. 356 // See SystemFlavorMap.getFlavorsForNative() for details. 357 if ("image".equals(primaryType)) { 358 Iterator<ImageReader> readers = ImageIO.getImageReadersByMIMEType(baseType); 359 if (readers.hasNext()) { 360 flavors.add(DataFlavor.imageFlavor); 361 } 362 } 363 364 flavors.add(value); 365 366 return flavors; 367 } 368 369 private static ImageTypeSpecifier defaultSpecifier = null; 370 371 private ImageTypeSpecifier getDefaultImageTypeSpecifier() { 372 if (defaultSpecifier == null) { 373 ColorModel model = ColorModel.getRGBdefault(); 374 WritableRaster raster = 375 model.createCompatibleWritableRaster(10, 10); 376 377 BufferedImage bufferedImage = 378 new BufferedImage(model, raster, model.isAlphaPremultiplied(), 379 null); 380 381 defaultSpecifier = new ImageTypeSpecifier(bufferedImage); 382 } 383 384 return defaultSpecifier; 385 } 386 387 /* 388 * The XDnD protocol prescribes that the Atoms used as targets for data 389 * transfer should have string names that represent the corresponding MIME 390 * types. 391 * To meet this requirement we return a list of formats that represent 392 * MIME types to which the data in this flavor can be translated by the Data 393 * Transfer subsystem. 394 */ 395 @Override 396 public List<String> getPlatformMappingsForFlavor(DataFlavor df) { 397 List<String> 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 byte[].class.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 (String mime : mimeTypes) { 424 Iterator<ImageWriter> writers = ImageIO.getImageWritersByMIMEType(mime); 425 while (writers.hasNext()) { 426 ImageWriter imageWriter = writers.next(); 427 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider(); 428 429 if (writerSpi != null && 430 writerSpi.canEncodeImage(getDefaultImageTypeSpecifier())) { 431 natives.add(mime); 432 break; 433 } 434 } 435 } 436 } 437 } else if (DataTransferer.isFlavorCharsetTextType(df)) { 438 // stringFlavor is semantically equivalent to the standard 439 // "text/plain" MIME type. 440 if (DataFlavor.stringFlavor.equals(df)) { 441 baseType = "text/plain"; 442 } 443 444 for (String encoding : DataTransferer.standardEncodings()) { 445 if (!encoding.equals(charset)) { 446 natives.add(baseType + ";charset=" + encoding); 447 } 448 } 449 450 // Add a MIME format without specified charset. 451 if (!natives.contains(baseType)) { 452 natives.add(baseType); 453 } 454 } 455 456 return natives; 457 } 458 }