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         Properties flavorProperties = new Properties();
 232         try (BufferedInputStream bis = new BufferedInputStream(is)) {
 233             flavorProperties.load(bis);
 234         } catch (IOException e) {
 235             throw new InternalError("Error reading default flavor mapping", e);
 236         }
 237 
 238         for (String key : flavorProperties.stringPropertyNames()) {
 239             String[] values = flavorProperties.getProperty(key).split(",");
 240             for (String value : values) {
 241                 try {
 242                     MimeType mime = new MimeType(value);
 243                     if ("text".equals(mime.getPrimaryType())) {
 244                         String charset = mime.getParameter("charset");
 245                         if (DataTransferer.doesSubtypeSupportCharset(mime.getSubType(), charset))
 246                         {
 247                             // We need to store the charset and eoln
 248                             // parameters, if any, so that the
 249                             // DataTransferer will have this information
 250                             // for conversion into the native format.
 251                             DataTransferer transferer = DataTransferer.getInstance();
 252                             if (transferer != null) {
 253                                 transferer.registerTextFlavorProperties(key, charset,
 254                                                 mime.getParameter("eoln"),
 255                                                 mime.getParameter("terminators"));
 256                             }
 257                         }
 258 
 259                         // But don't store any of these parameters in the
 260                         // DataFlavor itself for any text natives (even
 261                         // non-charset ones). The SystemFlavorMap will
 262                         // synthesize the appropriate mappings later.
 263                         mime.removeParameter("charset");
 264                         mime.removeParameter("class");
 265                         mime.removeParameter("eoln");
 266                         mime.removeParameter("terminators");
 267                         value = mime.toString();
 268                     }
 269                 } catch (MimeTypeParseException e) {
 270                     e.printStackTrace();
 271                     continue;
 272                 }
 273 
 274                 DataFlavor flavor;
 275                 try {
 276                     flavor = new DataFlavor(value);
 277                 } catch (Exception e) {
 278                     try {
 279                         flavor = new DataFlavor(value, null);
 280                     } catch (Exception ee) {
 281                         ee.printStackTrace();
 282                         continue;
 283                     }
 284                 }
 285 
 286                 final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();
 287                 dfs.add(flavor);
 288 
 289                 if ("text".equals(flavor.getPrimaryType())) {
 290                     dfs.addAll(convertMimeTypeToDataFlavors(value));
 291                     store(flavor.mimeType.getBaseType(), key, getTextTypeToNative());
 292                 }
 293 
 294                 for (DataFlavor df : dfs) {
 295                     store(df, key, getFlavorToNative());
 296                     store(key, df, getNativeToFlavor());
 297                 }
 298             }
 299         }
 300     }
 301 
 302     /**
 303      * Stores the listed object under the specified hash key in map. Unlike a
 304      * standard map, the listed object will not replace any object already at
 305      * the appropriate Map location, but rather will be appended to a List
 306      * stored in that location.
 307      */
 308     private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) {
 309         LinkedHashSet<L> list = map.get(hashed);
 310         if (list == null) {
 311             list = new LinkedHashSet<>(1);
 312             map.put(hashed, list);
 313         }
 314         if (!list.contains(listed)) {
 315             list.add(listed);
 316         }
 317     }
 318 
 319     /**
 320      * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
 321      * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
 322      * case, a new DataFlavor is synthesized, stored, and returned, if and
 323      * only if the specified native is encoded as a Java MIME type.
 324      */
 325     private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) {
 326         LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
 327 
 328         if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
 329             DataTransferer transferer = DataTransferer.getInstance();
 330             if (transferer != null) {
 331                 LinkedHashSet<DataFlavor> platformFlavors =
 332                         transferer.getPlatformMappingsForNative(nat);
 333                 if (!platformFlavors.isEmpty()) {
 334                     if (flavors != null) {
 335                         // Prepending the platform-specific mappings ensures
 336                         // that the flavors added with
 337                         // addFlavorForUnencodedNative() are at the end of
 338                         // list.
 339                         platformFlavors.addAll(flavors);
 340                     }
 341                     flavors = platformFlavors;
 342                 }
 343             }
 344         }
 345 
 346         if (flavors == null && isJavaMIMEType(nat)) {
 347             String decoded = decodeJavaMIMEType(nat);
 348             DataFlavor flavor = null;
 349 
 350             try {
 351                 flavor = new DataFlavor(decoded);
 352             } catch (Exception e) {
 353                 System.err.println("Exception \"" + e.getClass().getName() +
 354                                    ": " + e.getMessage()  +
 355                                    "\"while constructing DataFlavor for: " +
 356                                    decoded);
 357             }
 358 
 359             if (flavor != null) {
 360                 flavors = new LinkedHashSet<>(1);
 361                 getNativeToFlavor().put(nat, flavors);
 362                 flavors.add(flavor);
 363                 flavorsForNativeCache.remove(nat);
 364 
 365                 LinkedHashSet<String> natives = getFlavorToNative().get(flavor);
 366                 if (natives == null) {
 367                     natives = new LinkedHashSet<>(1);
 368                     getFlavorToNative().put(flavor, natives);
 369                 }
 370                 natives.add(nat);
 371                 nativesForFlavorCache.remove(flavor);
 372             }
 373         }
 374 
 375         return (flavors != null) ? flavors : new LinkedHashSet<>(0);
 376     }
 377 
 378     /**
 379      * Semantically equivalent to 'flavorToNative.get(flav)'. This method
 380      * handles the case where 'flav' is not found in 'flavorToNative' depending
 381      * on the value of passes 'synthesize' parameter. If 'synthesize' is
 382      * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
 383      * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
 384      * and 'flavorToNative' remains unaffected.
 385      */
 386     private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav,
 387                                                        final boolean synthesize) {
 388 
 389         LinkedHashSet<String> natives = getFlavorToNative().get(flav);
 390 
 391         if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
 392             DataTransferer transferer = DataTransferer.getInstance();
 393             if (transferer != null) {
 394                 LinkedHashSet<String> platformNatives =
 395                     transferer.getPlatformMappingsForFlavor(flav);
 396                 if (!platformNatives.isEmpty()) {
 397                     if (natives != null) {
 398                         // Prepend the platform-specific mappings to ensure
 399                         // that the natives added with
 400                         // addUnencodedNativeForFlavor() are at the end of
 401                         // list.
 402                         platformNatives.addAll(natives);
 403                     }
 404                     natives = platformNatives;
 405                 }
 406             }
 407         }
 408 
 409         if (natives == null) {
 410             if (synthesize) {
 411                 String encoded = encodeDataFlavor(flav);
 412                 natives = new LinkedHashSet<>(1);
 413                 getFlavorToNative().put(flav, natives);
 414                 natives.add(encoded);
 415 
 416                 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded);
 417                 if (flavors == null) {
 418                     flavors = new LinkedHashSet<>(1);
 419                     getNativeToFlavor().put(encoded, flavors);
 420                 }
 421                 flavors.add(flav);
 422 
 423                 nativesForFlavorCache.remove(flav);
 424                 flavorsForNativeCache.remove(encoded);
 425             } else {
 426                 natives = new LinkedHashSet<>(0);
 427             }
 428         }
 429 
 430         return new LinkedHashSet<>(natives);
 431     }
 432 
 433     /**
 434      * Returns a <code>List</code> of <code>String</code> natives to which the
 435      * specified <code>DataFlavor</code> can be translated by the data transfer
 436      * subsystem. The <code>List</code> will be sorted from best native to
 437      * worst. That is, the first native will best reflect data in the specified
 438      * flavor to the underlying native platform.
 439      * <p>
 440      * If the specified <code>DataFlavor</code> is previously unknown to the
 441      * data transfer subsystem and the data transfer subsystem is unable to
 442      * translate this <code>DataFlavor</code> to any existing native, then
 443      * invoking this method will establish a
 444      * mapping in both directions between the specified <code>DataFlavor</code>
 445      * and an encoded version of its MIME type as its native.
 446      *
 447      * @param flav the <code>DataFlavor</code> whose corresponding natives
 448      *        should be returned. If <code>null</code> is specified, all
 449      *        natives currently known to the data transfer subsystem are
 450      *        returned in a non-deterministic order.
 451      * @return a <code>java.util.List</code> of <code>java.lang.String</code>
 452      *         objects which are platform-specific representations of platform-
 453      *         specific data formats
 454      *
 455      * @see #encodeDataFlavor
 456      * @since 1.4
 457      */
 458     @Override
 459     public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
 460         LinkedHashSet<String> retval = nativesForFlavorCache.check(flav);
 461         if (retval != null) {
 462             return new ArrayList<>(retval);
 463         }
 464 
 465         if (flav == null) {
 466             retval = new LinkedHashSet<>(getNativeToFlavor().keySet());
 467         } else if (disabledMappingGenerationKeys.contains(flav)) {
 468             // In this case we shouldn't synthesize a native for this flavor,
 469             // since its mappings were explicitly specified.
 470             retval = flavorToNativeLookup(flav, false);
 471         } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
 472             retval = new LinkedHashSet<>(0);
 473 
 474             // For text/* flavors, flavor-to-native mappings specified in
 475             // flavormap.properties are stored per flavor's base type.
 476             if ("text".equals(flav.getPrimaryType())) {
 477                 LinkedHashSet<String> textTypeNatives =
 478                         getTextTypeToNative().get(flav.mimeType.getBaseType());
 479                 if (textTypeNatives != null) {
 480                     retval.addAll(textTypeNatives);
 481                 }
 482             }
 483 
 484             // Also include text/plain natives, but don't duplicate Strings
 485             LinkedHashSet<String> textTypeNatives =
 486                     getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE);
 487             if (textTypeNatives != null) {
 488                 retval.addAll(textTypeNatives);
 489             }
 490 
 491             if (retval.isEmpty()) {
 492                 retval = flavorToNativeLookup(flav, true);
 493             } else {
 494                 // In this branch it is guaranteed that natives explicitly
 495                 // listed for flav's MIME type were added with
 496                 // addUnencodedNativeForFlavor(), so they have lower priority.
 497                 retval.addAll(flavorToNativeLookup(flav, false));
 498             }
 499         } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
 500             retval = getTextTypeToNative().get(flav.mimeType.getBaseType());
 501 
 502             if (retval == null || retval.isEmpty()) {
 503                 retval = flavorToNativeLookup(flav, true);
 504             } else {
 505                 // In this branch it is guaranteed that natives explicitly
 506                 // listed for flav's MIME type were added with
 507                 // addUnencodedNativeForFlavor(), so they have lower priority.
 508                 retval.addAll(flavorToNativeLookup(flav, false));
 509             }
 510         } else {
 511             retval = flavorToNativeLookup(flav, true);
 512         }
 513 
 514         nativesForFlavorCache.put(flav, retval);
 515         // Create a copy, because client code can modify the returned list.
 516         return new ArrayList<>(retval);
 517     }
 518 
 519     /**
 520      * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
 521      * specified <code>String</code> native can be translated by the data
 522      * transfer subsystem. The <code>List</code> will be sorted from best
 523      * <code>DataFlavor</code> to worst. That is, the first
 524      * <code>DataFlavor</code> will best reflect data in the specified
 525      * native to a Java application.
 526      * <p>
 527      * If the specified native is previously unknown to the data transfer
 528      * subsystem, and that native has been properly encoded, then invoking this
 529      * method will establish a mapping in both directions between the specified
 530      * native and a <code>DataFlavor</code> whose MIME type is a decoded
 531      * version of the native.
 532      * <p>
 533      * If the specified native is not a properly encoded native and the
 534      * mappings for this native have not been altered with
 535      * <code>setFlavorsForNative</code>, then the contents of the
 536      * <code>List</code> is platform dependent, but <code>null</code>
 537      * cannot be returned.
 538      *
 539      * @param nat the native whose corresponding <code>DataFlavor</code>s
 540      *        should be returned. If <code>null</code> is specified, all
 541      *        <code>DataFlavor</code>s currently known to the data transfer
 542      *        subsystem are returned in a non-deterministic order.
 543      * @return a <code>java.util.List</code> of <code>DataFlavor</code>
 544      *         objects into which platform-specific data in the specified,
 545      *         platform-specific native can be translated
 546      *
 547      * @see #encodeJavaMIMEType
 548      * @since 1.4
 549      */
 550     @Override
 551     public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
 552         LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat);
 553         if (returnValue != null) {
 554             return new ArrayList<>(returnValue);
 555         } else {
 556             returnValue = new LinkedHashSet<>();
 557         }
 558 
 559         if (nat == null) {
 560             for (String n : getNativesForFlavor(null)) {
 561                 returnValue.addAll(getFlavorsForNative(n));
 562             }
 563         } else {
 564             final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat);
 565             if (disabledMappingGenerationKeys.contains(nat)) {
 566                 return new ArrayList<>(flavors);
 567             }
 568 
 569             final LinkedHashSet<DataFlavor> flavorsWithSynthesized =
 570                     nativeToFlavorLookup(nat);
 571 
 572             for (DataFlavor df : flavorsWithSynthesized) {
 573                 returnValue.add(df);
 574                 if ("text".equals(df.getPrimaryType())) {
 575                     String baseType = df.mimeType.getBaseType();
 576                     returnValue.addAll(convertMimeTypeToDataFlavors(baseType));
 577                 }
 578             }
 579         }
 580         flavorsForNativeCache.put(nat, returnValue);
 581         return new ArrayList<>(returnValue);
 582     }
 583 
 584     private static Set<DataFlavor> convertMimeTypeToDataFlavors(
 585         final String baseType) {
 586 
 587         final Set<DataFlavor> returnValue = new LinkedHashSet<>();
 588 
 589         String subType = null;
 590 
 591         try {
 592             final MimeType mimeType = new MimeType(baseType);
 593             subType = mimeType.getSubType();
 594         } catch (MimeTypeParseException mtpe) {
 595             // Cannot happen, since we checked all mappings
 596             // on load from flavormap.properties.
 597         }
 598 
 599         if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
 600             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
 601             {
 602                 returnValue.add(DataFlavor.stringFlavor);
 603             }
 604 
 605             for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
 606                 final String mimeType = baseType + ";charset=Unicode;class=" +
 607                                             unicodeClassName;
 608 
 609                 final LinkedHashSet<String> mimeTypes =
 610                     handleHtmlMimeTypes(baseType, mimeType);
 611                 for (String mt : mimeTypes) {
 612                     DataFlavor toAdd = null;
 613                     try {
 614                         toAdd = new DataFlavor(mt);
 615                     } catch (ClassNotFoundException cannotHappen) {
 616                     }
 617                     returnValue.add(toAdd);
 618                 }
 619             }
 620 
 621             for (String charset : DataTransferer.standardEncodings()) {
 622 
 623                 for (String encodedTextClass : ENCODED_TEXT_CLASSES) {
 624                     final String mimeType =
 625                             baseType + ";charset=" + charset +
 626                             ";class=" + encodedTextClass;
 627 
 628                     final LinkedHashSet<String> mimeTypes =
 629                         handleHtmlMimeTypes(baseType, mimeType);
 630 
 631                     for (String mt : mimeTypes) {
 632 
 633                         DataFlavor df = null;
 634 
 635                         try {
 636                             df = new DataFlavor(mt);
 637                             // Check for equality to plainTextFlavor so
 638                             // that we can ensure that the exact charset of
 639                             // plainTextFlavor, not the canonical charset
 640                             // or another equivalent charset with a
 641                             // different name, is used.
 642                             if (df.equals(DataFlavor.plainTextFlavor)) {
 643                                 df = DataFlavor.plainTextFlavor;
 644                             }
 645                         } catch (ClassNotFoundException cannotHappen) {
 646                         }
 647 
 648                         returnValue.add(df);
 649                     }
 650                 }
 651             }
 652 
 653             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
 654             {
 655                 returnValue.add(DataFlavor.plainTextFlavor);
 656             }
 657         } else {
 658             // Non-charset text natives should be treated as
 659             // opaque, 8-bit data in any of its various
 660             // representations.
 661             for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {
 662                 DataFlavor toAdd = null;
 663                 try {
 664                     toAdd = new DataFlavor(baseType +
 665                          ";class=" + encodedTextClassName);
 666                 } catch (ClassNotFoundException cannotHappen) {
 667                 }
 668                 returnValue.add(toAdd);
 669             }
 670         }
 671         return returnValue;
 672     }
 673 
 674     private static final String [] htmlDocumntTypes =
 675             new String [] {"all", "selection", "fragment"};
 676 
 677     private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType,
 678                                                              String mimeType) {
 679 
 680         LinkedHashSet<String> returnValues = new LinkedHashSet<>();
 681 
 682         if (HTML_TEXT_BASE_TYPE.equals(baseType)) {
 683             for (String documentType : htmlDocumntTypes) {
 684                 returnValues.add(mimeType + ";document=" + documentType);
 685             }
 686         } else {
 687             returnValues.add(mimeType);
 688         }
 689 
 690         return returnValues;
 691     }
 692 
 693     /**
 694      * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
 695      * their most preferred <code>String</code> native. Each native value will
 696      * be the same as the first native in the List returned by
 697      * <code>getNativesForFlavor</code> for the specified flavor.
 698      * <p>
 699      * If a specified <code>DataFlavor</code> is previously unknown to the
 700      * data transfer subsystem, then invoking this method will establish a
 701      * mapping in both directions between the specified <code>DataFlavor</code>
 702      * and an encoded version of its MIME type as its native.
 703      *
 704      * @param flavors an array of <code>DataFlavor</code>s which will be the
 705      *        key set of the returned <code>Map</code>. If <code>null</code> is
 706      *        specified, a mapping of all <code>DataFlavor</code>s known to the
 707      *        data transfer subsystem to their most preferred
 708      *        <code>String</code> natives will be returned.
 709      * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
 710      *         <code>String</code> natives
 711      *
 712      * @see #getNativesForFlavor
 713      * @see #encodeDataFlavor
 714      */
 715     @Override
 716     public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors)
 717     {
 718         // Use getNativesForFlavor to generate extra natives for text flavors
 719         // and stringFlavor
 720 
 721         if (flavors == null) {
 722             List<DataFlavor> flavor_list = getFlavorsForNative(null);
 723             flavors = new DataFlavor[flavor_list.size()];
 724             flavor_list.toArray(flavors);
 725         }
 726 
 727         Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
 728         for (DataFlavor flavor : flavors) {
 729             List<String> natives = getNativesForFlavor(flavor);
 730             String nat = (natives.isEmpty()) ? null : natives.get(0);
 731             retval.put(flavor, nat);
 732         }
 733 
 734         return retval;
 735     }
 736 
 737     /**
 738      * Returns a <code>Map</code> of the specified <code>String</code> natives
 739      * to their most preferred <code>DataFlavor</code>. Each
 740      * <code>DataFlavor</code> value will be the same as the first
 741      * <code>DataFlavor</code> in the List returned by
 742      * <code>getFlavorsForNative</code> for the specified native.
 743      * <p>
 744      * If a specified native is previously unknown to the data transfer
 745      * subsystem, and that native has been properly encoded, then invoking this
 746      * method will establish a mapping in both directions between the specified
 747      * native and a <code>DataFlavor</code> whose MIME type is a decoded
 748      * version of the native.
 749      *
 750      * @param natives an array of <code>String</code>s which will be the
 751      *        key set of the returned <code>Map</code>. If <code>null</code> is
 752      *        specified, a mapping of all supported <code>String</code> natives
 753      *        to their most preferred <code>DataFlavor</code>s will be
 754      *        returned.
 755      * @return a <code>java.util.Map</code> of <code>String</code> natives to
 756      *         <code>DataFlavor</code>s
 757      *
 758      * @see #getFlavorsForNative
 759      * @see #encodeJavaMIMEType
 760      */
 761     @Override
 762     public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives)
 763     {
 764         // Use getFlavorsForNative to generate extra flavors for text natives
 765         if (natives == null) {
 766             List<String> nativesList = getNativesForFlavor(null);
 767             natives = new String[nativesList.size()];
 768             nativesList.toArray(natives);
 769         }
 770 
 771         Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
 772         for (String aNative : natives) {
 773             List<DataFlavor> flavors = getFlavorsForNative(aNative);
 774             DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
 775             retval.put(aNative, flav);
 776         }
 777         return retval;
 778     }
 779 
 780     /**
 781      * Adds a mapping from the specified <code>DataFlavor</code> (and all
 782      * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
 783      * to the specified <code>String</code> native.
 784      * Unlike <code>getNativesForFlavor</code>, the mapping will only be
 785      * established in one direction, and the native will not be encoded. To
 786      * establish a two-way mapping, call
 787      * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
 788      * be of lower priority than any existing mapping.
 789      * This method has no effect if a mapping from the specified or equal
 790      * <code>DataFlavor</code> to the specified <code>String</code> native
 791      * already exists.
 792      *
 793      * @param flav the <code>DataFlavor</code> key for the mapping
 794      * @param nat the <code>String</code> native value for the mapping
 795      * @throws NullPointerException if flav or nat is <code>null</code>
 796      *
 797      * @see #addFlavorForUnencodedNative
 798      * @since 1.4
 799      */
 800     public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
 801                                                          String nat) {
 802         Objects.requireNonNull(nat, "Null native not permitted");
 803         Objects.requireNonNull(flav, "Null flavor not permitted");
 804 
 805         LinkedHashSet<String> natives = getFlavorToNative().get(flav);
 806         if (natives == null) {
 807             natives = new LinkedHashSet<>(1);
 808             getFlavorToNative().put(flav, natives);
 809         }
 810         natives.add(nat);
 811         nativesForFlavorCache.remove(flav);
 812     }
 813 
 814     /**
 815      * Discards the current mappings for the specified <code>DataFlavor</code>
 816      * and all <code>DataFlavor</code>s equal to the specified
 817      * <code>DataFlavor</code>, and creates new mappings to the
 818      * specified <code>String</code> natives.
 819      * Unlike <code>getNativesForFlavor</code>, the mappings will only be
 820      * established in one direction, and the natives will not be encoded. To
 821      * establish two-way mappings, call <code>setFlavorsForNative</code>
 822      * as well. The first native in the array will represent the highest
 823      * priority mapping. Subsequent natives will represent mappings of
 824      * decreasing priority.
 825      * <p>
 826      * If the array contains several elements that reference equal
 827      * <code>String</code> natives, this method will establish new mappings
 828      * for the first of those elements and ignore the rest of them.
 829      * <p>
 830      * It is recommended that client code not reset mappings established by the
 831      * data transfer subsystem. This method should only be used for
 832      * application-level mappings.
 833      *
 834      * @param flav the <code>DataFlavor</code> key for the mappings
 835      * @param natives the <code>String</code> native values for the mappings
 836      * @throws NullPointerException if flav or natives is <code>null</code>
 837      *         or if natives contains <code>null</code> elements
 838      *
 839      * @see #setFlavorsForNative
 840      * @since 1.4
 841      */
 842     public synchronized void setNativesForFlavor(DataFlavor flav,
 843                                                  String[] natives) {
 844         Objects.requireNonNull(natives, "Null natives not permitted");
 845         Objects.requireNonNull(flav, "Null flavors not permitted");
 846 
 847         getFlavorToNative().remove(flav);
 848         for (String aNative : natives) {
 849             addUnencodedNativeForFlavor(flav, aNative);
 850         }
 851         disabledMappingGenerationKeys.add(flav);
 852         nativesForFlavorCache.remove(flav);
 853     }
 854 
 855     /**
 856      * Adds a mapping from a single <code>String</code> native to a single
 857      * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
 858      * mapping will only be established in one direction, and the native will
 859      * not be encoded. To establish a two-way mapping, call
 860      * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
 861      * be of lower priority than any existing mapping.
 862      * This method has no effect if a mapping from the specified
 863      * <code>String</code> native to the specified or equal
 864      * <code>DataFlavor</code> already exists.
 865      *
 866      * @param nat the <code>String</code> native key for the mapping
 867      * @param flav the <code>DataFlavor</code> value for the mapping
 868      * @throws NullPointerException if nat or flav is <code>null</code>
 869      *
 870      * @see #addUnencodedNativeForFlavor
 871      * @since 1.4
 872      */
 873     public synchronized void addFlavorForUnencodedNative(String nat,
 874                                                          DataFlavor flav) {
 875         Objects.requireNonNull(nat, "Null native not permitted");
 876         Objects.requireNonNull(flav, "Null flavor not permitted");
 877 
 878         LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
 879         if (flavors == null) {
 880             flavors = new LinkedHashSet<>(1);
 881             getNativeToFlavor().put(nat, flavors);
 882         }
 883         flavors.add(flav);
 884         flavorsForNativeCache.remove(nat);
 885     }
 886 
 887     /**
 888      * Discards the current mappings for the specified <code>String</code>
 889      * native, and creates new mappings to the specified
 890      * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
 891      * mappings will only be established in one direction, and the natives need
 892      * not be encoded. To establish two-way mappings, call
 893      * <code>setNativesForFlavor</code> as well. The first
 894      * <code>DataFlavor</code> in the array will represent the highest priority
 895      * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
 896      * decreasing priority.
 897      * <p>
 898      * If the array contains several elements that reference equal
 899      * <code>DataFlavor</code>s, this method will establish new mappings
 900      * for the first of those elements and ignore the rest of them.
 901      * <p>
 902      * It is recommended that client code not reset mappings established by the
 903      * data transfer subsystem. This method should only be used for
 904      * application-level mappings.
 905      *
 906      * @param nat the <code>String</code> native key for the mappings
 907      * @param flavors the <code>DataFlavor</code> values for the mappings
 908      * @throws NullPointerException if nat or flavors is <code>null</code>
 909      *         or if flavors contains <code>null</code> elements
 910      *
 911      * @see #setNativesForFlavor
 912      * @since 1.4
 913      */
 914     public synchronized void setFlavorsForNative(String nat,
 915                                                  DataFlavor[] flavors) {
 916         Objects.requireNonNull(nat, "Null native not permitted");
 917         Objects.requireNonNull(flavors, "Null flavors not permitted");
 918 
 919         getNativeToFlavor().remove(nat);
 920         for (DataFlavor flavor : flavors) {
 921             addFlavorForUnencodedNative(nat, flavor);
 922         }
 923         disabledMappingGenerationKeys.add(nat);
 924         flavorsForNativeCache.remove(nat);
 925     }
 926 
 927     /**
 928      * Encodes a MIME type for use as a <code>String</code> native. The format
 929      * of an encoded representation of a MIME type is implementation-dependent.
 930      * The only restrictions are:
 931      * <ul>
 932      * <li>The encoded representation is <code>null</code> if and only if the
 933      * MIME type <code>String</code> is <code>null</code>.</li>
 934      * <li>The encoded representations for two non-<code>null</code> MIME type
 935      * <code>String</code>s are equal if and only if these <code>String</code>s
 936      * are equal according to <code>String.equals(Object)</code>.</li>
 937      * </ul>
 938      * <p>
 939      * The reference implementation of this method returns the specified MIME
 940      * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
 941      *
 942      * @param mimeType the MIME type to encode
 943      * @return the encoded <code>String</code>, or <code>null</code> if
 944      *         mimeType is <code>null</code>
 945      */
 946     public static String encodeJavaMIMEType(String mimeType) {
 947         return (mimeType != null)
 948             ? JavaMIME + mimeType
 949             : null;
 950     }
 951 
 952     /**
 953      * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
 954      * native. The format of an encoded <code>DataFlavor</code> is
 955      * implementation-dependent. The only restrictions are:
 956      * <ul>
 957      * <li>The encoded representation is <code>null</code> if and only if the
 958      * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
 959      * <code>String</code> is <code>null</code>.</li>
 960      * <li>The encoded representations for two non-<code>null</code>
 961      * <code>DataFlavor</code>s with non-<code>null</code> MIME type
 962      * <code>String</code>s are equal if and only if the MIME type
 963      * <code>String</code>s of these <code>DataFlavor</code>s are equal
 964      * according to <code>String.equals(Object)</code>.</li>
 965      * </ul>
 966      * <p>
 967      * The reference implementation of this method returns the MIME type
 968      * <code>String</code> of the specified <code>DataFlavor</code> prefixed
 969      * with <code>JAVA_DATAFLAVOR:</code>.
 970      *
 971      * @param flav the <code>DataFlavor</code> to encode
 972      * @return the encoded <code>String</code>, or <code>null</code> if
 973      *         flav is <code>null</code> or has a <code>null</code> MIME type
 974      */
 975     public static String encodeDataFlavor(DataFlavor flav) {
 976         return (flav != null)
 977             ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
 978             : null;
 979     }
 980 
 981     /**
 982      * Returns whether the specified <code>String</code> is an encoded Java
 983      * MIME type.
 984      *
 985      * @param str the <code>String</code> to test
 986      * @return <code>true</code> if the <code>String</code> is encoded;
 987      *         <code>false</code> otherwise
 988      */
 989     public static boolean isJavaMIMEType(String str) {
 990         return (str != null && str.startsWith(JavaMIME, 0));
 991     }
 992 
 993     /**
 994      * Decodes a <code>String</code> native for use as a Java MIME type.
 995      *
 996      * @param nat the <code>String</code> to decode
 997      * @return the decoded Java MIME type, or <code>null</code> if nat is not
 998      *         an encoded <code>String</code> native
 999      */
