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