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.InvocationTargetException; 65 import java.lang.reflect.Method; 66 import java.lang.reflect.Modifier; 67 68 import java.security.AccessController; 69 import java.security.PrivilegedAction; 70 import java.security.PrivilegedActionException; 71 import java.security.PrivilegedExceptionAction; 72 import java.security.ProtectionDomain; 73 74 import java.util.*; 75 76 import sun.util.logging.PlatformLogger; 77 78 import sun.awt.AppContext; 79 import sun.awt.SunToolkit; 80 81 import java.awt.image.BufferedImage; 82 import java.awt.image.ImageObserver; 83 import java.awt.image.RenderedImage; 84 import java.awt.image.WritableRaster; 85 import java.awt.image.ColorModel; 86 87 import javax.imageio.ImageIO; 88 import javax.imageio.ImageReader; 89 import javax.imageio.ImageReadParam; 90 import javax.imageio.ImageWriter; 91 import javax.imageio.ImageTypeSpecifier; 92 93 import javax.imageio.spi.ImageWriterSpi; 94 95 import javax.imageio.stream.ImageInputStream; 96 import javax.imageio.stream.ImageOutputStream; 97 98 import sun.awt.image.ImageRepresentation; 99 import sun.awt.image.ToolkitImage; 100 101 import java.io.FilePermission; 102 import java.util.stream.Stream; 103 104 105 /** 106 * Provides a set of functions to be shared among the DataFlavor class and 107 * platform-specific data transfer implementations. 108 * 109 * The concept of "flavors" and "natives" is extended to include "formats", 110 * which are the numeric values Win32 and X11 use to express particular data 111 * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and 112 * getFlavorsForNatives(String[]) functions, DataTransferer provides a set 113 * of getFormatsFor(Transferable|Flavor|Flavors) and 114 * getFlavorsFor(Format|Formats) functions. 115 * 116 * Also provided are functions for translating a Transferable into a byte 117 * array, given a source DataFlavor and a target format, and for translating 118 * a byte array or InputStream into an Object, given a source format and 119 * a target DataFlavor. 120 * 121 * @author David Mendenhall 122 * @author Danila Sinopalnikov 123 * 124 * @since 1.3.1 125 */ 126 public abstract class DataTransferer { 127 /** 128 * The <code>DataFlavor</code> representing a Java text encoding String 129 * encoded in UTF-8, where 130 * <pre> 131 * representationClass = [B 132 * mimeType = "application/x-java-text-encoding" 133 * </pre> 134 */ 135 public static final DataFlavor javaTextEncodingFlavor; 136 137 /** 138 * Lazy initialization of Standard Encodings. 139 */ 140 private static class StandardEncodingsHolder { 141 private static final SortedSet<String> standardEncodings = load(); 142 143 private static SortedSet<String> load() { 144 final Comparator<String> comparator = 145 new CharsetComparator(IndexedComparator.SELECT_WORST); 146 final SortedSet<String> tempSet = new TreeSet<>(comparator); 147 tempSet.add("US-ASCII"); 148 tempSet.add("ISO-8859-1"); 149 tempSet.add("UTF-8"); 150 tempSet.add("UTF-16BE"); 151 tempSet.add("UTF-16LE"); 152 tempSet.add("UTF-16"); 153 tempSet.add(Charset.defaultCharset().name()); 154 return Collections.unmodifiableSortedSet(tempSet); 155 } 156 } 157 158 /** 159 * Tracks whether a particular text/* MIME type supports the charset 160 * parameter. The Map is initialized with all of the standard MIME types 161 * listed in the DataFlavor.selectBestTextFlavor method comment. Additional 162 * entries may be added during the life of the JRE for text/<other> types. 163 */ 164 private static final Map<String, Boolean> textMIMESubtypeCharsetSupport; 165 166 /** 167 * A collection of all natives listed in flavormap.properties with 168 * a primary MIME type of "text". 169 */ 170 private static final Set<Long> textNatives = 171 Collections.synchronizedSet(new HashSet<>()); 172 173 /** 174 * The native encodings/charsets for the Set of textNatives. 175 */ 176 private static final Map<Long, String> nativeCharsets = 177 Collections.synchronizedMap(new HashMap<>()); 178 179 /** 180 * The end-of-line markers for the Set of textNatives. 181 */ 182 private static final Map<Long, String> nativeEOLNs = 183 Collections.synchronizedMap(new HashMap<>()); 184 185 /** 186 * The number of terminating NUL bytes for the Set of textNatives. 187 */ 188 private static final Map<Long, Integer> nativeTerminators = 189 Collections.synchronizedMap(new HashMap<>()); 190 191 /** 192 * The key used to store pending data conversion requests for an AppContext. 193 */ 194 private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY"; 195 196 private static final PlatformLogger dtLog = PlatformLogger.getLogger("sun.awt.datatransfer.DataTransfer"); 197 198 static { 199 DataFlavor tJavaTextEncodingFlavor = null; 200 try { 201 tJavaTextEncodingFlavor = new DataFlavor("application/x-java-text-encoding;class=\"[B\""); 202 } catch (ClassNotFoundException cannotHappen) { 203 } 204 javaTextEncodingFlavor = tJavaTextEncodingFlavor; 205 206 Map<String, Boolean> tempMap = new HashMap<>(17); 207 tempMap.put("sgml", Boolean.TRUE); 208 tempMap.put("xml", Boolean.TRUE); 209 tempMap.put("html", Boolean.TRUE); 210 tempMap.put("enriched", Boolean.TRUE); 211 tempMap.put("richtext", Boolean.TRUE); 212 tempMap.put("uri-list", Boolean.TRUE); 213 tempMap.put("directory", Boolean.TRUE); 214 tempMap.put("css", Boolean.TRUE); 215 tempMap.put("calendar", Boolean.TRUE); 216 tempMap.put("plain", Boolean.TRUE); 217 tempMap.put("rtf", Boolean.FALSE); 218 tempMap.put("tab-separated-values", Boolean.FALSE); 219 tempMap.put("t140", Boolean.FALSE); 220 tempMap.put("rfc822-headers", Boolean.FALSE); 221 tempMap.put("parityfec", Boolean.FALSE); 222 textMIMESubtypeCharsetSupport = Collections.synchronizedMap(tempMap); 223 } 224 225 /** 226 * The accessor method for the singleton DataTransferer instance. Note 227 * that in a headless environment, there may be no DataTransferer instance; 228 * instead, null will be returned. 229 */ 230 public static synchronized DataTransferer getInstance() { 231 return ((SunToolkit) Toolkit.getDefaultToolkit()).getDataTransferer(); 232 } 233 234 /** 235 * Converts an arbitrary text encoding to its canonical name. 236 */ 237 public static String canonicalName(String encoding) { 238 if (encoding == null) { 239 return null; 240 } 241 try { 242 return Charset.forName(encoding).name(); 243 } catch (IllegalCharsetNameException icne) { 244 return encoding; 245 } catch (UnsupportedCharsetException uce) { 246 return encoding; 247 } 248 } 249 250 /** 251 * If the specified flavor is a text flavor which supports the "charset" 252 * parameter, then this method returns that parameter, or the default 253 * charset if no such parameter was specified at construction. For non- 254 * text DataFlavors, and for non-charset text flavors, this method returns 255 * null. 256 */ 257 public static String getTextCharset(DataFlavor flavor) { 258 if (!isFlavorCharsetTextType(flavor)) { 259 return null; 260 } 261 262 String encoding = flavor.getParameter("charset"); 263 264 return (encoding != null) ? encoding : Charset.defaultCharset().name(); 265 } 266 267 /** 268 * Tests only whether the flavor's MIME type supports the charset 269 * parameter. Must only be called for flavors with a primary type of 270 * "text". 271 */ 272 public static boolean doesSubtypeSupportCharset(DataFlavor flavor) { 273 if (dtLog.isLoggable(PlatformLogger.Level.FINE)) { 274 if (!"text".equals(flavor.getPrimaryType())) { 275 dtLog.fine("Assertion (\"text\".equals(flavor.getPrimaryType())) failed"); 276 } 277 } 278 279 String subType = flavor.getSubType(); 280 if (subType == null) { 281 return false; 282 } 283 284 Boolean support = textMIMESubtypeCharsetSupport.get(subType); 285 286 if (support != null) { 287 return support; 288 } 289 290 boolean ret_val = (flavor.getParameter("charset") != null); 291 textMIMESubtypeCharsetSupport.put(subType, ret_val); 292 return ret_val; 293 } 294 public static boolean doesSubtypeSupportCharset(String subType, 295 String charset) 296 { 297 Boolean support = textMIMESubtypeCharsetSupport.get(subType); 298 299 if (support != null) { 300 return support; 301 } 302 303 boolean ret_val = (charset != null); 304 textMIMESubtypeCharsetSupport.put(subType, ret_val); 305 return ret_val; 306 } 307 308 /** 309 * Returns whether this flavor is a text type which supports the 310 * 'charset' parameter. 311 */ 312 public static boolean isFlavorCharsetTextType(DataFlavor flavor) { 313 // Although stringFlavor doesn't actually support the charset 314 // parameter (because its primary MIME type is not "text"), it should 315 // be treated as though it does. stringFlavor is semantically 316 // equivalent to "text/plain" data. 317 if (DataFlavor.stringFlavor.equals(flavor)) { 318 return true; 319 } 320 321 if (!"text".equals(flavor.getPrimaryType()) || 322 !doesSubtypeSupportCharset(flavor)) 323 { 324 return false; 325 } 326 327 Class<?> rep_class = flavor.getRepresentationClass(); 328 329 if (flavor.isRepresentationClassReader() || 330 String.class.equals(rep_class) || 331 flavor.isRepresentationClassCharBuffer() || 332 char[].class.equals(rep_class)) 333 { 334 return true; 335 } 336 337 if (!(flavor.isRepresentationClassInputStream() || 338 flavor.isRepresentationClassByteBuffer() || 339 byte[].class.equals(rep_class))) { 340 return false; 341 } 342 343 String charset = flavor.getParameter("charset"); 344 345 return (charset != null) 346 ? DataTransferer.isEncodingSupported(charset) 347 : true; // null equals default encoding which is always supported 348 } 349 350 /** 351 * Returns whether this flavor is a text type which does not support the 352 * 'charset' parameter. 353 */ 354 public static boolean isFlavorNoncharsetTextType(DataFlavor flavor) { 355 if (!"text".equals(flavor.getPrimaryType()) || 356 doesSubtypeSupportCharset(flavor)) 357 { 358 return false; 359 } 360 361 return (flavor.isRepresentationClassInputStream() || 362 flavor.isRepresentationClassByteBuffer() || 363 byte[].class.equals(flavor.getRepresentationClass())); 364 } 365 366 /** 367 * Determines whether this JRE can both encode and decode text in the 368 * specified encoding. 369 */ 370 private static boolean isEncodingSupported(String encoding) { 371 if (encoding == null) { 372 return false; 373 } 374 try { 375 return Charset.isSupported(encoding); 376 } catch (IllegalCharsetNameException icne) { 377 return false; 378 } 379 } 380 381 /** 382 * Returns {@code true} if the given type is a java.rmi.Remote. 383 */ 384 public static boolean isRemote(Class<?> type) { 385 return RMI.isRemote(type); 386 } 387 388 /** 389 * Returns an Iterator which traverses a SortedSet of Strings which are 390 * a total order of the standard character sets supported by the JRE. The 391 * ordering follows the same principles as DataFlavor.selectBestTextFlavor. 392 * So as to avoid loading all available character converters, optional, 393 * non-standard, character sets are not included. 394 */ 395 public static Set <String> standardEncodings() { 396 return StandardEncodingsHolder.standardEncodings; 397 } 398 399 /** 400 * Converts a FlavorMap to a FlavorTable. 401 */ 402 public static FlavorTable adaptFlavorMap(final FlavorMap map) { 403 if (map instanceof FlavorTable) { 404 return (FlavorTable)map; 405 } 406 407 return new FlavorTable() { 408 @Override 409 public Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] flavors) { 410 return map.getNativesForFlavors(flavors); 411 } 412 @Override 413 public Map<String, DataFlavor> getFlavorsForNatives(String[] natives) { 414 return map.getFlavorsForNatives(natives); 415 } 416 @Override 417 public List<String> getNativesForFlavor(DataFlavor flav) { 418 Map<DataFlavor, String> natives = getNativesForFlavors(new DataFlavor[]{flav}); 419 String nat = natives.get(flav); 420 if (nat != null) { 421 return Collections.singletonList(nat); 422 } else { 423 return Collections.emptyList(); 424 } 425 } 426 @Override 427 public List<DataFlavor> getFlavorsForNative(String nat) { 428 Map<String, DataFlavor> flavors = getFlavorsForNatives(new String[]{nat}); 429 DataFlavor flavor = flavors.get(nat); 430 if (flavor != null) { 431 return Collections.singletonList(flavor); 432 } else { 433 return Collections.emptyList(); 434 } 435 } 436 }; 437 } 438 439 /** 440 * Returns the default Unicode encoding for the platform. The encoding 441 * need not be canonical. This method is only used by the archaic function 442 * DataFlavor.getTextPlainUnicodeFlavor(). 443 */ 444 public abstract String getDefaultUnicodeEncoding(); 445 446 /** 447 * This method is called for text flavor mappings established while parsing 448 * the flavormap.properties file. It stores the "eoln" and "terminators" 449 * parameters which are not officially part of the MIME type. They are 450 * MIME parameters specific to the flavormap.properties file format. 451 */ 452 public void registerTextFlavorProperties(String nat, String charset, 453 String eoln, String terminators) { 454 Long format = getFormatForNativeAsLong(nat); 455 456 textNatives.add(format); 457 nativeCharsets.put(format, (charset != null && charset.length() != 0) 458 ? charset : Charset.defaultCharset().name()); 459 if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) { 460 nativeEOLNs.put(format, eoln); 461 } 462 if (terminators != null && terminators.length() != 0) { 463 Integer iTerminators = Integer.valueOf(terminators); 464 if (iTerminators > 0) { 465 nativeTerminators.put(format, iTerminators); 466 } 467 } 468 } 469 470 /** 471 * Determines whether the native corresponding to the specified long format 472 * was listed in the flavormap.properties file. 473 */ 474 protected boolean isTextFormat(long format) { 475 return textNatives.contains(Long.valueOf(format)); 476 } 477 478 protected String getCharsetForTextFormat(Long lFormat) { 479 return nativeCharsets.get(lFormat); 480 } 481 482 /** 483 * Specifies whether text imported from the native system in the specified 484 * format is locale-dependent. If so, when decoding such text, 485 * 'nativeCharsets' should be ignored, and instead, the Transferable should 486 * be queried for its javaTextEncodingFlavor data for the correct encoding. 487 */ 488 public abstract boolean isLocaleDependentTextFormat(long format); 489 490 /** 491 * Determines whether the DataFlavor corresponding to the specified long 492 * format is DataFlavor.javaFileListFlavor. 493 */ 494 public abstract boolean isFileFormat(long format); 495 496 /** 497 * Determines whether the DataFlavor corresponding to the specified long 498 * format is DataFlavor.imageFlavor. 499 */ 500 public abstract boolean isImageFormat(long format); 501 502 /** 503 * Determines whether the format is a URI list we can convert to 504 * a DataFlavor.javaFileListFlavor. 505 */ 506 protected boolean isURIListFormat(long format) { 507 return false; 508 } 509 510 /** 511 * Returns a Map whose keys are all of the possible formats into which the 512 * Transferable's transfer data flavors can be translated. The value of 513 * each key is the DataFlavor in which the Transferable's data should be 514 * requested when converting to the format. 515 * <p> 516 * The map keys are sorted according to the native formats preference 517 * order. 518 */ 519 public SortedMap<Long,DataFlavor> getFormatsForTransferable(Transferable contents, 520 FlavorTable map) 521 { 522 DataFlavor[] flavors = contents.getTransferDataFlavors(); 523 if (flavors == null) { 524 return Collections.emptySortedMap(); 525 } 526 return getFormatsForFlavors(flavors, map); 527 } 528 529 /** 530 * Returns a Map whose keys are all of the possible formats into which data 531 * in the specified DataFlavors can be translated. The value of each key 532 * is the DataFlavor in which the Transferable's data should be requested 533 * when converting to the format. 534 * <p> 535 * The map keys are sorted according to the native formats preference 536 * order. 537 * 538 * @param flavors the data flavors 539 * @param map the FlavorTable which contains mappings between 540 * DataFlavors and data formats 541 * @throws NullPointerException if flavors or map is <code>null</code> 542 */ 543 public SortedMap<Long, DataFlavor> getFormatsForFlavors(DataFlavor[] flavors, 544 FlavorTable map) 545 { 546 Map<Long,DataFlavor> formatMap = new HashMap<>(flavors.length); 547 Map<Long,DataFlavor> textPlainMap = new HashMap<>(flavors.length); 548 // Maps formats to indices that will be used to sort the formats 549 // according to the preference order. 550 // Larger index value corresponds to the more preferable format. 551 Map<Long, Integer> indexMap = new HashMap<>(flavors.length); 552 Map<Long, Integer> textPlainIndexMap = new HashMap<>(flavors.length); 553 554 int currentIndex = 0; 555 556 // Iterate backwards so that preferred DataFlavors are used over 557 // other DataFlavors. (See javadoc for 558 // Transferable.getTransferDataFlavors.) 559 for (int i = flavors.length - 1; i >= 0; i--) { 560 DataFlavor flavor = flavors[i]; 561 if (flavor == null) continue; 562 563 // Don't explicitly test for String, since it is just a special 564 // case of Serializable 565 if (flavor.isFlavorTextType() || 566 flavor.isFlavorJavaFileListType() || 567 DataFlavor.imageFlavor.equals(flavor) || 568 flavor.isRepresentationClassSerializable() || 569 flavor.isRepresentationClassInputStream() || 570 flavor.isRepresentationClassRemote()) 571 { 572 List<String> natives = map.getNativesForFlavor(flavor); 573 574 currentIndex += natives.size(); 575 576 for (String aNative : natives) { 577 Long lFormat = getFormatForNativeAsLong(aNative); 578 Integer index = currentIndex--; 579 580 formatMap.put(lFormat, flavor); 581 indexMap.put(lFormat, index); 582 583 // SystemFlavorMap.getNativesForFlavor will return 584 // text/plain natives for all text/*. While this is good 585 // for a single text/* flavor, we would prefer that 586 // text/plain native data come from a text/plain flavor. 587 if (("text".equals(flavor.getPrimaryType()) && 588 "plain".equals(flavor.getSubType())) || 589 flavor.equals(DataFlavor.stringFlavor)) { 590 textPlainMap.put(lFormat, flavor); 591 textPlainIndexMap.put(lFormat, index); 592 } 593 } 594 595 currentIndex += natives.size(); 596 } 597 } 598 599 formatMap.putAll(textPlainMap); 600 indexMap.putAll(textPlainIndexMap); 601 602 // Sort the map keys according to the formats preference order. 603 Comparator<Long> comparator = 604 new IndexOrderComparator(indexMap, IndexedComparator.SELECT_WORST); 605 SortedMap<Long, DataFlavor> sortedMap = new TreeMap<>(comparator); 606 sortedMap.putAll(formatMap); 607 608 return sortedMap; 609 } 610 611 /** 612 * Reduces the Map output for the root function to an array of the 613 * Map's keys. 614 */ 615 public long[] getFormatsForTransferableAsArray(Transferable contents, 616 FlavorTable map) { 617 return keysToLongArray(getFormatsForTransferable(contents, map)); 618 } 619 620 /** 621 * Returns a Map whose keys are all of the possible DataFlavors into which 622 * data in the specified formats can be translated. The value of each key 623 * is the format in which the Clipboard or dropped data should be requested 624 * when converting to the DataFlavor. 625 */ 626 public Map<DataFlavor, Long> getFlavorsForFormats(long[] formats, FlavorTable map) { 627 Map<DataFlavor, Long> flavorMap = new HashMap<>(formats.length); 628 Set<AbstractMap.SimpleEntry<Long, DataFlavor>> mappingSet = new HashSet<>(formats.length); 629 Set<DataFlavor> flavorSet = new HashSet<>(formats.length); 630 631 // First step: build flavorSet, mappingSet and initial flavorMap 632 // flavorSet - the set of all the DataFlavors into which 633 // data in the specified formats can be translated; 634 // mappingSet - the set of all the mappings from the specified formats 635 // into any DataFlavor; 636 // flavorMap - after this step, this map maps each of the DataFlavors 637 // from flavorSet to any of the specified formats. 638 for (long format : formats) { 639 String nat = getNativeForFormat(format); 640 List<DataFlavor> flavors = map.getFlavorsForNative(nat); 641 for (DataFlavor flavor : flavors) { 642 // Don't explicitly test for String, since it is just a special 643 // case of Serializable 644 if (flavor.isFlavorTextType() || 645 flavor.isFlavorJavaFileListType() || 646 DataFlavor.imageFlavor.equals(flavor) || 647 flavor.isRepresentationClassSerializable() || 648 flavor.isRepresentationClassInputStream() || 649 flavor.isRepresentationClassRemote()) { 650 651 AbstractMap.SimpleEntry<Long, DataFlavor> mapping = 652 new AbstractMap.SimpleEntry<>(format, flavor); 653 flavorMap.put(flavor, format); 654 mappingSet.add(mapping); 655 flavorSet.add(flavor); 656 } 657 } 658 } 659 660 // Second step: for each DataFlavor try to figure out which of the 661 // specified formats is the best to translate to this flavor. 662 // Then map each flavor to the best format. 663 // For the given flavor, FlavorTable indicates which native will 664 // best reflect data in the specified flavor to the underlying native 665 // platform. We assume that this native is the best to translate 666 // to this flavor. 667 // Note: FlavorTable allows one-way mappings, so we can occasionally 668 // map a flavor to the format for which the corresponding 669 // format-to-flavor mapping doesn't exist. For this reason we have built 670 // a mappingSet of all format-to-flavor mappings for the specified formats 671 // and check if the format-to-flavor mapping exists for the 672 // (flavor,format) pair being added. 673 for (DataFlavor flavor : flavorSet) { 674 List<String> natives = map.getNativesForFlavor(flavor); 675 for (String aNative : natives) { 676 Long lFormat = getFormatForNativeAsLong(aNative); 677 if (mappingSet.contains(new AbstractMap.SimpleEntry<>(lFormat, flavor))) { 678 flavorMap.put(flavor, lFormat); 679 break; 680 } 681 } 682 } 683 684 return flavorMap; 685 } 686 687 /** 688 * Returns a Set of all DataFlavors for which 689 * 1) a mapping from at least one of the specified formats exists in the 690 * specified map and 691 * 2) the data translation for this mapping can be performed by the data 692 * transfer subsystem. 693 * 694 * @param formats the data formats 695 * @param map the FlavorTable which contains mappings between 696 * DataFlavors and data formats 697 * @throws NullPointerException if formats or map is <code>null</code> 698 */ 699 public Set<DataFlavor> getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) { 700 Set<DataFlavor> flavorSet = new HashSet<>(formats.length); 701 702 for (long format : formats) { 703 List<DataFlavor> flavors = map.getFlavorsForNative(getNativeForFormat(format)); 704 for (DataFlavor flavor : flavors) { 705 // Don't explicitly test for String, since it is just a special 706 // case of Serializable 707 if (flavor.isFlavorTextType() || 708 flavor.isFlavorJavaFileListType() || 709 DataFlavor.imageFlavor.equals(flavor) || 710 flavor.isRepresentationClassSerializable() || 711 flavor.isRepresentationClassInputStream() || 712 flavor.isRepresentationClassRemote()) { 713 flavorSet.add(flavor); 714 } 715 } 716 } 717 718 return flavorSet; 719 } 720 721 /** 722 * Returns an array of all DataFlavors for which 723 * 1) a mapping from at least one of the specified formats exists in the 724 * specified map and 725 * 2) the data translation for this mapping can be performed by the data 726 * transfer subsystem. 727 * The array will be sorted according to a 728 * <code>DataFlavorComparator</code> created with the specified 729 * map as an argument. 730 * 731 * @param formats the data formats 732 * @param map the FlavorTable which contains mappings between 733 * DataFlavors and data formats 734 * @throws NullPointerException if formats or map is <code>null</code> 735 */ 736 public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats, 737 FlavorTable map) { 738 // getFlavorsForFormatsAsSet() is less expensive than 739 // getFlavorsForFormats(). 740 return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map)); 741 } 742 743 /** 744 * Looks-up or registers the String native with the native data transfer 745 * system and returns a long format corresponding to that native. 746 */ 747 protected abstract Long getFormatForNativeAsLong(String str); 748 749 /** 750 * Looks-up the String native corresponding to the specified long format in 751 * the native data transfer system. 752 */ 753 protected abstract String getNativeForFormat(long format); 754 755 /* Contains common code for finding the best charset for 756 * clipboard string encoding/decoding, basing on clipboard 757 * format and localeTransferable(on decoding, if available) 758 */ 759 protected String getBestCharsetForTextFormat(Long lFormat, 760 Transferable localeTransferable) throws IOException 761 { 762 String charset = null; 763 if (localeTransferable != null && 764 isLocaleDependentTextFormat(lFormat) && 765 localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) { 766 try { 767 byte[] charsetNameBytes = (byte[])localeTransferable 768 .getTransferData(javaTextEncodingFlavor); 769 charset = new String(charsetNameBytes, StandardCharsets.UTF_8); 770 } catch (UnsupportedFlavorException cannotHappen) { 771 } 772 } else { 773 charset = getCharsetForTextFormat(lFormat); 774 } 775 if (charset == null) { 776 // Only happens when we have a custom text type. 777 charset = Charset.defaultCharset().name(); 778 } 779 return charset; 780 } 781 782 /** 783 * Translation function for converting string into 784 * a byte array. Search-and-replace EOLN. Encode into the 785 * target format. Append terminating NUL bytes. 786 * 787 * Java to Native string conversion 788 */ 789 private byte[] translateTransferableString(String str, 790 long format) throws IOException 791 { 792 Long lFormat = format; 793 String charset = getBestCharsetForTextFormat(lFormat, null); 794 // Search and replace EOLN. Note that if EOLN is "\n", then we 795 // never added an entry to nativeEOLNs anyway, so we'll skip this 796 // code altogether. 797 // windows: "abc\nde"->"abc\r\nde" 798 String eoln = nativeEOLNs.get(lFormat); 799 if (eoln != null) { 800 int length = str.length(); 801 StringBuilder buffer = new StringBuilder(length * 2); // 2 is a heuristic 802 for (int i = 0; i < length; i++) { 803 // Fix for 4914613 - skip native EOLN 804 if (str.startsWith(eoln, i)) { 805 buffer.append(eoln); 806 i += eoln.length() - 1; 807 continue; 808 } 809 char c = str.charAt(i); 810 if (c == '\n') { 811 buffer.append(eoln); 812 } else { 813 buffer.append(c); 814 } 815 } 816 str = buffer.toString(); 817 } 818 819 // Encode text in target format. 820 byte[] bytes = str.getBytes(charset); 821 822 // Append terminating NUL bytes. Note that if terminators is 0, 823 // the we never added an entry to nativeTerminators anyway, so 824 // we'll skip code altogether. 825 // "abcde" -> "abcde\0" 826 Integer terminators = nativeTerminators.get(lFormat); 827 if (terminators != null) { 828 int numTerminators = terminators; 829 byte[] terminatedBytes = 830 new byte[bytes.length + numTerminators]; 831 System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length); 832 for (int i = bytes.length; i < terminatedBytes.length; i++) { 833 terminatedBytes[i] = 0x0; 834 } 835 bytes = terminatedBytes; 836 } 837 return bytes; 838 } 839 840 /** 841 * Translating either a byte array or an InputStream into an String. 842 * Strip terminators and search-and-replace EOLN. 843 * 844 * Native to Java string conversion 845 */ 846 private String translateBytesToString(byte[] bytes, long format, 847 Transferable localeTransferable) 848 throws IOException 849 { 850 851 Long lFormat = format; 852 String charset = getBestCharsetForTextFormat(lFormat, localeTransferable); 853 854 // Locate terminating NUL bytes. Note that if terminators is 0, 855 // the we never added an entry to nativeTerminators anyway, so 856 // we'll skip code altogether. 857 858 // In other words: we are doing char alignment here basing on suggestion 859 // that count of zero-'terminators' is a number of bytes in one symbol 860 // for selected charset (clipboard format). It is not complitly true for 861 // multibyte coding like UTF-8, but helps understand the procedure. 862 // "abcde\0" -> "abcde" 863 864 String eoln = nativeEOLNs.get(lFormat); 865 Integer terminators = nativeTerminators.get(lFormat); 866 int count; 867 if (terminators != null) { 868 int numTerminators = terminators; 869 search: 870 for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) { 871 for (int i = count; i < count + numTerminators; i++) { 872 if (bytes[i] != 0x0) { 873 continue search; 874 } 875 } 876 // found terminators 877 break search; 878 } 879 } else { 880 count = bytes.length; 881 } 882 883 // Decode text to chars. Don't include any terminators. 884 String converted = new String(bytes, 0, count, charset); 885 886 // Search and replace EOLN. Note that if EOLN is "\n", then we 887 // never added an entry to nativeEOLNs anyway, so we'll skip this 888 // code altogether. 889 // Count of NUL-terminators and EOLN coding are platform-specific and 890 // loaded from flavormap.properties file 891 // windows: "abc\r\nde" -> "abc\nde" 892 893 if (eoln != null) { 894 895 /* Fix for 4463560: replace EOLNs symbol-by-symbol instead 896 * of using buf.replace() 897 */ 898 899 char[] buf = converted.toCharArray(); 900 char[] eoln_arr = eoln.toCharArray(); 901 int j = 0; 902 boolean match; 903 904 for (int i = 0; i < buf.length; ) { 905 // Catch last few bytes 906 if (i + eoln_arr.length > buf.length) { 907 buf[j++] = buf[i++]; 908 continue; 909 } 910 911 match = true; 912 for (int k = 0, l = i; k < eoln_arr.length; k++, l++) { 913 if (eoln_arr[k] != buf[l]) { 914 match = false; 915 break; 916 } 917 } 918 if (match) { 919 buf[j++] = '\n'; 920 i += eoln_arr.length; 921 } else { 922 buf[j++] = buf[i++]; 923 } 924 } 925 converted = new String(buf, 0, j); 926 } 927 928 return converted; 929 } 930 931 932 /** 933 * Primary translation function for translating a Transferable into 934 * a byte array, given a source DataFlavor and target format. 935 */ 936 public byte[] translateTransferable(Transferable contents, 937 DataFlavor flavor, 938 long format) throws IOException 939 { 940 // Obtain the transfer data in the source DataFlavor. 941 // 942 // Note that we special case DataFlavor.plainTextFlavor because 943 // StringSelection supports this flavor incorrectly -- instead of 944 // returning an InputStream as the DataFlavor representation class 945 // states, it returns a Reader. Instead of using this broken 946 // functionality, we request the data in stringFlavor (the other 947 // DataFlavor which StringSelection supports) and use the String 948 // translator. 949 Object obj; 950 boolean stringSelectionHack; 951 try { 952 obj = contents.getTransferData(flavor); 953 if (obj == null) { 954 return null; 955 } 956 if (flavor.equals(DataFlavor.plainTextFlavor) && 957 !(obj instanceof InputStream)) 958 { 959 obj = contents.getTransferData(DataFlavor.stringFlavor); 960 if (obj == null) { 961 return null; 962 } 963 stringSelectionHack = true; 964 } else { 965 stringSelectionHack = false; 966 } 967 } catch (UnsupportedFlavorException e) { 968 throw new IOException(e.getMessage()); 969 } 970 971 // Source data is a String. Search-and-replace EOLN. Encode into the 972 // target format. Append terminating NUL bytes. 973 if (stringSelectionHack || 974 (String.class.equals(flavor.getRepresentationClass()) && 975 isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 976 977 String str = removeSuspectedData(flavor, contents, (String)obj); 978 979 return translateTransferableString( 980 str, 981 format); 982 983 // Source data is a Reader. Convert to a String and recur. In the 984 // future, we may want to rewrite this so that we encode on demand. 985 } else if (flavor.isRepresentationClassReader()) { 986 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 987 throw new IOException 988 ("cannot transfer non-text data as Reader"); 989 } 990 991 StringBuilder buf = new StringBuilder(); 992 try (Reader r = (Reader)obj) { 993 int c; 994 while ((c = r.read()) != -1) { 995 buf.append((char)c); 996 } 997 } 998 999 return translateTransferableString( 1000 buf.toString(), 1001 format); 1002 1003 // Source data is a CharBuffer. Convert to a String and recur. 1004 } else if (flavor.isRepresentationClassCharBuffer()) { 1005 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1006 throw new IOException 1007 ("cannot transfer non-text data as CharBuffer"); 1008 } 1009 1010 CharBuffer buffer = (CharBuffer)obj; 1011 int size = buffer.remaining(); 1012 char[] chars = new char[size]; 1013 buffer.get(chars, 0, size); 1014 1015 return translateTransferableString( 1016 new String(chars), 1017 format); 1018 1019 // Source data is a char array. Convert to a String and recur. 1020 } else if (char[].class.equals(flavor.getRepresentationClass())) { 1021 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1022 throw new IOException 1023 ("cannot transfer non-text data as char array"); 1024 } 1025 1026 return translateTransferableString( 1027 new String((char[])obj), 1028 format); 1029 1030 // Source data is a ByteBuffer. For arbitrary flavors, simply return 1031 // the array. For text flavors, decode back to a String and recur to 1032 // reencode according to the requested format. 1033 } else if (flavor.isRepresentationClassByteBuffer()) { 1034 ByteBuffer buffer = (ByteBuffer)obj; 1035 int size = buffer.remaining(); 1036 byte[] bytes = new byte[size]; 1037 buffer.get(bytes, 0, size); 1038 1039 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1040 String sourceEncoding = DataTransferer.getTextCharset(flavor); 1041 return translateTransferableString( 1042 new String(bytes, sourceEncoding), 1043 format); 1044 } else { 1045 return bytes; 1046 } 1047 1048 // Source data is a byte array. For arbitrary flavors, simply return 1049 // the array. For text flavors, decode back to a String and recur to 1050 // reencode according to the requested format. 1051 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 1052 byte[] bytes = (byte[])obj; 1053 1054 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1055 String sourceEncoding = DataTransferer.getTextCharset(flavor); 1056 return translateTransferableString( 1057 new String(bytes, sourceEncoding), 1058 format); 1059 } else { 1060 return bytes; 1061 } 1062 // Source data is Image 1063 } else if (DataFlavor.imageFlavor.equals(flavor)) { 1064 if (!isImageFormat(format)) { 1065 throw new IOException("Data translation failed: " + 1066 "not an image format"); 1067 } 1068 1069 Image image = (Image)obj; 1070 byte[] bytes = imageToPlatformBytes(image, format); 1071 1072 if (bytes == null) { 1073 throw new IOException("Data translation failed: " + 1074 "cannot convert java image to native format"); 1075 } 1076 return bytes; 1077 } 1078 1079 byte[] theByteArray = null; 1080 1081 // Target data is a file list. Source data must be a 1082 // java.util.List which contains java.io.File or String instances. 1083 if (isFileFormat(format)) { 1084 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 1085 throw new IOException("data translation failed"); 1086 } 1087 1088 final List<?> list = (List<?>)obj; 1089 1090 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 1091 1092 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain); 1093 1094 try (ByteArrayOutputStream bos = convertFileListToBytes(fileList)) { 1095 theByteArray = bos.toByteArray(); 1096 } 1097 1098 // Target data is a URI list. Source data must be a 1099 // java.util.List which contains java.io.File or String instances. 1100 } else if (isURIListFormat(format)) { 1101 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 1102 throw new IOException("data translation failed"); 1103 } 1104 String nat = getNativeForFormat(format); 1105 String targetCharset = null; 1106 if (nat != null) { 1107 try { 1108 targetCharset = new DataFlavor(nat).getParameter("charset"); 1109 } catch (ClassNotFoundException cnfe) { 1110 throw new IOException(cnfe); 1111 } 1112 } 1113 if (targetCharset == null) { 1114 targetCharset = "UTF-8"; 1115 } 1116 final List<?> list = (List<?>)obj; 1117 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 1118 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain); 1119 final ArrayList<String> uriList = new ArrayList<>(fileList.size()); 1120 for (String fileObject : fileList) { 1121 final URI uri = new File(fileObject).toURI(); 1122 // Some implementations are fussy about the number of slashes (file:///path/to/file is best) 1123 try { 1124 uriList.add(new URI(uri.getScheme(), "", uri.getPath(), uri.getFragment()).toString()); 1125 } catch (URISyntaxException uriSyntaxException) { 1126 throw new IOException(uriSyntaxException); 1127 } 1128 } 1129 1130 byte[] eoln = "\r\n".getBytes(targetCharset); 1131 1132 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 1133 for (String uri : uriList) { 1134 byte[] bytes = uri.getBytes(targetCharset); 1135 bos.write(bytes, 0, bytes.length); 1136 bos.write(eoln, 0, eoln.length); 1137 } 1138 theByteArray = bos.toByteArray(); 1139 } 1140 1141 // Source data is an InputStream. For arbitrary flavors, just grab the 1142 // bytes and dump them into a byte array. For text flavors, decode back 1143 // to a String and recur to reencode according to the requested format. 1144 } else if (flavor.isRepresentationClassInputStream()) { 1145 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 1146 try (InputStream is = (InputStream)obj) { 1147 boolean eof = false; 1148 int avail = is.available(); 1149 byte[] tmp = new byte[avail > 8192 ? avail : 8192]; 1150 do { 1151 int aValue; 1152 if (!(eof = (aValue = is.read(tmp, 0, tmp.length)) == -1)) { 1153 bos.write(tmp, 0, aValue); 1154 } 1155 } while (!eof); 1156 } 1157 1158 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1159 byte[] bytes = bos.toByteArray(); 1160 String sourceEncoding = DataTransferer.getTextCharset(flavor); 1161 return translateTransferableString( 1162 new String(bytes, sourceEncoding), 1163 format); 1164 } 1165 theByteArray = bos.toByteArray(); 1166 } 1167 1168 1169 1170 // Source data is an RMI object 1171 } else if (flavor.isRepresentationClassRemote()) { 1172 1173 Object mo = RMI.newMarshalledObject(obj); 1174 theByteArray = convertObjectToBytes(mo); 1175 1176 // Source data is Serializable 1177 } else if (flavor.isRepresentationClassSerializable()) { 1178 1179 theByteArray = convertObjectToBytes(obj); 1180 1181 } else { 1182 throw new IOException("data translation failed"); 1183 } 1184 1185 1186 1187 return theByteArray; 1188 } 1189 1190 private static byte[] convertObjectToBytes(Object object) throws IOException { 1191 try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); 1192 ObjectOutputStream oos = new ObjectOutputStream(bos)) 1193 { 1194 oos.writeObject(object); 1195 return bos.toByteArray(); 1196 } 1197 } 1198 1199 protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException; 1200 1201 private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str) 1202 throws IOException 1203 { 1204 if (null == System.getSecurityManager() 1205 || !flavor.isMimeTypeEqual("text/uri-list")) 1206 { 1207 return str; 1208 } 1209 1210 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents); 1211 1212 try { 1213 return AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> { 1214 1215 StringBuilder allowedFiles = new StringBuilder(str.length()); 1216 String [] uriArray = str.split("(\\s)+"); 1217 1218 for (String fileName : uriArray) 1219 { 1220 File file = new File(fileName); 1221 if (file.exists() && 1222 !(isFileInWebstartedCache(file) || 1223 isForbiddenToRead(file, userProtectionDomain))) 1224 { 1225 if (0 != allowedFiles.length()) 1226 { 1227 allowedFiles.append("\\r\\n"); 1228 } 1229 1230 allowedFiles.append(fileName); 1231 } 1232 } 1233 1234 return allowedFiles.toString(); 1235 }); 1236 } catch (PrivilegedActionException pae) { 1237 throw new IOException(pae.getMessage(), pae); 1238 } 1239 } 1240 1241 private static ProtectionDomain getUserProtectionDomain(Transferable contents) { 1242 return contents.getClass().getProtectionDomain(); 1243 } 1244 1245 private boolean isForbiddenToRead (File file, ProtectionDomain protectionDomain) 1246 { 1247 if (null == protectionDomain) { 1248 return false; 1249 } 1250 try { 1251 FilePermission filePermission = 1252 new FilePermission(file.getCanonicalPath(), "read, delete"); 1253 if (protectionDomain.implies(filePermission)) { 1254 return false; 1255 } 1256 } catch (IOException e) {} 1257 1258 return true; 1259 } 1260 1261 private ArrayList<String> castToFiles(final List<?> files, 1262 final ProtectionDomain userProtectionDomain) throws IOException { 1263 try { 1264 return AccessController.doPrivileged((PrivilegedExceptionAction<ArrayList<String>>) () -> { 1265 ArrayList<String> fileList = new ArrayList<>(); 1266 for (Object fileObject : files) 1267 { 1268 File file = castToFile(fileObject); 1269 if (file != null && 1270 (null == System.getSecurityManager() || 1271 !(isFileInWebstartedCache(file) || 1272 isForbiddenToRead(file, userProtectionDomain)))) 1273 { 1274 fileList.add(file.getCanonicalPath()); 1275 } 1276 } 1277 return fileList; 1278 }); 1279 } catch (PrivilegedActionException pae) { 1280 throw new IOException(pae.getMessage()); 1281 } 1282 } 1283 1284 // It is important do not use user's successors 1285 // of File class. 1286 private File castToFile(Object fileObject) throws IOException { 1287 String filePath = null; 1288 if (fileObject instanceof File) { 1289 filePath = ((File)fileObject).getCanonicalPath(); 1290 } else if (fileObject instanceof String) { 1291 filePath = (String) fileObject; 1292 } else { 1293 return null; 1294 } 1295 return new File(filePath); 1296 } 1297 1298 private final static String[] DEPLOYMENT_CACHE_PROPERTIES = { 1299 "deployment.system.cachedir", 1300 "deployment.user.cachedir", 1301 "deployment.javaws.cachedir", 1302 "deployment.javapi.cachedir" 1303 }; 1304 1305 private final static ArrayList <File> deploymentCacheDirectoryList = new ArrayList<>(); 1306 1307 private static boolean isFileInWebstartedCache(File f) { 1308 1309 if (deploymentCacheDirectoryList.isEmpty()) { 1310 for (String cacheDirectoryProperty : DEPLOYMENT_CACHE_PROPERTIES) { 1311 String cacheDirectoryPath = System.getProperty(cacheDirectoryProperty); 1312 if (cacheDirectoryPath != null) { 1313 try { 1314 File cacheDirectory = (new File(cacheDirectoryPath)).getCanonicalFile(); 1315 if (cacheDirectory != null) { 1316 deploymentCacheDirectoryList.add(cacheDirectory); 1317 } 1318 } catch (IOException ioe) {} 1319 } 1320 } 1321 } 1322 1323 for (File deploymentCacheDirectory : deploymentCacheDirectoryList) { 1324 for (File dir = f; dir != null; dir = dir.getParentFile()) { 1325 if (dir.equals(deploymentCacheDirectory)) { 1326 return true; 1327 } 1328 } 1329 } 1330 1331 return false; 1332 } 1333 1334 1335 public Object translateBytes(byte[] bytes, DataFlavor flavor, 1336 long format, Transferable localeTransferable) 1337 throws IOException 1338 { 1339 1340 Object theObject = null; 1341 1342 // Source data is a file list. Use the dragQueryFile native function to 1343 // do most of the decoding. Then wrap File objects around the String 1344 // filenames and return a List. 1345 if (isFileFormat(format)) { 1346 if (!DataFlavor.javaFileListFlavor.equals(flavor)) { 1347 throw new IOException("data translation failed"); 1348 } 1349 String[] filenames = dragQueryFile(bytes); 1350 if (filenames == null) { 1351 return null; 1352 } 1353 1354 // Convert the strings to File objects 1355 File[] files = new File[filenames.length]; 1356 for (int i = 0; i < filenames.length; i++) { 1357 files[i] = new File(filenames[i]); 1358 } 1359 1360 // Turn the list of Files into a List and return 1361 theObject = Arrays.asList(files); 1362 1363 // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor 1364 // where possible. 1365 } else if (isURIListFormat(format) 1366 && DataFlavor.javaFileListFlavor.equals(flavor)) { 1367 1368 try (ByteArrayInputStream str = new ByteArrayInputStream(bytes)) { 1369 1370 URI uris[] = dragQueryURIs(str, format, localeTransferable); 1371 if (uris == null) { 1372 return null; 1373 } 1374 List<File> files = new ArrayList<>(); 1375 for (URI uri : uris) { 1376 try { 1377 files.add(new File(uri)); 1378 } catch (IllegalArgumentException illegalArg) { 1379 // When converting from URIs to less generic files, 1380 // common practice (Wine, SWT) seems to be to 1381 // silently drop the URIs that aren't local files. 1382 } 1383 } 1384 theObject = files; 1385 } 1386 1387 // Target data is a String. Strip terminating NUL bytes. Decode bytes 1388 // into characters. Search-and-replace EOLN. 1389 } else if (String.class.equals(flavor.getRepresentationClass()) && 1390 isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1391 1392 theObject = translateBytesToString(bytes, format, localeTransferable); 1393 1394 // Target data is a Reader. Obtain data in InputStream format, encoded 1395 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode 1396 // back to chars on demand. 1397 } else if (flavor.isRepresentationClassReader()) { 1398 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1399 theObject = translateStream(bais, 1400 flavor, format, localeTransferable); 1401 } 1402 // Target data is a CharBuffer. Recur to obtain String and wrap. 1403 } else if (flavor.isRepresentationClassCharBuffer()) { 1404 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1405 throw new IOException 1406 ("cannot transfer non-text data as CharBuffer"); 1407 } 1408 1409 CharBuffer buffer = CharBuffer.wrap( 1410 translateBytesToString(bytes,format, localeTransferable)); 1411 1412 theObject = constructFlavoredObject(buffer, flavor, CharBuffer.class); 1413 1414 // Target data is a char array. Recur to obtain String and convert to 1415 // char array. 1416 } else if (char[].class.equals(flavor.getRepresentationClass())) { 1417 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1418 throw new IOException 1419 ("cannot transfer non-text data as char array"); 1420 } 1421 1422 theObject = translateBytesToString( 1423 bytes, format, localeTransferable).toCharArray(); 1424 1425 // Target data is a ByteBuffer. For arbitrary flavors, just return 1426 // the raw bytes. For text flavors, convert to a String to strip 1427 // terminators and search-and-replace EOLN, then reencode according to 1428 // the requested flavor. 1429 } else if (flavor.isRepresentationClassByteBuffer()) { 1430 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1431 bytes = translateBytesToString( 1432 bytes, format, localeTransferable).getBytes( 1433 DataTransferer.getTextCharset(flavor) 1434 ); 1435 } 1436 1437 ByteBuffer buffer = ByteBuffer.wrap(bytes); 1438 theObject = constructFlavoredObject(buffer, flavor, ByteBuffer.class); 1439 1440 // Target data is a byte array. For arbitrary flavors, just return 1441 // the raw bytes. For text flavors, convert to a String to strip 1442 // terminators and search-and-replace EOLN, then reencode according to 1443 // the requested flavor. 1444 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 1445 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1446 theObject = translateBytesToString( 1447 bytes, format, localeTransferable 1448 ).getBytes(DataTransferer.getTextCharset(flavor)); 1449 } else { 1450 theObject = bytes; 1451 } 1452 1453 // Target data is an InputStream. For arbitrary flavors, just return 1454 // the raw bytes. For text flavors, decode to strip terminators and 1455 // search-and-replace EOLN, then reencode according to the requested 1456 // flavor. 1457 } else if (flavor.isRepresentationClassInputStream()) { 1458 1459 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1460 theObject = translateStream(bais, flavor, format, localeTransferable); 1461 } 1462 1463 } else if (flavor.isRepresentationClassRemote()) { 1464 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 1465 ObjectInputStream ois = new ObjectInputStream(bais)) 1466 { 1467 theObject = RMI.getMarshalledObject(ois.readObject()); 1468 } catch (Exception e) { 1469 throw new IOException(e.getMessage()); 1470 } 1471 1472 // Target data is Serializable 1473 } else if (flavor.isRepresentationClassSerializable()) { 1474 1475 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1476 theObject = translateStream(bais, flavor, format, localeTransferable); 1477 } 1478 1479 // Target data is Image 1480 } else if (DataFlavor.imageFlavor.equals(flavor)) { 1481 if (!isImageFormat(format)) { 1482 throw new IOException("data translation failed"); 1483 } 1484 1485 theObject = platformImageBytesToImage(bytes, format); 1486 } 1487 1488 if (theObject == null) { 1489 throw new IOException("data translation failed"); 1490 } 1491 1492 return theObject; 1493 1494 } 1495 1496 /** 1497 * Primary translation function for translating 1498 * an InputStream into an Object, given a source format and a target 1499 * DataFlavor. 1500 */ 1501 public Object translateStream(InputStream str, DataFlavor flavor, 1502 long format, Transferable localeTransferable) 1503 throws IOException 1504 { 1505 1506 Object theObject = null; 1507 // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor 1508 // where possible. 1509 if (isURIListFormat(format) 1510 && DataFlavor.javaFileListFlavor.equals(flavor)) 1511 { 1512 1513 URI uris[] = dragQueryURIs(str, format, localeTransferable); 1514 if (uris == null) { 1515 return null; 1516 } 1517 List<File> files = new ArrayList<>(); 1518 for (URI uri : uris) { 1519 try { 1520 files.add(new File(uri)); 1521 } catch (IllegalArgumentException illegalArg) { 1522 // When converting from URIs to less generic files, 1523 // common practice (Wine, SWT) seems to be to 1524 // silently drop the URIs that aren't local files. 1525 } 1526 } 1527 theObject = files; 1528 1529 // Target data is a String. Strip terminating NUL bytes. Decode bytes 1530 // into characters. Search-and-replace EOLN. 1531 } else if (String.class.equals(flavor.getRepresentationClass()) && 1532 isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1533 1534 return translateBytesToString(inputStreamToByteArray(str), 1535 format, localeTransferable); 1536 1537 // Special hack to maintain backwards-compatibility with the brokenness 1538 // of StringSelection. Return a StringReader instead of an InputStream. 1539 // Recur to obtain String and encapsulate. 1540 } else if (DataFlavor.plainTextFlavor.equals(flavor)) { 1541 theObject = new StringReader(translateBytesToString( 1542 inputStreamToByteArray(str), 1543 format, localeTransferable)); 1544 1545 // Target data is an InputStream. For arbitrary flavors, just return 1546 // the raw bytes. For text flavors, decode to strip terminators and 1547 // search-and-replace EOLN, then reencode according to the requested 1548 // flavor. 1549 } else if (flavor.isRepresentationClassInputStream()) { 1550 theObject = translateStreamToInputStream(str, flavor, format, 1551 localeTransferable); 1552 1553 // Target data is a Reader. Obtain data in InputStream format, encoded 1554 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode 1555 // back to chars on demand. 1556 } else if (flavor.isRepresentationClassReader()) { 1557 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) { 1558 throw new IOException 1559 ("cannot transfer non-text data as Reader"); 1560 } 1561 1562 InputStream is = (InputStream)translateStreamToInputStream( 1563 str, DataFlavor.plainTextFlavor, 1564 format, localeTransferable); 1565 1566 String unicode = DataTransferer.getTextCharset(DataFlavor.plainTextFlavor); 1567 1568 Reader reader = new InputStreamReader(is, unicode); 1569 1570 theObject = constructFlavoredObject(reader, flavor, Reader.class); 1571 // Target data is a byte array 1572 } else if (byte[].class.equals(flavor.getRepresentationClass())) { 1573 if(isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1574 theObject = translateBytesToString(inputStreamToByteArray(str), format, localeTransferable) 1575 .getBytes(DataTransferer.getTextCharset(flavor)); 1576 } else { 1577 theObject = inputStreamToByteArray(str); 1578 } 1579 // Target data is an RMI object 1580 } else if (flavor.isRepresentationClassRemote()) { 1581 1582 try (ObjectInputStream ois = 1583 new ObjectInputStream(str)) 1584 { 1585 theObject = RMI.getMarshalledObject(ois.readObject()); 1586 }catch (Exception e) { 1587 throw new IOException(e.getMessage()); 1588 } 1589 1590 // Target data is Serializable 1591 } else if (flavor.isRepresentationClassSerializable()) { 1592 try (ObjectInputStream ois = 1593 new ObjectInputStream(str)) 1594 { 1595 theObject = ois.readObject(); 1596 } catch (Exception e) { 1597 throw new IOException(e.getMessage()); 1598 } 1599 // Target data is Image 1600 } else if (DataFlavor.imageFlavor.equals(flavor)) { 1601 if (!isImageFormat(format)) { 1602 throw new IOException("data translation failed"); 1603 } 1604 theObject = platformImageBytesToImage(inputStreamToByteArray(str), format); 1605 } 1606 1607 if (theObject == null) { 1608 throw new IOException("data translation failed"); 1609 } 1610 1611 return theObject; 1612 1613 } 1614 1615 /** 1616 * For arbitrary flavors, just use the raw InputStream. For text flavors, 1617 * ReencodingInputStream will decode and reencode the InputStream on demand 1618 * so that we can strip terminators and search-and-replace EOLN. 1619 */ 1620 private Object translateStreamToInputStream 1621 (InputStream str, DataFlavor flavor, long format, 1622 Transferable localeTransferable) throws IOException 1623 { 1624 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) { 1625 str = new ReencodingInputStream 1626 (str, format, DataTransferer.getTextCharset(flavor), 1627 localeTransferable); 1628 } 1629 1630 return constructFlavoredObject(str, flavor, InputStream.class); 1631 } 1632 1633 /** 1634 * We support representations which are exactly of the specified Class, 1635 * and also arbitrary Objects which have a constructor which takes an 1636 * instance of the Class as its sole parameter. 1637 */ 1638 private Object constructFlavoredObject(Object arg, DataFlavor flavor, 1639 Class<?> clazz) 1640 throws IOException 1641 { 1642 final Class<?> dfrc = flavor.getRepresentationClass(); 1643 1644 if (clazz.equals(dfrc)) { 1645 return arg; // simple case 1646 } else { 1647 Constructor<?>[] constructors; 1648 1649 try { 1650 constructors = AccessController.doPrivileged( 1651 (PrivilegedAction<Constructor<?>[]>) dfrc::getConstructors); 1652 } catch (SecurityException se) { 1653 throw new IOException(se.getMessage()); 1654 } 1655 1656 Constructor<?> constructor = Stream.of(constructors) 1657 .filter(c -> Modifier.isPublic(c.getModifiers())) 1658 .filter(c -> { 1659 Class<?>[] ptypes = c.getParameterTypes(); 1660 return ptypes != null 1661 && ptypes.length == 1 1662 && clazz.equals(ptypes[0]); 1663 }) 1664 .findFirst() 1665 .orElseThrow(() -> 1666 new IOException("can't find <init>(L"+ clazz + ";)V for class: " + dfrc.getName())); 1667 1668 try { 1669 return constructor.newInstance(arg); 1670 } catch (Exception e) { 1671 throw new IOException(e.getMessage()); 1672 } 1673 } 1674 } 1675 1676 /** 1677 * Used for decoding and reencoding an InputStream on demand so that we 1678 * can strip NUL terminators and perform EOLN search-and-replace. 1679 */ 1680 public class ReencodingInputStream extends InputStream { 1681 BufferedReader wrapped; 1682 final char[] in = new char[2]; 1683 byte[] out; 1684 1685 CharsetEncoder encoder; 1686 CharBuffer inBuf; 1687 ByteBuffer outBuf; 1688 1689 char[] eoln; 1690 int numTerminators; 1691 1692 boolean eos; 1693 int index, limit; 1694 1695 public ReencodingInputStream(InputStream bytestream, long format, 1696 String targetEncoding, 1697 Transferable localeTransferable) 1698 throws IOException 1699 { 1700 Long lFormat = format; 1701 1702 String sourceEncoding = getBestCharsetForTextFormat(format, localeTransferable); 1703 wrapped = new BufferedReader(new InputStreamReader(bytestream, sourceEncoding)); 1704 1705 if (targetEncoding == null) { 1706 // Throw NullPointerException for compatibility with the former 1707 // call to sun.io.CharToByteConverter.getConverter(null) 1708 // (Charset.forName(null) throws unspecified IllegalArgumentException 1709 // now; see 6228568) 1710 throw new NullPointerException("null target encoding"); 1711 } 1712 1713 try { 1714 encoder = Charset.forName(targetEncoding).newEncoder(); 1715 out = new byte[(int)(encoder.maxBytesPerChar() * 2 + 0.5)]; 1716 inBuf = CharBuffer.wrap(in); 1717 outBuf = ByteBuffer.wrap(out); 1718 } catch (IllegalCharsetNameException 1719 | UnsupportedCharsetException 1720 | UnsupportedOperationException e) { 1721 throw new IOException(e.toString()); 1722 } 1723 1724 String sEoln = nativeEOLNs.get(lFormat); 1725 if (sEoln != null) { 1726 eoln = sEoln.toCharArray(); 1727 } 1728 1729 // A hope and a prayer that this works generically. This will 1730 // definitely work on Win32. 1731 Integer terminators = nativeTerminators.get(lFormat); 1732 if (terminators != null) { 1733 numTerminators = terminators; 1734 } 1735 } 1736 1737 private int readChar() throws IOException { 1738 int c = wrapped.read(); 1739 1740 if (c == -1) { // -1 is EOS 1741 eos = true; 1742 return -1; 1743 } 1744 1745 // "c == 0" is not quite correct, but good enough on Windows. 1746 if (numTerminators > 0 && c == 0) { 1747 eos = true; 1748 return -1; 1749 } else if (eoln != null && matchCharArray(eoln, c)) { 1750 c = '\n' & 0xFFFF; 1751 } 1752 1753 return c; 1754 } 1755 1756 public int read() throws IOException { 1757 if (eos) { 1758 return -1; 1759 } 1760 1761 if (index >= limit) { 1762 // deal with supplementary characters 1763 int c = readChar(); 1764 if (c == -1) { 1765 return -1; 1766 } 1767 1768 in[0] = (char) c; 1769 in[1] = 0; 1770 inBuf.limit(1); 1771 if (Character.isHighSurrogate((char) c)) { 1772 c = readChar(); 1773 if (c != -1) { 1774 in[1] = (char) c; 1775 inBuf.limit(2); 1776 } 1777 } 1778 1779 inBuf.rewind(); 1780 outBuf.limit(out.length).rewind(); 1781 encoder.encode(inBuf, outBuf, false); 1782 outBuf.flip(); 1783 limit = outBuf.limit(); 1784 1785 index = 0; 1786 1787 return read(); 1788 } else { 1789 return out[index++] & 0xFF; 1790 } 1791 } 1792 1793 public int available() throws IOException { 1794 return ((eos) ? 0 : (limit - index)); 1795 } 1796 1797 public void close() throws IOException { 1798 wrapped.close(); 1799 } 1800 1801 /** 1802 * Checks to see if the next array.length characters in wrapped 1803 * match array. The first character is provided as c. Subsequent 1804 * characters are read from wrapped itself. When this method returns, 1805 * the wrapped index may be different from what it was when this 1806 * method was called. 1807 */ 1808 private boolean matchCharArray(char[] array, int c) 1809 throws IOException 1810 { 1811 wrapped.mark(array.length); // BufferedReader supports mark 1812 1813 int count = 0; 1814 if ((char)c == array[0]) { 1815 for (count = 1; count < array.length; count++) { 1816 c = wrapped.read(); 1817 if (c == -1 || ((char)c) != array[count]) { 1818 break; 1819 } 1820 } 1821 } 1822 1823 if (count == array.length) { 1824 return true; 1825 } else { 1826 wrapped.reset(); 1827 return false; 1828 } 1829 } 1830 } 1831 1832 /** 1833 * Decodes a byte array into a set of String filenames. 1834 */ 1835 protected abstract String[] dragQueryFile(byte[] bytes); 1836 1837 /** 1838 * Decodes URIs from either a byte array or a stream. 1839 */ 1840 protected URI[] dragQueryURIs(InputStream stream, 1841 long format, 1842 Transferable localeTransferable) 1843 throws IOException 1844 { 1845 throw new IOException( 1846 new UnsupportedOperationException("not implemented on this platform")); 1847 } 1848 1849 /** 1850 * Translates either a byte array or an input stream which contain 1851 * platform-specific image data in the given format into an Image. 1852 */ 1853 1854 1855 protected abstract Image platformImageBytesToImage( 1856 byte[] bytes,long format) throws IOException; 1857 1858 /** 1859 * Translates either a byte array or an input stream which contain 1860 * an image data in the given standard format into an Image. 1861 * 1862 * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif 1863 */ 1864 protected Image standardImageBytesToImage( 1865 byte[] bytes, String mimeType) throws IOException 1866 { 1867 1868 Iterator<ImageReader> readerIterator = 1869 ImageIO.getImageReadersByMIMEType(mimeType); 1870 1871 if (!readerIterator.hasNext()) { 1872 throw new IOException("No registered service provider can decode " + 1873 " an image from " + mimeType); 1874 } 1875 1876 IOException ioe = null; 1877 1878 while (readerIterator.hasNext()) { 1879 ImageReader imageReader = readerIterator.next(); 1880 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) { 1881 try (ImageInputStream imageInputStream = ImageIO.createImageInputStream(bais)) { 1882 ImageReadParam param = imageReader.getDefaultReadParam(); 1883 imageReader.setInput(imageInputStream, true, true); 1884 BufferedImage bufferedImage = imageReader.read(imageReader.getMinIndex(), param); 1885 if (bufferedImage != null) { 1886 return bufferedImage; 1887 } 1888 } finally { 1889 imageReader.dispose(); 1890 } 1891 } catch (IOException e) { 1892 ioe = e; 1893 continue; 1894 } 1895 } 1896 1897 if (ioe == null) { 1898 ioe = new IOException("Registered service providers failed to decode" 1899 + " an image from " + mimeType); 1900 } 1901 1902 throw ioe; 1903 } 1904 1905 /** 1906 * Translates a Java Image into a byte array which contains platform- 1907 * specific image data in the given format. 1908 */ 1909 protected abstract byte[] imageToPlatformBytes(Image image, long format) 1910 throws IOException; 1911 1912 /** 1913 * Translates a Java Image into a byte array which contains 1914 * an image data in the given standard format. 1915 * 1916 * @param mimeType image MIME type, such as: image/png, image/jpeg 1917 */ 1918 protected byte[] imageToStandardBytes(Image image, String mimeType) 1919 throws IOException { 1920 IOException originalIOE = null; 1921 1922 Iterator<ImageWriter> writerIterator = 1923 ImageIO.getImageWritersByMIMEType(mimeType); 1924 1925 if (!writerIterator.hasNext()) { 1926 throw new IOException("No registered service provider can encode " + 1927 " an image to " + mimeType); 1928 } 1929 1930 if (image instanceof RenderedImage) { 1931 // Try to encode the original image. 1932 try { 1933 return imageToStandardBytesImpl((RenderedImage)image, mimeType); 1934 } catch (IOException ioe) { 1935 originalIOE = ioe; 1936 } 1937 } 1938 1939 // Retry with a BufferedImage. 1940 int width = 0; 1941 int height = 0; 1942 if (image instanceof ToolkitImage) { 1943 ImageRepresentation ir = ((ToolkitImage)image).getImageRep(); 1944 ir.reconstruct(ImageObserver.ALLBITS); 1945 width = ir.getWidth(); 1946 height = ir.getHeight(); 1947 } else { 1948 width = image.getWidth(null); 1949 height = image.getHeight(null); 1950 } 1951 1952 ColorModel model = ColorModel.getRGBdefault(); 1953 WritableRaster raster = 1954 model.createCompatibleWritableRaster(width, height); 1955 1956 BufferedImage bufferedImage = 1957 new BufferedImage(model, raster, model.isAlphaPremultiplied(), 1958 null); 1959 1960 Graphics g = bufferedImage.getGraphics(); 1961 try { 1962 g.drawImage(image, 0, 0, width, height, null); 1963 } finally { 1964 g.dispose(); 1965 } 1966 1967 try { 1968 return imageToStandardBytesImpl(bufferedImage, mimeType); 1969 } catch (IOException ioe) { 1970 if (originalIOE != null) { 1971 throw originalIOE; 1972 } else { 1973 throw ioe; 1974 } 1975 } 1976 } 1977 1978 byte[] imageToStandardBytesImpl(RenderedImage renderedImage, 1979 String mimeType) 1980 throws IOException { 1981 1982 Iterator<ImageWriter> writerIterator = 1983 ImageIO.getImageWritersByMIMEType(mimeType); 1984 1985 ImageTypeSpecifier typeSpecifier = 1986 new ImageTypeSpecifier(renderedImage); 1987 1988 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1989 IOException ioe = null; 1990 1991 while (writerIterator.hasNext()) { 1992 ImageWriter imageWriter = writerIterator.next(); 1993 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider(); 1994 1995 if (!writerSpi.canEncodeImage(typeSpecifier)) { 1996 continue; 1997 } 1998 1999 try { 2000 try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(baos)) { 2001 imageWriter.setOutput(imageOutputStream); 2002 imageWriter.write(renderedImage); 2003 imageOutputStream.flush(); 2004 } 2005 } catch (IOException e) { 2006 imageWriter.dispose(); 2007 baos.reset(); 2008 ioe = e; 2009 continue; 2010 } 2011 2012 imageWriter.dispose(); 2013 baos.close(); 2014 return baos.toByteArray(); 2015 } 2016 2017 baos.close(); 2018 2019 if (ioe == null) { 2020 ioe = new IOException("Registered service providers failed to encode " 2021 + renderedImage + " to " + mimeType); 2022 } 2023 2024 throw ioe; 2025 } 2026 2027 /** 2028 * Concatenates the data represented by two objects. Objects can be either 2029 * byte arrays or instances of <code>InputStream</code>. If both arguments 2030 * are byte arrays byte array will be returned. Otherwise an 2031 * <code>InputStream</code> will be returned. 2032 * <p> 2033 * Currently is only called from native code to prepend palette data to 2034 * platform-specific image data during image transfer on Win32. 2035 * 2036 * @param obj1 the first object to be concatenated. 2037 * @param obj2 the second object to be concatenated. 2038 * @return a byte array or an <code>InputStream</code> which represents 2039 * a logical concatenation of the two arguments. 2040 * @throws NullPointerException is either of the arguments is 2041 * <code>null</code> 2042 * @throws ClassCastException is either of the arguments is 2043 * neither byte array nor an instance of <code>InputStream</code>. 2044 */ 2045 private Object concatData(Object obj1, Object obj2) { 2046 InputStream str1 = null; 2047 InputStream str2 = null; 2048 2049 if (obj1 instanceof byte[]) { 2050 byte[] arr1 = (byte[])obj1; 2051 if (obj2 instanceof byte[]) { 2052 byte[] arr2 = (byte[])obj2; 2053 byte[] ret = new byte[arr1.length + arr2.length]; 2054 System.arraycopy(arr1, 0, ret, 0, arr1.length); 2055 System.arraycopy(arr2, 0, ret, arr1.length, arr2.length); 2056 return ret; 2057 } else { 2058 str1 = new ByteArrayInputStream(arr1); 2059 str2 = (InputStream)obj2; 2060 } 2061 } else { 2062 str1 = (InputStream)obj1; 2063 if (obj2 instanceof byte[]) { 2064 str2 = new ByteArrayInputStream((byte[])obj2); 2065 } else { 2066 str2 = (InputStream)obj2; 2067 } 2068 } 2069 2070 return new SequenceInputStream(str1, str2); 2071 } 2072 2073 public byte[] convertData(final Object source, 2074 final Transferable contents, 2075 final long format, 2076 final Map<Long, DataFlavor> formatMap, 2077 final boolean isToolkitThread) 2078 throws IOException 2079 { 2080 byte[] ret = null; 2081 2082 /* 2083 * If the current thread is the Toolkit thread we should post a 2084 * Runnable to the event dispatch thread associated with source Object, 2085 * since translateTransferable() calls Transferable.getTransferData() 2086 * that may contain client code. 2087 */ 2088 if (isToolkitThread) try { 2089 final Stack<byte[]> stack = new Stack<>(); 2090 final Runnable dataConverter = new Runnable() { 2091 // Guard against multiple executions. 2092 private boolean done = false; 2093 public void run() { 2094 if (done) { 2095 return; 2096 } 2097 byte[] data = null; 2098 try { 2099 DataFlavor flavor = formatMap.get(format); 2100 if (flavor != null) { 2101 data = translateTransferable(contents, flavor, format); 2102 } 2103 } catch (Exception e) { 2104 e.printStackTrace(); 2105 data = null; 2106 } 2107 try { 2108 getToolkitThreadBlockedHandler().lock(); 2109 stack.push(data); 2110 getToolkitThreadBlockedHandler().exit(); 2111 } finally { 2112 getToolkitThreadBlockedHandler().unlock(); 2113 done = true; 2114 } 2115 } 2116 }; 2117 2118 final AppContext appContext = SunToolkit.targetToAppContext(source); 2119 2120 getToolkitThreadBlockedHandler().lock(); 2121 2122 if (appContext != null) { 2123 appContext.put(DATA_CONVERTER_KEY, dataConverter); 2124 } 2125 2126 SunToolkit.executeOnEventHandlerThread(source, dataConverter); 2127 2128 while (stack.empty()) { 2129 getToolkitThreadBlockedHandler().enter(); 2130 } 2131 2132 if (appContext != null) { 2133 appContext.remove(DATA_CONVERTER_KEY); 2134 } 2135 2136 ret = stack.pop(); 2137 } finally { 2138 getToolkitThreadBlockedHandler().unlock(); 2139 } else { 2140 DataFlavor flavor = formatMap.get(format); 2141 if (flavor != null) { 2142 ret = translateTransferable(contents, flavor, format); 2143 } 2144 } 2145 2146 return ret; 2147 } 2148 2149 public void processDataConversionRequests() { 2150 if (EventQueue.isDispatchThread()) { 2151 AppContext appContext = AppContext.getAppContext(); 2152 getToolkitThreadBlockedHandler().lock(); 2153 try { 2154 Runnable dataConverter = 2155 (Runnable)appContext.get(DATA_CONVERTER_KEY); 2156 if (dataConverter != null) { 2157 dataConverter.run(); 2158 appContext.remove(DATA_CONVERTER_KEY); 2159 } 2160 } finally { 2161 getToolkitThreadBlockedHandler().unlock(); 2162 } 2163 } 2164 } 2165 2166 public abstract ToolkitThreadBlockedHandler 2167 getToolkitThreadBlockedHandler(); 2168 2169 /** 2170 * Helper function to reduce a Map with Long keys to a long array. 2171 * <p> 2172 * The map keys are sorted according to the native formats preference 2173 * order. 2174 */ 2175 public static long[] keysToLongArray(SortedMap<Long, ?> map) { 2176 Set<Long> keySet = map.keySet(); 2177 long[] retval = new long[keySet.size()]; 2178 int i = 0; 2179 for (Iterator<Long> iter = keySet.iterator(); iter.hasNext(); i++) { 2180 retval[i] = iter.next(); 2181 } 2182 return retval; 2183 } 2184 2185 /** 2186 * Helper function to convert a Set of DataFlavors to a sorted array. 2187 * The array will be sorted according to <code>DataFlavorComparator</code>. 2188 */ 2189 public static DataFlavor[] setToSortedDataFlavorArray(Set<DataFlavor> flavorsSet) { 2190 DataFlavor[] flavors = new DataFlavor[flavorsSet.size()]; 2191 flavorsSet.toArray(flavors); 2192 final Comparator<DataFlavor> comparator = 2193 new DataFlavorComparator(IndexedComparator.SELECT_WORST); 2194 Arrays.sort(flavors, comparator); 2195 return flavors; 2196 } 2197 2198 /** 2199 * Helper function to convert an InputStream to a byte[] array. 2200 */ 2201 protected static byte[] inputStreamToByteArray(InputStream str) 2202 throws IOException 2203 { 2204 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 2205 int len = 0; 2206 byte[] buf = new byte[8192]; 2207 2208 while ((len = str.read(buf)) != -1) { 2209 baos.write(buf, 0, len); 2210 } 2211 2212 return baos.toByteArray(); 2213 } 2214 } 2215 2216 /** 2217 * Returns platform-specific mappings for the specified native. 2218 * If there are no platform-specific mappings for this native, the method 2219 * returns an empty <code>List</code>. 2220 */ 2221 public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) { 2222 return new LinkedHashSet<>(); 2223 } 2224 2225 /** 2226 * Returns platform-specific mappings for the specified flavor. 2227 * If there are no platform-specific mappings for this flavor, the method 2228 * returns an empty <code>List</code>. 2229 */ 2230 public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) { 2231 return new LinkedHashSet<>(); 2232 } 2233 2234 /** 2235 * A Comparator which includes a helper function for comparing two Objects 2236 * which are likely to be keys in the specified Map. 2237 */ 2238 public abstract static class IndexedComparator<T> implements Comparator<T> { 2239 2240 /** 2241 * The best Object (e.g., DataFlavor) will be the last in sequence. 2242 */ 2243 public static final boolean SELECT_BEST = true; 2244 2245 /** 2246 * The best Object (e.g., DataFlavor) will be the first in sequence. 2247 */ 2248 public static final boolean SELECT_WORST = false; 2249 2250 final boolean order; 2251 2252 public IndexedComparator(boolean order) { 2253 this.order = order; 2254 } 2255 2256 /** 2257 * Helper method to compare two objects by their Integer indices in the 2258 * given map. If the map doesn't contain an entry for either of the 2259 * objects, the fallback index will be used for the object instead. 2260 * 2261 * @param indexMap the map which maps objects into Integer indexes. 2262 * @param obj1 the first object to be compared. 2263 * @param obj2 the second object to be compared. 2264 * @param fallbackIndex the Integer to be used as a fallback index. 2265 * @return a negative integer, zero, or a positive integer as the 2266 * first object is mapped to a less, equal to, or greater 2267 * index than the second. 2268 */ 2269 static <T> int compareIndices(Map<T, Integer> indexMap, 2270 T obj1, T obj2, 2271 Integer fallbackIndex) { 2272 Integer index1 = indexMap.getOrDefault(obj1, fallbackIndex); 2273 Integer index2 = indexMap.getOrDefault(obj2, fallbackIndex); 2274 return index1.compareTo(index2); 2275 } 2276 } 2277 2278 /** 2279 * An IndexedComparator which compares two String charsets. The comparison 2280 * follows the rules outlined in DataFlavor.selectBestTextFlavor. In order 2281 * to ensure that non-Unicode, non-ASCII, non-default charsets are sorted 2282 * in alphabetical order, charsets are not automatically converted to their 2283 * canonical forms. 2284 */ 2285 public static class CharsetComparator extends IndexedComparator<String> { 2286 private static final Map<String, Integer> charsets; 2287 2288 private static final Integer DEFAULT_CHARSET_INDEX = 2; 2289 private static final Integer OTHER_CHARSET_INDEX = 1; 2290 private static final Integer WORST_CHARSET_INDEX = 0; 2291 private static final Integer UNSUPPORTED_CHARSET_INDEX = Integer.MIN_VALUE; 2292 2293 private static final String UNSUPPORTED_CHARSET = "UNSUPPORTED"; 2294 2295 static { 2296 Map<String, Integer> charsetsMap = new HashMap<>(8, 1.0f); 2297 2298 // we prefer Unicode charsets 2299 charsetsMap.put(canonicalName("UTF-16LE"), 4); 2300 charsetsMap.put(canonicalName("UTF-16BE"), 5); 2301 charsetsMap.put(canonicalName("UTF-8"), 6); 2302 charsetsMap.put(canonicalName("UTF-16"), 7); 2303 2304 // US-ASCII is the worst charset supported 2305 charsetsMap.put(canonicalName("US-ASCII"), WORST_CHARSET_INDEX); 2306 2307 charsetsMap.putIfAbsent(Charset.defaultCharset().name(), DEFAULT_CHARSET_INDEX); 2308 2309 charsetsMap.put(UNSUPPORTED_CHARSET, UNSUPPORTED_CHARSET_INDEX); 2310 2311 charsets = Collections.unmodifiableMap(charsetsMap); 2312 } 2313 2314 public CharsetComparator(boolean order) { 2315 super(order); 2316 } 2317 2318 /** 2319 * Compares two String objects. Returns a negative integer, zero, 2320 * or a positive integer as the first charset is worse than, equal to, 2321 * or better than the second. 2322 * 2323 * @param obj1 the first charset to be compared 2324 * @param obj2 the second charset to be compared 2325 * @return a negative integer, zero, or a positive integer as the 2326 * first argument is worse, equal to, or better than the 2327 * second. 2328 * @throws ClassCastException if either of the arguments is not 2329 * instance of String 2330 * @throws NullPointerException if either of the arguments is 2331 * <code>null</code>. 2332 */ 2333 public int compare(String obj1, String obj2) { 2334 if (order == SELECT_BEST) { 2335 return compareCharsets(obj1, obj2); 2336 } else { 2337 return compareCharsets(obj2, obj1); 2338 } 2339 } 2340 2341 /** 2342 * Compares charsets. Returns a negative integer, zero, or a positive 2343 * integer as the first charset is worse than, equal to, or better than 2344 * the second. 2345 * <p> 2346 * Charsets are ordered according to the following rules: 2347 * <ul> 2348 * <li>All unsupported charsets are equal. 2349 * <li>Any unsupported charset is worse than any supported charset. 2350 * <li>Unicode charsets, such as "UTF-16", "UTF-8", "UTF-16BE" and 2351 * "UTF-16LE", are considered best. 2352 * <li>After them, platform default charset is selected. 2353 * <li>"US-ASCII" is the worst of supported charsets. 2354 * <li>For all other supported charsets, the lexicographically less 2355 * one is considered the better. 2356 * </ul> 2357 * 2358 * @param charset1 the first charset to be compared 2359 * @param charset2 the second charset to be compared. 2360 * @return a negative integer, zero, or a positive integer as the 2361 * first argument is worse, equal to, or better than the 2362 * second. 2363 */ 2364 int compareCharsets(String charset1, String charset2) { 2365 charset1 = getEncoding(charset1); 2366 charset2 = getEncoding(charset2); 2367 2368 int comp = compareIndices(charsets, charset1, charset2, 2369 OTHER_CHARSET_INDEX); 2370 2371 if (comp == 0) { 2372 return charset2.compareTo(charset1); 2373 } 2374 2375 return comp; 2376 } 2377 2378 /** 2379 * Returns encoding for the specified charset according to the 2380 * following rules: 2381 * <ul> 2382 * <li>If the charset is <code>null</code>, then <code>null</code> will 2383 * be returned. 2384 * <li>Iff the charset specifies an encoding unsupported by this JRE, 2385 * <code>UNSUPPORTED_CHARSET</code> will be returned. 2386 * <li>If the charset specifies an alias name, the corresponding 2387 * canonical name will be returned iff the charset is a known 2388 * Unicode, ASCII, or default charset. 2389 * </ul> 2390 * 2391 * @param charset the charset. 2392 * @return an encoding for this charset. 2393 */ 2394 static String getEncoding(String charset) { 2395 if (charset == null) { 2396 return null; 2397 } else if (!DataTransferer.isEncodingSupported(charset)) { 2398 return UNSUPPORTED_CHARSET; 2399 } else { 2400 // Only convert to canonical form if the charset is one 2401 // of the charsets explicitly listed in the known charsets 2402 // map. This will happen only for Unicode, ASCII, or default 2403 // charsets. 2404 String canonicalName = DataTransferer.canonicalName(charset); 2405 return (charsets.containsKey(canonicalName)) 2406 ? canonicalName 2407 : charset; 2408 } 2409 } 2410 } 2411 2412 /** 2413 * An IndexedComparator which compares two DataFlavors. For text flavors, 2414 * the comparison follows the rules outlined in 2415 * DataFlavor.selectBestTextFlavor. For non-text flavors, unknown 2416 * application MIME types are preferred, followed by known 2417 * application/x-java-* MIME types. Unknown application types are preferred 2418 * because if the user provides his own data flavor, it will likely be the 2419 * most descriptive one. For flavors which are otherwise equal, the 2420 * flavors' string representation are compared in the alphabetical order. 2421 */ 2422 public static class DataFlavorComparator extends IndexedComparator<DataFlavor> { 2423 2424 private final CharsetComparator charsetComparator; 2425 2426 private static final Map<String, Integer> exactTypes; 2427 private static final Map<String, Integer> primaryTypes; 2428 private static final Map<Class<?>, Integer> nonTextRepresentations; 2429 private static final Map<String, Integer> textTypes; 2430 private static final Map<Class<?>, Integer> decodedTextRepresentations; 2431 private static final Map<Class<?>, Integer> encodedTextRepresentations; 2432 2433 private static final Integer UNKNOWN_OBJECT_LOSES = Integer.MIN_VALUE; 2434 private static final Integer UNKNOWN_OBJECT_WINS = Integer.MAX_VALUE; 2435 2436 static { 2437 { 2438 Map<String, Integer> exactTypesMap = new HashMap<>(4, 1.0f); 2439 2440 // application/x-java-* MIME types 2441 exactTypesMap.put("application/x-java-file-list", 0); 2442 exactTypesMap.put("application/x-java-serialized-object", 1); 2443 exactTypesMap.put("application/x-java-jvm-local-objectref", 2); 2444 exactTypesMap.put("application/x-java-remote-object", 3); 2445 2446 exactTypes = Collections.unmodifiableMap(exactTypesMap); 2447 } 2448 2449 { 2450 Map<String, Integer> primaryTypesMap = new HashMap<>(1, 1.0f); 2451 2452 primaryTypesMap.put("application", 0); 2453 2454 primaryTypes = Collections.unmodifiableMap(primaryTypesMap); 2455 } 2456 2457 { 2458 Map<Class<?>, Integer> nonTextRepresentationsMap = new HashMap<>(3, 1.0f); 2459 2460 nonTextRepresentationsMap.put(java.io.InputStream.class, 0); 2461 nonTextRepresentationsMap.put(java.io.Serializable.class, 1); 2462 2463 Class<?> remoteClass = RMI.remoteClass(); 2464 if (remoteClass != null) { 2465 nonTextRepresentationsMap.put(remoteClass, 2); 2466 } 2467 2468 nonTextRepresentations = Collections.unmodifiableMap(nonTextRepresentationsMap); 2469 } 2470 2471 { 2472 Map<String, Integer> textTypesMap = new HashMap<>(16, 1.0f); 2473 2474 // plain text 2475 textTypesMap.put("text/plain", 0); 2476 2477 // stringFlavor 2478 textTypesMap.put("application/x-java-serialized-object", 1); 2479 2480 // misc 2481 textTypesMap.put("text/calendar", 2); 2482 textTypesMap.put("text/css", 3); 2483 textTypesMap.put("text/directory", 4); 2484 textTypesMap.put("text/parityfec", 5); 2485 textTypesMap.put("text/rfc822-headers", 6); 2486 textTypesMap.put("text/t140", 7); 2487 textTypesMap.put("text/tab-separated-values", 8); 2488 textTypesMap.put("text/uri-list", 9); 2489 2490 // enriched 2491 textTypesMap.put("text/richtext", 10); 2492 textTypesMap.put("text/enriched", 11); 2493 textTypesMap.put("text/rtf", 12); 2494 2495 // markup 2496 textTypesMap.put("text/html", 13); 2497 textTypesMap.put("text/xml", 14); 2498 textTypesMap.put("text/sgml", 15); 2499 2500 textTypes = Collections.unmodifiableMap(textTypesMap); 2501 } 2502 2503 { 2504 Map<Class<?>, Integer> decodedTextRepresentationsMap = new HashMap<>(4, 1.0f); 2505 2506 decodedTextRepresentationsMap.put(char[].class, 0); 2507 decodedTextRepresentationsMap.put(CharBuffer.class, 1); 2508 decodedTextRepresentationsMap.put(String.class, 2); 2509 decodedTextRepresentationsMap.put(Reader.class, 3); 2510 2511 decodedTextRepresentations = 2512 Collections.unmodifiableMap(decodedTextRepresentationsMap); 2513 } 2514 2515 { 2516 Map<Class<?>, Integer> encodedTextRepresentationsMap = new HashMap<>(3, 1.0f); 2517 2518 encodedTextRepresentationsMap.put(byte[].class, 0); 2519 encodedTextRepresentationsMap.put(ByteBuffer.class, 1); 2520 encodedTextRepresentationsMap.put(InputStream.class, 2); 2521 2522 encodedTextRepresentations = 2523 Collections.unmodifiableMap(encodedTextRepresentationsMap); 2524 } 2525 } 2526 2527 public DataFlavorComparator() { 2528 this(SELECT_BEST); 2529 } 2530 2531 public DataFlavorComparator(boolean order) { 2532 super(order); 2533 2534 charsetComparator = new CharsetComparator(order); 2535 } 2536 2537 public int compare(DataFlavor obj1, DataFlavor obj2) { 2538 DataFlavor flavor1 = order == SELECT_BEST ? obj1 : obj2; 2539 DataFlavor flavor2 = order == SELECT_BEST ? obj2 : obj1; 2540 2541 if (flavor1.equals(flavor2)) { 2542 return 0; 2543 } 2544 2545 int comp = 0; 2546 2547 String primaryType1 = flavor1.getPrimaryType(); 2548 String subType1 = flavor1.getSubType(); 2549 String mimeType1 = primaryType1 + "/" + subType1; 2550 Class<?> class1 = flavor1.getRepresentationClass(); 2551 2552 String primaryType2 = flavor2.getPrimaryType(); 2553 String subType2 = flavor2.getSubType(); 2554 String mimeType2 = primaryType2 + "/" + subType2; 2555 Class<?> class2 = flavor2.getRepresentationClass(); 2556 2557 if (flavor1.isFlavorTextType() && flavor2.isFlavorTextType()) { 2558 // First, compare MIME types 2559 comp = compareIndices(textTypes, mimeType1, mimeType2, 2560 UNKNOWN_OBJECT_LOSES); 2561 if (comp != 0) { 2562 return comp; 2563 } 2564 2565 // Only need to test one flavor because they both have the 2566 // same MIME type. Also don't need to worry about accidentally 2567 // passing stringFlavor because either 2568 // 1. Both flavors are stringFlavor, in which case the 2569 // equality test at the top of the function succeeded. 2570 // 2. Only one flavor is stringFlavor, in which case the MIME 2571 // type comparison returned a non-zero value. 2572 if (doesSubtypeSupportCharset(flavor1)) { 2573 // Next, prefer the decoded text representations of Reader, 2574 // String, CharBuffer, and [C, in that order. 2575 comp = compareIndices(decodedTextRepresentations, class1, 2576 class2, UNKNOWN_OBJECT_LOSES); 2577 if (comp != 0) { 2578 return comp; 2579 } 2580 2581 // Next, compare charsets 2582 comp = charsetComparator.compareCharsets 2583 (DataTransferer.getTextCharset(flavor1), 2584 DataTransferer.getTextCharset(flavor2)); 2585 if (comp != 0) { 2586 return comp; 2587 } 2588 } 2589 2590 // Finally, prefer the encoded text representations of 2591 // InputStream, ByteBuffer, and [B, in that order. 2592 comp = compareIndices(encodedTextRepresentations, class1, 2593 class2, UNKNOWN_OBJECT_LOSES); 2594 if (comp != 0) { 2595 return comp; 2596 } 2597 } else { 2598 // First, prefer application types. 2599 comp = compareIndices(primaryTypes, primaryType1, primaryType2, 2600 UNKNOWN_OBJECT_LOSES); 2601 if (comp != 0) { 2602 return comp; 2603 } 2604 2605 // Next, look for application/x-java-* types. Prefer unknown 2606 // MIME types because if the user provides his own data flavor, 2607 // it will likely be the most descriptive one. 2608 comp = compareIndices(exactTypes, mimeType1, mimeType2, 2609 UNKNOWN_OBJECT_WINS); 2610 if (comp != 0) { 2611 return comp; 2612 } 2613 2614 // Finally, prefer the representation classes of Remote, 2615 // Serializable, and InputStream, in that order. 2616 comp = compareIndices(nonTextRepresentations, class1, class2, 2617 UNKNOWN_OBJECT_LOSES); 2618 if (comp != 0) { 2619 return comp; 2620 } 2621 } 2622 2623 // The flavours are not equal but still not distinguishable. 2624 // Compare String representations in alphabetical order 2625 return flavor1.getMimeType().compareTo(flavor2.getMimeType()); 2626 } 2627 } 2628 2629 /* 2630 * Given the Map that maps objects to Integer indices and a boolean value, 2631 * this Comparator imposes a direct or reverse order on set of objects. 2632 * <p> 2633 * If the specified boolean value is SELECT_BEST, the Comparator imposes the 2634 * direct index-based order: an object A is greater than an object B if and 2635 * only if the index of A is greater than the index of B. An object that 2636 * doesn't have an associated index is less or equal than any other object. 2637 * <p> 2638 * If the specified boolean value is SELECT_WORST, the Comparator imposes the 2639 * reverse index-based order: an object A is greater than an object B if and 2640 * only if A is less than B with the direct index-based order. 2641 */ 2642 public static class IndexOrderComparator extends IndexedComparator<Long> { 2643 private final Map<Long, Integer> indexMap; 2644 private static final Integer FALLBACK_INDEX = Integer.MIN_VALUE; 2645 2646 public IndexOrderComparator(Map<Long, Integer> indexMap, boolean order) { 2647 super(order); 2648 this.indexMap = indexMap; 2649 } 2650 2651 public int compare(Long obj1, Long obj2) { 2652 if (order == SELECT_WORST) { 2653 return -compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX); 2654 } else { 2655 return compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX); 2656 } 2657 } 2658 } 2659 2660 /** 2661 * A class that provides access to java.rmi.Remote and java.rmi.MarshalledObject 2662 * without creating a static dependency. 2663 */ 2664 private static class RMI { 2665 private static final Class<?> remoteClass = getClass("java.rmi.Remote"); 2666 private static final Class<?> marshallObjectClass = 2667 getClass("java.rmi.MarshalledObject"); 2668 private static final Constructor<?> marshallCtor = 2669 getConstructor(marshallObjectClass, Object.class); 2670 private static final Method marshallGet = 2671 getMethod(marshallObjectClass, "get"); 2672 2673 private static Class<?> getClass(String name) { 2674 try { 2675 return Class.forName(name, true, null); 2676 } catch (ClassNotFoundException e) { 2677 return null; 2678 } 2679 } 2680 2681 private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) { 2682 try { 2683 return (c == null) ? null : c.getDeclaredConstructor(types); 2684 } catch (NoSuchMethodException x) { 2685 throw new AssertionError(x); 2686 } 2687 } 2688 2689 private static Method getMethod(Class<?> c, String name, Class<?>... types) { 2690 try { 2691 return (c == null) ? null : c.getMethod(name, types); 2692 } catch (NoSuchMethodException e) { 2693 throw new AssertionError(e); 2694 } 2695 } 2696 2697 /** 2698 * Returns {@code true} if the given class is java.rmi.Remote. 2699 */ 2700 static boolean isRemote(Class<?> c) { 2701 return (remoteClass == null) ? false : remoteClass.isAssignableFrom(c); 2702 } 2703 2704 /** 2705 * Returns java.rmi.Remote.class if RMI is present; otherwise {@code null}. 2706 */ 2707 static Class<?> remoteClass() { 2708 return remoteClass; 2709 } 2710 2711 /** 2712 * Returns a new MarshalledObject containing the serialized representation 2713 * of the given object. 2714 */ 2715 static Object newMarshalledObject(Object obj) throws IOException { 2716 try { 2717 return marshallCtor.newInstance(obj); 2718 } catch (InstantiationException | IllegalAccessException x) { 2719 throw new AssertionError(x); 2720 } catch (InvocationTargetException x) { 2721 Throwable cause = x.getCause(); 2722 if (cause instanceof IOException) 2723 throw (IOException)cause; 2724 throw new AssertionError(x); 2725 } 2726 } 2727 2728 /** 2729 * Returns a new copy of the contained marshalled object. 2730 */ 2731 static Object getMarshalledObject(Object obj) 2732 throws IOException, ClassNotFoundException 2733 { 2734 try { 2735 return marshallGet.invoke(obj); 2736 } catch (IllegalAccessException x) { 2737 throw new AssertionError(x); 2738 } catch (InvocationTargetException x) { 2739 Throwable cause = x.getCause(); 2740 if (cause instanceof IOException) 2741 throw (IOException)cause; 2742 if (cause instanceof ClassNotFoundException) 2743 throw (ClassNotFoundException)cause; 2744 throw new AssertionError(x); 2745 } 2746 } 2747 } 2748 }