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