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