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 = getBestCharsetForTextFormat(format, localeTransferable); 262 try (InputStreamReader isr = new InputStreamReader(stream, charset); 263 BufferedReader reader = new BufferedReader(isr)) { 264 String line; 265 ArrayList<URI> uriList = new ArrayList<>(); 266 URI uri; 267 while ((line = reader.readLine()) != null) { 268 try { 269 uri = new URI(line); 270 } catch (URISyntaxException uriSyntaxException) { 271 throw new IOException(uriSyntaxException); 272 } 273 uriList.add(uri); 274 } 275 return uriList.toArray(new URI[uriList.size()]); 276 } 277 } 278 279 /** 280 * Returns true if and only if the name of the specified format Atom 281 * constitutes a valid MIME type with the specified primary type. 282 */ 283 private boolean isMimeFormat(long format, String primaryType) { 284 String nat = getNativeForFormat(format); 285 286 if (nat == null) { 287 return false; 288 } 289 290 try { 291 DataFlavor df = new DataFlavor(nat); 292 if (primaryType.equals(df.getPrimaryType())) { 293 return true; 294 } 295 } catch (Exception e) { 296 // Not a MIME format. 297 } 298 299 return false; 300 } 301 302 /* 303 * The XDnD protocol prescribes that the Atoms used as targets for data 304 * transfer should have string names that represent the corresponding MIME 305 * types. 306 * To meet this requirement we check if the passed native format constitutes 307 * a valid MIME and return a list of flavors to which the data in this MIME 308 * type can be translated by the Data Transfer subsystem. 309 */ 310 @Override 311 public List<DataFlavor> getPlatformMappingsForNative(String nat) { 312 List<DataFlavor> flavors = new ArrayList<>(); 313 314 if (nat == null) { 315 return flavors; 316 } 317 318 DataFlavor df = null; 319 320 try { 321 df = new DataFlavor(nat); 322 } catch (Exception e) { 323 // The string doesn't constitute a valid MIME type. 324 return flavors; 325 } 326 327 DataFlavor value = df; 328 final String primaryType = df.getPrimaryType(); 329 final String baseType = primaryType + "/" + df.getSubType(); 330 331 // For text formats we map natives to MIME strings instead of data 332 // flavors to enable dynamic text native-to-flavor mapping generation. 333 // See SystemFlavorMap.getFlavorsForNative() for details. 334 if ("image".equals(primaryType)) { 335 Iterator readers = ImageIO.getImageReadersByMIMEType(baseType); 336 if (readers.hasNext()) { 337 flavors.add(DataFlavor.imageFlavor); 338 } 339 } 340 341 flavors.add(value); 342 343 return flavors; 344 } 345 346 private static ImageTypeSpecifier defaultSpecifier = null; 347 348 private ImageTypeSpecifier getDefaultImageTypeSpecifier() { 349 if (defaultSpecifier == null) { 350 ColorModel model = ColorModel.getRGBdefault(); 351 WritableRaster raster = 352 model.createCompatibleWritableRaster(10, 10); 353 354 BufferedImage bufferedImage = 355 new BufferedImage(model, raster, model.isAlphaPremultiplied(), 356 null); 357 358 defaultSpecifier = new ImageTypeSpecifier(bufferedImage); 359 } 360 361 return defaultSpecifier; 362 } 363 364 /* 365 * The XDnD protocol prescribes that the Atoms used as targets for data 366 * transfer should have string names that represent the corresponding MIME 367 * types. 368 * To meet this requirement we return a list of formats that represent 369 * MIME types to which the data in this flavor can be translated by the Data 370 * Transfer subsystem. 371 */ 372 @Override 373 public List<String> getPlatformMappingsForFlavor(DataFlavor df) { 374 List<String> natives = new ArrayList<>(1); 375 376 if (df == null) { 377 return natives; 378 } 379 380 String charset = df.getParameter("charset"); 381 String baseType = df.getPrimaryType() + "/" + df.getSubType(); 382 String mimeType = baseType; 383 384 if (charset != null && DataTransferer.isFlavorCharsetTextType(df)) { 385 mimeType += ";charset=" + charset; 386 } 387 388 // Add a mapping to the MIME native whenever the representation class 389 // doesn't require translation. 390 if (df.getRepresentationClass() != null && 391 (df.isRepresentationClassInputStream() || 392 df.isRepresentationClassByteBuffer() || 393 byte[].class.equals(df.getRepresentationClass()))) { 394 natives.add(mimeType); 395 } 396 397 if (DataFlavor.imageFlavor.equals(df)) { 398 String[] mimeTypes = ImageIO.getWriterMIMETypes(); 399 if (mimeTypes != null) { 400 for (String mime : mimeTypes) { 401 Iterator<ImageWriter> writers = ImageIO.getImageWritersByMIMEType(mime); 402 while (writers.hasNext()) { 403 ImageWriter imageWriter = writers.next(); 404 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider(); 405 406 if (writerSpi != null && 407 writerSpi.canEncodeImage(getDefaultImageTypeSpecifier())) { 408 natives.add(mime); 409 break; 410 } 411 } 412 } 413 } 414 } else if (DataTransferer.isFlavorCharsetTextType(df)) { 415 // stringFlavor is semantically equivalent to the standard 416 // "text/plain" MIME type. 417 if (DataFlavor.stringFlavor.equals(df)) { 418 baseType = "text/plain"; 419 } 420 421 for (String encoding : DataTransferer.standardEncodings()) { 422 if (!encoding.equals(charset)) { 423 natives.add(baseType + ";charset=" + encoding); 424 } 425 } 426 427 // Add a MIME format without specified charset. 428 if (!natives.contains(baseType)) { 429 natives.add(baseType); 430 } 431 } 432 433 return natives; 434 } 435 }