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 }