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.lang.ref.SoftReference;
  31 
  32 import java.io.BufferedReader;
  33 import java.io.File;
  34 import java.io.InputStreamReader;
  35 import java.io.IOException;
  36 
  37 import java.net.URL;
  38 import java.net.MalformedURLException;
  39 
  40 import java.util.ArrayList;
  41 import java.util.HashMap;
  42 import java.util.HashSet;
  43 import java.util.LinkedHashSet;
  44 import java.util.List;
  45 import java.util.Map;
  46 import java.util.Objects;
  47 import java.util.Set;
  48 
  49 import sun.awt.AppContext;
  50 import sun.awt.datatransfer.DataTransferer;
  51 
  52 /**
  53  * The SystemFlavorMap is a configurable map between "natives" (Strings), which
  54  * correspond to platform-specific data formats, and "flavors" (DataFlavors),
  55  * which correspond to platform-independent MIME types. This mapping is used
  56  * by the data transfer subsystem to transfer data between Java and native
  57  * applications, and between Java applications in separate VMs.
  58  *
  59  * @since 1.2
  60  */
  61 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
  62 
  63     /**
  64      * Constant prefix used to tag Java types converted to native platform
  65      * type.
  66      */
  67     private static String JavaMIME = "JAVA_DATAFLAVOR:";
  68 
  69     private static final Object FLAVOR_MAP_KEY = new Object();
  70 
  71     /**
  72      * Copied from java.util.Properties.
  73      */
  74     private static final String keyValueSeparators = "=: \t\r\n\f";
  75     private static final String strictKeyValueSeparators = "=:";
  76     private static final String whiteSpaceChars = " \t\r\n\f";
  77 
  78     /**
  79      * The list of valid, decoded text flavor representation classes, in order
  80      * from best to worst.
  81      */
  82     private static final String[] UNICODE_TEXT_CLASSES = {
  83         "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
  84     };
  85 
  86     /**
  87      * The list of valid, encoded text flavor representation classes, in order
  88      * from best to worst.
  89      */
  90     private static final String[] ENCODED_TEXT_CLASSES = {
  91         "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
  92     };
  93 
  94     /**
  95      * A String representing text/plain MIME type.
  96      */
  97     private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
  98 
  99     /**
 100      * A String representing text/html MIME type.
 101      */
 102     private static final String HTML_TEXT_BASE_TYPE = "text/html";
 103 
 104     /**
 105      * Maps native Strings to Lists of DataFlavors (or base type Strings for
 106      * text DataFlavors).
 107      * Do not use the field directly, use getNativeToFlavor() instead.
 108      */
 109     private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>();
 110 
 111     /**
 112      * Accessor to nativeToFlavor map.  Since we use lazy initialization we must
 113      * use this accessor instead of direct access to the field which may not be
 114      * initialized yet.  This method will initialize the field if needed.
 115      *
 116      * @return nativeToFlavor
 117      */
 118     private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() {
 119         if (!isMapInitialized) {
 120             initSystemFlavorMap();
 121         }
 122         return nativeToFlavor;
 123     }
 124 
 125     /**
 126      * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
 127      * native Strings.
 128      * Do not use the field directly, use getFlavorToNative() instead.
 129      */
 130     private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>();
 131 
 132     /**
 133      * Accessor to flavorToNative map.  Since we use lazy initialization we must
 134      * use this accessor instead of direct access to the field which may not be
 135      * initialized yet.  This method will initialize the field if needed.
 136      *
 137      * @return flavorToNative
 138      */
 139     private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() {
 140         if (!isMapInitialized) {
 141             initSystemFlavorMap();
 142         }
 143         return flavorToNative;
 144     }
 145 
 146     /**
 147      * Maps a text DataFlavor primary mime-type to the native. Used only to store
 148      * standard mappings registered in the flavormap.properties
 149      * Do not use this field directly, use getTextTypeToNative() instead.
 150      */
 151     private final Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>();
 152 
 153     /**
 154      * An accessor to textTypeToNative map.  Since we use lazy initialization we
 155      * must use this accessor instead of direct access to the field which may not
 156      * be initialized yet. This method will initialize the field if needed.
 157      *
 158      * @return textTypeToNative
 159      */
 160     private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() {
 161         if (!isMapInitialized) {
 162             initSystemFlavorMap();
 163         }
 164         return textTypeToNative;
 165     }
 166 
 167     /**
 168      * Shows if the object has been initialized.
 169      */
 170     private boolean isMapInitialized = false;
 171 
 172     /**
 173      * Caches the result of getNativesForFlavor(). Maps DataFlavors to
 174      * SoftReferences which reference LinkedHashSet of String natives.
 175      */
 176     private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>();
 177 
 178     /**
 179      * Caches the result getFlavorsForNative(). Maps String natives to
 180      * SoftReferences which reference LinkedHashSet of DataFlavors.
 181      */
 182     private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>();
 183 
 184     /**
 185      * Dynamic mapping generation used for text mappings should not be applied
 186      * to the DataFlavors and String natives for which the mappings have been
 187      * explicitly specified with setFlavorsForNative() or
 188      * setNativesForFlavor(). This keeps all such keys.
 189      */
 190     private Set<Object> disabledMappingGenerationKeys = new HashSet<>();
 191 
 192     /**
 193      * Returns the default FlavorMap for this thread's ClassLoader.
 194      * @return the default FlavorMap for this thread's ClassLoader
 195      */
 196     public static FlavorMap getDefaultFlavorMap() {
 197         AppContext context = AppContext.getAppContext();
 198         FlavorMap fm = (FlavorMap) context.get(FLAVOR_MAP_KEY);
 199         if (fm == null) {
 200             fm = new SystemFlavorMap();
 201             context.put(FLAVOR_MAP_KEY, fm);
 202         }
 203         return fm;
 204     }
 205 
 206     private SystemFlavorMap() {
 207     }
 208 
 209     /**
 210      * Initializes a SystemFlavorMap by reading flavormap.properties and
 211      * AWT.DnD.flavorMapFileURL.
 212      * For thread-safety must be called under lock on this.
 213      */
 214     private void initSystemFlavorMap() {
 215         if (isMapInitialized) {
 216             return;
 217         }
 218 
 219         isMapInitialized = true;
 220         BufferedReader flavormapDotProperties =
 221             java.security.AccessController.doPrivileged(
 222                 new java.security.PrivilegedAction<BufferedReader>() {
 223                     public BufferedReader run() {
 224                         String fileName =
 225                             System.getProperty("java.home") +
 226                             File.separator +
 227                             "lib" +
 228                             File.separator +
 229                             "flavormap.properties";
 230                         try {
 231                             return new BufferedReader
 232                                 (new InputStreamReader
 233                                     (new File(fileName).toURI().toURL().openStream(), "ISO-8859-1"));
 234                         } catch (MalformedURLException e) {
 235                             System.err.println("MalformedURLException:" + e + " while loading default flavormap.properties file:" + fileName);
 236                         } catch (IOException e) {
 237                             System.err.println("IOException:" + e + " while loading default flavormap.properties file:" + fileName);
 238                         }
 239                         return null;
 240                     }
 241                 });
 242 
 243         String url =
 244             java.security.AccessController.doPrivileged(
 245                 new java.security.PrivilegedAction<String>() {
 246                     public String run() {
 247                         return Toolkit.getProperty("AWT.DnD.flavorMapFileURL", null);
 248                     }
 249                 });
 250 
 251         if (flavormapDotProperties != null) {
 252             try {
 253                 parseAndStoreReader(flavormapDotProperties);
 254             } catch (IOException e) {
 255                 System.err.println("IOException:" + e + " while parsing default flavormap.properties file");
 256             }
 257         }
 258 
 259         BufferedReader flavormapURL = null;
 260         if (url != null) {
 261             try {
 262                 flavormapURL = new BufferedReader(new InputStreamReader(new URL(url).openStream(), "ISO-8859-1"));
 263             } catch (MalformedURLException e) {
 264                 System.err.println("MalformedURLException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
 265             } catch (IOException e) {
 266                 System.err.println("IOException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
 267             } catch (SecurityException e) {
 268                 // ignored
 269             }
 270         }
 271 
 272         if (flavormapURL != null) {
 273             try {
 274                 parseAndStoreReader(flavormapURL);
 275             } catch (IOException e) {
 276                 System.err.println("IOException:" + e + " while parsing AWT.DnD.flavorMapFileURL");
 277             }
 278         }
 279     }
 280     /**
 281      * Copied code from java.util.Properties. Parsing the data ourselves is the
 282      * only way to handle duplicate keys and values.
 283      */
 284     private void parseAndStoreReader(BufferedReader in) throws IOException {
 285         while (true) {
 286             // Get next line
 287             String line = in.readLine();
 288             if (line == null) {
 289                 return;
 290             }
 291 
 292             if (line.length() > 0) {
 293                 // Continue lines that end in slashes if they are not comments
 294                 char firstChar = line.charAt(0);
 295                 if (firstChar != '#' && firstChar != '!') {
 296                     while (continueLine(line)) {
 297                         String nextLine = in.readLine();
 298                         if (nextLine == null) {
 299                             nextLine = "";
 300                         }
 301                         String loppedLine =
 302                             line.substring(0, line.length() - 1);
 303                         // Advance beyond whitespace on new line
 304                         int startIndex = 0;
 305                         for(; startIndex < nextLine.length(); startIndex++) {
 306                             if (whiteSpaceChars.
 307                                     indexOf(nextLine.charAt(startIndex)) == -1)
 308                             {
 309                                 break;
 310                             }
 311                         }
 312                         nextLine = nextLine.substring(startIndex,
 313                                                       nextLine.length());
 314                         line = loppedLine+nextLine;
 315                     }
 316 
 317                     // Find start of key
 318                     int len = line.length();
 319                     int keyStart = 0;
 320                     for(; keyStart < len; keyStart++) {
 321                         if(whiteSpaceChars.
 322                                indexOf(line.charAt(keyStart)) == -1) {
 323                             break;
 324                         }
 325                     }
 326 
 327                     // Blank lines are ignored
 328                     if (keyStart == len) {
 329                         continue;
 330                     }
 331 
 332                     // Find separation between key and value
 333                     int separatorIndex = keyStart;
 334                     for(; separatorIndex < len; separatorIndex++) {
 335                         char currentChar = line.charAt(separatorIndex);
 336                         if (currentChar == '\\') {
 337                             separatorIndex++;
 338                         } else if (keyValueSeparators.
 339                                        indexOf(currentChar) != -1) {
 340                             break;
 341                         }
 342                     }
 343 
 344                     // Skip over whitespace after key if any
 345                     int valueIndex = separatorIndex;
 346                     for (; valueIndex < len; valueIndex++) {
 347                         if (whiteSpaceChars.
 348                                 indexOf(line.charAt(valueIndex)) == -1) {
 349                             break;
 350                         }
 351                     }
 352 
 353                     // Skip over one non whitespace key value separators if any
 354                     if (valueIndex < len) {
 355                         if (strictKeyValueSeparators.
 356                                 indexOf(line.charAt(valueIndex)) != -1) {
 357                             valueIndex++;
 358                         }
 359                     }
 360 
 361                     // Skip over white space after other separators if any
 362                     while (valueIndex < len) {
 363                         if (whiteSpaceChars.
 364                                 indexOf(line.charAt(valueIndex)) == -1) {
 365                             break;
 366                         }
 367                         valueIndex++;
 368                     }
 369 
 370                     String key = line.substring(keyStart, separatorIndex);
 371                     String value = (separatorIndex < len)
 372                         ? line.substring(valueIndex, len)
 373                         : "";
 374 
 375                     // Convert then store key and value
 376                     key = loadConvert(key);
 377                     value = loadConvert(value);
 378 
 379                     try {
 380                         MimeType mime = new MimeType(value);
 381                         if ("text".equals(mime.getPrimaryType())) {
 382                             String charset = mime.getParameter("charset");
 383                             if (DataTransferer.doesSubtypeSupportCharset
 384                                     (mime.getSubType(), charset))
 385                             {
 386                                 // We need to store the charset and eoln
 387                                 // parameters, if any, so that the
 388                                 // DataTransferer will have this information
 389                                 // for conversion into the native format.
 390                                 DataTransferer transferer =
 391                                     DataTransferer.getInstance();
 392                                 if (transferer != null) {
 393                                     transferer.registerTextFlavorProperties
 394                                         (key, charset,
 395                                          mime.getParameter("eoln"),
 396                                          mime.getParameter("terminators"));
 397                                 }
 398                             }
 399 
 400                             // But don't store any of these parameters in the
 401                             // DataFlavor itself for any text natives (even
 402                             // non-charset ones). The SystemFlavorMap will
 403                             // synthesize the appropriate mappings later.
 404                             mime.removeParameter("charset");
 405                             mime.removeParameter("class");
 406                             mime.removeParameter("eoln");
 407                             mime.removeParameter("terminators");
 408                             value = mime.toString();
 409                         }
 410                     } catch (MimeTypeParseException e) {
 411                         e.printStackTrace();
 412                         continue;
 413                     }
 414 
 415                     DataFlavor flavor;
 416                     try {
 417                         flavor = new DataFlavor(value);
 418                     } catch (Exception e) {
 419                         try {
 420                             flavor = new DataFlavor(value, null);
 421                         } catch (Exception ee) {
 422                             ee.printStackTrace();
 423                             continue;
 424                         }
 425                     }
 426 
 427                     final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();
 428                     dfs.add(flavor);
 429 
 430                     if ("text".equals(flavor.getPrimaryType())) {
 431                         dfs.addAll(convertMimeTypeToDataFlavors(value));
 432                         store(flavor.mimeType.getBaseType(), key, getTextTypeToNative());
 433                     }
 434 
 435                     for (DataFlavor df : dfs) {
 436                         store(df, key, getFlavorToNative());
 437                         store(key, df, getNativeToFlavor());
 438                     }
 439                 }
 440             }
 441         }
 442     }
 443 
 444     /**
 445      * Copied from java.util.Properties.
 446      */
 447     private boolean continueLine (String line) {
 448         int slashCount = 0;
 449         int index = line.length() - 1;
 450         while((index >= 0) && (line.charAt(index--) == '\\')) {
 451             slashCount++;
 452         }
 453         return (slashCount % 2 == 1);
 454     }
 455 
 456     /**
 457      * Copied from java.util.Properties.
 458      */
 459     private String loadConvert(String theString) {
 460         char aChar;
 461         int len = theString.length();
 462         StringBuilder outBuffer = new StringBuilder(len);
 463 
 464         for (int x = 0; x < len; ) {
 465             aChar = theString.charAt(x++);
 466             if (aChar == '\\') {
 467                 aChar = theString.charAt(x++);
 468                 if (aChar == 'u') {
 469                     // Read the xxxx
 470                     int value = 0;
 471                     for (int i = 0; i < 4; i++) {
 472                         aChar = theString.charAt(x++);
 473                         switch (aChar) {
 474                           case '0': case '1': case '2': case '3': case '4':
 475                           case '5': case '6': case '7': case '8': case '9': {
 476                              value = (value << 4) + aChar - '0';
 477                              break;
 478                           }
 479                           case 'a': case 'b': case 'c':
 480                           case 'd': case 'e': case 'f': {
 481                              value = (value << 4) + 10 + aChar - 'a';
 482                              break;
 483                           }
 484                           case 'A': case 'B': case 'C':
 485                           case 'D': case 'E': case 'F': {
 486                              value = (value << 4) + 10 + aChar - 'A';
 487                              break;
 488                           }
 489                           default: {
 490                               throw new IllegalArgumentException(
 491                                            "Malformed \\uxxxx encoding.");
 492                           }
 493                         }
 494                     }
 495                     outBuffer.append((char)value);
 496                 } else {
 497                     if (aChar == 't') {
 498                         aChar = '\t';
 499                     } else if (aChar == 'r') {
 500                         aChar = '\r';
 501                     } else if (aChar == 'n') {
 502                         aChar = '\n';
 503                     } else if (aChar == 'f') {
 504                         aChar = '\f';
 505                     }
 506                     outBuffer.append(aChar);
 507                 }
 508             } else {
 509                 outBuffer.append(aChar);
 510             }
 511         }
 512         return outBuffer.toString();
 513     }
 514 
 515     /**
 516      * Stores the listed object under the specified hash key in map. Unlike a
 517      * standard map, the listed object will not replace any object already at
 518      * the appropriate Map location, but rather will be appended to a List
 519      * stored in that location.
 520      */
 521     private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) {
 522         LinkedHashSet<L> list = map.get(hashed);
 523         if (list == null) {
 524             list = new LinkedHashSet<>(1);
 525             map.put(hashed, list);
 526         }
 527         if (!list.contains(listed)) {
 528             list.add(listed);
 529         }
 530     }
 531 
 532     /**
 533      * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
 534      * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
 535      * case, a new DataFlavor is synthesized, stored, and returned, if and
 536      * only if the specified native is encoded as a Java MIME type.
 537      */
 538     private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) {
 539         LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
 540 
 541         if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
 542             DataTransferer transferer = DataTransferer.getInstance();
 543             if (transferer != null) {
 544                 LinkedHashSet<DataFlavor> platformFlavors =
 545                         transferer.getPlatformMappingsForNative(nat);
 546                 if (!platformFlavors.isEmpty()) {
 547                     if (flavors != null) {
 548                         // Prepending the platform-specific mappings ensures
 549                         // that the flavors added with
 550                         // addFlavorForUnencodedNative() are at the end of
 551                         // list.
 552                         platformFlavors.addAll(flavors);
 553                     }
 554                     flavors = platformFlavors;
 555                 }
 556             }
 557         }
 558 
 559         if (flavors == null && isJavaMIMEType(nat)) {
 560             String decoded = decodeJavaMIMEType(nat);
 561             DataFlavor flavor = null;
 562 
 563             try {
 564                 flavor = new DataFlavor(decoded);
 565             } catch (Exception e) {
 566                 System.err.println("Exception \"" + e.getClass().getName() +
 567                                    ": " + e.getMessage()  +
 568                                    "\"while constructing DataFlavor for: " +
 569                                    decoded);
 570             }
 571 
 572             if (flavor != null) {
 573                 flavors = new LinkedHashSet<>(1);
 574                 getNativeToFlavor().put(nat, flavors);
 575                 flavors.add(flavor);
 576                 flavorsForNativeCache.remove(nat);
 577 
 578                 LinkedHashSet<String> natives = getFlavorToNative().get(flavor);
 579                 if (natives == null) {
 580                     natives = new LinkedHashSet<>(1);
 581                     getFlavorToNative().put(flavor, natives);
 582                 }
 583                 natives.add(nat);
 584                 nativesForFlavorCache.remove(flavor);
 585             }
 586         }
 587 
 588         return (flavors != null) ? flavors : new LinkedHashSet<>(0);
 589     }
 590 
 591     /**
 592      * Semantically equivalent to 'flavorToNative.get(flav)'. This method
 593      * handles the case where 'flav' is not found in 'flavorToNative' depending
 594      * on the value of passes 'synthesize' parameter. If 'synthesize' is
 595      * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
 596      * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
 597      * and 'flavorToNative' remains unaffected.
 598      */
 599     private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav,
 600                                                        final boolean synthesize) {
 601 
 602         LinkedHashSet<String> natives = getFlavorToNative().get(flav);
 603 
 604         if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
 605             DataTransferer transferer = DataTransferer.getInstance();
 606             if (transferer != null) {
 607                 LinkedHashSet<String> platformNatives =
 608                     transferer.getPlatformMappingsForFlavor(flav);
 609                 if (!platformNatives.isEmpty()) {
 610                     if (natives != null) {
 611                         // Prepend the platform-specific mappings to ensure
 612                         // that the natives added with
 613                         // addUnencodedNativeForFlavor() are at the end of
 614                         // list.
 615                         platformNatives.addAll(natives);
 616                     }
 617                     natives = platformNatives;
 618                 }
 619             }
 620         }
 621 
 622         if (natives == null) {
 623             if (synthesize) {
 624                 String encoded = encodeDataFlavor(flav);
 625                 natives = new LinkedHashSet<>(1);
 626                 getFlavorToNative().put(flav, natives);
 627                 natives.add(encoded);
 628 
 629                 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded);
 630                 if (flavors == null) {
 631                     flavors = new LinkedHashSet<>(1);
 632                     getNativeToFlavor().put(encoded, flavors);
 633                 }
 634                 flavors.add(flav);
 635 
 636                 nativesForFlavorCache.remove(flav);
 637                 flavorsForNativeCache.remove(encoded);
 638             } else {
 639                 natives = new LinkedHashSet<>(0);
 640             }
 641         }
 642 
 643         return new LinkedHashSet<>(natives);
 644     }
 645 
 646     /**
 647      * Returns a <code>List</code> of <code>String</code> natives to which the
 648      * specified <code>DataFlavor</code> can be translated by the data transfer
 649      * subsystem. The <code>List</code> will be sorted from best native to
 650      * worst. That is, the first native will best reflect data in the specified
 651      * flavor to the underlying native platform.
 652      * <p>
 653      * If the specified <code>DataFlavor</code> is previously unknown to the
 654      * data transfer subsystem and the data transfer subsystem is unable to
 655      * translate this <code>DataFlavor</code> to any existing native, then
 656      * invoking this method will establish a
 657      * mapping in both directions between the specified <code>DataFlavor</code>
 658      * and an encoded version of its MIME type as its native.
 659      *
 660      * @param flav the <code>DataFlavor</code> whose corresponding natives
 661      *        should be returned. If <code>null</code> is specified, all
 662      *        natives currently known to the data transfer subsystem are
 663      *        returned in a non-deterministic order.
 664      * @return a <code>java.util.List</code> of <code>java.lang.String</code>
 665      *         objects which are platform-specific representations of platform-
 666      *         specific data formats
 667      *
 668      * @see #encodeDataFlavor
 669      * @since 1.4
 670      */
 671     @Override
 672     public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
 673         LinkedHashSet<String> retval = nativesForFlavorCache.check(flav);
 674         if (retval != null) {
 675             return new ArrayList<>(retval);
 676         }
 677 
 678         if (flav == null) {
 679             retval = new LinkedHashSet<>(getNativeToFlavor().keySet());
 680         } else if (disabledMappingGenerationKeys.contains(flav)) {
 681             // In this case we shouldn't synthesize a native for this flavor,
 682             // since its mappings were explicitly specified.
 683             retval = flavorToNativeLookup(flav, false);
 684         } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
 685             retval = new LinkedHashSet<>(0);
 686 
 687             // For text/* flavors, flavor-to-native mappings specified in
 688             // flavormap.properties are stored per flavor's base type.
 689             if ("text".equals(flav.getPrimaryType())) {
 690                 LinkedHashSet<String> textTypeNatives =
 691                         getTextTypeToNative().get(flav.mimeType.getBaseType());
 692                 if (textTypeNatives != null) {
 693                     retval.addAll(textTypeNatives);
 694                 }
 695             }
 696 
 697             // Also include text/plain natives, but don't duplicate Strings
 698             LinkedHashSet<String> textTypeNatives =
 699                     getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE);
 700             if (textTypeNatives != null) {
 701                 retval.addAll(textTypeNatives);
 702             }
 703 
 704             if (retval.isEmpty()) {
 705                 retval = flavorToNativeLookup(flav, true);
 706             } else {
 707                 // In this branch it is guaranteed that natives explicitly
 708                 // listed for flav's MIME type were added with
 709                 // addUnencodedNativeForFlavor(), so they have lower priority.
 710                 retval.addAll(flavorToNativeLookup(flav, false));
 711             }
 712         } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
 713             retval = getTextTypeToNative().get(flav.mimeType.getBaseType());
 714 
 715             if (retval == null || retval.isEmpty()) {
 716                 retval = flavorToNativeLookup(flav, true);
 717             } else {
 718                 // In this branch it is guaranteed that natives explicitly
 719                 // listed for flav's MIME type were added with
 720                 // addUnencodedNativeForFlavor(), so they have lower priority.
 721                 retval.addAll(flavorToNativeLookup(flav, false));
 722             }
 723         } else {
 724             retval = flavorToNativeLookup(flav, true);
 725         }
 726 
 727         nativesForFlavorCache.put(flav, retval);
 728         // Create a copy, because client code can modify the returned list.
 729         return new ArrayList<>(retval);
 730     }
 731 
 732     /**
 733      * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
 734      * specified <code>String</code> native can be translated by the data
 735      * transfer subsystem. The <code>List</code> will be sorted from best
 736      * <code>DataFlavor</code> to worst. That is, the first
 737      * <code>DataFlavor</code> will best reflect data in the specified
 738      * native to a Java application.
 739      * <p>
 740      * If the specified native is previously unknown to the data transfer
 741      * subsystem, and that native has been properly encoded, then invoking this
 742      * method will establish a mapping in both directions between the specified
 743      * native and a <code>DataFlavor</code> whose MIME type is a decoded
 744      * version of the native.
 745      * <p>
 746      * If the specified native is not a properly encoded native and the
 747      * mappings for this native have not been altered with
 748      * <code>setFlavorsForNative</code>, then the contents of the
 749      * <code>List</code> is platform dependent, but <code>null</code>
 750      * cannot be returned.
 751      *
 752      * @param nat the native whose corresponding <code>DataFlavor</code>s
 753      *        should be returned. If <code>null</code> is specified, all
 754      *        <code>DataFlavor</code>s currently known to the data transfer
 755      *        subsystem are returned in a non-deterministic order.
 756      * @return a <code>java.util.List</code> of <code>DataFlavor</code>
 757      *         objects into which platform-specific data in the specified,
 758      *         platform-specific native can be translated
 759      *
 760      * @see #encodeJavaMIMEType
 761      * @since 1.4
 762      */
 763     @Override
 764     public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
 765         LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat);
 766         if (returnValue != null) {
 767             return new ArrayList<>(returnValue);
 768         } else {
 769             returnValue = new LinkedHashSet<>();
 770         }
 771 
 772         if (nat == null) {
 773             for (String n : getNativesForFlavor(null)) {
 774                 returnValue.addAll(getFlavorsForNative(n));
 775             }
 776         } else {
 777             final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat);
 778             if (disabledMappingGenerationKeys.contains(nat)) {
 779                 return new ArrayList<>(flavors);
 780             }
 781 
 782             final LinkedHashSet<DataFlavor> flavorsWithSynthesized =
 783                     nativeToFlavorLookup(nat);
 784 
 785             for (DataFlavor df : flavorsWithSynthesized) {
 786                 returnValue.add(df);
 787                 if ("text".equals(df.getPrimaryType())) {
 788                     String baseType = df.mimeType.getBaseType();
 789                     returnValue.addAll(convertMimeTypeToDataFlavors(baseType));
 790                 }
 791             }
 792         }
 793         flavorsForNativeCache.put(nat, returnValue);
 794         return new ArrayList<>(returnValue);
 795     }
 796 
 797     private static Set<DataFlavor> convertMimeTypeToDataFlavors(
 798         final String baseType) {
 799 
 800         final Set<DataFlavor> returnValue = new LinkedHashSet<>();
 801 
 802         String subType = null;
 803 
 804         try {
 805             final MimeType mimeType = new MimeType(baseType);
 806             subType = mimeType.getSubType();
 807         } catch (MimeTypeParseException mtpe) {
 808             // Cannot happen, since we checked all mappings
 809             // on load from flavormap.properties.
 810         }
 811 
 812         if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
 813             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
 814             {
 815                 returnValue.add(DataFlavor.stringFlavor);
 816             }
 817 
 818             for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
 819                 final String mimeType = baseType + ";charset=Unicode;class=" +
 820                                             unicodeClassName;
 821 
 822                 final LinkedHashSet<String> mimeTypes =
 823                     handleHtmlMimeTypes(baseType, mimeType);
 824                 for (String mt : mimeTypes) {
 825                     DataFlavor toAdd = null;
 826                     try {
 827                         toAdd = new DataFlavor(mt);
 828                     } catch (ClassNotFoundException cannotHappen) {
 829                     }
 830                     returnValue.add(toAdd);
 831                 }
 832             }
 833 
 834             for (String charset : DataTransferer.standardEncodings()) {
 835 
 836                 for (String encodedTextClass : ENCODED_TEXT_CLASSES) {
 837                     final String mimeType =
 838                             baseType + ";charset=" + charset +
 839                             ";class=" + encodedTextClass;
 840 
 841                     final LinkedHashSet<String> mimeTypes =
 842                         handleHtmlMimeTypes(baseType, mimeType);
 843 
 844                     for (String mt : mimeTypes) {
 845 
 846                         DataFlavor df = null;
 847 
 848                         try {
 849                             df = new DataFlavor(mt);
 850                             // Check for equality to plainTextFlavor so
 851                             // that we can ensure that the exact charset of
 852                             // plainTextFlavor, not the canonical charset
 853                             // or another equivalent charset with a
 854                             // different name, is used.
 855                             if (df.equals(DataFlavor.plainTextFlavor)) {
 856                                 df = DataFlavor.plainTextFlavor;
 857                             }
 858                         } catch (ClassNotFoundException cannotHappen) {
 859                         }
 860 
 861                         returnValue.add(df);
 862                     }
 863                 }
 864             }
 865 
 866             if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
 867             {
 868                 returnValue.add(DataFlavor.plainTextFlavor);
 869             }
 870         } else {
 871             // Non-charset text natives should be treated as
 872             // opaque, 8-bit data in any of its various
 873             // representations.
 874             for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {
 875                 DataFlavor toAdd = null;
 876                 try {
 877                     toAdd = new DataFlavor(baseType +
 878                          ";class=" + encodedTextClassName);
 879                 } catch (ClassNotFoundException cannotHappen) {
 880                 }
 881                 returnValue.add(toAdd);
 882             }
 883         }
 884         return returnValue;
 885     }
 886 
 887     private static final String [] htmlDocumntTypes =
 888             new String [] {"all", "selection", "fragment"};
 889 
 890     private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType,
 891                                                              String mimeType) {
 892 
 893         LinkedHashSet<String> returnValues = new LinkedHashSet<>();
 894 
 895         if (HTML_TEXT_BASE_TYPE.equals(baseType)) {
 896             for (String documentType : htmlDocumntTypes) {
 897                 returnValues.add(mimeType + ";document=" + documentType);
 898             }
 899         } else {
 900             returnValues.add(mimeType);
 901         }
 902 
 903         return returnValues;
 904     }
 905 
 906     /**
 907      * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
 908      * their most preferred <code>String</code> native. Each native value will
 909      * be the same as the first native in the List returned by
 910      * <code>getNativesForFlavor</code> for the specified flavor.
 911      * <p>
 912      * If a specified <code>DataFlavor</code> is previously unknown to the
 913      * data transfer subsystem, then invoking this method will establish a
 914      * mapping in both directions between the specified <code>DataFlavor</code>
 915      * and an encoded version of its MIME type as its native.
 916      *
 917      * @param flavors an array of <code>DataFlavor</code>s which will be the
 918      *        key set of the returned <code>Map</code>. If <code>null</code> is
 919      *        specified, a mapping of all <code>DataFlavor</code>s known to the
 920      *        data transfer subsystem to their most preferred
 921      *        <code>String</code> natives will be returned.
 922      * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
 923      *         <code>String</code> natives
 924      *
 925      * @see #getNativesForFlavor
 926      * @see #encodeDataFlavor
 927      */
 928     @Override
 929     public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors)
 930     {
 931         // Use getNativesForFlavor to generate extra natives for text flavors
 932         // and stringFlavor
 933 
 934         if (flavors == null) {
 935             List<DataFlavor> flavor_list = getFlavorsForNative(null);
 936             flavors = new DataFlavor[flavor_list.size()];
 937             flavor_list.toArray(flavors);
 938         }
 939 
 940         Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
 941         for (DataFlavor flavor : flavors) {
 942             List<String> natives = getNativesForFlavor(flavor);
 943             String nat = (natives.isEmpty()) ? null : natives.get(0);
 944             retval.put(flavor, nat);
 945         }
 946 
 947         return retval;
 948     }
 949 
 950     /**
 951      * Returns a <code>Map</code> of the specified <code>String</code> natives
 952      * to their most preferred <code>DataFlavor</code>. Each
 953      * <code>DataFlavor</code> value will be the same as the first
 954      * <code>DataFlavor</code> in the List returned by
 955      * <code>getFlavorsForNative</code> for the specified native.
 956      * <p>
 957      * If a specified native is previously unknown to the data transfer
 958      * subsystem, and that native has been properly encoded, then invoking this
 959      * method will establish a mapping in both directions between the specified
 960      * native and a <code>DataFlavor</code> whose MIME type is a decoded
 961      * version of the native.
 962      *
 963      * @param natives an array of <code>String</code>s which will be the
 964      *        key set of the returned <code>Map</code>. If <code>null</code> is
 965      *        specified, a mapping of all supported <code>String</code> natives
 966      *        to their most preferred <code>DataFlavor</code>s will be
 967      *        returned.
 968      * @return a <code>java.util.Map</code> of <code>String</code> natives to
 969      *         <code>DataFlavor</code>s
 970      *
 971      * @see #getFlavorsForNative
 972      * @see #encodeJavaMIMEType
 973      */
 974     @Override
 975     public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives)
 976     {
 977         // Use getFlavorsForNative to generate extra flavors for text natives
 978         if (natives == null) {
 979             List<String> nativesList = getNativesForFlavor(null);
 980             natives = new String[nativesList.size()];
 981             nativesList.toArray(natives);
 982         }
 983 
 984         Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
 985         for (String aNative : natives) {
 986             List<DataFlavor> flavors = getFlavorsForNative(aNative);
 987             DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
 988             retval.put(aNative, flav);
 989         }
 990         return retval;
 991     }
 992 
 993     /**
 994      * Adds a mapping from the specified <code>DataFlavor</code> (and all
 995      * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
 996      * to the specified <code>String</code> native.
 997      * Unlike <code>getNativesForFlavor</code>, the mapping will only be
 998      * established in one direction, and the native will not be encoded. To
 999      * establish a two-way mapping, call
1000      * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1001      * be of lower priority than any existing mapping.
1002      * This method has no effect if a mapping from the specified or equal
1003      * <code>DataFlavor</code> to the specified <code>String</code> native
1004      * already exists.
1005      *
1006      * @param flav the <code>DataFlavor</code> key for the mapping
1007      * @param nat the <code>String</code> native value for the mapping
1008      * @throws NullPointerException if flav or nat is <code>null</code>
1009      *
1010      * @see #addFlavorForUnencodedNative
1011      * @since 1.4
1012      */
1013     public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
1014                                                          String nat) {
1015         Objects.requireNonNull(nat, "Null native not permitted");
1016         Objects.requireNonNull(flav, "Null flavor not permitted");
1017 
1018         LinkedHashSet<String> natives = getFlavorToNative().get(flav);
1019         if (natives == null) {
1020             natives = new LinkedHashSet<>(1);
1021             getFlavorToNative().put(flav, natives);
1022         }
1023         natives.add(nat);
1024         nativesForFlavorCache.remove(flav);
1025     }
1026 
1027     /**
1028      * Discards the current mappings for the specified <code>DataFlavor</code>
1029      * and all <code>DataFlavor</code>s equal to the specified
1030      * <code>DataFlavor</code>, and creates new mappings to the
1031      * specified <code>String</code> natives.
1032      * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1033      * established in one direction, and the natives will not be encoded. To
1034      * establish two-way mappings, call <code>setFlavorsForNative</code>
1035      * as well. The first native in the array will represent the highest
1036      * priority mapping. Subsequent natives will represent mappings of
1037      * decreasing priority.
1038      * <p>
1039      * If the array contains several elements that reference equal
1040      * <code>String</code> natives, this method will establish new mappings
1041      * for the first of those elements and ignore the rest of them.
1042      * <p>
1043      * It is recommended that client code not reset mappings established by the
1044      * data transfer subsystem. This method should only be used for
1045      * application-level mappings.
1046      *
1047      * @param flav the <code>DataFlavor</code> key for the mappings
1048      * @param natives the <code>String</code> native values for the mappings
1049      * @throws NullPointerException if flav or natives is <code>null</code>
1050      *         or if natives contains <code>null</code> elements
1051      *
1052      * @see #setFlavorsForNative
1053      * @since 1.4
1054      */
1055     public synchronized void setNativesForFlavor(DataFlavor flav,
1056                                                  String[] natives) {
1057         Objects.requireNonNull(natives, "Null natives not permitted");
1058         Objects.requireNonNull(flav, "Null flavors not permitted");
1059 
1060         getFlavorToNative().remove(flav);
1061         for (String aNative : natives) {
1062             addUnencodedNativeForFlavor(flav, aNative);
1063         }
1064         disabledMappingGenerationKeys.add(flav);
1065         nativesForFlavorCache.remove(flav);
1066     }
1067 
1068     /**
1069      * Adds a mapping from a single <code>String</code> native to a single
1070      * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1071      * mapping will only be established in one direction, and the native will
1072      * not be encoded. To establish a two-way mapping, call
1073      * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1074      * be of lower priority than any existing mapping.
1075      * This method has no effect if a mapping from the specified
1076      * <code>String</code> native to the specified or equal
1077      * <code>DataFlavor</code> already exists.
1078      *
1079      * @param nat the <code>String</code> native key for the mapping
1080      * @param flav the <code>DataFlavor</code> value for the mapping
1081      * @throws NullPointerException if nat or flav is <code>null</code>
1082      *
1083      * @see #addUnencodedNativeForFlavor
1084      * @since 1.4
1085      */
1086     public synchronized void addFlavorForUnencodedNative(String nat,
1087                                                          DataFlavor flav) {
1088         Objects.requireNonNull(nat, "Null native not permitted");
1089         Objects.requireNonNull(flav, "Null flavor not permitted");
1090 
1091         LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
1092         if (flavors == null) {
1093             flavors = new LinkedHashSet<>(1);
1094             getNativeToFlavor().put(nat, flavors);
1095         }
1096         flavors.add(flav);
1097         flavorsForNativeCache.remove(nat);
1098     }
1099 
1100     /**
1101      * Discards the current mappings for the specified <code>String</code>
1102      * native, and creates new mappings to the specified
1103      * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1104      * mappings will only be established in one direction, and the natives need
1105      * not be encoded. To establish two-way mappings, call
1106      * <code>setNativesForFlavor</code> as well. The first
1107      * <code>DataFlavor</code> in the array will represent the highest priority
1108      * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1109      * decreasing priority.
1110      * <p>
1111      * If the array contains several elements that reference equal
1112      * <code>DataFlavor</code>s, this method will establish new mappings
1113      * for the first of those elements and ignore the rest of them.
1114      * <p>
1115      * It is recommended that client code not reset mappings established by the
1116      * data transfer subsystem. This method should only be used for
1117      * application-level mappings.
1118      *
1119      * @param nat the <code>String</code> native key for the mappings
1120      * @param flavors the <code>DataFlavor</code> values for the mappings
1121      * @throws NullPointerException if nat or flavors is <code>null</code>
1122      *         or if flavors contains <code>null</code> elements
1123      *
1124      * @see #setNativesForFlavor
1125      * @since 1.4
1126      */
1127     public synchronized void setFlavorsForNative(String nat,
1128                                                  DataFlavor[] flavors) {
1129         Objects.requireNonNull(nat, "Null native not permitted");
1130         Objects.requireNonNull(flavors, "Null flavors not permitted");
1131 
1132         getNativeToFlavor().remove(nat);
1133         for (DataFlavor flavor : flavors) {
1134             addFlavorForUnencodedNative(nat, flavor);
1135         }
1136         disabledMappingGenerationKeys.add(nat);
1137         flavorsForNativeCache.remove(nat);
1138     }
1139 
1140     /**
1141      * Encodes a MIME type for use as a <code>String</code> native. The format
1142      * of an encoded representation of a MIME type is implementation-dependent.
1143      * The only restrictions are:
1144      * <ul>
1145      * <li>The encoded representation is <code>null</code> if and only if the
1146      * MIME type <code>String</code> is <code>null</code>.</li>
1147      * <li>The encoded representations for two non-<code>null</code> MIME type
1148      * <code>String</code>s are equal if and only if these <code>String</code>s
1149      * are equal according to <code>String.equals(Object)</code>.</li>
1150      * </ul>
1151      * <p>
1152      * The reference implementation of this method returns the specified MIME
1153      * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
1154      *
1155      * @param mimeType the MIME type to encode
1156      * @return the encoded <code>String</code>, or <code>null</code> if
1157      *         mimeType is <code>null</code>
1158      */
1159     public static String encodeJavaMIMEType(String mimeType) {
1160         return (mimeType != null)
1161             ? JavaMIME + mimeType
1162             : null;
1163     }
1164 
1165     /**
1166      * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
1167      * native. The format of an encoded <code>DataFlavor</code> is
1168      * implementation-dependent. The only restrictions are:
1169      * <ul>
1170      * <li>The encoded representation is <code>null</code> if and only if the
1171      * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
1172      * <code>String</code> is <code>null</code>.</li>
1173      * <li>The encoded representations for two non-<code>null</code>
1174      * <code>DataFlavor</code>s with non-<code>null</code> MIME type
1175      * <code>String</code>s are equal if and only if the MIME type
1176      * <code>String</code>s of these <code>DataFlavor</code>s are equal
1177      * according to <code>String.equals(Object)</code>.</li>
1178      * </ul>
1179      * <p>
1180      * The reference implementation of this method returns the MIME type
1181      * <code>String</code> of the specified <code>DataFlavor</code> prefixed
1182      * with <code>JAVA_DATAFLAVOR:</code>.
1183      *
1184      * @param flav the <code>DataFlavor</code> to encode
1185      * @return the encoded <code>String</code>, or <code>null</code> if
1186      *         flav is <code>null</code> or has a <code>null</code> MIME type
1187      */
1188     public static String encodeDataFlavor(DataFlavor flav) {
1189         return (flav != null)
1190             ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
1191             : null;
1192     }
1193 
1194     /**
1195      * Returns whether the specified <code>String</code> is an encoded Java
1196      * MIME type.
1197      *
1198      * @param str the <code>String</code> to test
1199      * @return <code>true</code> if the <code>String</code> is encoded;
1200      *         <code>false</code> otherwise
1201      */
1202     public static boolean isJavaMIMEType(String str) {
1203         return (str != null && str.startsWith(JavaMIME, 0));
1204     }
1205 
1206     /**
1207      * Decodes a <code>String</code> native for use as a Java MIME type.
1208      *
1209      * @param nat the <code>String</code> to decode
1210      * @return the decoded Java MIME type, or <code>null</code> if nat is not
1211      *         an encoded <code>String</code> native
1212      */
1213     public static String decodeJavaMIMEType(String nat) {
1214         return (isJavaMIMEType(nat))
1215             ? nat.substring(JavaMIME.length(), nat.length()).trim()
1216             : null;
1217     }
1218 
1219     /**
1220      * Decodes a <code>String</code> native for use as a
1221      * <code>DataFlavor</code>.
1222      *
1223      * @param nat the <code>String</code> to decode
1224      * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1225      *         nat is not an encoded <code>String</code> native
1226      * @throws ClassNotFoundException if the class of the data flavor
1227      * is not loaded
1228      */
1229     public static DataFlavor decodeDataFlavor(String nat)
1230         throws ClassNotFoundException
1231     {
1232         String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1233         return (retval_str != null)
1234             ? new DataFlavor(retval_str)
1235             : null;
1236     }
1237 
1238     private static final class SoftCache<K, V> {
1239         Map<K, SoftReference<LinkedHashSet<V>>> cache;
1240 
1241         public void put(K key, LinkedHashSet<V> value) {
1242             if (cache == null) {
1243                 cache = new HashMap<>(1);
1244             }
1245             cache.put(key, new SoftReference<>(value));
1246         }
1247 
1248         public void remove(K key) {
1249             if (cache == null) return;
1250             cache.remove(null);
1251             cache.remove(key);
1252         }
1253 
1254         public LinkedHashSet<V> check(K key) {
1255             if (cache == null) return null;
1256             SoftReference<LinkedHashSet<V>> ref = cache.get(key);
1257             if (ref != null) {
1258                 return ref.get();
1259             }
1260             return null;
1261         }
1262     }
1263 }