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