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