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