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