1 /* 2 * Copyright (c) 2000, 2018, 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.datatransfer; 27 28 import java.awt.EventQueue; 29 import java.awt.Graphics; 30 import java.awt.Image; 31 import java.awt.Toolkit; 32 33 import java.awt.datatransfer.DataFlavor; 34 import java.awt.datatransfer.FlavorMap; 35 import java.awt.datatransfer.FlavorTable; 36 import java.awt.datatransfer.Transferable; 37 import java.awt.datatransfer.UnsupportedFlavorException; 38 39 import java.io.BufferedReader; 40 import java.io.ByteArrayInputStream; 41 import java.io.ByteArrayOutputStream; 42 import java.io.File; 43 import java.io.InputStream; 44 import java.io.InputStreamReader; 45 import java.io.IOException; 46 import java.io.ObjectInputStream; 47 import java.io.ObjectOutputStream; 48 import java.io.Reader; 49 import java.io.SequenceInputStream; 50 import java.io.StringReader; 51 52 import java.net.URI; 53 import java.net.URISyntaxException; 54 55 import java.nio.ByteBuffer; 56 import java.nio.CharBuffer; 57 import java.nio.charset.Charset; 58 import java.nio.charset.CharsetEncoder; 59 import java.nio.charset.IllegalCharsetNameException; 60 import java.nio.charset.StandardCharsets; 61 import java.nio.charset.UnsupportedCharsetException; 62 63 import java.lang.reflect.Constructor; 64 import java.lang.reflect.Modifier; 65 66 import java.security.AccessController; 67 import java.security.PrivilegedAction; 68 import java.security.PrivilegedActionException; 69 import java.security.PrivilegedExceptionAction; 70 import java.security.ProtectionDomain; 71 72 import java.util.*; 73 74 import sun.datatransfer.DataFlavorUtil; 75 76 import sun.awt.AppContext; 77 import sun.awt.ComponentFactory; 78 import sun.awt.SunToolkit; 79 80 import java.awt.image.BufferedImage; 81 import java.awt.image.ImageObserver; 82 import java.awt.image.RenderedImage; 83 import java.awt.image.WritableRaster; 84 import java.awt.image.ColorModel; 85 86 import javax.imageio.ImageIO; 87 import javax.imageio.ImageReader; 88 import javax.imageio.ImageReadParam; 89 import javax.imageio.ImageWriter; 90 import javax.imageio.ImageTypeSpecifier; 91 92 import javax.imageio.spi.ImageWriterSpi; 93 94 import javax.imageio.stream.ImageInputStream; 95 import javax.imageio.stream.ImageOutputStream; 96 97 import sun.awt.image.ImageRepresentation; 98 import sun.awt.image.ToolkitImage; 99 100 import java.io.FilePermission; 101 import java.util.stream.Stream; 102 103 104 /** 105 * Provides a set of functions to be shared among the DataFlavor class and 106 * platform-specific data transfer implementations. 107 * 108 * The concept of "flavors" and "natives" is extended to include "formats", 109 * which are the numeric values Win32 and X11 use to express particular data 110 * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and 111 * getFlavorsForNatives(String[]) functions, DataTransferer provides a set 112 * of getFormatsFor(Transferable|Flavor|Flavors) and 113 * getFlavorsFor(Format|Formats) functions. 114 * 115 * Also provided are functions for translating a Transferable into a byte 116 * array, given a source DataFlavor and a target format, and for translating 117 * a byte array or InputStream into an Object, given a source format and 118 * a target DataFlavor. 119 * 120 * @author David Mendenhall 121 * @author Danila Sinopalnikov 122 * 123 * @since 1.3.1 124 */ 125 public abstract class DataTransferer { 126 /** 127 * The {@code DataFlavor} representing a Java text encoding String 128 * encoded in UTF-8, where 129 * <pre> 130 * representationClass = [B 131 * mimeType = "application/x-java-text-encoding" 132 * </pre> 133 */ 134 public static final DataFlavor javaTextEncodingFlavor; 135 136 /** 137 * A collection of all natives listed in flavormap.properties with 138 * a primary MIME type of "text". 139 */ 140 private static final Set<Long> textNatives = 141 Collections.synchronizedSet(new HashSet<>()); 142 143 /** 144 * The native encodings/charsets for the Set of textNatives. 145 */ 146 private static final Map<Long, String> nativeCharsets = 147 Collections.synchronizedMap(new HashMap<>()); 148 149 /** 150 * The end-of-line markers for the Set of textNatives. 151 */ 152 private static final Map<Long, String> nativeEOLNs = 153 Collections.synchronizedMap(new HashMap<>()); 154 155 /** 156 * The number of terminating NUL bytes for the Set of textNatives. 157 */ 158 private static final Map<Long, Integer> nativeTerminators = 159 Collections.synchronizedMap(new HashMap<>()); 160 161 /** 162 * The key used to store pending data conversion requests for an AppContext. 163 */ 164 private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY"; 165 166 static { 167 DataFlavor tJavaTextEncodingFlavor = null; 168 try { 169 tJavaTextEncodingFlavor = new DataFlavor("application/x-java-text-encoding;class=\"[B\""); 170 } catch (ClassNotFoundException cannotHappen) { 171 } 172 javaTextEncodingFlavor = tJavaTextEncodingFlavor; 173 } 174 175 /** 176 * The accessor method for the singleton DataTransferer instance. Note 177 * that in a headless environment, there may be no DataTransferer instance; 178 * instead, null will be returned. 179 */ 180 public static synchronized DataTransferer getInstance() { 181 return ((ComponentFactory) Toolkit.getDefaultToolkit()).getDataTransferer(); 182 } 183 184 /** 185 * Converts a FlavorMap to a FlavorTable. 186 */ 187 public static FlavorTable adaptFlavorMap(final FlavorMap map) { 188 if (map instanceof FlavorTable) { 189 return (FlavorTable)map; 190 } 191 192 return new FlavorTable() { 193 @Override 194 public Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] flavors) { 195 return map.getNativesForFlavors(flavors); 196 } 197 @Override 198 public Map<String, DataFlavor> getFlavorsForNatives(String[] natives) { 199 return map.getFlavorsForNatives(natives); 200 } 201 @Override 202 public List<String> getNativesForFlavor(DataFlavor flav) { 203 Map<DataFlavor, String> natives = getNativesForFlavors(new DataFlavor[]{flav}); 204 String nat = natives.get(flav); 205 if (nat != null) { 206 return Collections.singletonList(nat); 207 } else { 208 return Collections.emptyList(); 209 } 210 } 211 @Override 212 public List<DataFlavor> getFlavorsForNative(String nat) { 213 Map<String, DataFlavor> flavors = getFlavorsForNatives(new String[]{nat}); 214 DataFlavor flavor = flavors.get(nat); 215 if (flavor != null) { 216 return Collections.singletonList(flavor); 217 } else { 218 return Collections.emptyList(); 219 } 220 } 221 }; 222 } 223 224 /** 225 * Returns the default Unicode encoding for the platform. The encoding 226 * need not be canonical. This method is only used by the archaic function 227 * DataFlavor.getTextPlainUnicodeFlavor(). 228 */ 229 public abstract String getDefaultUnicodeEncoding(); 230 231 /** 232 * This method is called for text flavor mappings established while parsing 233 * the flavormap.properties file. It stores the "eoln" and "terminators" 234 * parameters which are not officially part of the MIME type. They are 235 * MIME parameters specific to the flavormap.properties file format. 236 */ 237 public void registerTextFlavorProperties(String nat, String charset, 238 String eoln, String terminators) { 239 Long format = getFormatForNativeAsLong(nat); 240 241 textNatives.add(format); 242 nativeCharsets.put(format, (charset != null && charset.length() != 0) 243 ? charset : Charset.defaultCharset().name()); 244 if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) { 245 nativeEOLNs.put(format, eoln); 246 } 247 if (terminators != null && terminators.length() != 0) { 248 Integer iTerminators = Integer.valueOf(terminators); 249 if (iTerminators > 0) { 250 nativeTerminators.put(format, iTerminators); 251 } 252 } 253 } 254 255 /** 256 * Determines whether the native corresponding to the specified long format 257 * was listed in the flavormap.properties file. 258 */ 259 protected boolean isTextFormat(long format) { 260 return textNatives.contains(Long.valueOf(format)); 261 } 262 263 protected String getCharsetForTextFormat(Long lFormat) { 264 return nativeCharsets.get(lFormat); 265 } 266 267 /** 268 * Specifies whether text imported from the native system in the specified 269 * format is locale-dependent. If so, when decoding such text, 270 * 'nativeCharsets' should be ignored, and instead, the Transferable should 271 * be queried for its javaTextEncodingFlavor data for the correct encoding. 272 */ 273 public abstract boolean isLocaleDependentTextFormat(long format); 274 275 /** 276 * Determines whether the DataFlavor corresponding to the specified long 277 * format is DataFlavor.javaFileListFlavor. 278 */ 279 public abstract boolean isFileFormat(long format); 280 281 /** 282 * Determines whether the DataFlavor corresponding to the specified long 283 * format is DataFlavor.imageFlavor. 284 */ 285 public abstract boolean isImageFormat(long format); 286 287 /** 288 * Determines whether the format is a URI list we can convert to 289 * a DataFlavor.javaFileListFlavor. 290 */ 291 protected boolean isURIListFormat(long format) { 292 return false; 293 } 294 295 /** 296 * Returns a Map whose keys are all of the possible formats into which the 297 * Transferable's transfer data flavors can be translated. The value of 298 * each key is the DataFlavor in which the Transferable's data should be 299 * requested when converting to the format. 300 * <p> 301 * The map keys are sorted according to the native formats preference 302 * order. 303 */ 304 public SortedMap<Long,DataFlavor> getFormatsForTransferable(Transferable contents, 305 FlavorTable map) 306 { 307 DataFlavor[] flavors = contents.getTransferDataFlavors(); 308 if (flavors == null) { 309 return Collections.emptySortedMap(); 310 } 311 return getFormatsForFlavors(flavors, map); 312 } 313 314 /** 315 * Returns a Map whose keys are all of the possible formats into which data 316 * in the specified DataFlavors can be translated. The value of each key 317 * is the DataFlavor in which the Transferable's data should be requested 318 * when converting to the format. 319 * <p> 320 * The map keys are sorted according to the native formats preference 321 * order. 322 * 323 * @param flavors the data flavors 324 * @param map the FlavorTable which contains mappings between 325 * DataFlavors and data formats 326 * @throws NullPointerException if flavors or map is {@code null} 327 */ 328 public SortedMap<Long, DataFlavor> getFormatsForFlavors(DataFlavor[] flavors, 329 FlavorTable map) 330 { 331 Map<Long,DataFlavor> formatMap = new HashMap<>(flavors.length); 332 Map<Long,DataFlavor> textPlainMap = new HashMap<>(flavors.length); 333 // Maps formats to indices that will be used to sort the formats 334 // according to the preference order. 335 // Larger index value corresponds to the more preferable format. 336 Map<Long, Integer> indexMap = new HashMap<>(flavors.length); 337 Map<Long, Integer> textPlainIndexMap = new HashMap<>(flavors.length); 338 339 int currentIndex = 0; 340 341 // Iterate backwards so that preferred DataFlavors are used over 342 // other DataFlavors. (See javadoc for 343 // Transferable.getTransferDataFlavors.) 344 for (int i = flavors.length - 1; i >= 0; i--) { 345 DataFlavor flavor = flavors[i]; 346 if (flavor == null) continue; 347 348 // Don't explicitly test for String, since it is just a special 349 // case of Serializable 350 if (flavor.isFlavorTextType() || 351 flavor.isFlavorJavaFileListType() || 352 DataFlavor.imageFlavor.equals(flavor) || 353 flavor.isRepresentationClassSerializable() || 354 flavor.isRepresentationClassInputStream() || 355 flavor.isRepresentationClassRemote()) 356 { 357 List<String> natives = map.getNativesForFlavor(flavor); 358 359 currentIndex += natives.size(); 360 361 for (String aNative : natives) { 362 Long lFormat = getFormatForNativeAsLong(aNative); 363 Integer index = currentIndex--; 364 365 formatMap.put(lFormat, flavor); 366 indexMap.put(lFormat, index); 367 368 // SystemFlavorMap.getNativesForFlavor will return 369 // text/plain natives for all text/*. While this is good 370 // for a single text/* flavor, we would prefer that 371 // text/plain native data come from a text/plain flavor. 372 if (("text".equals(flavor.getPrimaryType()) && 373 "plain".equals(flavor.getSubType())) || 374 flavor.equals(DataFlavor.stringFlavor)) { 375 textPlainMap.put(lFormat, flavor); 376 textPlainIndexMap.put(lFormat, index); 377 } 378 } 379 380 currentIndex += natives.size(); 381 } 382 } 383 384 formatMap.putAll(textPlainMap); 385 indexMap.putAll(textPlainIndexMap); 386 387 // Sort the map keys according to the formats preference order. 388 Comparator<Long> comparator = DataFlavorUtil.getIndexOrderComparator(indexMap).reversed(); 389 SortedMap<Long, DataFlavor> sortedMap = new TreeMap<>(comparator); 390 sortedMap.putAll(formatMap); 391 392 return sortedMap; 393 } 394 395 /** 396 * Reduces the Map output for the root function to an array of the 397 * Map's keys. 398 */ 399 public long[] getFormatsForTransferableAsArray(Transferable contents, 400 FlavorTable map) { 401 return keysToLongArray(getFormatsForTransferable(contents, map)); 402 } 403 404 /** 405 * Returns a Map whose keys are all of the possible DataFlavors into which 406 * data in the specified formats can be translated. The value of each key 407 * is the format in which the Clipboard or dropped data should be requested 408 * when converting to the DataFlavor. 409 */ 410 public Map<DataFlavor, Long> getFlavorsForFormats(long[] formats, FlavorTable map) { 411 Map<DataFlavor, Long> flavorMap = new HashMap<>(formats.length); 412 Set<AbstractMap.SimpleEntry<Long, DataFlavor>> mappingSet = new HashSet<>(formats.length); 413 Set<DataFlavor> flavorSet = new HashSet<>(formats.length); 414 415 // First step: build flavorSet, mappingSet and initial flavorMap 416 // flavorSet - the set of all the DataFlavors into which 417 // data in the specified formats can be translated; 418 // mappingSet - the set of all the mappings from the specified formats 419 // into any DataFlavor; 420 // flavorMap - after this step, this map maps each of the DataFlavors 421 // from flavorSet to any of the specified formats. 422 for (long format : formats) { 423 String nat = getNativeForFormat(format); 424 List<DataFlavor> flavors = map.getFlavorsForNative(nat); 425 for (DataFlavor flavor : flavors) { 426 // Don't explicitly test for String, since it is just a special 427 // case of Serializable 428 if (flavor.isFlavorTextType() || 429 flavor.isFlavorJavaFileListType() || 430 DataFlavor.imageFlavor.equals(flavor) || 431 flavor.isRepresentationClassSerializable() || 432 flavor.isRepresentationClassInputStream() || 433 flavor.isRepresentationClassRemote()) { 434 435 AbstractMap.SimpleEntry<Long, DataFlavor> mapping = 436 new AbstractMap.SimpleEntry<>(format, flavor); 437 flavorMap.put(flavor, format); 438 mappingSet.add(mapping); 439 flavorSet.add(flavor); 440 } 441 } 442 } 443 444 // Second step: for each DataFlavor try to figure out which of the 445 // specified formats is the best to translate to this flavor. 446 // Then map each flavor to the best format. 447 // For the given flavor, FlavorTable indicates which native will 448 // best reflect data in the specified flavor to the underlying native 449 // platform. We assume that this native is the best to translate 450 // to this flavor. 451 // Note: FlavorTable allows one-way mappings, so we can occasionally 452 // map a flavor to the format for which the corresponding 453 // format-to-flavor mapping doesn't exist. For this reason we have built 454 // a mappingSet of all format-to-flavor mappings for the specified formats 455 // and check if the format-to-flavor mapping exists for the 456 // (flavor,format) pair being added. 457 for (DataFlavor flavor : flavorSet) { 458 List<String> natives = map.getNativesForFlavor(flavor); 459 for (String aNative : natives) { 460 Long lFormat = getFormatForNativeAsLong(aNative); 461 if (mappingSet.contains(new AbstractMap.SimpleEntry<>(lFormat, flavor))) { 462 flavorMap.put(flavor, lFormat); 463 break; 464 } 465 } 466 } 467 468 return flavorMap; 469 } 470 471 /** 472 * Returns a Set of all DataFlavors for which 473 * 1) a mapping from at least one of the specified formats exists in the 474 * specified map and 475 * 2) the data translation for this mapping can be performed by the data 476 * transfer subsystem. 477 * 478 * @param formats the data formats 479 * @param map the FlavorTable which contains mappings between 480 * DataFlavors and data formats 481 * @throws NullPointerException if formats or map is {@code null} 482 */ 483 public Set<DataFlavor> getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) { 484 Set<DataFlavor> flavorSet = new HashSet<>(formats.length); 485 486 for (long format : formats) { 487 List<DataFlavor> flavors = map.getFlavorsForNative(getNativeForFormat(format)); 488 for (DataFlavor flavor : flavors) { 489 // Don't explicitly test for String, since it is just a special 490 // case of Serializable 491 if (flavor.isFlavorTextType() || 492 flavor.isFlavorJavaFileListType() || 493 DataFlavor.imageFlavor.equals(flavor) || 494 flavor.isRepresentationClassSerializable() || 495 flavor.isRepresentationClassInputStream() || 496 flavor.isRepresentationClassRemote()) { 497 flavorSet.add(flavor); 498 } 499 } 500 } 501 502 return flavorSet; 503 } 504 505 /** 506 * Returns an array of all DataFlavors for which 507 * 1) a mapping from at least one of the specified formats exists in the 508 * specified map and 509 * 2) the data translation for this mapping can be performed by the data 510 * transfer subsystem. 511 * The array will be sorted according to a 512 * {@code DataFlavorComparator} created with the specified 513 * map as an argument. 514 * 515 * @param formats the data formats 516 * @param map the FlavorTable which contains mappings between 517 * DataFlavors and data formats 518 * @throws NullPointerException if formats or map is {@code null} 519 */ 520 public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats, 521 FlavorTable map) { 522 // getFlavorsForFormatsAsSet() is less expensive than 523 // getFlavorsForFormats(). 524 return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map)); 525 } 526 527 /** 528 * Looks-up or registers the String native with the native data transfer 529 * system and returns a long format corresponding to that native. 530 */ 531 protected abstract Long getFormatForNativeAsLong(String str); 532 533 /** 534 * Looks-up the String native corresponding to the specified long format in 535 * the native data transfer system. 536 */ 537 protected abstract String getNativeForFormat(long format); 538 539 /* Contains common code for finding the best charset for 540 * clipboard string encoding/decoding, basing on clipboard 541 * format and localeTransferable(on decoding, if available) 542 */ 543 protected String getBestCharsetForTextFormat(Long lFormat, 544 Transferable localeTransferable) throws IOException 545 { 546 String charset = null; 547 if (localeTransferable != null && 548 isLocaleDependentTextFormat(lFormat) && 549 localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) { 550 try { 551 byte[] charsetNameBytes = (byte[])localeTransferable 552 .getTransferData(javaTextEncodingFlavor); 553 charset = new String(charsetNameBytes, StandardCharsets.UTF_8); 554 } catch (UnsupportedFlavorException cannotHappen) { 555 } 556 } else { 557 charset = getCharsetForTextFormat(lFormat); 558 } 559 if (charset == null) { 560 // Only happens when we have a custom text type. 561 charset = Charset.defaultCharset().name(); 562 } 563 return charset; 564 } 565 566 /** 567 * Translation function for converting string into 568 * a byte array. Search-and-replace EOLN. Encode into the 569 * target format. Append terminating NUL bytes. 570 * 571 * Java to Native string conversion 572 */ 573 private byte[] translateTransferableString(String str, 574 long format) throws IOException 575 { 576 Long lFormat = format; 577 String charset = getBestCharsetForTextFormat(lFormat, null); 578 // Search and replace EOLN. Note that if EOLN is "\n", then we 579 // never added an entry to nativeEOLNs anyway, so we'll skip this 580 // code altogether. 581 // windows: "abc\nde"->"abc\r\nde" 582 String eoln = nativeEOLNs.get(lFormat); 583 if (eoln != null) { 584 int length = str.length(); 585 StringBuilder buffer = new StringBuilder(length * 2); // 2 is a heuristic 586 for (int i = 0; i < length; i++) { 587 // Fix for 4914613 - skip native EOLN 588 if (str.startsWith(eoln, i)) { 589 buffer.append(eoln); 590 i += eoln.length() - 1; 591 continue; 592 } 593 char c = str.charAt(i); 594 if (c == '\n') { 595 buffer.append(eoln); 596 } else { 597 buffer.append(c); 598 } 599 } 600 str = buffer.toString(); 601 } 602 603 // Encode text in target format. 604 byte[] bytes = str.getBytes(charset); 605 606 // Append terminating NUL bytes. Note that if terminators is 0, 607 // the we never added an entry to nativeTerminators anyway, so 608 // we'll skip code altogether. 609 // "abcde" -> "abcde\0" 610 Integer terminators = nativeTerminators.get(lFormat); 611 if (terminators != null) { 612 int numTerminators = terminators; 613 byte[] terminatedBytes = 614 new byte[bytes.length + numTerminators]; 615 System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length); 616 for (int i = bytes.length; i < terminatedBytes.length; i++) { 617 terminatedBytes[i] = 0x0; 618 } 619 bytes = terminatedBytes; 620 } 621 return bytes; 622 } 623 624 /** 625 * Translating either a byte array or an InputStream into an String. 626 * Strip terminators and search-and-replace EOLN. 627 * 628 * Native to Java string conversion 629 */ 630 private String translateBytesToString(byte[] bytes, long format, 631 Transferable localeTransferable) 632 throws IOException 633 { 634 635 Long lFormat = format; 636 String charset = getBestCharsetForTextFormat(lFormat, localeTransferable); 637 638 // Locate terminating NUL bytes. Note that if terminators is 0, 639 // the we never added an entry to nativeTerminators anyway, so 640 // we'll skip code altogether. 641 642 // In other words: we are doing char alignment here basing on suggestion 643 // that count of zero-'terminators' is a number of bytes in one symbol 644 // for selected charset (clipboard format). It is not complitly true for 645 // multibyte coding like UTF-8, but helps understand the procedure. 646 // "abcde\0" -> "abcde" 647 648 String eoln = nativeEOLNs.get(lFormat); 649 Integer terminators = nativeTerminators.get(lFormat); 650 int count; 651 if (terminators != null) { 652 int numTerminators = terminators; 653 search: 654 for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) { 655 for (int i = count; i < count + numTerminators; i++) { 656 if (bytes[i] != 0x0) { 657 continue search; 658 } 659 } 660 // found terminators 661 break search; 662 } 663 } else { 664 count = bytes.length; 665 } 666 667 // Decode text to chars. Don't include any terminators. 668 String converted = new String(bytes, 0, count, charset); 669 670 // Search and replace EOLN. Note that if EOLN is "\n", then we 671 // never added an entry to nativeEOLNs anyway, so we'll skip this 672 // code altogether. 673 // Count of NUL-terminators and EOLN coding are platform-specific and 674 // loaded from flavormap.properties file 675 // windows: "abc\r\nde" -> "abc\nde" 676 677 if (eoln != null) { 678 679 /* Fix for 4463560: replace EOLNs symbol-by-symbol instead 680 * of using buf.replace() 681 */ 682 683 char[] buf = converted.toCharArray(); 684 char[] eoln_arr = eoln.toCharArray(); 685 int j = 0; 686 boolean match; 687 688 for (int i = 0; i < buf.length; ) { 689 // Catch last few bytes 690 if (i + eoln_arr.length > buf.length) { 691 buf[j++] = buf[i++]; 692 continue; 693 } 694 695 match = true; 696 for (int k = 0, l = i; k < eoln_arr.length; k++, l++) { 697 if (eoln_arr[k] != buf[l]) { 698 match = false; 699 break; 700 } 701 } 702 if (match) { 703 buf[j++] = '\n'; 704 i += eoln_arr.length; 705 } else { 706 buf[j++] = buf[i++]; 707 } 708 } 709 converted = new String(buf, 0, j); 710 } 711 712 return converted; 713 } 714 715 716 /** 717 * Primary translation function for translating a Transferable into 718 * a byte array, given a source DataFlavor and target format. 719 */ 720 @SuppressWarnings("deprecation") 721 public byte[] translateTransferable(Transferable contents, 722 DataFlavor flavor, 723 long format) throws IOException 724 { 725 // Obtain the transfer data in the source DataFlavor. 726 // 727 // Note that we special case DataFlavor.plainTextFlavor because 728 // StringSelection supports this flavor incorrectly -- instead of 729 // returning an InputStream as the DataFlavor representation class 730 // states, it returns a Reader. Instead of using this broken 731 // functionality, we request the data in stringFlavor (the other 732 // DataFlavor which StringSelection supports) and use the String 733 // translator. 734 Object obj; 735 boolean stringSelectionHack; 736 try { 737 obj = contents.getTransferData(flavor); 738 if (obj == null) { 739 return null; 740 } 741 if (flavor.equals(DataFlavor.plainTextFlavor) && 742 !(obj instanceof InputStream)) 743 { 744 obj = contents.getTransferData(DataFlavor.stringFlavor); 745 if (obj == null) { 746 return null; 747 } 748 stringSelectionHack = true; 749 } else { 750 stringSelectionHack = false; 751 } 752 } catch (UnsupportedFlavorException e) { 753 throw new IOException(e.getMessage()); 754 } 755 756 // Source data is a String. Search-and-replace EOLN. Encode into the 757 // target format. Append terminating NUL bytes. 758 if (stringSelectionHack || 759 (String.class.equals(flavor.getRepresentationClass()) && 760 DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 761 762 String str = removeSuspectedData(flavor, contents, (String)obj); 763 764 return translateTransferableString( 765 str, 766 format); 767 768 // Source data is a Reader. Convert to a String and recur. In the 769 // future, we may want to rewrite this so that we encode on demand. 770 } else if (flavor.isRepresentationClassReader()) { 771 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 772 throw new IOException 773 ("cannot transfer non-text data as Reader"); 774 } 775 776 StringBuilder buf = new StringBuilder(); 777 try (Reader r = (Reader)obj) { 778 int c; 779 while ((c = r.read()) != -1) { 780 buf.append((char)c); 781 } 782 } 783 784 return translateTransferableString( 785 buf.toString(), 786 format); 787 788 // Source data is a CharBuffer. Convert to a String and recur. 789 } else if (flavor.isRepresentationClassCharBuffer()) { 790 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 791 throw new IOException 792 ("cannot transfer non-text data as CharBuffer"); 793 } 794 795 CharBuffer buffer = (CharBuffer)obj; 796 int size = buffer.remaining(); 797 char[] chars = new char[size]; 798 buffer.get(chars, 0, size); 799 800 return translateTransferableString( 801 new String(chars), 802 format); 803 804 // Source data is a char array. Convert to a String and recur. 805 } else if (char[].class.equals(flavor.getRepresentationClass())) { 806 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 807 throw new IOException 808 ("cannot transfer non-text data as char array"); 809 } 810 811 return translateTransferableString( 812 new String((char[])obj), 813 format); 814 815 // Source data is a ByteBuffer. For arbitrary flavors, simply return 816 // the array. For text flavors, decode back to a String and recur to 817 // reencode according to the requested format. 818 } else if (flavor.isRepresentationClassByteBuffer()) { 819 ByteBuffer buffer = (ByteBuffer)obj; 820 int size = buffer.remaining(); 821 byte[] bytes = new byte[size]; 822 buffer.get(bytes, 0, size); 823 824 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 825 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor); 826 return translateTransferableString( 827 new String(bytes, sourceEncoding), 828 format); 829 } else { 830 return bytes; 831 } 832 833 // Source data is a byte array. For arbitrary flavors, simply return 834 // the array. For text flavors, decode back to a String and recur to 835 // reencode according to the requested format. 836 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 837 byte[] bytes = (byte[])obj; 838 839 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 840 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor); 841 return translateTransferableString( 842 new String(bytes, sourceEncoding), 843 format); 844 } else { 845 return bytes; 846 } 847 // Source data is Image 848 } else if (DataFlavor.imageFlavor.equals(flavor)) { 849 if (!isImageFormat(format)) { 850 throw new IOException("Data translation failed: " + 851 "not an image format"); 852 } 853 854 Image image = (Image)obj; 855 byte[] bytes = imageToPlatformBytes(image, format); 856 857 if (bytes == null) { 858 throw new IOException("Data translation failed: " + 859 "cannot convert java image to native format"); 860 } 861 return bytes; 862 } 863 864 byte[] theByteArray = null; 865 866 // Target data is a file list. Source data must be a 867 // java.util.List which contains java.io.File or String instances. 868 if (isFileFormat(format)) { 869 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 870 throw new IOException("data translation failed"); 871 } 872 873 final List<?> list = (List<?>)obj; 874 875 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 876 877 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain); 878 879 try (ByteArrayOutputStream bos = convertFileListToBytes(fileList)) { 880 theByteArray = bos.toByteArray(); 881 } 882 883 // Target data is a URI list. Source data must be a 884 // java.util.List which contains java.io.File or String instances. 885 } else if (isURIListFormat(format)) { 886 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 887 throw new IOException("data translation failed"); 888 } 889 String nat = getNativeForFormat(format); 890 String targetCharset = null; 891 if (nat != null) { 892 try { 893 targetCharset = new DataFlavor(nat).getParameter("charset"); 894 } catch (ClassNotFoundException cnfe) { 895 throw new IOException(cnfe); 896 } 897 } 898 if (targetCharset == null) { 899 targetCharset = "UTF-8"; 900 } 901 final List<?> list = (List<?>)obj; 902 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 903 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain); 904 final ArrayList<String> uriList = new ArrayList<>(fileList.size()); 905 for (String fileObject : fileList) { 906 final URI uri = new File(fileObject).toURI(); 907 // Some implementations are fussy about the number of slashes (file:///path/to/file is best) 908 try { 909 uriList.add(new URI(uri.getScheme(), "", uri.getPath(), uri.getFragment()).toString()); 910 } catch (URISyntaxException uriSyntaxException) { 911 throw new IOException(uriSyntaxException); 912 } 913 } 914 915 byte[] eoln = "\r\n".getBytes(targetCharset); 916 917 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 918 for (String uri : uriList) { 919 byte[] bytes = uri.getBytes(targetCharset); 920 bos.write(bytes, 0, bytes.length); 921 bos.write(eoln, 0, eoln.length); 922 } 923 theByteArray = bos.toByteArray(); 924 } 925 926 // Source data is an InputStream. For arbitrary flavors, just grab the 927 // bytes and dump them into a byte array. For text flavors, decode back 928 // to a String and recur to reencode according to the requested format. 929 } else if (flavor.isRepresentationClassInputStream()) { 930 931 // Workaround to JDK-8024061: Exception thrown when drag and drop 932 // between two components is executed quickly. 933 // and JDK-8065098: JColorChooser no longer supports drag and drop 934 // between two JVM instances 935 if (!(obj instanceof InputStream)) { 936 return new byte[0]; 937 } 938 939 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 940 try (InputStream is = (InputStream)obj) { 941 is.mark(Integer.MAX_VALUE); 942 is.transferTo(bos); 943 is.reset(); 944 } 945 946 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 947 byte[] bytes = bos.toByteArray(); 948 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor); 949 return translateTransferableString( 950 new String(bytes, sourceEncoding), 951 format); 952 } 953 theByteArray = bos.toByteArray(); 954 } 955 956 957 // Source data is an RMI object 958 } else if (flavor.isRepresentationClassRemote()) { 959 theByteArray = convertObjectToBytes(DataFlavorUtil.RMI.newMarshalledObject(obj)); 960 961 // Source data is Serializable 962 } else if (flavor.isRepresentationClassSerializable()) { 963 964 theByteArray = convertObjectToBytes(obj); 965 966 } else { 967 throw new IOException("data translation failed"); 968 } 969 970 971 972 return theByteArray; 973 } 974 975 private static byte[] convertObjectToBytes(Object object) throws IOException { 976 try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); 977 ObjectOutputStream oos = new ObjectOutputStream(bos)) 978 { 979 oos.writeObject(object); 980 return bos.toByteArray(); 981 } 982 } 983 984 protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException; 985 986 private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str) 987 throws IOException 988 { 989 if (null == System.getSecurityManager() 990 || !flavor.isMimeTypeEqual("text/uri-list")) 991 { 992 return str; 993 } 994 995 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 996 997 try { 998 return AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> { 999 1000 StringBuilder allowedFiles = new StringBuilder(str.length()); 1001 String [] uriArray = str.split("(\\s)+"); 1002 1003 for (String fileName : uriArray) 1004 { 1005 File file = new File(fileName); 1006 if (file.exists() && 1007 !(isFileInWebstartedCache(file) || 1008 isForbiddenToRead(file, userProtectionDomain))) 1009 { 1010 if (0 != allowedFiles.length()) 1011 { 1012 allowedFiles.append("\\r\\n"); 1013 } 1014 1015 allowedFiles.append(fileName); 1016 } 1017 } 1018 1019 return allowedFiles.toString(); 1020 }); 1021 } catch (PrivilegedActionException pae) { 1022 throw new IOException(pae.getMessage(), pae); 1023 } 1024 } 1025 1026 private static ProtectionDomain getUserProtectionDomain(Transferable contents) { 1027 return contents.getClass().getProtectionDomain(); 1028 } 1029 1030 private boolean isForbiddenToRead (File file, ProtectionDomain protectionDomain) 1031 { 1032 if (null == protectionDomain) { 1033 return false; 1034 } 1035 try { 1036 FilePermission filePermission = 1037 new FilePermission(file.getCanonicalPath(), "read, delete"); 1038 if (protectionDomain.implies(filePermission)) { 1039 return false; 1040 } 1041 } catch (IOException e) {} 1042 1043 return true; 1044 } 1045 1046 private ArrayList<String> castToFiles(final List<?> files, 1047 final ProtectionDomain userProtectionDomain) throws IOException { 1048 try { 1049 return AccessController.doPrivileged((PrivilegedExceptionAction<ArrayList<String>>) () -> { 1050 ArrayList<String> fileList = new ArrayList<>(); 1051 for (Object fileObject : files) 1052 { 1053 File file = castToFile(fileObject); 1054 if (file != null && 1055 (null == System.getSecurityManager() || 1056 !(isFileInWebstartedCache(file) || 1057 isForbiddenToRead(file, userProtectionDomain)))) 1058 { 1059 fileList.add(file.getCanonicalPath()); 1060 } 1061 } 1062 return fileList; 1063 }); 1064 } catch (PrivilegedActionException pae) { 1065 throw new IOException(pae.getMessage()); 1066 } 1067 } 1068 1069 // It is important do not use user's successors 1070 // of File class. 1071 private File castToFile(Object fileObject) throws IOException { 1072 String filePath = null; 1073 if (fileObject instanceof File) { 1074 filePath = ((File)fileObject).getCanonicalPath(); 1075 } else if (fileObject instanceof String) { 1076 filePath = (String) fileObject; 1077 } else { 1078 return null; 1079 } 1080 return new File(filePath); 1081 } 1082 1083 private static final String[] DEPLOYMENT_CACHE_PROPERTIES = { 1084 "deployment.system.cachedir", 1085 "deployment.user.cachedir", 1086 "deployment.javaws.cachedir", 1087 "deployment.javapi.cachedir" 1088 }; 1089 1090 private static final ArrayList <File> deploymentCacheDirectoryList = new ArrayList<>(); 1091 1092 private static boolean isFileInWebstartedCache(File f) { 1093 1094 if (deploymentCacheDirectoryList.isEmpty()) { 1095 for (String cacheDirectoryProperty : DEPLOYMENT_CACHE_PROPERTIES) { 1096 String cacheDirectoryPath = System.getProperty(cacheDirectoryProperty); 1097 if (cacheDirectoryPath != null) { 1098 try { 1099 File cacheDirectory = (new File(cacheDirectoryPath)).getCanonicalFile(); 1100 if (cacheDirectory != null) { 1101 deploymentCacheDirectoryList.add(cacheDirectory); 1102 } 1103 } catch (IOException ioe) {} 1104 } 1105 } 1106 } 1107 1108 for (File deploymentCacheDirectory : deploymentCacheDirectoryList) { 1109 for (File dir = f; dir != null; dir = dir.getParentFile()) { 1110 if (dir.equals(deploymentCacheDirectory)) { 1111 return true; 1112 } 1113 } 1114 } 1115 1116 return false; 1117 } 1118 1119 1120 public Object translateBytes(byte[] bytes, DataFlavor flavor, 1121 long format, Transferable localeTransferable) 1122 throws IOException 1123 { 1124 1125 Object theObject = null; 1126 1127 // Source data is a file list. Use the dragQueryFile native function to 1128 // do most of the decoding. Then wrap File objects around the String 1129 // filenames and return a List. 1130 if (isFileFormat(format)) { 1131 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 1132 throw new IOException("data translation failed"); 1133 } 1134 String[] filenames = dragQueryFile(bytes); 1135 if (filenames == null) { 1136 return null; 1137 } 1138 1139 // Convert the strings to File objects 1140 File[] files = new File[filenames.length]; 1141 for (int i = 0; i < filenames.length; i++) { 1142 files[i] = new File(filenames[i]); 1143 } 1144 1145 // Turn the list of Files into a List and return 1146 theObject = Arrays.asList(files); 1147 1148 // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor 1149 // where possible. 1150 } else if (isURIListFormat(format) 1151 && DataFlavor.javaFileListFlavor.equals(flavor)) { 1152 1153 try (ByteArrayInputStream str = new ByteArrayInputStream(bytes)) { 1154 1155 URI[] uris = dragQueryURIs(str, format, localeTransferable); 1156 if (uris == null) { 1157 return null; 1158 } 1159 List<File> files = new ArrayList<>(); 1160 for (URI uri : uris) { 1161 try { 1162 files.add(new File(uri)); 1163 } catch (IllegalArgumentException illegalArg) { 1164 // When converting from URIs to less generic files, 1165 // common practice (Wine, SWT) seems to be to 1166 // silently drop the URIs that aren't local files. 1167 } 1168 } 1169 theObject = files; 1170 } 1171 1172 // Target data is a String. Strip terminating NUL bytes. Decode bytes 1173 // into characters. Search-and-replace EOLN. 1174 } else if (String.class.equals(flavor.getRepresentationClass()) && 1175 DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1176 1177 theObject = translateBytesToString(bytes, format, localeTransferable); 1178 1179 // Target data is a Reader. Obtain data in InputStream format, encoded 1180 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode 1181 // back to chars on demand. 1182 } else if (flavor.isRepresentationClassReader()) { 1183 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1184 theObject = translateStream(bais, 1185 flavor, format, localeTransferable); 1186 } 1187 // Target data is a CharBuffer. Recur to obtain String and wrap. 1188 } else if (flavor.isRepresentationClassCharBuffer()) { 1189 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1190 throw new IOException("cannot transfer non-text data as CharBuffer"); 1191 } 1192 1193 CharBuffer buffer = CharBuffer.wrap( 1194 translateBytesToString(bytes,format, localeTransferable)); 1195 1196 theObject = constructFlavoredObject(buffer, flavor, CharBuffer.class); 1197 1198 // Target data is a char array. Recur to obtain String and convert to 1199 // char array. 1200 } else if (char[].class.equals(flavor.getRepresentationClass())) { 1201 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1202 throw new IOException 1203 ("cannot transfer non-text data as char array"); 1204 } 1205 1206 theObject = translateBytesToString( 1207 bytes, format, localeTransferable).toCharArray(); 1208 1209 // Target data is a ByteBuffer. For arbitrary flavors, just return 1210 // the raw bytes. For text flavors, convert to a String to strip 1211 // terminators and search-and-replace EOLN, then reencode according to 1212 // the requested flavor. 1213 } else if (flavor.isRepresentationClassByteBuffer()) { 1214 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1215 bytes = translateBytesToString( 1216 bytes, format, localeTransferable).getBytes( 1217 DataFlavorUtil.getTextCharset(flavor) 1218 ); 1219 } 1220 1221 ByteBuffer buffer = ByteBuffer.wrap(bytes); 1222 theObject = constructFlavoredObject(buffer, flavor, ByteBuffer.class); 1223 1224 // Target data is a byte array. For arbitrary flavors, just return 1225 // the raw bytes. For text flavors, convert to a String to strip 1226 // terminators and search-and-replace EOLN, then reencode according to 1227 // the requested flavor. 1228 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 1229 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1230 theObject = translateBytesToString( 1231 bytes, format, localeTransferable 1232 ).getBytes(DataFlavorUtil.getTextCharset(flavor)); 1233 } else { 1234 theObject = bytes; 1235 } 1236 1237 // Target data is an InputStream. For arbitrary flavors, just return 1238 // the raw bytes. For text flavors, decode to strip terminators and 1239 // search-and-replace EOLN, then reencode according to the requested 1240 // flavor. 1241 } else if (flavor.isRepresentationClassInputStream()) { 1242 1243 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1244 theObject = translateStream(bais, flavor, format, localeTransferable); 1245 } 1246 1247 } else if (flavor.isRepresentationClassRemote()) { 1248 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 1249 ObjectInputStream ois = new ObjectInputStream(bais)) { 1250 1251 theObject = DataFlavorUtil.RMI.getMarshalledObject(ois.readObject()); 1252 } catch (Exception e) { 1253 throw new IOException(e.getMessage()); 1254 } 1255 1256 // Target data is Serializable 1257 } else if (flavor.isRepresentationClassSerializable()) { 1258 1259 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1260 theObject = translateStream(bais, flavor, format, localeTransferable); 1261 } 1262 1263 // Target data is Image 1264 } else if (DataFlavor.imageFlavor.equals(flavor)) { 1265 if (!isImageFormat(format)) { 1266 throw new IOException("data translation failed"); 1267 } 1268 1269 theObject = platformImageBytesToImage(bytes, format); 1270 } 1271 1272 if (theObject == null) { 1273 throw new IOException("data translation failed"); 1274 } 1275 1276 return theObject; 1277 1278 } 1279 1280 /** 1281 * Primary translation function for translating 1282 * an InputStream into an Object, given a source format and a target 1283 * DataFlavor. 1284 */ 1285 @SuppressWarnings("deprecation") 1286 public Object translateStream(InputStream str, DataFlavor flavor, 1287 long format, Transferable localeTransferable) 1288 throws IOException 1289 { 1290 1291 Object theObject = null; 1292 // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor 1293 // where possible. 1294 if (isURIListFormat(format) 1295 && DataFlavor.javaFileListFlavor.equals(flavor)) 1296 { 1297 1298 URI[] uris = dragQueryURIs(str, format, localeTransferable); 1299 if (uris == null) { 1300 return null; 1301 } 1302 List<File> files = new ArrayList<>(); 1303 for (URI uri : uris) { 1304 try { 1305 files.add(new File(uri)); 1306 } catch (IllegalArgumentException illegalArg) { 1307 // When converting from URIs to less generic files, 1308 // common practice (Wine, SWT) seems to be to 1309 // silently drop the URIs that aren't local files. 1310 } 1311 } 1312 theObject = files; 1313 1314 // Target data is a String. Strip terminating NUL bytes. Decode bytes 1315 // into characters. Search-and-replace EOLN. 1316 } else if (String.class.equals(flavor.getRepresentationClass()) && 1317 DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1318 1319 return translateBytesToString(inputStreamToByteArray(str), 1320 format, localeTransferable); 1321 1322 // Special hack to maintain backwards-compatibility with the brokenness 1323 // of StringSelection. Return a StringReader instead of an InputStream. 1324 // Recur to obtain String and encapsulate. 1325 } else if (DataFlavor.plainTextFlavor.equals(flavor)) { 1326 theObject = new StringReader(translateBytesToString( 1327 inputStreamToByteArray(str), 1328 format, localeTransferable)); 1329 1330 // Target data is an InputStream. For arbitrary flavors, just return 1331 // the raw bytes. For text flavors, decode to strip terminators and 1332 // search-and-replace EOLN, then reencode according to the requested 1333 // flavor. 1334 } else if (flavor.isRepresentationClassInputStream()) { 1335 theObject = translateStreamToInputStream(str, flavor, format, 1336 localeTransferable); 1337 1338 // Target data is a Reader. Obtain data in InputStream format, encoded 1339 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode 1340 // back to chars on demand. 1341 } else if (flavor.isRepresentationClassReader()) { 1342 if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1343 throw new IOException 1344 ("cannot transfer non-text data as Reader"); 1345 } 1346 1347 InputStream is = (InputStream)translateStreamToInputStream( 1348 str, DataFlavor.plainTextFlavor, 1349 format, localeTransferable); 1350 1351 String unicode = DataFlavorUtil.getTextCharset(DataFlavor.plainTextFlavor); 1352 1353 Reader reader = new InputStreamReader(is, unicode); 1354 1355 theObject = constructFlavoredObject(reader, flavor, Reader.class); 1356 // Target data is a byte array 1357 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 1358 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1359 theObject = translateBytesToString(inputStreamToByteArray(str), format, localeTransferable) 1360 .getBytes(DataFlavorUtil.getTextCharset(flavor)); 1361 } else { 1362 theObject = inputStreamToByteArray(str); 1363 } 1364 // Target data is an RMI object 1365 } else if (flavor.isRepresentationClassRemote()) { 1366 try (ObjectInputStream ois = new ObjectInputStream(str)) { 1367 theObject = DataFlavorUtil.RMI.getMarshalledObject(ois.readObject()); 1368 } catch (Exception e) { 1369 throw new IOException(e.getMessage()); 1370 } 1371 1372 // Target data is Serializable 1373 } else if (flavor.isRepresentationClassSerializable()) { 1374 try (ObjectInputStream ois = 1375 new ObjectInputStream(str)) 1376 { 1377 theObject = ois.readObject(); 1378 } catch (Exception e) { 1379 throw new IOException(e.getMessage()); 1380 } 1381 // Target data is Image 1382 } else if (DataFlavor.imageFlavor.equals(flavor)) { 1383 if (!isImageFormat(format)) { 1384 throw new IOException("data translation failed"); 1385 } 1386 theObject = platformImageBytesToImage(inputStreamToByteArray(str), format); 1387 } 1388 1389 if (theObject == null) { 1390 throw new IOException("data translation failed"); 1391 } 1392 1393 return theObject; 1394 1395 } 1396 1397 /** 1398 * For arbitrary flavors, just use the raw InputStream. For text flavors, 1399 * ReencodingInputStream will decode and reencode the InputStream on demand 1400 * so that we can strip terminators and search-and-replace EOLN. 1401 */ 1402 private Object translateStreamToInputStream 1403 (InputStream str, DataFlavor flavor, long format, 1404 Transferable localeTransferable) throws IOException 1405 { 1406 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1407 str = new ReencodingInputStream 1408 (str, format, DataFlavorUtil.getTextCharset(flavor), 1409 localeTransferable); 1410 } 1411 1412 return constructFlavoredObject(str, flavor, InputStream.class); 1413 } 1414 1415 /** 1416 * We support representations which are exactly of the specified Class, 1417 * and also arbitrary Objects which have a constructor which takes an 1418 * instance of the Class as its sole parameter. 1419 */ 1420 private Object constructFlavoredObject(Object arg, DataFlavor flavor, 1421 Class<?> clazz) 1422 throws IOException 1423 { 1424 final Class<?> dfrc = flavor.getRepresentationClass(); 1425 1426 if (clazz.equals(dfrc)) { 1427 return arg; // simple case 1428 } else { 1429 Constructor<?>[] constructors; 1430 1431 try { 1432 constructors = AccessController.doPrivileged( 1433 (PrivilegedAction<Constructor<?>[]>) dfrc::getConstructors); 1434 } catch (SecurityException se) { 1435 throw new IOException(se.getMessage()); 1436 } 1437 1438 Constructor<?> constructor = Stream.of(constructors) 1439 .filter(c -> Modifier.isPublic(c.getModifiers())) 1440 .filter(c -> { 1441 Class<?>[] ptypes = c.getParameterTypes(); 1442 return ptypes != null 1443 && ptypes.length == 1 1444 && clazz.equals(ptypes[0]); 1445 }) 1446 .findFirst() 1447 .orElseThrow(() -> 1448 new IOException("can't find <init>(L"+ clazz + ";)V for class: " + dfrc.getName())); 1449 1450 try { 1451 return constructor.newInstance(arg); 1452 } catch (Exception e) { 1453 throw new IOException(e.getMessage()); 1454 } 1455 } 1456 } 1457 1458 /** 1459 * Used for decoding and reencoding an InputStream on demand so that we 1460 * can strip NUL terminators and perform EOLN search-and-replace. 1461 */ 1462 public class ReencodingInputStream extends InputStream { 1463 BufferedReader wrapped; 1464 final char[] in = new char[2]; 1465 byte[] out; 1466 1467 CharsetEncoder encoder; 1468 CharBuffer inBuf; 1469 ByteBuffer outBuf; 1470 1471 char[] eoln; 1472 int numTerminators; 1473 1474 boolean eos; 1475 int index, limit; 1476 1477 public ReencodingInputStream(InputStream bytestream, long format, 1478 String targetEncoding, 1479 Transferable localeTransferable) 1480 throws IOException 1481 { 1482 Long lFormat = format; 1483 1484 String sourceEncoding = getBestCharsetForTextFormat(format, localeTransferable); 1485 wrapped = new BufferedReader(new InputStreamReader(bytestream, sourceEncoding)); 1486 1487 if (targetEncoding == null) { 1488 // Throw NullPointerException for compatibility with the former 1489 // call to sun.io.CharToByteConverter.getConverter(null) 1490 // (Charset.forName(null) throws unspecified IllegalArgumentException 1491 // now; see 6228568) 1492 throw new NullPointerException("null target encoding"); 1493 } 1494 1495 try { 1496 encoder = Charset.forName(targetEncoding).newEncoder(); 1497 out = new byte[(int)(encoder.maxBytesPerChar() * 2 + 0.5)]; 1498 inBuf = CharBuffer.wrap(in); 1499 outBuf = ByteBuffer.wrap(out); 1500 } catch (IllegalCharsetNameException 1501 | UnsupportedCharsetException 1502 | UnsupportedOperationException e) { 1503 throw new IOException(e.toString()); 1504 } 1505 1506 String sEoln = nativeEOLNs.get(lFormat); 1507 if (sEoln != null) { 1508 eoln = sEoln.toCharArray(); 1509 } 1510 1511 // A hope and a prayer that this works generically. This will 1512 // definitely work on Win32. 1513 Integer terminators = nativeTerminators.get(lFormat); 1514 if (terminators != null) { 1515 numTerminators = terminators; 1516 } 1517 } 1518 1519 private int readChar() throws IOException { 1520 int c = wrapped.read(); 1521 1522 if (c == -1) { // -1 is EOS 1523 eos = true; 1524 return -1; 1525 } 1526 1527 // "c == 0" is not quite correct, but good enough on Windows. 1528 if (numTerminators > 0 && c == 0) { 1529 eos = true; 1530 return -1; 1531 } else if (eoln != null && matchCharArray(eoln, c)) { 1532 c = '\n' & 0xFFFF; 1533 } 1534 1535 return c; 1536 } 1537 1538 public int read() throws IOException { 1539 if (eos) { 1540 return -1; 1541 } 1542 1543 if (index >= limit) { 1544 // deal with supplementary characters 1545 int c = readChar(); 1546 if (c == -1) { 1547 return -1; 1548 } 1549 1550 in[0] = (char) c; 1551 in[1] = 0; 1552 inBuf.limit(1); 1553 if (Character.isHighSurrogate((char) c)) { 1554 c = readChar(); 1555 if (c != -1) { 1556 in[1] = (char) c; 1557 inBuf.limit(2); 1558 } 1559 } 1560 1561 inBuf.rewind(); 1562 outBuf.limit(out.length).rewind(); 1563 encoder.encode(inBuf, outBuf, false); 1564 outBuf.flip(); 1565 limit = outBuf.limit(); 1566 1567 index = 0; 1568 1569 return read(); 1570 } else { 1571 return out[index++] & 0xFF; 1572 } 1573 } 1574 1575 public int available() throws IOException { 1576 return ((eos) ? 0 : (limit - index)); 1577 } 1578 1579 public void close() throws IOException { 1580 wrapped.close(); 1581 } 1582 1583 /** 1584 * Checks to see if the next array.length characters in wrapped 1585 * match array. The first character is provided as c. Subsequent 1586 * characters are read from wrapped itself. When this method returns, 1587 * the wrapped index may be different from what it was when this 1588 * method was called. 1589 */ 1590 private boolean matchCharArray(char[] array, int c) 1591 throws IOException 1592 { 1593 wrapped.mark(array.length); // BufferedReader supports mark 1594 1595 int count = 0; 1596 if ((char)c == array[0]) { 1597 for (count = 1; count < array.length; count++) { 1598 c = wrapped.read(); 1599 if (c == -1 || ((char)c) != array[count]) { 1600 break; 1601 } 1602 } 1603 } 1604 1605 if (count == array.length) { 1606 return true; 1607 } else { 1608 wrapped.reset(); 1609 return false; 1610 } 1611 } 1612 } 1613 1614 /** 1615 * Decodes a byte array into a set of String filenames. 1616 */ 1617 protected abstract String[] dragQueryFile(byte[] bytes); 1618 1619 /** 1620 * Decodes URIs from either a byte array or a stream. 1621 */ 1622 protected URI[] dragQueryURIs(InputStream stream, 1623 long format, 1624 Transferable localeTransferable) 1625 throws IOException 1626 { 1627 throw new IOException( 1628 new UnsupportedOperationException("not implemented on this platform")); 1629 } 1630 1631 /** 1632 * Translates either a byte array or an input stream which contain 1633 * platform-specific image data in the given format into an Image. 1634 */ 1635 1636 1637 protected abstract Image platformImageBytesToImage( 1638 byte[] bytes,long format) throws IOException; 1639 1640 /** 1641 * Translates either a byte array or an input stream which contain 1642 * an image data in the given standard format into an Image. 1643 * 1644 * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif 1645 */ 1646 protected Image standardImageBytesToImage( 1647 byte[] bytes, String mimeType) throws IOException 1648 { 1649 1650 Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByMIMEType(mimeType); 1651 1652 if (!readerIterator.hasNext()) { 1653 throw new IOException("No registered service provider can decode " + 1654 " an image from " + mimeType); 1655 } 1656 1657 IOException ioe = null; 1658 1659 while (readerIterator.hasNext()) { 1660 ImageReader imageReader = readerIterator.next(); 1661 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1662 try (ImageInputStream imageInputStream = ImageIO.createImageInputStream(bais)) { 1663 ImageReadParam param = imageReader.getDefaultReadParam(); 1664 imageReader.setInput(imageInputStream, true, true); 1665 BufferedImage bufferedImage = imageReader.read(imageReader.getMinIndex(), param); 1666 if (bufferedImage != null) { 1667 return bufferedImage; 1668 } 1669 } finally { 1670 imageReader.dispose(); 1671 } 1672 } catch (IOException e) { 1673 ioe = e; 1674 continue; 1675 } 1676 } 1677 1678 if (ioe == null) { 1679 ioe = new IOException("Registered service providers failed to decode" 1680 + " an image from " + mimeType); 1681 } 1682 1683 throw ioe; 1684 } 1685 1686 /** 1687 * Translates a Java Image into a byte array which contains platform- 1688 * specific image data in the given format. 1689 */ 1690 protected abstract byte[] imageToPlatformBytes(Image image, long format) 1691 throws IOException; 1692 1693 /** 1694 * Translates a Java Image into a byte array which contains 1695 * an image data in the given standard format. 1696 * 1697 * @param mimeType image MIME type, such as: image/png, image/jpeg 1698 */ 1699 protected byte[] imageToStandardBytes(Image image, String mimeType) 1700 throws IOException { 1701 IOException originalIOE = null; 1702 1703 Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType); 1704 1705 if (!writerIterator.hasNext()) { 1706 throw new IOException("No registered service provider can encode " + 1707 " an image to " + mimeType); 1708 } 1709 1710 if (image instanceof RenderedImage) { 1711 // Try to encode the original image. 1712 try { 1713 return imageToStandardBytesImpl((RenderedImage)image, mimeType); 1714 } catch (IOException ioe) { 1715 originalIOE = ioe; 1716 } 1717 } 1718 1719 // Retry with a BufferedImage. 1720 int width = 0; 1721 int height = 0; 1722 if (image instanceof ToolkitImage) { 1723 ImageRepresentation ir = ((ToolkitImage)image).getImageRep(); 1724 ir.reconstruct(ImageObserver.ALLBITS); 1725 width = ir.getWidth(); 1726 height = ir.getHeight(); 1727 } else { 1728 width = image.getWidth(null); 1729 height = image.getHeight(null); 1730 } 1731 1732 ColorModel model = ColorModel.getRGBdefault(); 1733 WritableRaster raster = 1734 model.createCompatibleWritableRaster(width, height); 1735 1736 BufferedImage bufferedImage = 1737 new BufferedImage(model, raster, model.isAlphaPremultiplied(), 1738 null); 1739 1740 Graphics g = bufferedImage.getGraphics(); 1741 try { 1742 g.drawImage(image, 0, 0, width, height, null); 1743 } finally { 1744 g.dispose(); 1745 } 1746 1747 try { 1748 return imageToStandardBytesImpl(bufferedImage, mimeType); 1749 } catch (IOException ioe) { 1750 if (originalIOE != null) { 1751 throw originalIOE; 1752 } else { 1753 throw ioe; 1754 } 1755 } 1756 } 1757 1758 byte[] imageToStandardBytesImpl(RenderedImage renderedImage, 1759 String mimeType) 1760 throws IOException { 1761 1762 Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType); 1763 1764 ImageTypeSpecifier typeSpecifier = 1765 new ImageTypeSpecifier(renderedImage); 1766 1767 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1768 IOException ioe = null; 1769 1770 while (writerIterator.hasNext()) { 1771 ImageWriter imageWriter = writerIterator.next(); 1772 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider(); 1773 1774 if (!writerSpi.canEncodeImage(typeSpecifier)) { 1775 continue; 1776 } 1777 1778 try { 1779 try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(baos)) { 1780 imageWriter.setOutput(imageOutputStream); 1781 imageWriter.write(renderedImage); 1782 imageOutputStream.flush(); 1783 } 1784 } catch (IOException e) { 1785 imageWriter.dispose(); 1786 baos.reset(); 1787 ioe = e; 1788 continue; 1789 } 1790 1791 imageWriter.dispose(); 1792 baos.close(); 1793 return baos.toByteArray(); 1794 } 1795 1796 baos.close(); 1797 1798 if (ioe == null) { 1799 ioe = new IOException("Registered service providers failed to encode " 1800 + renderedImage + " to " + mimeType); 1801 } 1802 1803 throw ioe; 1804 } 1805 1806 /** 1807 * Concatenates the data represented by two objects. Objects can be either 1808 * byte arrays or instances of {@code InputStream}. If both arguments 1809 * are byte arrays byte array will be returned. Otherwise an 1810 * {@code InputStream} will be returned. 1811 * <p> 1812 * Currently is only called from native code to prepend palette data to 1813 * platform-specific image data during image transfer on Win32. 1814 * 1815 * @param obj1 the first object to be concatenated. 1816 * @param obj2 the second object to be concatenated. 1817 * @return a byte array or an {@code InputStream} which represents 1818 * a logical concatenation of the two arguments. 1819 * @throws NullPointerException is either of the arguments is 1820 * {@code null} 1821 * @throws ClassCastException is either of the arguments is 1822 * neither byte array nor an instance of {@code InputStream}. 1823 */ 1824 private Object concatData(Object obj1, Object obj2) { 1825 InputStream str1 = null; 1826 InputStream str2 = null; 1827 1828 if (obj1 instanceof byte[]) { 1829 byte[] arr1 = (byte[])obj1; 1830 if (obj2 instanceof byte[]) { 1831 byte[] arr2 = (byte[])obj2; 1832 byte[] ret = new byte[arr1.length + arr2.length]; 1833 System.arraycopy(arr1, 0, ret, 0, arr1.length); 1834 System.arraycopy(arr2, 0, ret, arr1.length, arr2.length); 1835 return ret; 1836 } else { 1837 str1 = new ByteArrayInputStream(arr1); 1838 str2 = (InputStream)obj2; 1839 } 1840 } else { 1841 str1 = (InputStream)obj1; 1842 if (obj2 instanceof byte[]) { 1843 str2 = new ByteArrayInputStream((byte[])obj2); 1844 } else { 1845 str2 = (InputStream)obj2; 1846 } 1847 } 1848 1849 return new SequenceInputStream(str1, str2); 1850 } 1851 1852 public byte[] convertData(final Object source, 1853 final Transferable contents, 1854 final long format, 1855 final Map<Long, DataFlavor> formatMap, 1856 final boolean isToolkitThread) 1857 throws IOException 1858 { 1859 byte[] ret = null; 1860 1861 /* 1862 * If the current thread is the Toolkit thread we should post a 1863 * Runnable to the event dispatch thread associated with source Object, 1864 * since translateTransferable() calls Transferable.getTransferData() 1865 * that may contain client code. 1866 */ 1867 if (isToolkitThread) try { 1868 final Stack<byte[]> stack = new Stack<>(); 1869 final Runnable dataConverter = new Runnable() { 1870 // Guard against multiple executions. 1871 private boolean done = false; 1872 public void run() { 1873 if (done) { 1874 return; 1875 } 1876 byte[] data = null; 1877 try { 1878 DataFlavor flavor = formatMap.get(format); 1879 if (flavor != null) { 1880 data = translateTransferable(contents, flavor, format); 1881 } 1882 } catch (Exception e) { 1883 e.printStackTrace(); 1884 data = null; 1885 } 1886 try { 1887 getToolkitThreadBlockedHandler().lock(); 1888 stack.push(data); 1889 getToolkitThreadBlockedHandler().exit(); 1890 } finally { 1891 getToolkitThreadBlockedHandler().unlock(); 1892 done = true; 1893 } 1894 } 1895 }; 1896 1897 final AppContext appContext = SunToolkit.targetToAppContext(source); 1898 1899 getToolkitThreadBlockedHandler().lock(); 1900 1901 if (appContext != null) { 1902 appContext.put(DATA_CONVERTER_KEY, dataConverter); 1903 } 1904 1905 SunToolkit.executeOnEventHandlerThread(source, dataConverter); 1906 1907 while (stack.empty()) { 1908 getToolkitThreadBlockedHandler().enter(); 1909 } 1910 1911 if (appContext != null) { 1912 appContext.remove(DATA_CONVERTER_KEY); 1913 } 1914 1915 ret = stack.pop(); 1916 } finally { 1917 getToolkitThreadBlockedHandler().unlock(); 1918 } else { 1919 DataFlavor flavor = formatMap.get(format); 1920 if (flavor != null) { 1921 ret = translateTransferable(contents, flavor, format); 1922 } 1923 } 1924 1925 return ret; 1926 } 1927 1928 public void processDataConversionRequests() { 1929 if (EventQueue.isDispatchThread()) { 1930 AppContext appContext = AppContext.getAppContext(); 1931 getToolkitThreadBlockedHandler().lock(); 1932 try { 1933 Runnable dataConverter = 1934 (Runnable)appContext.get(DATA_CONVERTER_KEY); 1935 if (dataConverter != null) { 1936 dataConverter.run(); 1937 appContext.remove(DATA_CONVERTER_KEY); 1938 } 1939 } finally { 1940 getToolkitThreadBlockedHandler().unlock(); 1941 } 1942 } 1943 } 1944 1945 public abstract ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler(); 1946 1947 /** 1948 * Helper function to reduce a Map with Long keys to a long array. 1949 * <p> 1950 * The map keys are sorted according to the native formats preference 1951 * order. 1952 */ 1953 public static long[] keysToLongArray(SortedMap<Long, ?> map) { 1954 Set<Long> keySet = map.keySet(); 1955 long[] retval = new long[keySet.size()]; 1956 int i = 0; 1957 for (Iterator<Long> iter = keySet.iterator(); iter.hasNext(); i++) { 1958 retval[i] = iter.next(); 1959 } 1960 return retval; 1961 } 1962 1963 /** 1964 * Helper function to convert a Set of DataFlavors to a sorted array. 1965 * The array will be sorted according to {@code DataFlavorComparator}. 1966 */ 1967 public static DataFlavor[] setToSortedDataFlavorArray(Set<DataFlavor> flavorsSet) { 1968 DataFlavor[] flavors = new DataFlavor[flavorsSet.size()]; 1969 flavorsSet.toArray(flavors); 1970 final Comparator<DataFlavor> comparator = DataFlavorUtil.getDataFlavorComparator().reversed(); 1971 Arrays.sort(flavors, comparator); 1972 return flavors; 1973 } 1974 1975 /** 1976 * Helper function to convert an InputStream to a byte[] array. 1977 */ 1978 protected static byte[] inputStreamToByteArray(InputStream str) 1979 throws IOException 1980 { 1981 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 1982 int len = 0; 1983 byte[] buf = new byte[8192]; 1984 1985 while ((len = str.read(buf)) != -1) { 1986 baos.write(buf, 0, len); 1987 } 1988 1989 return baos.toByteArray(); 1990 } 1991 } 1992 1993 /** 1994 * Returns platform-specific mappings for the specified native. 1995 * If there are no platform-specific mappings for this native, the method 1996 * returns an empty {@code List}. 1997 */ 1998 public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) { 1999 return new LinkedHashSet<>(); 2000 } 2001 2002 /** 2003 * Returns platform-specific mappings for the specified flavor. 2004 * If there are no platform-specific mappings for this flavor, the method 2005 * returns an empty {@code List}. 2006 */ 2007 public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) { 2008 return new LinkedHashSet<>(); 2009 } 2010 }