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