1 /*
   2  * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.datatransfer;
  27 
  28 import java.awt.EventQueue;
  29 import java.awt.Graphics;
  30 import java.awt.Image;
  31 import java.awt.Toolkit;
  32 
  33 import java.awt.datatransfer.DataFlavor;
  34 import java.awt.datatransfer.FlavorMap;
  35 import java.awt.datatransfer.FlavorTable;
  36 import java.awt.datatransfer.Transferable;
  37 import java.awt.datatransfer.UnsupportedFlavorException;
  38 
  39 import java.io.BufferedReader;
  40 import java.io.ByteArrayInputStream;
  41 import java.io.ByteArrayOutputStream;
  42 import java.io.File;
  43 import java.io.InputStream;
  44 import java.io.InputStreamReader;
  45 import java.io.IOException;
  46 import java.io.ObjectInputStream;
  47 import java.io.ObjectOutputStream;
  48 import java.io.Reader;
  49 import java.io.SequenceInputStream;
  50 import java.io.StringReader;
  51 
  52 import java.net.URI;
  53 import java.net.URISyntaxException;
  54 
  55 import java.nio.ByteBuffer;
  56 import java.nio.CharBuffer;
  57 import java.nio.charset.Charset;
  58 import java.nio.charset.CharsetEncoder;
  59 import java.nio.charset.IllegalCharsetNameException;
  60 import java.nio.charset.StandardCharsets;
  61 import java.nio.charset.UnsupportedCharsetException;
  62 
  63 import java.lang.reflect.Constructor;
  64 import java.lang.reflect.Modifier;
  65 
  66 import java.security.AccessController;
  67 import java.security.PrivilegedAction;
  68 import java.security.PrivilegedActionException;
  69 import java.security.PrivilegedExceptionAction;
  70 import java.security.ProtectionDomain;
  71 
  72 import java.util.*;
  73 
  74 import sun.datatransfer.DataFlavorUtil;
  75 
  76 import sun.awt.AppContext;
  77 import sun.awt.SunToolkit;
  78 
  79 import java.awt.image.BufferedImage;
  80 import java.awt.image.ImageObserver;
  81 import java.awt.image.RenderedImage;
  82 import java.awt.image.WritableRaster;
  83 import java.awt.image.ColorModel;
  84 
  85 import javax.imageio.ImageIO;
  86 import javax.imageio.ImageReader;
  87 import javax.imageio.ImageReadParam;
  88 import javax.imageio.ImageWriter;
  89 import javax.imageio.ImageTypeSpecifier;
  90 
  91 import javax.imageio.spi.ImageWriterSpi;
  92 
  93 import javax.imageio.stream.ImageInputStream;
  94 import javax.imageio.stream.ImageOutputStream;
  95 
  96 import sun.awt.image.ImageRepresentation;
  97 import sun.awt.image.ToolkitImage;
  98 
  99 import java.io.FilePermission;
 100 import java.util.stream.Stream;
 101 
 102 
 103 /**
 104  * Provides a set of functions to be shared among the DataFlavor class and
 105  * platform-specific data transfer implementations.
 106  *
 107  * The concept of "flavors" and "natives" is extended to include "formats",
 108  * which are the numeric values Win32 and X11 use to express particular data
 109  * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and
 110  * getFlavorsForNatives(String[]) functions, DataTransferer provides a set
 111  * of getFormatsFor(Transferable|Flavor|Flavors) and
 112  * getFlavorsFor(Format|Formats) functions.
 113  *
 114  * Also provided are functions for translating a Transferable into a byte
 115  * array, given a source DataFlavor and a target format, and for translating
 116  * a byte array or InputStream into an Object, given a source format and
 117  * a target DataFlavor.
 118  *
 119  * @author David Mendenhall
 120  * @author Danila Sinopalnikov
 121  *
 122  * @since 1.3.1
 123  */
 124 public abstract class DataTransferer {
 125     /**
 126      * The <code>DataFlavor</code> representing a Java text encoding String
 127      * encoded in UTF-8, where
 128      * <pre>
 129      *     representationClass = [B
 130      *     mimeType            = "application/x-java-text-encoding"
 131      * </pre>
 132      */
 133     public static final DataFlavor javaTextEncodingFlavor;
 134 
 135     /**
 136      * A collection of all natives listed in flavormap.properties with
 137      * a primary MIME type of "text".
 138      */
 139     private static final Set<Long> textNatives =
 140             Collections.synchronizedSet(new HashSet<>());
 141 
 142     /**
 143      * The native encodings/charsets for the Set of textNatives.
 144      */
 145     private static final Map<Long, String> nativeCharsets =
 146             Collections.synchronizedMap(new HashMap<>());
 147 
 148     /**
 149      * The end-of-line markers for the Set of textNatives.
 150      */
 151     private static final Map<Long, String> nativeEOLNs =
 152             Collections.synchronizedMap(new HashMap<>());
 153 
 154     /**
 155      * The number of terminating NUL bytes for the Set of textNatives.
 156      */
 157     private static final Map<Long, Integer> nativeTerminators =
 158             Collections.synchronizedMap(new HashMap<>());
 159 
 160     /**
 161      * The key used to store pending data conversion requests for an AppContext.
 162      */
 163     private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY";
 164 
 165     static {
 166         DataFlavor tJavaTextEncodingFlavor = null;
 167         try {
 168             tJavaTextEncodingFlavor = new DataFlavor("application/x-java-text-encoding;class=\"[B\"");
 169         } catch (ClassNotFoundException cannotHappen) {
 170         }
 171         javaTextEncodingFlavor = tJavaTextEncodingFlavor;
 172     }
 173 
 174     /**
 175      * The accessor method for the singleton DataTransferer instance. Note
 176      * that in a headless environment, there may be no DataTransferer instance;
 177      * instead, null will be returned.
 178      */
 179     public static synchronized DataTransferer getInstance() {
 180         return ((SunToolkit) Toolkit.getDefaultToolkit()).getDataTransferer();
 181     }
 182 
 183     /**
 184      * Converts a FlavorMap to a FlavorTable.
 185      */
 186     public static FlavorTable adaptFlavorMap(final FlavorMap map) {
 187         if (map instanceof FlavorTable) {
 188             return (FlavorTable)map;
 189         }
 190 
 191         return new FlavorTable() {
 192             @Override
 193             public Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] flavors) {
 194                 return map.getNativesForFlavors(flavors);
 195             }
 196             @Override
 197             public Map<String, DataFlavor> getFlavorsForNatives(String[] natives) {
 198                 return map.getFlavorsForNatives(natives);
 199             }
 200             @Override
 201             public List<String> getNativesForFlavor(DataFlavor flav) {
 202                 Map<DataFlavor, String> natives = getNativesForFlavors(new DataFlavor[]{flav});
 203                 String nat = natives.get(flav);
 204                 if (nat != null) {
 205                     return Collections.singletonList(nat);
 206                 } else {
 207                     return Collections.emptyList();
 208                 }
 209             }
 210             @Override
 211             public List<DataFlavor> getFlavorsForNative(String nat) {
 212                 Map<String, DataFlavor> flavors = getFlavorsForNatives(new String[]{nat});
 213                 DataFlavor flavor = flavors.get(nat);
 214                 if (flavor != null) {
 215                     return Collections.singletonList(flavor);
 216                 } else {
 217                     return Collections.emptyList();
 218                 }
 219             }
 220         };
 221     }
 222 
 223     /**
 224      * Returns the default Unicode encoding for the platform. The encoding
 225      * need not be canonical. This method is only used by the archaic function
 226      * DataFlavor.getTextPlainUnicodeFlavor().
 227      */
 228     public abstract String getDefaultUnicodeEncoding();
 229 
 230     /**
 231      * This method is called for text flavor mappings established while parsing
 232      * the flavormap.properties file. It stores the "eoln" and "terminators"
 233      * parameters which are not officially part of the MIME type. They are
 234      * MIME parameters specific to the flavormap.properties file format.
 235      */
 236     public void registerTextFlavorProperties(String nat, String charset,
 237                                              String eoln, String terminators) {
 238         Long format = getFormatForNativeAsLong(nat);
 239 
 240         textNatives.add(format);
 241         nativeCharsets.put(format, (charset != null && charset.length() != 0)
 242                 ? charset : Charset.defaultCharset().name());
 243         if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) {
 244             nativeEOLNs.put(format, eoln);
 245         }
 246         if (terminators != null && terminators.length() != 0) {
 247             Integer iTerminators = Integer.valueOf(terminators);
 248             if (iTerminators > 0) {
 249                 nativeTerminators.put(format, iTerminators);
 250             }
 251         }
 252     }
 253 
 254     /**
 255      * Determines whether the native corresponding to the specified long format
 256      * was listed in the flavormap.properties file.
 257      */
 258     protected boolean isTextFormat(long format) {
 259         return textNatives.contains(Long.valueOf(format));
 260     }
 261 
 262     protected String getCharsetForTextFormat(Long lFormat) {
 263         return nativeCharsets.get(lFormat);
 264     }
 265 
 266     /**
 267      * Specifies whether text imported from the native system in the specified
 268      * format is locale-dependent. If so, when decoding such text,
 269      * 'nativeCharsets' should be ignored, and instead, the Transferable should
 270      * be queried for its javaTextEncodingFlavor data for the correct encoding.
 271      */
 272     public abstract boolean isLocaleDependentTextFormat(long format);
 273 
 274     /**
 275      * Determines whether the DataFlavor corresponding to the specified long
 276      * format is DataFlavor.javaFileListFlavor.
 277      */
 278     public abstract boolean isFileFormat(long format);
 279 
 280     /**
 281      * Determines whether the DataFlavor corresponding to the specified long
 282      * format is DataFlavor.imageFlavor.
 283      */
 284     public abstract boolean isImageFormat(long format);
 285 
 286     /**
 287      * Determines whether the format is a URI list we can convert to
 288      * a DataFlavor.javaFileListFlavor.
 289      */
 290     protected boolean isURIListFormat(long format) {
 291         return false;
 292     }
 293 
 294     /**
 295      * Returns a Map whose keys are all of the possible formats into which the
 296      * Transferable's transfer data flavors can be translated. The value of
 297      * each key is the DataFlavor in which the Transferable's data should be
 298      * requested when converting to the format.
 299      * <p>
 300      * The map keys are sorted according to the native formats preference
 301      * order.
 302      */
 303     public SortedMap<Long,DataFlavor> getFormatsForTransferable(Transferable contents,
 304                                                                 FlavorTable map)
 305     {
 306         DataFlavor[] flavors = contents.getTransferDataFlavors();
 307         if (flavors == null) {
 308             return Collections.emptySortedMap();
 309         }
 310         return getFormatsForFlavors(flavors, map);
 311     }
 312 
 313     /**
 314      * Returns a Map whose keys are all of the possible formats into which data
 315      * in the specified DataFlavors can be translated. The value of each key
 316      * is the DataFlavor in which the Transferable's data should be requested
 317      * when converting to the format.
 318      * <p>
 319      * The map keys are sorted according to the native formats preference
 320      * order.
 321      *
 322      * @param flavors the data flavors
 323      * @param map the FlavorTable which contains mappings between
 324      *            DataFlavors and data formats
 325      * @throws NullPointerException if flavors or map is <code>null</code>
 326      */
 327     public SortedMap<Long, DataFlavor> getFormatsForFlavors(DataFlavor[] flavors,
 328                                                             FlavorTable map)
 329     {
 330         Map<Long,DataFlavor> formatMap = new HashMap<>(flavors.length);
 331         Map<Long,DataFlavor> textPlainMap = new HashMap<>(flavors.length);
 332         // Maps formats to indices that will be used to sort the formats
 333         // according to the preference order.
 334         // Larger index value corresponds to the more preferable format.
 335         Map<Long, Integer> indexMap = new HashMap<>(flavors.length);
 336         Map<Long, Integer> textPlainIndexMap = new HashMap<>(flavors.length);
 337 
 338         int currentIndex = 0;
 339 
 340         // Iterate backwards so that preferred DataFlavors are used over
 341         // other DataFlavors. (See javadoc for
 342         // Transferable.getTransferDataFlavors.)
 343         for (int i = flavors.length - 1; i >= 0; i--) {
 344             DataFlavor flavor = flavors[i];
 345             if (flavor == null) continue;
 346 
 347             // Don't explicitly test for String, since it is just a special
 348             // case of Serializable
 349             if (flavor.isFlavorTextType() ||
 350                 flavor.isFlavorJavaFileListType() ||
 351                 DataFlavor.imageFlavor.equals(flavor) ||
 352                 flavor.isRepresentationClassSerializable() ||
 353                 flavor.isRepresentationClassInputStream() ||
 354                 flavor.isRepresentationClassRemote())
 355             {
 356                 List<String> natives = map.getNativesForFlavor(flavor);
 357 
 358                 currentIndex += natives.size();
 359 
 360                 for (String aNative : natives) {
 361                     Long lFormat = getFormatForNativeAsLong(aNative);
 362                     Integer index = currentIndex--;
 363 
 364                     formatMap.put(lFormat, flavor);
 365                     indexMap.put(lFormat, index);
 366 
 367                     // SystemFlavorMap.getNativesForFlavor will return
 368                     // text/plain natives for all text/*. While this is good
 369                     // for a single text/* flavor, we would prefer that
 370                     // text/plain native data come from a text/plain flavor.
 371                     if (("text".equals(flavor.getPrimaryType()) &&
 372                             "plain".equals(flavor.getSubType())) ||
 373                             flavor.equals(DataFlavor.stringFlavor)) {
 374                         textPlainMap.put(lFormat, flavor);
 375                         textPlainIndexMap.put(lFormat, index);
 376                     }
 377                 }
 378 
 379                 currentIndex += natives.size();
 380             }
 381         }
 382 
 383         formatMap.putAll(textPlainMap);
 384         indexMap.putAll(textPlainIndexMap);
 385 
 386         // Sort the map keys according to the formats preference order.
 387         Comparator<Long> comparator = DataFlavorUtil.getIndexOrderComparator(indexMap).reversed();
 388         SortedMap<Long, DataFlavor> sortedMap = new TreeMap<>(comparator);
 389         sortedMap.putAll(formatMap);
 390 
 391         return sortedMap;
 392     }
 393 
 394     /**
 395      * Reduces the Map output for the root function to an array of the
 396      * Map's keys.
 397      */
 398     public long[] getFormatsForTransferableAsArray(Transferable contents,
 399                                                    FlavorTable map) {
 400         return keysToLongArray(getFormatsForTransferable(contents, map));
 401     }
 402 
 403     /**
 404      * Returns a Map whose keys are all of the possible DataFlavors into which
 405      * data in the specified formats can be translated. The value of each key
 406      * is the format in which the Clipboard or dropped data should be requested
 407      * when converting to the DataFlavor.
 408      */
 409     public Map<DataFlavor, Long> getFlavorsForFormats(long[] formats, FlavorTable map) {
 410         Map<DataFlavor, Long> flavorMap = new HashMap<>(formats.length);
 411         Set<AbstractMap.SimpleEntry<Long, DataFlavor>> mappingSet = new HashSet<>(formats.length);
 412         Set<DataFlavor> flavorSet = new HashSet<>(formats.length);
 413 
 414         // First step: build flavorSet, mappingSet and initial flavorMap
 415         // flavorSet  - the set of all the DataFlavors into which
 416         //              data in the specified formats can be translated;
 417         // mappingSet - the set of all the mappings from the specified formats
 418         //              into any DataFlavor;
 419         // flavorMap  - after this step, this map maps each of the DataFlavors
 420         //              from flavorSet to any of the specified formats.
 421         for (long format : formats) {
 422             String nat = getNativeForFormat(format);
 423             List<DataFlavor> flavors = map.getFlavorsForNative(nat);
 424             for (DataFlavor flavor : flavors) {
 425                 // Don't explicitly test for String, since it is just a special
 426                 // case of Serializable
 427                 if (flavor.isFlavorTextType() ||
 428                         flavor.isFlavorJavaFileListType() ||
 429                         DataFlavor.imageFlavor.equals(flavor) ||
 430                         flavor.isRepresentationClassSerializable() ||
 431                         flavor.isRepresentationClassInputStream() ||
 432                         flavor.isRepresentationClassRemote()) {
 433 
 434                     AbstractMap.SimpleEntry<Long, DataFlavor> mapping =
 435                             new AbstractMap.SimpleEntry<>(format, flavor);
 436                     flavorMap.put(flavor, format);
 437                     mappingSet.add(mapping);
 438                     flavorSet.add(flavor);
 439                 }
 440             }
 441         }
 442 
 443         // Second step: for each DataFlavor try to figure out which of the
 444         // specified formats is the best to translate to this flavor.
 445         // Then map each flavor to the best format.
 446         // For the given flavor, FlavorTable indicates which native will
 447         // best reflect data in the specified flavor to the underlying native
 448         // platform. We assume that this native is the best to translate
 449         // to this flavor.
 450         // Note: FlavorTable allows one-way mappings, so we can occasionally
 451         // map a flavor to the format for which the corresponding
 452         // format-to-flavor mapping doesn't exist. For this reason we have built
 453         // a mappingSet of all format-to-flavor mappings for the specified formats
 454         // and check if the format-to-flavor mapping exists for the
 455         // (flavor,format) pair being added.
 456         for (DataFlavor flavor : flavorSet) {
 457             List<String> natives = map.getNativesForFlavor(flavor);
 458             for (String aNative : natives) {
 459                 Long lFormat = getFormatForNativeAsLong(aNative);
 460                 if (mappingSet.contains(new AbstractMap.SimpleEntry<>(lFormat, flavor))) {
 461                     flavorMap.put(flavor, lFormat);
 462                     break;
 463                 }
 464             }
 465         }
 466 
 467         return flavorMap;
 468     }
 469 
 470     /**
 471      * Returns a Set of all DataFlavors for which
 472      * 1) a mapping from at least one of the specified formats exists in the
 473      * specified map and
 474      * 2) the data translation for this mapping can be performed by the data
 475      * transfer subsystem.
 476      *
 477      * @param formats the data formats
 478      * @param map the FlavorTable which contains mappings between
 479      *            DataFlavors and data formats
 480      * @throws NullPointerException if formats or map is <code>null</code>
 481      */
 482     public Set<DataFlavor> getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) {
 483         Set<DataFlavor> flavorSet = new HashSet<>(formats.length);
 484 
 485         for (long format : formats) {
 486             List<DataFlavor> flavors = map.getFlavorsForNative(getNativeForFormat(format));
 487             for (DataFlavor flavor : flavors) {
 488                 // Don't explicitly test for String, since it is just a special
 489                 // case of Serializable
 490                 if (flavor.isFlavorTextType() ||
 491                         flavor.isFlavorJavaFileListType() ||
 492                         DataFlavor.imageFlavor.equals(flavor) ||
 493                         flavor.isRepresentationClassSerializable() ||
 494                         flavor.isRepresentationClassInputStream() ||
 495                         flavor.isRepresentationClassRemote()) {
 496                     flavorSet.add(flavor);
 497                 }
 498             }
 499         }
 500 
 501         return flavorSet;
 502     }
 503 
 504     /**
 505      * Returns an array of all DataFlavors for which
 506      * 1) a mapping from at least one of the specified formats exists in the
 507      * specified map and
 508      * 2) the data translation for this mapping can be performed by the data
 509      * transfer subsystem.
 510      * The array will be sorted according to a
 511      * <code>DataFlavorComparator</code> created with the specified
 512      * map as an argument.
 513      *
 514      * @param formats the data formats
 515      * @param map the FlavorTable which contains mappings between
 516      *            DataFlavors and data formats
 517      * @throws NullPointerException if formats or map is <code>null</code>
 518      */
 519     public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats,
 520                                                     FlavorTable map) {
 521         // getFlavorsForFormatsAsSet() is less expensive than
 522         // getFlavorsForFormats().
 523         return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map));
 524     }
 525 
 526     /**
 527      * Looks-up or registers the String native with the native data transfer
 528      * system and returns a long format corresponding to that native.
 529      */
 530     protected abstract Long getFormatForNativeAsLong(String str);
 531 
 532     /**
 533      * Looks-up the String native corresponding to the specified long format in
 534      * the native data transfer system.
 535      */
 536     protected abstract String getNativeForFormat(long format);
 537 
 538     /* Contains common code for finding the best charset for
 539      * clipboard string encoding/decoding, basing on clipboard
 540      * format and localeTransferable(on decoding, if available)
 541      */
 542     protected String getBestCharsetForTextFormat(Long lFormat,
 543         Transferable localeTransferable) throws IOException
 544     {
 545         String charset = null;
 546         if (localeTransferable != null &&
 547             isLocaleDependentTextFormat(lFormat) &&
 548             localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) {
 549             try {
 550                 byte[] charsetNameBytes = (byte[])localeTransferable
 551                         .getTransferData(javaTextEncodingFlavor);
 552                 charset = new String(charsetNameBytes, StandardCharsets.UTF_8);
 553             } catch (UnsupportedFlavorException cannotHappen) {
 554             }
 555         } else {
 556             charset = getCharsetForTextFormat(lFormat);
 557         }
 558         if (charset == null) {
 559             // Only happens when we have a custom text type.
 560             charset = Charset.defaultCharset().name();
 561         }
 562         return charset;
 563     }
 564 
 565     /**
 566      *  Translation function for converting string into
 567      *  a byte array. Search-and-replace EOLN. Encode into the
 568      *  target format. Append terminating NUL bytes.
 569      *
 570      *  Java to Native string conversion
 571      */
 572     private byte[] translateTransferableString(String str,
 573                                                long format) throws IOException
 574     {
 575         Long lFormat = format;
 576         String charset = getBestCharsetForTextFormat(lFormat, null);
 577         // Search and replace EOLN. Note that if EOLN is "\n", then we
 578         // never added an entry to nativeEOLNs anyway, so we'll skip this
 579         // code altogether.
 580         // windows: "abc\nde"->"abc\r\nde"
 581         String eoln = nativeEOLNs.get(lFormat);
 582         if (eoln != null) {
 583             int length = str.length();
 584             StringBuilder buffer = new StringBuilder(length * 2); // 2 is a heuristic
 585             for (int i = 0; i < length; i++) {
 586                 // Fix for 4914613 - skip native EOLN
 587                 if (str.startsWith(eoln, i)) {
 588                     buffer.append(eoln);
 589                     i += eoln.length() - 1;
 590                     continue;
 591                 }
 592                 char c = str.charAt(i);
 593                 if (c == '\n') {
 594                     buffer.append(eoln);
 595                 } else {
 596                     buffer.append(c);
 597                 }
 598             }
 599             str = buffer.toString();
 600         }
 601 
 602         // Encode text in target format.
 603         byte[] bytes = str.getBytes(charset);
 604 
 605         // Append terminating NUL bytes. Note that if terminators is 0,
 606         // the we never added an entry to nativeTerminators anyway, so
 607         // we'll skip code altogether.
 608         // "abcde" -> "abcde\0"
 609         Integer terminators = nativeTerminators.get(lFormat);
 610         if (terminators != null) {
 611             int numTerminators = terminators;
 612             byte[] terminatedBytes =
 613                 new byte[bytes.length + numTerminators];
 614             System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length);
 615             for (int i = bytes.length; i < terminatedBytes.length; i++) {
 616                 terminatedBytes[i] = 0x0;
 617             }
 618             bytes = terminatedBytes;
 619         }
 620         return bytes;
 621     }
 622 
 623     /**
 624      * Translating either a byte array or an InputStream into an String.
 625      * Strip terminators and search-and-replace EOLN.
 626      *
 627      * Native to Java string conversion
 628      */
 629     private String translateBytesToString(byte[] bytes, long format,
 630                                           Transferable localeTransferable)
 631             throws IOException
 632     {
 633 
 634         Long lFormat = format;
 635         String charset = getBestCharsetForTextFormat(lFormat, localeTransferable);
 636 
 637         // Locate terminating NUL bytes. Note that if terminators is 0,
 638         // the we never added an entry to nativeTerminators anyway, so
 639         // we'll skip code altogether.
 640 
 641         // In other words: we are doing char alignment here basing on suggestion
 642         // that count of zero-'terminators' is a number of bytes in one symbol
 643         // for selected charset (clipboard format). It is not complitly true for
 644         // multibyte coding like UTF-8, but helps understand the procedure.
 645         // "abcde\0" -> "abcde"
 646 
 647         String eoln = nativeEOLNs.get(lFormat);
 648         Integer terminators = nativeTerminators.get(lFormat);
 649         int count;
 650         if (terminators != null) {
 651             int numTerminators = terminators;
 652 search:
 653             for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) {
 654                 for (int i = count; i < count + numTerminators; i++) {
 655                     if (bytes[i] != 0x0) {
 656                         continue search;
 657                     }
 658                 }
 659                 // found terminators
 660                 break search;
 661             }
 662         } else {
 663             count = bytes.length;
 664         }
 665 
 666         // Decode text to chars. Don't include any terminators.
 667         String converted = new String(bytes, 0, count, charset);
 668 
 669         // Search and replace EOLN. Note that if EOLN is "\n", then we
 670         // never added an entry to nativeEOLNs anyway, so we'll skip this
 671         // code altogether.
 672         // Count of NUL-terminators and EOLN coding are platform-specific and
 673         // loaded from flavormap.properties file
 674         // windows: "abc\r\nde" -> "abc\nde"
 675 
 676         if (eoln != null) {
 677 
 678             /* Fix for 4463560: replace EOLNs symbol-by-symbol instead
 679              * of using buf.replace()
 680              */
 681 
 682             char[] buf = converted.toCharArray();
 683             char[] eoln_arr = eoln.toCharArray();
 684             int j = 0;
 685             boolean match;
 686 
 687             for (int i = 0; i < buf.length; ) {
 688                 // Catch last few bytes
 689                 if (i + eoln_arr.length > buf.length) {
 690                     buf[j++] = buf[i++];
 691                     continue;
 692                 }
 693 
 694                 match = true;
 695                 for (int k = 0, l = i; k < eoln_arr.length; k++, l++) {
 696                     if (eoln_arr[k] != buf[l]) {
 697                         match = false;
 698                         break;
 699                     }
 700                 }
 701                 if (match) {
 702                     buf[j++] = '\n';
 703                     i += eoln_arr.length;
 704                 } else {
 705                     buf[j++] = buf[i++];
 706                 }
 707             }
 708             converted = new String(buf, 0, j);
 709         }
 710 
 711         return converted;
 712     }
 713 
 714 
 715     /**
 716      * Primary translation function for translating a Transferable into
 717      * a byte array, given a source DataFlavor and target format.
 718      */
 719     public byte[] translateTransferable(Transferable contents,
 720                                         DataFlavor flavor,
 721                                         long format) throws IOException
 722     {
 723         // Obtain the transfer data in the source DataFlavor.
 724         //
 725         // Note that we special case DataFlavor.plainTextFlavor because
 726         // StringSelection supports this flavor incorrectly -- instead of
 727         // returning an InputStream as the DataFlavor representation class
 728         // states, it returns a Reader. Instead of using this broken
 729         // functionality, we request the data in stringFlavor (the other
 730         // DataFlavor which StringSelection supports) and use the String
 731         // translator.
 732         Object obj;
 733         boolean stringSelectionHack;
 734         try {
 735             obj = contents.getTransferData(flavor);
 736             if (obj == null) {
 737                 return null;
 738             }
 739             if (flavor.equals(DataFlavor.plainTextFlavor) &&
 740                 !(obj instanceof InputStream))
 741             {
 742                 obj = contents.getTransferData(DataFlavor.stringFlavor);
 743                 if (obj == null) {
 744                     return null;
 745                 }
 746                 stringSelectionHack = true;
 747             } else {
 748                 stringSelectionHack = false;
 749             }
 750         } catch (UnsupportedFlavorException e) {
 751             throw new IOException(e.getMessage());
 752         }
 753 
 754         // Source data is a String. Search-and-replace EOLN. Encode into the
 755         // target format. Append terminating NUL bytes.
 756         if (stringSelectionHack ||
 757             (String.class.equals(flavor.getRepresentationClass()) &&
 758              DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
 759 
 760             String str = removeSuspectedData(flavor, contents, (String)obj);
 761 
 762             return translateTransferableString(
 763                 str,
 764                 format);
 765 
 766         // Source data is a Reader. Convert to a String and recur. In the
 767         // future, we may want to rewrite this so that we encode on demand.
 768         } else if (flavor.isRepresentationClassReader()) {
 769             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
 770                 throw new IOException
 771                     ("cannot transfer non-text data as Reader");
 772             }
 773 
 774             StringBuilder buf = new StringBuilder();
 775             try (Reader r = (Reader)obj) {
 776                 int c;
 777                 while ((c = r.read()) != -1) {
 778                     buf.append((char)c);
 779                 }
 780             }
 781 
 782             return translateTransferableString(
 783                 buf.toString(),
 784                 format);
 785 
 786         // Source data is a CharBuffer. Convert to a String and recur.
 787         } else if (flavor.isRepresentationClassCharBuffer()) {
 788             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
 789                 throw new IOException
 790                     ("cannot transfer non-text data as CharBuffer");
 791             }
 792 
 793             CharBuffer buffer = (CharBuffer)obj;
 794             int size = buffer.remaining();
 795             char[] chars = new char[size];
 796             buffer.get(chars, 0, size);
 797 
 798             return translateTransferableString(
 799                 new String(chars),
 800                 format);
 801 
 802         // Source data is a char array. Convert to a String and recur.
 803         } else if (char[].class.equals(flavor.getRepresentationClass())) {
 804             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
 805                 throw new IOException
 806                     ("cannot transfer non-text data as char array");
 807             }
 808 
 809             return translateTransferableString(
 810                 new String((char[])obj),
 811                 format);
 812 
 813         // Source data is a ByteBuffer. For arbitrary flavors, simply return
 814         // the array. For text flavors, decode back to a String and recur to
 815         // reencode according to the requested format.
 816         } else if (flavor.isRepresentationClassByteBuffer()) {
 817             ByteBuffer buffer = (ByteBuffer)obj;
 818             int size = buffer.remaining();
 819             byte[] bytes = new byte[size];
 820             buffer.get(bytes, 0, size);
 821 
 822             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
 823                 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
 824                 return translateTransferableString(
 825                     new String(bytes, sourceEncoding),
 826                     format);
 827             } else {
 828                 return bytes;
 829             }
 830 
 831         // Source data is a byte array. For arbitrary flavors, simply return
 832         // the array. For text flavors, decode back to a String and recur to
 833         // reencode according to the requested format.
 834         } else if (byte[].class.equals(flavor.getRepresentationClass())) {
 835             byte[] bytes = (byte[])obj;
 836 
 837             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
 838                 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
 839                 return translateTransferableString(
 840                     new String(bytes, sourceEncoding),
 841                     format);
 842             } else {
 843                 return bytes;
 844             }
 845         // Source data is Image
 846         } else if (DataFlavor.imageFlavor.equals(flavor)) {
 847             if (!isImageFormat(format)) {
 848                 throw new IOException("Data translation failed: " +
 849                                       "not an image format");
 850             }
 851 
 852             Image image = (Image)obj;
 853             byte[] bytes = imageToPlatformBytes(image, format);
 854 
 855             if (bytes == null) {
 856                 throw new IOException("Data translation failed: " +
 857                     "cannot convert java image to native format");
 858             }
 859             return bytes;
 860         }
 861 
 862         byte[] theByteArray = null;
 863 
 864         // Target data is a file list. Source data must be a
 865         // java.util.List which contains java.io.File or String instances.
 866         if (isFileFormat(format)) {
 867             if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
 868                 throw new IOException("data translation failed");
 869             }
 870 
 871             final List<?> list = (List<?>)obj;
 872 
 873             final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
 874 
 875             final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);
 876 
 877             try (ByteArrayOutputStream bos = convertFileListToBytes(fileList)) {
 878                 theByteArray = bos.toByteArray();
 879             }
 880 
 881         // Target data is a URI list. Source data must be a
 882         // java.util.List which contains java.io.File or String instances.
 883         } else if (isURIListFormat(format)) {
 884             if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
 885                 throw new IOException("data translation failed");
 886             }
 887             String nat = getNativeForFormat(format);
 888             String targetCharset = null;
 889             if (nat != null) {
 890                 try {
 891                     targetCharset = new DataFlavor(nat).getParameter("charset");
 892                 } catch (ClassNotFoundException cnfe) {
 893                     throw new IOException(cnfe);
 894                 }
 895             }
 896             if (targetCharset == null) {
 897                 targetCharset = "UTF-8";
 898             }
 899             final List<?> list = (List<?>)obj;
 900             final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
 901             final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);
 902             final ArrayList<String> uriList = new ArrayList<>(fileList.size());
 903             for (String fileObject : fileList) {
 904                 final URI uri = new File(fileObject).toURI();
 905                 // Some implementations are fussy about the number of slashes (file:///path/to/file is best)
 906                 try {
 907                     uriList.add(new URI(uri.getScheme(), "", uri.getPath(), uri.getFragment()).toString());
 908                 } catch (URISyntaxException uriSyntaxException) {
 909                     throw new IOException(uriSyntaxException);
 910                   }
 911               }
 912 
 913             byte[] eoln = "\r\n".getBytes(targetCharset);
 914 
 915             try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
 916                 for (String uri : uriList) {
 917                     byte[] bytes = uri.getBytes(targetCharset);
 918                     bos.write(bytes, 0, bytes.length);
 919                     bos.write(eoln, 0, eoln.length);
 920                 }
 921                 theByteArray = bos.toByteArray();
 922             }
 923 
 924         // Source data is an InputStream. For arbitrary flavors, just grab the
 925         // bytes and dump them into a byte array. For text flavors, decode back
 926         // to a String and recur to reencode according to the requested format.
 927         } else if (flavor.isRepresentationClassInputStream()) {
 928             try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
 929                 try (InputStream is = (InputStream)obj) {
 930                     boolean eof = false;
 931                     int avail = is.available();
 932                     byte[] tmp = new byte[avail > 8192 ? avail : 8192];
 933                     do {
 934                         int aValue;
 935                         if (!(eof = (aValue = is.read(tmp, 0, tmp.length)) == -1)) {
 936                             bos.write(tmp, 0, aValue);
 937                         }
 938                     } while (!eof);
 939                 }
 940 
 941                 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
 942                     byte[] bytes = bos.toByteArray();
 943                     String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
 944                     return translateTransferableString(
 945                                new String(bytes, sourceEncoding),
 946                                format);
 947                 }
 948                 theByteArray = bos.toByteArray();
 949             }
 950 
 951 
 952         // Source data is an RMI object
 953         } else if (flavor.isRepresentationClassRemote()) {
 954             theByteArray = convertObjectToBytes(DataFlavorUtil.RMI.newMarshalledObject(obj));
 955 
 956         // Source data is Serializable
 957         } else if (flavor.isRepresentationClassSerializable()) {
 958 
 959             theByteArray = convertObjectToBytes(obj);
 960 
 961         } else {
 962             throw new IOException("data translation failed");
 963         }
 964 
 965 
 966 
 967         return theByteArray;
 968     }
 969 
 970     private static byte[] convertObjectToBytes(Object object) throws IOException {
 971         try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
 972              ObjectOutputStream oos = new ObjectOutputStream(bos))
 973         {
 974             oos.writeObject(object);
 975             return bos.toByteArray();
 976         }
 977     }
 978 
 979     protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException;
 980 
 981     private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str)
 982             throws IOException
 983     {
 984         if (null == System.getSecurityManager()
 985             || !flavor.isMimeTypeEqual("text/uri-list"))
 986         {
 987             return str;
 988         }
 989 
 990         final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
 991 
 992         try {
 993             return AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> {
 994 
 995                 StringBuilder allowedFiles = new StringBuilder(str.length());
 996                 String [] uriArray = str.split("(\\s)+");
 997 
 998                 for (String fileName : uriArray)
 999                 {
1000                     File file = new File(fileName);
1001                     if (file.exists() &&
1002                         !(isFileInWebstartedCache(file) ||
1003                         isForbiddenToRead(file, userProtectionDomain)))
1004                     {
1005                         if (0 != allowedFiles.length())
1006                         {
1007                             allowedFiles.append("\\r\\n");
1008                         }
1009 
1010                         allowedFiles.append(fileName);
1011                     }
1012                 }
1013 
1014                 return allowedFiles.toString();
1015             });
1016         } catch (PrivilegedActionException pae) {
1017             throw new IOException(pae.getMessage(), pae);
1018         }
1019     }
1020 
1021     private static ProtectionDomain getUserProtectionDomain(Transferable contents) {
1022         return contents.getClass().getProtectionDomain();
1023     }
1024 
1025     private boolean isForbiddenToRead (File file, ProtectionDomain protectionDomain)
1026     {
1027         if (null == protectionDomain) {
1028             return false;
1029         }
1030         try {
1031             FilePermission filePermission =
1032                     new FilePermission(file.getCanonicalPath(), "read, delete");
1033             if (protectionDomain.implies(filePermission)) {
1034                 return false;
1035             }
1036         } catch (IOException e) {}
1037 
1038         return true;
1039     }
1040 
1041     private ArrayList<String> castToFiles(final List<?> files,
1042                                           final ProtectionDomain userProtectionDomain) throws IOException {
1043         try {
1044             return AccessController.doPrivileged((PrivilegedExceptionAction<ArrayList<String>>) () -> {
1045                 ArrayList<String> fileList = new ArrayList<>();
1046                 for (Object fileObject : files)
1047                 {
1048                     File file = castToFile(fileObject);
1049                     if (file != null &&
1050                         (null == System.getSecurityManager() ||
1051                         !(isFileInWebstartedCache(file) ||
1052                         isForbiddenToRead(file, userProtectionDomain))))
1053                     {
1054                         fileList.add(file.getCanonicalPath());
1055                     }
1056                 }
1057                 return fileList;
1058             });
1059         } catch (PrivilegedActionException pae) {
1060             throw new IOException(pae.getMessage());
1061         }
1062     }
1063 
1064     // It is important do not use user's successors
1065     // of File class.
1066     private File castToFile(Object fileObject) throws IOException {
1067         String filePath = null;
1068         if (fileObject instanceof File) {
1069             filePath = ((File)fileObject).getCanonicalPath();
1070         } else if (fileObject instanceof String) {
1071            filePath = (String) fileObject;
1072         } else {
1073            return null;
1074         }
1075         return new File(filePath);
1076     }
1077 
1078     private final static String[] DEPLOYMENT_CACHE_PROPERTIES = {
1079         "deployment.system.cachedir",
1080         "deployment.user.cachedir",
1081         "deployment.javaws.cachedir",
1082         "deployment.javapi.cachedir"
1083     };
1084 
1085     private final static ArrayList <File> deploymentCacheDirectoryList = new ArrayList<>();
1086 
1087     private static boolean isFileInWebstartedCache(File f) {
1088 
1089         if (deploymentCacheDirectoryList.isEmpty()) {
1090             for (String cacheDirectoryProperty : DEPLOYMENT_CACHE_PROPERTIES) {
1091                 String cacheDirectoryPath = System.getProperty(cacheDirectoryProperty);
1092                 if (cacheDirectoryPath != null) {
1093                     try {
1094                         File cacheDirectory = (new File(cacheDirectoryPath)).getCanonicalFile();
1095                         if (cacheDirectory != null) {
1096                             deploymentCacheDirectoryList.add(cacheDirectory);
1097                         }
1098                     } catch (IOException ioe) {}
1099                 }
1100             }
1101         }
1102 
1103         for (File deploymentCacheDirectory : deploymentCacheDirectoryList) {
1104             for (File dir = f; dir != null; dir = dir.getParentFile()) {
1105                 if (dir.equals(deploymentCacheDirectory)) {
1106                     return true;
1107                 }
1108             }
1109         }
1110 
1111         return false;
1112     }
1113 
1114 
1115     public Object translateBytes(byte[] bytes, DataFlavor flavor,
1116                                  long format, Transferable localeTransferable)
1117         throws IOException
1118     {
1119 
1120         Object theObject = null;
1121 
1122         // Source data is a file list. Use the dragQueryFile native function to
1123         // do most of the decoding. Then wrap File objects around the String
1124         // filenames and return a List.
1125         if (isFileFormat(format)) {
1126             if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
1127                 throw new IOException("data translation failed");
1128             }
1129             String[] filenames = dragQueryFile(bytes);
1130             if (filenames == null) {
1131                 return null;
1132             }
1133 
1134             // Convert the strings to File objects
1135             File[] files = new File[filenames.length];
1136             for (int i = 0; i < filenames.length; i++) {
1137                 files[i] = new File(filenames[i]);
1138             }
1139 
1140             // Turn the list of Files into a List and return
1141             theObject = Arrays.asList(files);
1142 
1143             // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor
1144             // where possible.
1145         } else if (isURIListFormat(format)
1146                     && DataFlavor.javaFileListFlavor.equals(flavor)) {
1147 
1148             try (ByteArrayInputStream str = new ByteArrayInputStream(bytes))  {
1149 
1150                 URI uris[] = dragQueryURIs(str, format, localeTransferable);
1151                 if (uris == null) {
1152                     return null;
1153                 }
1154                 List<File> files = new ArrayList<>();
1155                 for (URI uri : uris) {
1156                     try {
1157                         files.add(new File(uri));
1158                     } catch (IllegalArgumentException illegalArg) {
1159                         // When converting from URIs to less generic files,
1160                         // common practice (Wine, SWT) seems to be to
1161                         // silently drop the URIs that aren't local files.
1162                     }
1163                 }
1164                 theObject = files;
1165             }
1166 
1167             // Target data is a String. Strip terminating NUL bytes. Decode bytes
1168             // into characters. Search-and-replace EOLN.
1169         } else if (String.class.equals(flavor.getRepresentationClass()) &&
1170                    DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1171 
1172             theObject = translateBytesToString(bytes, format, localeTransferable);
1173 
1174             // Target data is a Reader. Obtain data in InputStream format, encoded
1175             // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
1176             // back to chars on demand.
1177         } else if (flavor.isRepresentationClassReader()) {
1178             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1179                 theObject = translateStream(bais,
1180                         flavor, format, localeTransferable);
1181             }
1182             // Target data is a CharBuffer. Recur to obtain String and wrap.
1183         } else if (flavor.isRepresentationClassCharBuffer()) {
1184             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1185                 throw new IOException("cannot transfer non-text data as CharBuffer");
1186             }
1187 
1188             CharBuffer buffer = CharBuffer.wrap(
1189                 translateBytesToString(bytes,format, localeTransferable));
1190 
1191             theObject = constructFlavoredObject(buffer, flavor, CharBuffer.class);
1192 
1193             // Target data is a char array. Recur to obtain String and convert to
1194             // char array.
1195         } else if (char[].class.equals(flavor.getRepresentationClass())) {
1196             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1197                 throw new IOException
1198                           ("cannot transfer non-text data as char array");
1199             }
1200 
1201             theObject = translateBytesToString(
1202                 bytes, format, localeTransferable).toCharArray();
1203 
1204             // Target data is a ByteBuffer. For arbitrary flavors, just return
1205             // the raw bytes. For text flavors, convert to a String to strip
1206             // terminators and search-and-replace EOLN, then reencode according to
1207             // the requested flavor.
1208         } else if (flavor.isRepresentationClassByteBuffer()) {
1209             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1210                 bytes = translateBytesToString(
1211                     bytes, format, localeTransferable).getBytes(
1212                         DataFlavorUtil.getTextCharset(flavor)
1213                     );
1214             }
1215 
1216             ByteBuffer buffer = ByteBuffer.wrap(bytes);
1217             theObject = constructFlavoredObject(buffer, flavor, ByteBuffer.class);
1218 
1219             // Target data is a byte array. For arbitrary flavors, just return
1220             // the raw bytes. For text flavors, convert to a String to strip
1221             // terminators and search-and-replace EOLN, then reencode according to
1222             // the requested flavor.
1223         } else if (byte[].class.equals(flavor.getRepresentationClass())) {
1224             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1225                 theObject = translateBytesToString(
1226                     bytes, format, localeTransferable
1227                 ).getBytes(DataFlavorUtil.getTextCharset(flavor));
1228             } else {
1229                 theObject = bytes;
1230             }
1231 
1232             // Target data is an InputStream. For arbitrary flavors, just return
1233             // the raw bytes. For text flavors, decode to strip terminators and
1234             // search-and-replace EOLN, then reencode according to the requested
1235             // flavor.
1236         } else if (flavor.isRepresentationClassInputStream()) {
1237 
1238             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1239                 theObject = translateStream(bais, flavor, format, localeTransferable);
1240             }
1241 
1242         } else if (flavor.isRepresentationClassRemote()) {
1243             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
1244                  ObjectInputStream ois = new ObjectInputStream(bais)) {
1245 
1246                 DataFlavorUtil.RMI.getMarshalledObject(ois.readObject());
1247             } catch (Exception e) {
1248                 throw new IOException(e.getMessage());
1249             }
1250 
1251             // Target data is Serializable
1252         } else if (flavor.isRepresentationClassSerializable()) {
1253 
1254             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1255                 theObject = translateStream(bais, flavor, format, localeTransferable);
1256             }
1257 
1258             // Target data is Image
1259         } else if (DataFlavor.imageFlavor.equals(flavor)) {
1260             if (!isImageFormat(format)) {
1261                 throw new IOException("data translation failed");
1262             }
1263 
1264             theObject = platformImageBytesToImage(bytes, format);
1265         }
1266 
1267         if (theObject == null) {
1268             throw new IOException("data translation failed");
1269         }
1270 
1271         return theObject;
1272 
1273     }
1274 
1275     /**
1276      * Primary translation function for translating
1277      * an InputStream into an Object, given a source format and a target
1278      * DataFlavor.
1279      */
1280     public Object translateStream(InputStream str, DataFlavor flavor,
1281                                   long format, Transferable localeTransferable)
1282         throws IOException
1283     {
1284 
1285         Object theObject = null;
1286         // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor
1287         // where possible.
1288         if (isURIListFormat(format)
1289                 && DataFlavor.javaFileListFlavor.equals(flavor))
1290         {
1291 
1292             URI uris[] = dragQueryURIs(str, format, localeTransferable);
1293             if (uris == null) {
1294                 return null;
1295             }
1296             List<File> files = new ArrayList<>();
1297             for (URI uri : uris) {
1298                 try {
1299                     files.add(new File(uri));
1300                 } catch (IllegalArgumentException illegalArg) {
1301                     // When converting from URIs to less generic files,
1302                     // common practice (Wine, SWT) seems to be to
1303                     // silently drop the URIs that aren't local files.
1304                 }
1305             }
1306             theObject = files;
1307 
1308         // Target data is a String. Strip terminating NUL bytes. Decode bytes
1309         // into characters. Search-and-replace EOLN.
1310         } else if (String.class.equals(flavor.getRepresentationClass()) &&
1311                    DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1312 
1313             return translateBytesToString(inputStreamToByteArray(str),
1314                 format, localeTransferable);
1315 
1316             // Special hack to maintain backwards-compatibility with the brokenness
1317             // of StringSelection. Return a StringReader instead of an InputStream.
1318             // Recur to obtain String and encapsulate.
1319         } else if (DataFlavor.plainTextFlavor.equals(flavor)) {
1320             theObject = new StringReader(translateBytesToString(
1321                 inputStreamToByteArray(str),
1322                 format, localeTransferable));
1323 
1324             // Target data is an InputStream. For arbitrary flavors, just return
1325             // the raw bytes. For text flavors, decode to strip terminators and
1326             // search-and-replace EOLN, then reencode according to the requested
1327             // flavor.
1328         } else if (flavor.isRepresentationClassInputStream()) {
1329             theObject = translateStreamToInputStream(str, flavor, format,
1330                                                                localeTransferable);
1331 
1332             // Target data is a Reader. Obtain data in InputStream format, encoded
1333             // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
1334             // back to chars on demand.
1335         } else if (flavor.isRepresentationClassReader()) {
1336             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1337                 throw new IOException
1338                           ("cannot transfer non-text data as Reader");
1339             }
1340 
1341             InputStream is = (InputStream)translateStreamToInputStream(
1342                     str, DataFlavor.plainTextFlavor,
1343                     format, localeTransferable);
1344 
1345             String unicode = DataFlavorUtil.getTextCharset(DataFlavor.plainTextFlavor);
1346 
1347             Reader reader = new InputStreamReader(is, unicode);
1348 
1349             theObject = constructFlavoredObject(reader, flavor, Reader.class);
1350             // Target data is a byte array
1351         } else if (byte[].class.equals(flavor.getRepresentationClass())) {
1352             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1353                 theObject = translateBytesToString(inputStreamToByteArray(str), format, localeTransferable)
1354                         .getBytes(DataFlavorUtil.getTextCharset(flavor));
1355             } else {
1356                 theObject = inputStreamToByteArray(str);
1357             }
1358             // Target data is an RMI object
1359         } else if (flavor.isRepresentationClassRemote()) {
1360             try (ObjectInputStream ois = new ObjectInputStream(str)) {
1361                 theObject = DataFlavorUtil.RMI.getMarshalledObject(ois.readObject());
1362             } catch (Exception e) {
1363                 throw new IOException(e.getMessage());
1364             }
1365 
1366             // Target data is Serializable
1367         } else if (flavor.isRepresentationClassSerializable()) {
1368             try (ObjectInputStream ois =
1369                      new ObjectInputStream(str))
1370             {
1371                 theObject = ois.readObject();
1372             } catch (Exception e) {
1373                 throw new IOException(e.getMessage());
1374             }
1375             // Target data is Image
1376         } else if (DataFlavor.imageFlavor.equals(flavor)) {
1377             if (!isImageFormat(format)) {
1378                 throw new IOException("data translation failed");
1379             }
1380             theObject = platformImageBytesToImage(inputStreamToByteArray(str), format);
1381         }
1382 
1383         if (theObject == null) {
1384             throw new IOException("data translation failed");
1385         }
1386 
1387         return theObject;
1388 
1389     }
1390 
1391     /**
1392      * For arbitrary flavors, just use the raw InputStream. For text flavors,
1393      * ReencodingInputStream will decode and reencode the InputStream on demand
1394      * so that we can strip terminators and search-and-replace EOLN.
1395      */
1396     private Object translateStreamToInputStream
1397         (InputStream str, DataFlavor flavor, long format,
1398          Transferable localeTransferable) throws IOException
1399     {
1400         if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1401             str = new ReencodingInputStream
1402                 (str, format, DataFlavorUtil.getTextCharset(flavor),
1403                  localeTransferable);
1404         }
1405 
1406         return constructFlavoredObject(str, flavor, InputStream.class);
1407     }
1408 
1409     /**
1410      * We support representations which are exactly of the specified Class,
1411      * and also arbitrary Objects which have a constructor which takes an
1412      * instance of the Class as its sole parameter.
1413      */
1414     private Object constructFlavoredObject(Object arg, DataFlavor flavor,
1415                                            Class<?> clazz)
1416         throws IOException
1417     {
1418         final Class<?> dfrc = flavor.getRepresentationClass();
1419 
1420         if (clazz.equals(dfrc)) {
1421             return arg; // simple case
1422         } else {
1423             Constructor<?>[] constructors;
1424 
1425             try {
1426                 constructors = AccessController.doPrivileged(
1427                         (PrivilegedAction<Constructor<?>[]>) dfrc::getConstructors);
1428             } catch (SecurityException se) {
1429                 throw new IOException(se.getMessage());
1430             }
1431 
1432             Constructor<?> constructor = Stream.of(constructors)
1433                     .filter(c -> Modifier.isPublic(c.getModifiers()))
1434                     .filter(c -> {
1435                         Class<?>[] ptypes = c.getParameterTypes();
1436                         return ptypes != null
1437                                 && ptypes.length == 1
1438                                 && clazz.equals(ptypes[0]);
1439                     })
1440                     .findFirst()
1441                     .orElseThrow(() ->
1442                             new IOException("can't find <init>(L"+ clazz + ";)V for class: " + dfrc.getName()));
1443 
1444             try {
1445                 return constructor.newInstance(arg);
1446             } catch (Exception e) {
1447                 throw new IOException(e.getMessage());
1448             }
1449         }
1450     }
1451 
1452     /**
1453      * Used for decoding and reencoding an InputStream on demand so that we
1454      * can strip NUL terminators and perform EOLN search-and-replace.
1455      */
1456     public class ReencodingInputStream extends InputStream {
1457         BufferedReader wrapped;
1458         final char[] in = new char[2];
1459         byte[] out;
1460 
1461         CharsetEncoder encoder;
1462         CharBuffer inBuf;
1463         ByteBuffer outBuf;
1464 
1465         char[] eoln;
1466         int numTerminators;
1467 
1468         boolean eos;
1469         int index, limit;
1470 
1471         public ReencodingInputStream(InputStream bytestream, long format,
1472                                      String targetEncoding,
1473                                      Transferable localeTransferable)
1474             throws IOException
1475         {
1476             Long lFormat = format;
1477 
1478             String sourceEncoding = getBestCharsetForTextFormat(format, localeTransferable);
1479             wrapped = new BufferedReader(new InputStreamReader(bytestream, sourceEncoding));
1480 
1481             if (targetEncoding == null) {
1482                 // Throw NullPointerException for compatibility with the former
1483                 // call to sun.io.CharToByteConverter.getConverter(null)
1484                 // (Charset.forName(null) throws unspecified IllegalArgumentException
1485                 // now; see 6228568)
1486                 throw new NullPointerException("null target encoding");
1487             }
1488 
1489             try {
1490                 encoder = Charset.forName(targetEncoding).newEncoder();
1491                 out = new byte[(int)(encoder.maxBytesPerChar() * 2 + 0.5)];
1492                 inBuf = CharBuffer.wrap(in);
1493                 outBuf = ByteBuffer.wrap(out);
1494             } catch (IllegalCharsetNameException
1495                     | UnsupportedCharsetException
1496                     | UnsupportedOperationException e) {
1497                 throw new IOException(e.toString());
1498             }
1499 
1500             String sEoln = nativeEOLNs.get(lFormat);
1501             if (sEoln != null) {
1502                 eoln = sEoln.toCharArray();
1503             }
1504 
1505             // A hope and a prayer that this works generically. This will
1506             // definitely work on Win32.
1507             Integer terminators = nativeTerminators.get(lFormat);
1508             if (terminators != null) {
1509                 numTerminators = terminators;
1510             }
1511         }
1512 
1513         private int readChar() throws IOException {
1514             int c = wrapped.read();
1515 
1516             if (c == -1) { // -1 is EOS
1517                 eos = true;
1518                 return -1;
1519             }
1520 
1521             // "c == 0" is not quite correct, but good enough on Windows.
1522             if (numTerminators > 0 && c == 0) {
1523                 eos = true;
1524                 return -1;
1525             } else if (eoln != null && matchCharArray(eoln, c)) {
1526                 c = '\n' & 0xFFFF;
1527             }
1528 
1529             return c;
1530         }
1531 
1532         public int read() throws IOException {
1533             if (eos) {
1534                 return -1;
1535             }
1536 
1537             if (index >= limit) {
1538                 // deal with supplementary characters
1539                 int c = readChar();
1540                 if (c == -1) {
1541                     return -1;
1542                 }
1543 
1544                 in[0] = (char) c;
1545                 in[1] = 0;
1546                 inBuf.limit(1);
1547                 if (Character.isHighSurrogate((char) c)) {
1548                     c = readChar();
1549                     if (c != -1) {
1550                         in[1] = (char) c;
1551                         inBuf.limit(2);
1552                     }
1553                 }
1554 
1555                 inBuf.rewind();
1556                 outBuf.limit(out.length).rewind();
1557                 encoder.encode(inBuf, outBuf, false);
1558                 outBuf.flip();
1559                 limit = outBuf.limit();
1560 
1561                 index = 0;
1562 
1563                 return read();
1564             } else {
1565                 return out[index++] & 0xFF;
1566             }
1567         }
1568 
1569         public int available() throws IOException {
1570             return ((eos) ? 0 : (limit - index));
1571         }
1572 
1573         public void close() throws IOException {
1574             wrapped.close();
1575         }
1576 
1577         /**
1578          * Checks to see if the next array.length characters in wrapped
1579          * match array. The first character is provided as c. Subsequent
1580          * characters are read from wrapped itself. When this method returns,
1581          * the wrapped index may be different from what it was when this
1582          * method was called.
1583          */
1584         private boolean matchCharArray(char[] array, int c)
1585             throws IOException
1586         {
1587             wrapped.mark(array.length);  // BufferedReader supports mark
1588 
1589             int count = 0;
1590             if ((char)c == array[0]) {
1591                 for (count = 1; count < array.length; count++) {
1592                     c = wrapped.read();
1593                     if (c == -1 || ((char)c) != array[count]) {
1594                         break;
1595                     }
1596                 }
1597             }
1598 
1599             if (count == array.length) {
1600                 return true;
1601             } else {
1602                 wrapped.reset();
1603                 return false;
1604             }
1605         }
1606     }
1607 
1608     /**
1609      * Decodes a byte array into a set of String filenames.
1610      */
1611     protected abstract String[] dragQueryFile(byte[] bytes);
1612 
1613     /**
1614      * Decodes URIs from either a byte array or a stream.
1615      */
1616     protected URI[] dragQueryURIs(InputStream stream,
1617                                   long format,
1618                                   Transferable localeTransferable)
1619       throws IOException
1620     {
1621         throw new IOException(
1622             new UnsupportedOperationException("not implemented on this platform"));
1623     }
1624 
1625     /**
1626      * Translates either a byte array or an input stream which contain
1627      * platform-specific image data in the given format into an Image.
1628      */
1629 
1630 
1631     protected abstract Image platformImageBytesToImage(
1632         byte[] bytes,long format) throws IOException;
1633 
1634     /**
1635      * Translates either a byte array or an input stream which contain
1636      * an image data in the given standard format into an Image.
1637      *
1638      * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif
1639      */
1640     protected Image standardImageBytesToImage(
1641         byte[] bytes, String mimeType) throws IOException
1642     {
1643 
1644         Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByMIMEType(mimeType);
1645 
1646         if (!readerIterator.hasNext()) {
1647             throw new IOException("No registered service provider can decode " +
1648                                   " an image from " + mimeType);
1649         }
1650 
1651         IOException ioe = null;
1652 
1653         while (readerIterator.hasNext()) {
1654             ImageReader imageReader = readerIterator.next();
1655             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1656                 try (ImageInputStream imageInputStream = ImageIO.createImageInputStream(bais)) {
1657                     ImageReadParam param = imageReader.getDefaultReadParam();
1658                     imageReader.setInput(imageInputStream, true, true);
1659                     BufferedImage bufferedImage = imageReader.read(imageReader.getMinIndex(), param);
1660                     if (bufferedImage != null) {
1661                         return bufferedImage;
1662                     }
1663                 } finally {
1664                     imageReader.dispose();
1665                 }
1666             } catch (IOException e) {
1667                 ioe = e;
1668                 continue;
1669             }
1670         }
1671 
1672         if (ioe == null) {
1673             ioe = new IOException("Registered service providers failed to decode"
1674                                   + " an image from " + mimeType);
1675         }
1676 
1677         throw ioe;
1678     }
1679 
1680     /**
1681      * Translates a Java Image into a byte array which contains platform-
1682      * specific image data in the given format.
1683      */
1684     protected abstract byte[] imageToPlatformBytes(Image image, long format)
1685       throws IOException;
1686 
1687     /**
1688      * Translates a Java Image into a byte array which contains
1689      * an image data in the given standard format.
1690      *
1691      * @param mimeType image MIME type, such as: image/png, image/jpeg
1692      */
1693     protected byte[] imageToStandardBytes(Image image, String mimeType)
1694       throws IOException {
1695         IOException originalIOE = null;
1696 
1697         Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
1698 
1699         if (!writerIterator.hasNext()) {
1700             throw new IOException("No registered service provider can encode " +
1701                                   " an image to " + mimeType);
1702         }
1703 
1704         if (image instanceof RenderedImage) {
1705             // Try to encode the original image.
1706             try {
1707                 return imageToStandardBytesImpl((RenderedImage)image, mimeType);
1708             } catch (IOException ioe) {
1709                 originalIOE = ioe;
1710             }
1711         }
1712 
1713         // Retry with a BufferedImage.
1714         int width = 0;
1715         int height = 0;
1716         if (image instanceof ToolkitImage) {
1717             ImageRepresentation ir = ((ToolkitImage)image).getImageRep();
1718             ir.reconstruct(ImageObserver.ALLBITS);
1719             width = ir.getWidth();
1720             height = ir.getHeight();
1721         } else {
1722             width = image.getWidth(null);
1723             height = image.getHeight(null);
1724         }
1725 
1726         ColorModel model = ColorModel.getRGBdefault();
1727         WritableRaster raster =
1728             model.createCompatibleWritableRaster(width, height);
1729 
1730         BufferedImage bufferedImage =
1731             new BufferedImage(model, raster, model.isAlphaPremultiplied(),
1732                               null);
1733 
1734         Graphics g = bufferedImage.getGraphics();
1735         try {
1736             g.drawImage(image, 0, 0, width, height, null);
1737         } finally {
1738             g.dispose();
1739         }
1740 
1741         try {
1742             return imageToStandardBytesImpl(bufferedImage, mimeType);
1743         } catch (IOException ioe) {
1744             if (originalIOE != null) {
1745                 throw originalIOE;
1746             } else {
1747                 throw ioe;
1748             }
1749         }
1750     }
1751 
1752     byte[] imageToStandardBytesImpl(RenderedImage renderedImage,
1753                                               String mimeType)
1754         throws IOException {
1755 
1756         Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
1757 
1758         ImageTypeSpecifier typeSpecifier =
1759             new ImageTypeSpecifier(renderedImage);
1760 
1761         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1762         IOException ioe = null;
1763 
1764         while (writerIterator.hasNext()) {
1765             ImageWriter imageWriter = writerIterator.next();
1766             ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();
1767 
1768             if (!writerSpi.canEncodeImage(typeSpecifier)) {
1769                 continue;
1770             }
1771 
1772             try {
1773                 try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(baos)) {
1774                     imageWriter.setOutput(imageOutputStream);
1775                     imageWriter.write(renderedImage);
1776                     imageOutputStream.flush();
1777                 }
1778             } catch (IOException e) {
1779                 imageWriter.dispose();
1780                 baos.reset();
1781                 ioe = e;
1782                 continue;
1783             }
1784 
1785             imageWriter.dispose();
1786             baos.close();
1787             return baos.toByteArray();
1788         }
1789 
1790         baos.close();
1791 
1792         if (ioe == null) {
1793             ioe = new IOException("Registered service providers failed to encode "
1794                                   + renderedImage + " to " + mimeType);
1795         }
1796 
1797         throw ioe;
1798     }
1799 
1800     /**
1801      * Concatenates the data represented by two objects. Objects can be either
1802      * byte arrays or instances of <code>InputStream</code>. If both arguments
1803      * are byte arrays byte array will be returned. Otherwise an
1804      * <code>InputStream</code> will be returned.
1805      * <p>
1806      * Currently is only called from native code to prepend palette data to
1807      * platform-specific image data during image transfer on Win32.
1808      *
1809      * @param obj1 the first object to be concatenated.
1810      * @param obj2 the second object to be concatenated.
1811      * @return a byte array or an <code>InputStream</code> which represents
1812      *         a logical concatenation of the two arguments.
1813      * @throws NullPointerException is either of the arguments is
1814      *         <code>null</code>
1815      * @throws ClassCastException is either of the arguments is
1816      *         neither byte array nor an instance of <code>InputStream</code>.
1817      */
1818     private Object concatData(Object obj1, Object obj2) {
1819         InputStream str1 = null;
1820         InputStream str2 = null;
1821 
1822         if (obj1 instanceof byte[]) {
1823             byte[] arr1 = (byte[])obj1;
1824             if (obj2 instanceof byte[]) {
1825                 byte[] arr2 = (byte[])obj2;
1826                 byte[] ret = new byte[arr1.length + arr2.length];
1827                 System.arraycopy(arr1, 0, ret, 0, arr1.length);
1828                 System.arraycopy(arr2, 0, ret, arr1.length, arr2.length);
1829                 return ret;
1830             } else {
1831                 str1 = new ByteArrayInputStream(arr1);
1832                 str2 = (InputStream)obj2;
1833             }
1834         } else {
1835             str1 = (InputStream)obj1;
1836             if (obj2 instanceof byte[]) {
1837                 str2 = new ByteArrayInputStream((byte[])obj2);
1838             } else {
1839                 str2 = (InputStream)obj2;
1840             }
1841         }
1842 
1843         return new SequenceInputStream(str1, str2);
1844     }
1845 
1846     public byte[] convertData(final Object source,
1847                               final Transferable contents,
1848                               final long format,
1849                               final Map<Long, DataFlavor> formatMap,
1850                               final boolean isToolkitThread)
1851         throws IOException
1852     {
1853         byte[] ret = null;
1854 
1855         /*
1856          * If the current thread is the Toolkit thread we should post a
1857          * Runnable to the event dispatch thread associated with source Object,
1858          * since translateTransferable() calls Transferable.getTransferData()
1859          * that may contain client code.
1860          */
1861         if (isToolkitThread) try {
1862             final Stack<byte[]> stack = new Stack<>();
1863             final Runnable dataConverter = new Runnable() {
1864                 // Guard against multiple executions.
1865                 private boolean done = false;
1866                 public void run() {
1867                     if (done) {
1868                         return;
1869                     }
1870                     byte[] data = null;
1871                     try {
1872                         DataFlavor flavor = formatMap.get(format);
1873                         if (flavor != null) {
1874                             data = translateTransferable(contents, flavor, format);
1875                         }
1876                     } catch (Exception e) {
1877                         e.printStackTrace();
1878                         data = null;
1879                     }
1880                     try {
1881                         getToolkitThreadBlockedHandler().lock();
1882                         stack.push(data);
1883                         getToolkitThreadBlockedHandler().exit();
1884                     } finally {
1885                         getToolkitThreadBlockedHandler().unlock();
1886                         done = true;
1887                     }
1888                 }
1889             };
1890 
1891             final AppContext appContext = SunToolkit.targetToAppContext(source);
1892 
1893             getToolkitThreadBlockedHandler().lock();
1894 
1895             if (appContext != null) {
1896                 appContext.put(DATA_CONVERTER_KEY, dataConverter);
1897             }
1898 
1899             SunToolkit.executeOnEventHandlerThread(source, dataConverter);
1900 
1901             while (stack.empty()) {
1902                 getToolkitThreadBlockedHandler().enter();
1903             }
1904 
1905             if (appContext != null) {
1906                 appContext.remove(DATA_CONVERTER_KEY);
1907             }
1908 
1909             ret = stack.pop();
1910         } finally {
1911             getToolkitThreadBlockedHandler().unlock();
1912         } else {
1913             DataFlavor flavor = formatMap.get(format);
1914             if (flavor != null) {
1915                 ret = translateTransferable(contents, flavor, format);
1916             }
1917         }
1918 
1919         return ret;
1920     }
1921 
1922     public void processDataConversionRequests() {
1923         if (EventQueue.isDispatchThread()) {
1924             AppContext appContext = AppContext.getAppContext();
1925             getToolkitThreadBlockedHandler().lock();
1926             try {
1927                 Runnable dataConverter =
1928                     (Runnable)appContext.get(DATA_CONVERTER_KEY);
1929                 if (dataConverter != null) {
1930                     dataConverter.run();
1931                     appContext.remove(DATA_CONVERTER_KEY);
1932                 }
1933             } finally {
1934                 getToolkitThreadBlockedHandler().unlock();
1935             }
1936         }
1937     }
1938 
1939     public abstract ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler();
1940 
1941     /**
1942      * Helper function to reduce a Map with Long keys to a long array.
1943      * <p>
1944      * The map keys are sorted according to the native formats preference
1945      * order.
1946      */
1947     public static long[] keysToLongArray(SortedMap<Long, ?> map) {
1948         Set<Long> keySet = map.keySet();
1949         long[] retval = new long[keySet.size()];
1950         int i = 0;
1951         for (Iterator<Long> iter = keySet.iterator(); iter.hasNext(); i++) {
1952             retval[i] = iter.next();
1953         }
1954         return retval;
1955     }
1956 
1957     /**
1958      * Helper function to convert a Set of DataFlavors to a sorted array.
1959      * The array will be sorted according to <code>DataFlavorComparator</code>.
1960      */
1961     public static DataFlavor[] setToSortedDataFlavorArray(Set<DataFlavor> flavorsSet) {
1962         DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
1963         flavorsSet.toArray(flavors);
1964         final Comparator<DataFlavor> comparator = DataFlavorUtil.getDataFlavorComparator().reversed();
1965         Arrays.sort(flavors, comparator);
1966         return flavors;
1967     }
1968 
1969     /**
1970      * Helper function to convert an InputStream to a byte[] array.
1971      */
1972     protected static byte[] inputStreamToByteArray(InputStream str)
1973         throws IOException
1974     {
1975         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1976             int len = 0;
1977             byte[] buf = new byte[8192];
1978 
1979             while ((len = str.read(buf)) != -1) {
1980                 baos.write(buf, 0, len);
1981             }
1982 
1983             return baos.toByteArray();
1984         }
1985     }
1986 
1987     /**
1988      * Returns platform-specific mappings for the specified native.
1989      * If there are no platform-specific mappings for this native, the method
1990      * returns an empty <code>List</code>.
1991      */
1992     public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
1993         return new LinkedHashSet<>();
1994     }
1995 
1996     /**
1997      * Returns platform-specific mappings for the specified flavor.
1998      * If there are no platform-specific mappings for this flavor, the method
1999      * returns an empty <code>List</code>.
2000      */
2001     public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
2002         return new LinkedHashSet<>();
2003     }
2004 }