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.*;















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