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