1000     public static String decodeJavaMIMEType(String nat) {
1001         return (isJavaMIMEType(nat))
1002             ? nat.substring(JavaMIME.length(), nat.length()).trim()
1003             : null;
1004     }
1005 
1006     /**
1007      * Decodes a <code>String</code> native for use as a
1008      * <code>DataFlavor</code>.
1009      *
1010      * @param nat the <code>String</code> to decode
1011      * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1012      *         nat is not an encoded <code>String</code> native
1013      * @throws ClassNotFoundException if the class of the data flavor
1014      * is not loaded
1015      */
1016     public static DataFlavor decodeDataFlavor(String nat)
1017         throws ClassNotFoundException
1018     {
1019         String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1020         return (retval_str != null)
1021             ? new DataFlavor(retval_str)
1022             : null;
1023     }
1024 
1025     private static final class SoftCache<K, V> {
1026         Map<K, SoftReference<LinkedHashSet<V>>> cache;
1027 
1028         public void put(K key, LinkedHashSet<V> value) {
1029             if (cache == null) {
1030                 cache = new HashMap<>(1);
1031             }
1032             cache.put(key, new SoftReference<>(value));
1033         }
1034 
1035         public void remove(K key) {
1036             if (cache == null) return;
1037             cache.remove(null);
1038             cache.remove(key);
1039         }
1040 
1041         public LinkedHashSet<V> check(K key) {
1042             if (cache == null) return null;
1043             SoftReference<LinkedHashSet<V>> ref = cache.get(key);
1044             if (ref != null) {
1045                 return ref.get();
1046             }
1047             return null;
1048         }
1049     }
1050 }