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