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.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 import java.util.stream.Stream; 60 61 /** 62 * Platform-specific support for the data transfer subsystem. 63 */ 64 public class XDataTransferer extends DataTransferer { 65 static final XAtom FILE_NAME_ATOM = XAtom.get("FILE_NAME"); 66 static final XAtom DT_NET_FILE_ATOM = XAtom.get("_DT_NETFILE"); 67 static final XAtom PNG_ATOM = XAtom.get("PNG"); 68 static final XAtom JFIF_ATOM = XAtom.get("JFIF"); 69 static final XAtom TARGETS_ATOM = XAtom.get("TARGETS"); 70 static final XAtom INCR_ATOM = XAtom.get("INCR"); 71 static final XAtom MULTIPLE_ATOM = XAtom.get("MULTIPLE"); 72 73 /** 74 * Singleton constructor 75 */ 76 private XDataTransferer() { 77 } 78 79 private static XDataTransferer transferer; 80 81 static synchronized XDataTransferer getInstanceImpl() { 82 if (transferer == null) { 83 transferer = new XDataTransferer(); 84 } 85 return transferer; 86 } 87 88 public String getDefaultUnicodeEncoding() { 89 return "iso-10646-ucs-2"; 90 } 91 92 public boolean isLocaleDependentTextFormat(long format) { 93 return false; 94 } 95 96 public boolean isTextFormat(long format) { 97 return super.isTextFormat(format) 98 || isMimeFormat(format, "text"); 99 } 100 101 protected String getCharsetForTextFormat(Long lFormat) { 102 long format = lFormat.longValue(); 103 if (isMimeFormat(format, "text")) { 104 String nat = getNativeForFormat(format); 105 DataFlavor df = new DataFlavor(nat, null); 106 // Ignore the charset parameter of the MIME type if the subtype 107 // doesn't support charset. 108 if (!DataTransferer.doesSubtypeSupportCharset(df)) { 109 return null; 110 } 111 String charset = df.getParameter("charset"); 112 if (charset != null) { 113 return charset; 114 } 115 } 116 return super.getCharsetForTextFormat(lFormat); 117 } 118 119 protected boolean isURIListFormat(long format) { 120 String nat = getNativeForFormat(format); 121 if (nat == null) { 122 return false; 123 } 124 try { 125 DataFlavor df = new DataFlavor(nat); 126 if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) { 127 return true; 128 } 129 } catch (Exception e) { 130 // Not a MIME format. 131 } 132 return false; 133 } 134 135 public boolean isFileFormat(long format) { 136 return format == FILE_NAME_ATOM.getAtom() || 137 format == DT_NET_FILE_ATOM.getAtom(); 138 } 139 140 public boolean isImageFormat(long format) { 141 return format == PNG_ATOM.getAtom() || 142 format == JFIF_ATOM.getAtom() || 143 isMimeFormat(format, "image"); 144 } 145 146 protected Long getFormatForNativeAsLong(String str) { 147 // Just get the atom. If it has already been retrived 148 // once, we'll get a copy so this should be very fast. 149 long atom = XAtom.get(str).getAtom(); 150 return Long.valueOf(atom); 151 } 152 153 protected String getNativeForFormat(long format) { 154 return getTargetNameForAtom(format); 155 } 156 157 public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() { 158 return XToolkitThreadBlockedHandler.getToolkitThreadBlockedHandler(); 159 } 160 161 /** 162 * Gets an format name for a given format (atom) 163 */ 164 private String getTargetNameForAtom(long atom) { 165 return XAtom.get(atom).getName(); 166 } 167 168 protected byte[] imageToPlatformBytes(Image image, long format) 169 throws IOException { 170 String mimeType = null; 171 if (format == PNG_ATOM.getAtom()) { 172 mimeType = "image/png"; 173 } else if (format == JFIF_ATOM.getAtom()) { 174 mimeType = "image/jpeg"; 175 } else { 176 // Check if an image MIME format. 177 try { 178 String nat = getNativeForFormat(format); 179 DataFlavor df = new DataFlavor(nat); 180 String primaryType = df.getPrimaryType(); 181 if ("image".equals(primaryType)) { 182 mimeType = df.getPrimaryType() + "/" + df.getSubType(); 183 } 184 } catch (Exception e) { 185 // Not an image MIME format. 186 } 187 } 188 if (mimeType != null) { 189 return imageToStandardBytes(image, mimeType); 190 } else { 191 String nativeFormat = getNativeForFormat(format); 192 throw new IOException("Translation to " + nativeFormat + 193 " is not supported."); 194 } 195 } 196 197 protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) 198 throws IOException 199 { 200 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 201 for (int i = 0; i < fileList.size(); i++) 202 { 203 byte[] bytes = fileList.get(i).getBytes(); 204 if (i != 0) bos.write(0); 205 bos.write(bytes, 0, bytes.length); 206 } 207 return bos; 208 } 209 210 /** 211 * Translates either a byte array or an input stream which contain 212 * platform-specific image data in the given format into an Image. 213 */ 214 protected Image platformImageBytesToImage( 215 byte[] bytes, long format) throws IOException 216 { 217 String mimeType = null; 218 if (format == PNG_ATOM.getAtom()) { 219 mimeType = "image/png"; 220 } else if (format == JFIF_ATOM.getAtom()) { 221 mimeType = "image/jpeg"; 222 } else { 223 // Check if an image MIME format. 224 try { 225 String nat = getNativeForFormat(format); 226 DataFlavor df = new DataFlavor(nat); 227 String primaryType = df.getPrimaryType(); 228 if ("image".equals(primaryType)) { 229 mimeType = df.getPrimaryType() + "/" + df.getSubType(); 230 } 231 } catch (Exception e) { 232 // Not an image MIME format. 233 } 234 } 235 if (mimeType != null) { 236 return standardImageBytesToImage(bytes, mimeType); 237 } else { 238 String nativeFormat = getNativeForFormat(format); 239 throw new IOException("Translation from " + nativeFormat + 240 " is not supported."); 241 } 242 } 243 244 @Override 245 protected String[] dragQueryFile(byte[] bytes) { 246 XToolkit.awtLock(); 247 try { 248 return XlibWrapper.XTextPropertyToStringList(bytes, 249 XAtom.get("STRING").getAtom()); 250 } finally { 251 XToolkit.awtUnlock(); 252 } 253 } 254 255 @Override 256 protected URI[] dragQueryURIs(InputStream stream, 257 long format, 258 Transferable localeTransferable) 259 throws IOException { 260 261 String charset = null; 262 if (localeTransferable != null && 263 isLocaleDependentTextFormat(format) && 264 localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) { 265 try { 266 charset = new String( 267 (byte[])localeTransferable.getTransferData(javaTextEncodingFlavor), 268 "UTF-8" 269 ); 270 } catch (UnsupportedFlavorException cannotHappen) { 271 } 272 } else { 273 charset = getCharsetForTextFormat(format); 274 } 275 if (charset == null) { 276 // Only happens when we have a custom text type. 277 charset = getDefaultTextCharset(); 278 } 279 280 BufferedReader reader = null; 281 try { 282 reader = new BufferedReader(new InputStreamReader(stream, charset)); 283 String line; 284 ArrayList<URI> uriList = new ArrayList<>(); 285 URI uri; 286 while ((line = reader.readLine()) != null) { 287 try { 288 uri = new URI(line); 289 } catch (URISyntaxException uriSyntaxException) { 290 throw new IOException(uriSyntaxException); 291 } 292 uriList.add(uri); 293 } 294 return uriList.toArray(new URI[uriList.size()]); 295 } finally { 296 if (reader != null) 297 reader.close(); 298 } 299 } 300 301 /** 302 * Returns true if and only if the name of the specified format Atom 303 * constitutes a valid MIME type with the specified primary type. 304 */ 305 private boolean isMimeFormat(long format, String primaryType) { 306 String nat = getNativeForFormat(format); 307 308 if (nat == null) { 309 return false; 310 } 311 312 try { 313 DataFlavor df = new DataFlavor(nat); 314 if (primaryType.equals(df.getPrimaryType())) { 315 return true; 316 } 317 } catch (Exception e) { 318 // Not a MIME format. 319 } 320 321 return false; 322 } 323 324 /* 325 * The XDnD protocol prescribes that the Atoms used as targets for data 326 * transfer should have string names that represent the corresponding MIME 327 * types. 328 * To meet this requirement we check if the passed native format constitutes 329 * a valid MIME and return a list of flavors to which the data in this MIME 330 * type can be translated by the Data Transfer subsystem. 331 */ 332 @Override 333 public List<DataFlavor> getPlatformMappingsForNative(String nat) { 334 List<DataFlavor> 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 DataFlavor 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 ("image".equals(primaryType)) { 357 Iterator readers = ImageIO.getImageReadersByMIMEType(baseType); 358 if (readers.hasNext()) { 359 flavors.add(DataFlavor.imageFlavor); 360 } 361 } 362 363 flavors.add(value); 364 365 return flavors; 366 } 367 368 private static ImageTypeSpecifier defaultSpecifier = null; 369 370 private ImageTypeSpecifier getDefaultImageTypeSpecifier() { 371 if (defaultSpecifier == null) { 372 ColorModel model = ColorModel.getRGBdefault(); 373 WritableRaster raster = 374 model.createCompatibleWritableRaster(10, 10); 375 376 BufferedImage bufferedImage = 377 new BufferedImage(model, raster, model.isAlphaPremultiplied(), 378 null); 379 380 defaultSpecifier = new ImageTypeSpecifier(bufferedImage); 381 } 382 383 return defaultSpecifier; 384 } 385 386 /* 387 * The XDnD protocol prescribes that the Atoms used as targets for data 388 * transfer should have string names that represent the corresponding MIME 389 * types. 390 * To meet this requirement we return a list of formats that represent 391 * MIME types to which the data in this flavor can be translated by the Data 392 * Transfer subsystem. 393 */ 394 @Override 395 public List<String> getPlatformMappingsForFlavor(DataFlavor df) { 396 List<String> natives = new ArrayList<>(1); 397 398 if (df == null) { 399 return natives; 400 } 401 402 String charset = df.getParameter("charset"); 403 String baseType = df.getPrimaryType() + "/" + df.getSubType(); 404 String mimeType = baseType; 405 406 if (charset != null && DataTransferer.isFlavorCharsetTextType(df)) { 407 mimeType += ";charset=" + charset; 408 } 409 410 // Add a mapping to the MIME native whenever the representation class 411 // doesn't require translation. 412 if (df.getRepresentationClass() != null && 413 (df.isRepresentationClassInputStream() || 414 df.isRepresentationClassByteBuffer() || 415 byte[].class.equals(df.getRepresentationClass()))) { 416 natives.add(mimeType); 417 } 418 419 if (DataFlavor.imageFlavor.equals(df)) { 420 String[] mimeTypes = ImageIO.getWriterMIMETypes(); 421 if (mimeTypes != null) { 422 for (String mime : mimeTypes) { 423 Iterator<ImageWriter> writers = ImageIO.getImageWritersByMIMEType(mime); 424 while (writers.hasNext()) { 425 ImageWriter imageWriter = writers.next(); 426 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider(); 427 428 if (writerSpi != null && 429 writerSpi.canEncodeImage(getDefaultImageTypeSpecifier())) { 430 natives.add(mime); 431 break; 432 } 433 } 434 } 435 } 436 } else if (DataTransferer.isFlavorCharsetTextType(df)) { 437 // stringFlavor is semantically equivalent to the standard 438 // "text/plain" MIME type. 439 if (DataFlavor.stringFlavor.equals(df)) { 440 baseType = "text/plain"; 441 } 442 443 for (String encoding : DataTransferer.standardEncodings()) { 444 if (!encoding.equals(charset)) { 445 natives.add(baseType + ";charset=" + encoding); 446 } 447 } 448 449 // Add a MIME format without specified charset. 450 if (!natives.contains(baseType)) { 451 natives.add(baseType); 452 } 453 } 454 455 return natives; 456 } 457 }