1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt.datatransfer; 27 28 import java.awt.Toolkit; 29 30 import java.io.BufferedInputStream; 31 import java.io.InputStream; 32 import java.lang.ref.SoftReference; 33 34 import java.io.BufferedReader; 35 import java.io.File; 36 import java.io.InputStreamReader; 37 import java.io.IOException; 38 39 import java.net.URL; 40 import java.net.MalformedURLException; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Collections; 45 import java.util.HashMap; 46 import java.util.HashSet; 47 import java.util.LinkedHashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.Properties; 52 import java.util.Set; 53 54 import sun.awt.AppContext; 55 import sun.awt.datatransfer.DataTransferer; 56 57 /** 58 * The SystemFlavorMap is a configurable map between "natives" (Strings), which 59 * correspond to platform-specific data formats, and "flavors" (DataFlavors), 60 * which correspond to platform-independent MIME types. This mapping is used 61 * by the data transfer subsystem to transfer data between Java and native 62 * applications, and between Java applications in separate VMs. 63 * 64 * @since 1.2 65 */ 66 public final class SystemFlavorMap implements FlavorMap, FlavorTable { 67 68 /** 69 * Constant prefix used to tag Java types converted to native platform 70 * type. 71 */ 72 private static String JavaMIME = "JAVA_DATAFLAVOR:"; 73 74 private static final Object FLAVOR_MAP_KEY = new Object(); 75 76 /** 77 * Copied from java.util.Properties. 78 */ 79 private static final String keyValueSeparators = "=: \t\r\n\f"; 80 private static final String strictKeyValueSeparators = "=:"; 81 private static final String whiteSpaceChars = " \t\r\n\f"; 82 83 /** 84 * The list of valid, decoded text flavor representation classes, in order 85 * from best to worst. 86 */ 87 private static final String[] UNICODE_TEXT_CLASSES = { 88 "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\"" 89 }; 90 91 /** 92 * The list of valid, encoded text flavor representation classes, in order 93 * from best to worst. 94 */ 95 private static final String[] ENCODED_TEXT_CLASSES = { 96 "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\"" 97 }; 98 99 /** 100 * A String representing text/plain MIME type. 101 */ 102 private static final String TEXT_PLAIN_BASE_TYPE = "text/plain"; 103 104 /** 105 * A String representing text/html MIME type. 106 */ 107 private static final String HTML_TEXT_BASE_TYPE = "text/html"; 108 109 /** 110 * Maps native Strings to Lists of DataFlavors (or base type Strings for 111 * text DataFlavors). 112 * Do not use the field directly, use getNativeToFlavor() instead. 113 */ 114 private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>(); 115 116 /** 117 * Accessor to nativeToFlavor map. Since we use lazy initialization we must 118 * use this accessor instead of direct access to the field which may not be 119 * initialized yet. This method will initialize the field if needed. 120 * 121 * @return nativeToFlavor 122 */ 123 private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() { 124 if (!isMapInitialized) { 125 initSystemFlavorMap(); 126 } 127 return nativeToFlavor; 128 } 129 130 /** 131 * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of 132 * native Strings. 133 * Do not use the field directly, use getFlavorToNative() instead. 134 */ 135 private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>(); 136 137 /** 138 * Accessor to flavorToNative map. Since we use lazy initialization we must 139 * use this accessor instead of direct access to the field which may not be 140 * initialized yet. This method will initialize the field if needed. 141 * 142 * @return flavorToNative 143 */ 144 private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() { 145 if (!isMapInitialized) { 146 initSystemFlavorMap(); 147 } 148 return flavorToNative; 149 } 150 151 /** 152 * Maps a text DataFlavor primary mime-type to the native. Used only to store 153 * standard mappings registered in the flavormap.properties 154 * Do not use this field directly, use getTextTypeToNative() instead. 155 */ 156 private Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>(); 157 158 /** 159 * Shows if the object has been initialized. 160 */ 161 private boolean isMapInitialized = false; 162 163 /** 164 * An accessor to textTypeToNative map. Since we use lazy initialization we 165 * must use this accessor instead of direct access to the field which may not 166 * be initialized yet. This method will initialize the field if needed. 167 * 168 * @return textTypeToNative 169 */ 170 private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() { 171 if (!isMapInitialized) { 172 initSystemFlavorMap(); 173 // From this point the map should not be modified 174 textTypeToNative = Collections.unmodifiableMap(textTypeToNative); 175 } 176 return textTypeToNative; 177 } 178 179 /** 180 * Caches the result of getNativesForFlavor(). Maps DataFlavors to 181 * SoftReferences which reference LinkedHashSet of String natives. 182 */ 183 private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>(); 184 185 /** 186 * Caches the result getFlavorsForNative(). Maps String natives to 187 * SoftReferences which reference LinkedHashSet of DataFlavors. 188 */ 189 private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>(); 190 191 /** 192 * Dynamic mapping generation used for text mappings should not be applied 193 * to the DataFlavors and String natives for which the mappings have been 194 * explicitly specified with setFlavorsForNative() or 195 * setNativesForFlavor(). This keeps all such keys. 196 */ 197 private Set<Object> disabledMappingGenerationKeys = new HashSet<>(); 198 199 /** 200 * Returns the default FlavorMap for this thread's ClassLoader. 201 * @return the default FlavorMap for this thread's ClassLoader 202 */ 203 public static FlavorMap getDefaultFlavorMap() { 204 AppContext context = AppContext.getAppContext(); 205 FlavorMap fm = (FlavorMap) context.get(FLAVOR_MAP_KEY); 206 if (fm == null) { 207 fm = new SystemFlavorMap(); 208 context.put(FLAVOR_MAP_KEY, fm); 209 } 210 return fm; 211 } 212 213 private SystemFlavorMap() { 214 } 215 216 /** 217 * Initializes a SystemFlavorMap by reading flavormap.properties 218 * For thread-safety must be called under lock on this. 219 */ 220 private void initSystemFlavorMap() { 221 if (isMapInitialized) { 222 return; 223 } 224 isMapInitialized = true; 225 226 InputStream is = SystemFlavorMap.class.getResourceAsStream("/sun/awt/datatransfer/flavormap.properties"); 227 if (is == null) { 228 throw new InternalError("Default flavor mapping not found"); 229 } 230 231 try (InputStreamReader isr = new InputStreamReader(is); 232 BufferedReader reader = new BufferedReader(isr)) { 233 String line; 234 while ((line = reader.readLine()) != null) { 235 line = line.trim(); 236 if (line.startsWith("#") || line.isEmpty()) continue; 237 while (line.endsWith("\\")) { 238 line = line.substring(0, line.length() - 1) + reader.readLine().trim(); 239 } 240 int delimiterPosition = line.indexOf('='); 241 String key = line.substring(0, delimiterPosition).replace("\\ ", " "); 242 String[] values = line.substring(delimiterPosition + 1, line.length()).split(","); 243 for (String value : values) { 244 try { 245 MimeType mime = new MimeType(value); 246 if ("text".equals(mime.getPrimaryType())) { 247 String charset = mime.getParameter("charset"); 248 if (DataTransferer.doesSubtypeSupportCharset(mime.getSubType(), charset)) 249 { 250 // We need to store the charset and eoln 251 // parameters, if any, so that the 252 // DataTransferer will have this information 253 // for conversion into the native format. 254 DataTransferer transferer = DataTransferer.getInstance(); 255 if (transferer != null) { 256 transferer.registerTextFlavorProperties(key, charset, 257 mime.getParameter("eoln"), 258 mime.getParameter("terminators")); 259 } 260 } 261 262 // But don't store any of these parameters in the 263 // DataFlavor itself for any text natives (even 264 // non-charset ones). The SystemFlavorMap will 265 // synthesize the appropriate mappings later. 266 mime.removeParameter("charset"); 267 mime.removeParameter("class"); 268 mime.removeParameter("eoln"); 269 mime.removeParameter("terminators"); 270 value = mime.toString(); 271 } 272 } catch (MimeTypeParseException e) { 273 e.printStackTrace(); 274 continue; 275 } 276 277 DataFlavor flavor; 278 try { 279 flavor = new DataFlavor(value); 280 } catch (Exception e) { 281 try { 282 flavor = new DataFlavor(value, null); 283 } catch (Exception ee) { 284 ee.printStackTrace(); 285 continue; 286 } 287 } 288 289 final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>(); 290 dfs.add(flavor); 291 292 if ("text".equals(flavor.getPrimaryType())) { 293 dfs.addAll(convertMimeTypeToDataFlavors(value)); 294 store(flavor.mimeType.getBaseType(), key, getTextTypeToNative()); 295 } 296 297 for (DataFlavor df : dfs) { 298 store(df, key, getFlavorToNative()); 299 store(key, df, getNativeToFlavor()); 300 } 301 } 302 } 303 } catch (IOException e) { 304 throw new InternalError("Error reading default flavor mapping", e); 305 } 306 } 307 308 /** 309 * Stores the listed object under the specified hash key in map. Unlike a 310 * standard map, the listed object will not replace any object already at 311 * the appropriate Map location, but rather will be appended to a List 312 * stored in that location. 313 */ 314 private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) { 315 LinkedHashSet<L> list = map.get(hashed); 316 if (list == null) { 317 list = new LinkedHashSet<>(1); 318 map.put(hashed, list); 319 } 320 if (!list.contains(listed)) { 321 list.add(listed); 322 } 323 } 324 325 /** 326 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method 327 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that 328 * case, a new DataFlavor is synthesized, stored, and returned, if and 329 * only if the specified native is encoded as a Java MIME type. 330 */ 331 private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) { 332 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 333 334 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) { 335 DataTransferer transferer = DataTransferer.getInstance(); 336 if (transferer != null) { 337 LinkedHashSet<DataFlavor> platformFlavors = 338 transferer.getPlatformMappingsForNative(nat); 339 if (!platformFlavors.isEmpty()) { 340 if (flavors != null) { 341 // Prepending the platform-specific mappings ensures 342 // that the flavors added with 343 // addFlavorForUnencodedNative() are at the end of 344 // list. 345 platformFlavors.addAll(flavors); 346 } 347 flavors = platformFlavors; 348 } 349 } 350 } 351 352 if (flavors == null && isJavaMIMEType(nat)) { 353 String decoded = decodeJavaMIMEType(nat); 354 DataFlavor flavor = null; 355 356 try { 357 flavor = new DataFlavor(decoded); 358 } catch (Exception e) { 359 System.err.println("Exception \"" + e.getClass().getName() + 360 ": " + e.getMessage() + 361 "\"while constructing DataFlavor for: " + 362 decoded); 363 } 364 365 if (flavor != null) { 366 flavors = new LinkedHashSet<>(1); 367 getNativeToFlavor().put(nat, flavors); 368 flavors.add(flavor); 369 flavorsForNativeCache.remove(nat); 370 371 LinkedHashSet<String> natives = getFlavorToNative().get(flavor); 372 if (natives == null) { 373 natives = new LinkedHashSet<>(1); 374 getFlavorToNative().put(flavor, natives); 375 } 376 natives.add(nat); 377 nativesForFlavorCache.remove(flavor); 378 } 379 } 380 381 return (flavors != null) ? flavors : new LinkedHashSet<>(0); 382 } 383 384 /** 385 * Semantically equivalent to 'flavorToNative.get(flav)'. This method 386 * handles the case where 'flav' is not found in 'flavorToNative' depending 387 * on the value of passes 'synthesize' parameter. If 'synthesize' is 388 * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by 389 * encoding the DataFlavor's MIME type. Otherwise an empty List is returned 390 * and 'flavorToNative' remains unaffected. 391 */ 392 private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav, 393 final boolean synthesize) { 394 395 LinkedHashSet<String> natives = getFlavorToNative().get(flav); 396 397 if (flav != null && !disabledMappingGenerationKeys.contains(flav)) { 398 DataTransferer transferer = DataTransferer.getInstance(); 399 if (transferer != null) { 400 LinkedHashSet<String> platformNatives = 401 transferer.getPlatformMappingsForFlavor(flav); 402 if (!platformNatives.isEmpty()) { 403 if (natives != null) { 404 // Prepend the platform-specific mappings to ensure 405 // that the natives added with 406 // addUnencodedNativeForFlavor() are at the end of 407 // list. 408 platformNatives.addAll(natives); 409 } 410 natives = platformNatives; 411 } 412 } 413 } 414 415 if (natives == null) { 416 if (synthesize) { 417 String encoded = encodeDataFlavor(flav); 418 natives = new LinkedHashSet<>(1); 419 getFlavorToNative().put(flav, natives); 420 natives.add(encoded); 421 422 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded); 423 if (flavors == null) { 424 flavors = new LinkedHashSet<>(1); 425 getNativeToFlavor().put(encoded, flavors); 426 } 427 flavors.add(flav); 428 429 nativesForFlavorCache.remove(flav); 430 flavorsForNativeCache.remove(encoded); 431 } else { 432 natives = new LinkedHashSet<>(0); 433 } 434 } 435 436 return new LinkedHashSet<>(natives); 437 } 438 439 /** 440 * Returns a <code>List</code> of <code>String</code> natives to which the 441 * specified <code>DataFlavor</code> can be translated by the data transfer 442 * subsystem. The <code>List</code> will be sorted from best native to 443 * worst. That is, the first native will best reflect data in the specified 444 * flavor to the underlying native platform. 445 * <p> 446 * If the specified <code>DataFlavor</code> is previously unknown to the 447 * data transfer subsystem and the data transfer subsystem is unable to 448 * translate this <code>DataFlavor</code> to any existing native, then 449 * invoking this method will establish a 450 * mapping in both directions between the specified <code>DataFlavor</code> 451 * and an encoded version of its MIME type as its native. 452 * 453 * @param flav the <code>DataFlavor</code> whose corresponding natives 454 * should be returned. If <code>null</code> is specified, all 455 * natives currently known to the data transfer subsystem are 456 * returned in a non-deterministic order. 457 * @return a <code>java.util.List</code> of <code>java.lang.String</code> 458 * objects which are platform-specific representations of platform- 459 * specific data formats 460 * 461 * @see #encodeDataFlavor 462 * @since 1.4 463 */ 464 @Override 465 public synchronized List<String> getNativesForFlavor(DataFlavor flav) { 466 LinkedHashSet<String> retval = nativesForFlavorCache.check(flav); 467 if (retval != null) { 468 return new ArrayList<>(retval); 469 } 470 471 if (flav == null) { 472 retval = new LinkedHashSet<>(getNativeToFlavor().keySet()); 473 } else if (disabledMappingGenerationKeys.contains(flav)) { 474 // In this case we shouldn't synthesize a native for this flavor, 475 // since its mappings were explicitly specified. 476 retval = flavorToNativeLookup(flav, false); 477 } else if (DataTransferer.isFlavorCharsetTextType(flav)) { 478 retval = new LinkedHashSet<>(0); 479 480 // For text/* flavors, flavor-to-native mappings specified in 481 // flavormap.properties are stored per flavor's base type. 482 if ("text".equals(flav.getPrimaryType())) { 483 LinkedHashSet<String> textTypeNatives = 484 getTextTypeToNative().get(flav.mimeType.getBaseType()); 485 if (textTypeNatives != null) { 486 retval.addAll(textTypeNatives); 487 } 488 } 489 490 // Also include text/plain natives, but don't duplicate Strings 491 LinkedHashSet<String> textTypeNatives = 492 getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE); 493 if (textTypeNatives != null) { 494 retval.addAll(textTypeNatives); 495 } 496 497 if (retval.isEmpty()) { 498 retval = flavorToNativeLookup(flav, true); 499 } else { 500 // In this branch it is guaranteed that natives explicitly 501 // listed for flav's MIME type were added with 502 // addUnencodedNativeForFlavor(), so they have lower priority. 503 retval.addAll(flavorToNativeLookup(flav, false)); 504 } 505 } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) { 506 retval = getTextTypeToNative().get(flav.mimeType.getBaseType()); 507 508 if (retval == null || retval.isEmpty()) { 509 retval = flavorToNativeLookup(flav, true); 510 } else { 511 // In this branch it is guaranteed that natives explicitly 512 // listed for flav's MIME type were added with 513 // addUnencodedNativeForFlavor(), so they have lower priority. 514 retval.addAll(flavorToNativeLookup(flav, false)); 515 } 516 } else { 517 retval = flavorToNativeLookup(flav, true); 518 } 519 520 nativesForFlavorCache.put(flav, retval); 521 // Create a copy, because client code can modify the returned list. 522 return new ArrayList<>(retval); 523 } 524 525 /** 526 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the 527 * specified <code>String</code> native can be translated by the data 528 * transfer subsystem. The <code>List</code> will be sorted from best 529 * <code>DataFlavor</code> to worst. That is, the first 530 * <code>DataFlavor</code> will best reflect data in the specified 531 * native to a Java application. 532 * <p> 533 * If the specified native is previously unknown to the data transfer 534 * subsystem, and that native has been properly encoded, then invoking this 535 * method will establish a mapping in both directions between the specified 536 * native and a <code>DataFlavor</code> whose MIME type is a decoded 537 * version of the native. 538 * <p> 539 * If the specified native is not a properly encoded native and the 540 * mappings for this native have not been altered with 541 * <code>setFlavorsForNative</code>, then the contents of the 542 * <code>List</code> is platform dependent, but <code>null</code> 543 * cannot be returned. 544 * 545 * @param nat the native whose corresponding <code>DataFlavor</code>s 546 * should be returned. If <code>null</code> is specified, all 547 * <code>DataFlavor</code>s currently known to the data transfer 548 * subsystem are returned in a non-deterministic order. 549 * @return a <code>java.util.List</code> of <code>DataFlavor</code> 550 * objects into which platform-specific data in the specified, 551 * platform-specific native can be translated 552 * 553 * @see #encodeJavaMIMEType 554 * @since 1.4 555 */ 556 @Override 557 public synchronized List<DataFlavor> getFlavorsForNative(String nat) { 558 LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat); 559 if (returnValue != null) { 560 return new ArrayList<>(returnValue); 561 } else { 562 returnValue = new LinkedHashSet<>(); 563 } 564 565 if (nat == null) { 566 for (String n : getNativesForFlavor(null)) { 567 returnValue.addAll(getFlavorsForNative(n)); 568 } 569 } else { 570 final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat); 571 if (disabledMappingGenerationKeys.contains(nat)) { 572 return new ArrayList<>(flavors); 573 } 574 575 final LinkedHashSet<DataFlavor> flavorsWithSynthesized = 576 nativeToFlavorLookup(nat); 577 578 for (DataFlavor df : flavorsWithSynthesized) { 579 returnValue.add(df); 580 if ("text".equals(df.getPrimaryType())) { 581 String baseType = df.mimeType.getBaseType(); 582 returnValue.addAll(convertMimeTypeToDataFlavors(baseType)); 583 } 584 } 585 } 586 flavorsForNativeCache.put(nat, returnValue); 587 return new ArrayList<>(returnValue); 588 } 589 590 private static Set<DataFlavor> convertMimeTypeToDataFlavors( 591 final String baseType) { 592 593 final Set<DataFlavor> returnValue = new LinkedHashSet<>(); 594 595 String subType = null; 596 597 try { 598 final MimeType mimeType = new MimeType(baseType); 599 subType = mimeType.getSubType(); 600 } catch (MimeTypeParseException mtpe) { 601 // Cannot happen, since we checked all mappings 602 // on load from flavormap.properties. 603 } 604 605 if (DataTransferer.doesSubtypeSupportCharset(subType, null)) { 606 if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) 607 { 608 returnValue.add(DataFlavor.stringFlavor); 609 } 610 611 for (String unicodeClassName : UNICODE_TEXT_CLASSES) { 612 final String mimeType = baseType + ";charset=Unicode;class=" + 613 unicodeClassName; 614 615 final LinkedHashSet<String> mimeTypes = 616 handleHtmlMimeTypes(baseType, mimeType); 617 for (String mt : mimeTypes) { 618 DataFlavor toAdd = null; 619 try { 620 toAdd = new DataFlavor(mt); 621 } catch (ClassNotFoundException cannotHappen) { 622 } 623 returnValue.add(toAdd); 624 } 625 } 626 627 for (String charset : DataTransferer.standardEncodings()) { 628 629 for (String encodedTextClass : ENCODED_TEXT_CLASSES) { 630 final String mimeType = 631 baseType + ";charset=" + charset + 632 ";class=" + encodedTextClass; 633 634 final LinkedHashSet<String> mimeTypes = 635 handleHtmlMimeTypes(baseType, mimeType); 636 637 for (String mt : mimeTypes) { 638 639 DataFlavor df = null; 640 641 try { 642 df = new DataFlavor(mt); 643 // Check for equality to plainTextFlavor so 644 // that we can ensure that the exact charset of 645 // plainTextFlavor, not the canonical charset 646 // or another equivalent charset with a 647 // different name, is used. 648 if (df.equals(DataFlavor.plainTextFlavor)) { 649 df = DataFlavor.plainTextFlavor; 650 } 651 } catch (ClassNotFoundException cannotHappen) { 652 } 653 654 returnValue.add(df); 655 } 656 } 657 } 658 659 if (TEXT_PLAIN_BASE_TYPE.equals(baseType)) 660 { 661 returnValue.add(DataFlavor.plainTextFlavor); 662 } 663 } else { 664 // Non-charset text natives should be treated as 665 // opaque, 8-bit data in any of its various 666 // representations. 667 for (String encodedTextClassName : ENCODED_TEXT_CLASSES) { 668 DataFlavor toAdd = null; 669 try { 670 toAdd = new DataFlavor(baseType + 671 ";class=" + encodedTextClassName); 672 } catch (ClassNotFoundException cannotHappen) { 673 } 674 returnValue.add(toAdd); 675 } 676 } 677 return returnValue; 678 } 679 680 private static final String [] htmlDocumntTypes = 681 new String [] {"all", "selection", "fragment"}; 682 683 private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType, 684 String mimeType) { 685 686 LinkedHashSet<String> returnValues = new LinkedHashSet<>(); 687 688 if (HTML_TEXT_BASE_TYPE.equals(baseType)) { 689 for (String documentType : htmlDocumntTypes) { 690 returnValues.add(mimeType + ";document=" + documentType); 691 } 692 } else { 693 returnValues.add(mimeType); 694 } 695 696 return returnValues; 697 } 698 699 /** 700 * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to 701 * their most preferred <code>String</code> native. Each native value will 702 * be the same as the first native in the List returned by 703 * <code>getNativesForFlavor</code> for the specified flavor. 704 * <p> 705 * If a specified <code>DataFlavor</code> is previously unknown to the 706 * data transfer subsystem, then invoking this method will establish a 707 * mapping in both directions between the specified <code>DataFlavor</code> 708 * and an encoded version of its MIME type as its native. 709 * 710 * @param flavors an array of <code>DataFlavor</code>s which will be the 711 * key set of the returned <code>Map</code>. If <code>null</code> is 712 * specified, a mapping of all <code>DataFlavor</code>s known to the 713 * data transfer subsystem to their most preferred 714 * <code>String</code> natives will be returned. 715 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to 716 * <code>String</code> natives 717 * 718 * @see #getNativesForFlavor 719 * @see #encodeDataFlavor 720 */ 721 @Override 722 public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors) 723 { 724 // Use getNativesForFlavor to generate extra natives for text flavors 725 // and stringFlavor 726 727 if (flavors == null) { 728 List<DataFlavor> flavor_list = getFlavorsForNative(null); 729 flavors = new DataFlavor[flavor_list.size()]; 730 flavor_list.toArray(flavors); 731 } 732 733 Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f); 734 for (DataFlavor flavor : flavors) { 735 List<String> natives = getNativesForFlavor(flavor); 736 String nat = (natives.isEmpty()) ? null : natives.get(0); 737 retval.put(flavor, nat); 738 } 739 740 return retval; 741 } 742 743 /** 744 * Returns a <code>Map</code> of the specified <code>String</code> natives 745 * to their most preferred <code>DataFlavor</code>. Each 746 * <code>DataFlavor</code> value will be the same as the first 747 * <code>DataFlavor</code> in the List returned by 748 * <code>getFlavorsForNative</code> for the specified native. 749 * <p> 750 * If a specified native is previously unknown to the data transfer 751 * subsystem, and that native has been properly encoded, then invoking this 752 * method will establish a mapping in both directions between the specified 753 * native and a <code>DataFlavor</code> whose MIME type is a decoded 754 * version of the native. 755 * 756 * @param natives an array of <code>String</code>s which will be the 757 * key set of the returned <code>Map</code>. If <code>null</code> is 758 * specified, a mapping of all supported <code>String</code> natives 759 * to their most preferred <code>DataFlavor</code>s will be 760 * returned. 761 * @return a <code>java.util.Map</code> of <code>String</code> natives to 762 * <code>DataFlavor</code>s 763 * 764 * @see #getFlavorsForNative 765 * @see #encodeJavaMIMEType 766 */ 767 @Override 768 public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives) 769 { 770 // Use getFlavorsForNative to generate extra flavors for text natives 771 if (natives == null) { 772 List<String> nativesList = getNativesForFlavor(null); 773 natives = new String[nativesList.size()]; 774 nativesList.toArray(natives); 775 } 776 777 Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f); 778 for (String aNative : natives) { 779 List<DataFlavor> flavors = getFlavorsForNative(aNative); 780 DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0); 781 retval.put(aNative, flav); 782 } 783 return retval; 784 } 785 786 /** 787 * Adds a mapping from the specified <code>DataFlavor</code> (and all 788 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>) 789 * to the specified <code>String</code> native. 790 * Unlike <code>getNativesForFlavor</code>, the mapping will only be 791 * established in one direction, and the native will not be encoded. To 792 * establish a two-way mapping, call 793 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will 794 * be of lower priority than any existing mapping. 795 * This method has no effect if a mapping from the specified or equal 796 * <code>DataFlavor</code> to the specified <code>String</code> native 797 * already exists. 798 * 799 * @param flav the <code>DataFlavor</code> key for the mapping 800 * @param nat the <code>String</code> native value for the mapping 801 * @throws NullPointerException if flav or nat is <code>null</code> 802 * 803 * @see #addFlavorForUnencodedNative 804 * @since 1.4 805 */ 806 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav, 807 String nat) { 808 Objects.requireNonNull(nat, "Null native not permitted"); 809 Objects.requireNonNull(flav, "Null flavor not permitted"); 810 811 LinkedHashSet<String> natives = getFlavorToNative().get(flav); 812 if (natives == null) { 813 natives = new LinkedHashSet<>(1); 814 getFlavorToNative().put(flav, natives); 815 } 816 natives.add(nat); 817 nativesForFlavorCache.remove(flav); 818 } 819 820 /** 821 * Discards the current mappings for the specified <code>DataFlavor</code> 822 * and all <code>DataFlavor</code>s equal to the specified 823 * <code>DataFlavor</code>, and creates new mappings to the 824 * specified <code>String</code> natives. 825 * Unlike <code>getNativesForFlavor</code>, the mappings will only be 826 * established in one direction, and the natives will not be encoded. To 827 * establish two-way mappings, call <code>setFlavorsForNative</code> 828 * as well. The first native in the array will represent the highest 829 * priority mapping. Subsequent natives will represent mappings of 830 * decreasing priority. 831 * <p> 832 * If the array contains several elements that reference equal 833 * <code>String</code> natives, this method will establish new mappings 834 * for the first of those elements and ignore the rest of them. 835 * <p> 836 * It is recommended that client code not reset mappings established by the 837 * data transfer subsystem. This method should only be used for 838 * application-level mappings. 839 * 840 * @param flav the <code>DataFlavor</code> key for the mappings 841 * @param natives the <code>String</code> native values for the mappings 842 * @throws NullPointerException if flav or natives is <code>null</code> 843 * or if natives contains <code>null</code> elements 844 * 845 * @see #setFlavorsForNative 846 * @since 1.4 847 */ 848 public synchronized void setNativesForFlavor(DataFlavor flav, 849 String[] natives) { 850 Objects.requireNonNull(natives, "Null natives not permitted"); 851 Objects.requireNonNull(flav, "Null flavors not permitted"); 852 853 getFlavorToNative().remove(flav); 854 for (String aNative : natives) { 855 addUnencodedNativeForFlavor(flav, aNative); 856 } 857 disabledMappingGenerationKeys.add(flav); 858 nativesForFlavorCache.remove(flav); 859 } 860 861 /** 862 * Adds a mapping from a single <code>String</code> native to a single 863 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the 864 * mapping will only be established in one direction, and the native will 865 * not be encoded. To establish a two-way mapping, call 866 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will 867 * be of lower priority than any existing mapping. 868 * This method has no effect if a mapping from the specified 869 * <code>String</code> native to the specified or equal 870 * <code>DataFlavor</code> already exists. 871 * 872 * @param nat the <code>String</code> native key for the mapping 873 * @param flav the <code>DataFlavor</code> value for the mapping 874 * @throws NullPointerException if nat or flav is <code>null</code> 875 * 876 * @see #addUnencodedNativeForFlavor 877 * @since 1.4 878 */ 879 public synchronized void addFlavorForUnencodedNative(String nat, 880 DataFlavor flav) { 881 Objects.requireNonNull(nat, "Null native not permitted"); 882 Objects.requireNonNull(flav, "Null flavor not permitted"); 883 884 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat); 885 if (flavors == null) { 886 flavors = new LinkedHashSet<>(1); 887 getNativeToFlavor().put(nat, flavors); 888 } 889 flavors.add(flav); 890 flavorsForNativeCache.remove(nat); 891 } 892 893 /** 894 * Discards the current mappings for the specified <code>String</code> 895 * native, and creates new mappings to the specified 896 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the 897 * mappings will only be established in one direction, and the natives need 898 * not be encoded. To establish two-way mappings, call 899 * <code>setNativesForFlavor</code> as well. The first 900 * <code>DataFlavor</code> in the array will represent the highest priority 901 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of 902 * decreasing priority. 903 * <p> 904 * If the array contains several elements that reference equal 905 * <code>DataFlavor</code>s, this method will establish new mappings 906 * for the first of those elements and ignore the rest of them. 907 * <p> 908 * It is recommended that client code not reset mappings established by the 909 * data transfer subsystem. This method should only be used for 910 * application-level mappings. 911 * 912 * @param nat the <code>String</code> native key for the mappings 913 * @param flavors the <code>DataFlavor</code> values for the mappings 914 * @throws NullPointerException if nat or flavors is <code>null</code> 915 * or if flavors contains <code>null</code> elements 916 * 917 * @see #setNativesForFlavor 918 * @since 1.4 919 */ 920 public synchronized void setFlavorsForNative(String nat, 921 DataFlavor[] flavors) { 922 Objects.requireNonNull(nat, "Null native not permitted"); 923 Objects.requireNonNull(flavors, "Null flavors not permitted"); 924 925 getNativeToFlavor().remove(nat); 926 for (DataFlavor flavor : flavors) { 927 addFlavorForUnencodedNative(nat, flavor); 928 } 929 disabledMappingGenerationKeys.add(nat); 930 flavorsForNativeCache.remove(nat); 931 } 932 933 /** 934 * Encodes a MIME type for use as a <code>String</code> native. The format 935 * of an encoded representation of a MIME type is implementation-dependent. 936 * The only restrictions are: 937 * <ul> 938 * <li>The encoded representation is <code>null</code> if and only if the 939 * MIME type <code>String</code> is <code>null</code>.</li> 940 * <li>The encoded representations for two non-<code>null</code> MIME type 941 * <code>String</code>s are equal if and only if these <code>String</code>s 942 * are equal according to <code>String.equals(Object)</code>.</li> 943 * </ul> 944 * <p> 945 * The reference implementation of this method returns the specified MIME 946 * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>. 947 * 948 * @param mimeType the MIME type to encode 949 * @return the encoded <code>String</code>, or <code>null</code> if 950 * mimeType is <code>null</code> 951 */ 952 public static String encodeJavaMIMEType(String mimeType) { 953 return (mimeType != null) 954 ? JavaMIME + mimeType 955 : null; 956 } 957 958 /** 959 * Encodes a <code>DataFlavor</code> for use as a <code>String</code> 960 * native. The format of an encoded <code>DataFlavor</code> is 961 * implementation-dependent. The only restrictions are: 962 * <ul> 963 * <li>The encoded representation is <code>null</code> if and only if the 964 * specified <code>DataFlavor</code> is <code>null</code> or its MIME type 965 * <code>String</code> is <code>null</code>.</li> 966 * <li>The encoded representations for two non-<code>null</code> 967 * <code>DataFlavor</code>s with non-<code>null</code> MIME type 968 * <code>String</code>s are equal if and only if the MIME type 969 * <code>String</code>s of these <code>DataFlavor</code>s are equal 970 * according to <code>String.equals(Object)</code>.</li> 971 * </ul> 972 * <p> 973 * The reference implementation of this method returns the MIME type 974 * <code>String</code> of the specified <code>DataFlavor</code> prefixed 975 * with <code>JAVA_DATAFLAVOR:</code>. 976 * 977 * @param flav the <code>DataFlavor</code> to encode 978 * @return the encoded <code>String</code>, or <code>null</code> if 979 * flav is <code>null</code> or has a <code>null</code> MIME type 980 */ 981 public static String encodeDataFlavor(DataFlavor flav) { 982 return (flav != null) 983 ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType()) 984 : null; 985 } 986 987 /** 988 * Returns whether the specified <code>String</code> is an encoded Java 989 * MIME type. 990 * 991 * @param str the <code>String</code> to test 992 * @return <code>true</code> if the <code>String</code> is encoded; 993 * <code>false</code> otherwise 994 */ 995 public static boolean isJavaMIMEType(String str) { 996 return (str != null && str.startsWith(JavaMIME, 0)); 997 } 998 999 /** 1000 * Decodes a <code>String</code> native for use as a Java MIME type. 1001 * 1002 * @param nat the <code>String</code> to decode 1003 * @return the decoded Java MIME type, or <code>null</code> if nat is not 1004 * an encoded <code>String</code> native 1005 */ 1006 public static String decodeJavaMIMEType(String nat) { 1007 return (isJavaMIMEType(nat)) 1008 ? nat.substring(JavaMIME.length(), nat.length()).trim() 1009 : null; 1010 } 1011 1012 /** 1013 * Decodes a <code>String</code> native for use as a 1014 * <code>DataFlavor</code>. 1015 * 1016 * @param nat the <code>String</code> to decode 1017 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if 1018 * nat is not an encoded <code>String</code> native 1019 * @throws ClassNotFoundException if the class of the data flavor 1020 * is not loaded 1021 */ 1022 public static DataFlavor decodeDataFlavor(String nat) 1023 throws ClassNotFoundException 1024 { 1025 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat); 1026 return (retval_str != null) 1027 ? new DataFlavor(retval_str) 1028 : null; 1029 } 1030 1031 private static final class SoftCache<K, V> { 1032 Map<K, SoftReference<LinkedHashSet<V>>> cache; 1033 1034 public void put(K key, LinkedHashSet<V> value) { 1035 if (cache == null) { 1036 cache = new HashMap<>(1); 1037 } 1038 cache.put(key, new SoftReference<>(value)); 1039 } 1040 1041 public void remove(K key) { 1042 if (cache == null) return; 1043 cache.remove(null); 1044 cache.remove(key); 1045 } 1046 1047 public LinkedHashSet<V> check(K key) { 1048 if (cache == null) return null; 1049 SoftReference<LinkedHashSet<V>> ref = cache.get(key); 1050 if (ref != null) { 1051 return ref.get(); 1052 } 1053 return null; 1054 } 1055 } 1056 }