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