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