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