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 }