KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > datatransfer > SystemFlavorMap


1 /*
2  * @(#)SystemFlavorMap.java 1.36 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.awt.datatransfer;
9
10 import java.awt.Toolkit JavaDoc;
11
12 import java.lang.ref.SoftReference JavaDoc;
13
14 import java.io.BufferedReader JavaDoc;
15 import java.io.File JavaDoc;
16 import java.io.InputStreamReader JavaDoc;
17 import java.io.IOException JavaDoc;
18
19 import java.net.URL JavaDoc;
20 import java.net.MalformedURLException JavaDoc;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.LinkedList JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.Set JavaDoc;
30 import java.util.WeakHashMap JavaDoc;
31
32 import sun.awt.datatransfer.DataTransferer;
33
34
35 /**
36  * The SystemFlavorMap is a configurable map between "natives" (Strings), which
37  * correspond to platform-specific data formats, and "flavors" (DataFlavors),
38  * which correspond to platform-independent MIME types. This mapping is used
39  * by the data transfer subsystem to transfer data between Java and native
40  * applications, and between Java applications in separate VMs.
41  * <p>
42  * In the Sun reference implementation, the default SystemFlavorMap is
43  * initialized by the file <code>jre/lib/flavormap.properties</code> and the
44  * contents of the URL referenced by the AWT property
45  * <code>AWT.DnD.flavorMapFileURL</code>. See <code>flavormap.properties</code>
46  * for details.
47  *
48  * @version 1.36, 05/05/04
49  * @since 1.2
50  */

51 public final class SystemFlavorMap implements FlavorMap JavaDoc, FlavorTable JavaDoc {
52
53     /**
54      * Constant prefix used to tag Java types converted to native platform
55      * type.
56      */

57     private static String JavaDoc JavaMIME = "JAVA_DATAFLAVOR:";
58
59     /**
60      * System singleton which maps a thread's ClassLoader to a SystemFlavorMap.
61      */

62     private static final WeakHashMap JavaDoc flavorMaps = new WeakHashMap JavaDoc();
63
64     /**
65      * Copied from java.util.Properties.
66      */

67     private static final String JavaDoc keyValueSeparators = "=: \t\r\n\f";
68     private static final String JavaDoc strictKeyValueSeparators = "=:";
69     private static final String JavaDoc whiteSpaceChars = " \t\r\n\f";
70
71     /**
72      * The list of valid, decoded text flavor representation classes, in order
73      * from best to worst.
74      */

75     private static final String JavaDoc[] UNICODE_TEXT_CLASSES = {
76         "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
77     };
78
79     /**
80      * The list of valid, encoded text flavor representation classes, in order
81      * from best to worst.
82      */

83     private static final String JavaDoc[] ENCODED_TEXT_CLASSES = {
84         "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
85     };
86
87     /**
88      * A String representing text/plain MIME type.
89      */

90     private static final String JavaDoc TEXT_PLAIN_BASE_TYPE = "text/plain";
91
92     /**
93      * This constant is passed to flavorToNativeLookup() to indicate that a
94      * a native should be synthesized, stored, and returned by encoding the
95      * DataFlavor's MIME type in case if the DataFlavor is not found in
96      * 'flavorToNative' map.
97      */

98     private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
99
100     /**
101      * Maps native Strings to Lists of DataFlavors (or base type Strings for
102      * text DataFlavors).
103      */

104     private Map JavaDoc nativeToFlavor = new HashMap JavaDoc();
105
106     /**
107      * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
108      * native Strings.
109      */

110     private Map JavaDoc flavorToNative = new HashMap JavaDoc();
111
112     /**
113      * Caches the result of getNativesForFlavor(). Maps DataFlavors to
114      * SoftReferences which reference Lists of String natives.
115      */

116     private Map JavaDoc getNativesForFlavorCache = new HashMap JavaDoc();
117
118     /**
119      * Caches the result getFlavorsForNative(). Maps String natives to
120      * SoftReferences which reference Lists of DataFlavors.
121      */

122     private Map JavaDoc getFlavorsForNativeCache = new HashMap JavaDoc();
123
124     /**
125      * Dynamic mapping generation used for text mappings should not be applied
126      * to the DataFlavors and String natives for which the mappings have been
127      * explicitly specified with setFlavorsForNative() or
128      * setNativesForFlavor(). This keeps all such keys.
129      */

130     private Set JavaDoc disabledMappingGenerationKeys = new HashSet JavaDoc();
131
132     /**
133      * Returns the default FlavorMap for this thread's ClassLoader.
134      */

135     public static FlavorMap JavaDoc getDefaultFlavorMap() {
136         ClassLoader JavaDoc contextClassLoader =
137             Thread.currentThread().getContextClassLoader();
138         if (contextClassLoader == null) {
139             contextClassLoader = ClassLoader.getSystemClassLoader();
140         }
141
142         FlavorMap JavaDoc fm;
143
144         synchronized(flavorMaps) {
145             fm = (FlavorMap JavaDoc)flavorMaps.get(contextClassLoader);
146             if (fm == null) {
147                 fm = new SystemFlavorMap JavaDoc();
148                 flavorMaps.put(contextClassLoader, fm);
149             }
150         }
151
152         return fm;
153     }
154
155     /**
156      * Constructs a SystemFlavorMap by reading flavormap.properties and
157      * AWT.DnD.flavorMapFileURL.
158      */

159     private SystemFlavorMap() {
160         BufferedReader JavaDoc flavormapDotProperties = (BufferedReader JavaDoc)
161             java.security.AccessController.doPrivileged(
162                 new java.security.PrivilegedAction JavaDoc() {
163                     public Object JavaDoc run() {
164                         String JavaDoc fileName =
165                             System.getProperty("java.home") +
166                             File.separator +
167                             "lib" +
168                             File.separator +
169                             "flavormap.properties";
170                         try {
171                             return new BufferedReader JavaDoc
172                                 (new InputStreamReader JavaDoc
173                                     (new File JavaDoc(fileName).toURI().toURL().openStream(), "ISO-8859-1"));
174                         } catch (MalformedURLException JavaDoc e) {
175                             System.err.println("MalformedURLException:" + e + " while loading default flavormap.properties file:" + fileName);
176                         } catch (IOException JavaDoc e) {
177                             System.err.println("IOException:" + e + " while loading default flavormap.properties file:" + fileName);
178                         }
179                         return null;
180                     }
181                 });
182
183         BufferedReader JavaDoc flavormapURL = (BufferedReader JavaDoc)
184             java.security.AccessController.doPrivileged(
185                 new java.security.PrivilegedAction JavaDoc() {
186                     public Object JavaDoc run() {
187                         String JavaDoc url = Toolkit.getDefaultToolkit().getProperty
188                             ("AWT.DnD.flavorMapFileURL", null);
189
190                         if (url == null) {
191                             return null;
192                         }
193
194                         try {
195                             return new BufferedReader JavaDoc
196                                 (new InputStreamReader JavaDoc
197                                     (new URL JavaDoc(url).openStream(), "ISO-8859-1"));
198                         } catch (MalformedURLException JavaDoc e) {
199                             System.err.println("MalformedURLException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
200                         } catch (IOException JavaDoc e) {
201                             System.err.println("IOException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
202                         }
203                         return null;
204                     }
205                 });
206
207         if (flavormapDotProperties != null) {
208             try {
209                 parseAndStoreReader(flavormapDotProperties);
210             } catch (IOException JavaDoc e) {
211                 System.err.println("IOException:" + e + " while parsing default flavormap.properties file");
212             }
213         }
214
215         if (flavormapURL != null) {
216             try {
217                 parseAndStoreReader(flavormapURL);
218             } catch (IOException JavaDoc e) {
219                 System.err.println("IOException:" + e + " while parsing AWT.DnD.flavorMapFileURL");
220             }
221         }
222     }
223
224     /**
225      * Copied code from java.util.Properties. Parsing the data ourselves is the
226      * only way to handle duplicate keys and values.
227      */

228     private void parseAndStoreReader(BufferedReader JavaDoc in) throws IOException JavaDoc {
229         while (true) {
230             // Get next line
231
String JavaDoc line = in.readLine();
232             if (line == null) {
233                 return;
234             }
235
236             if (line.length() > 0) {
237                 // Continue lines that end in slashes if they are not comments
238
char firstChar = line.charAt(0);
239                 if (firstChar != '#' && firstChar != '!') {
240                     while (continueLine(line)) {
241                         String JavaDoc nextLine = in.readLine();
242                         if (nextLine == null) {
243                             nextLine = new String JavaDoc("");
244                         }
245                         String JavaDoc loppedLine =
246                             line.substring(0, line.length() - 1);
247                         // Advance beyond whitespace on new line
248
int startIndex = 0;
249                         for(; startIndex < nextLine.length(); startIndex++) {
250                             if (whiteSpaceChars.
251                                     indexOf(nextLine.charAt(startIndex)) == -1)
252                             {
253                                 break;
254                             }
255                         }
256                         nextLine = nextLine.substring(startIndex,
257                                                       nextLine.length());
258                         line = new String JavaDoc(loppedLine+nextLine);
259                     }
260
261                     // Find start of key
262
int len = line.length();
263                     int keyStart = 0;
264                     for(; keyStart < len; keyStart++) {
265                         if(whiteSpaceChars.
266                                indexOf(line.charAt(keyStart)) == -1) {
267                             break;
268                         }
269                     }
270
271                     // Blank lines are ignored
272
if (keyStart == len) {
273                         continue;
274                     }
275
276                     // Find separation between key and value
277
int separatorIndex = keyStart;
278                     for(; separatorIndex < len; separatorIndex++) {
279                         char currentChar = line.charAt(separatorIndex);
280                         if (currentChar == '\\') {
281                             separatorIndex++;
282                         } else if (keyValueSeparators.
283                                        indexOf(currentChar) != -1) {
284                             break;
285                         }
286                     }
287
288                     // Skip over whitespace after key if any
289
int valueIndex = separatorIndex;
290                     for (; valueIndex < len; valueIndex++) {
291                         if (whiteSpaceChars.
292                                 indexOf(line.charAt(valueIndex)) == -1) {
293                             break;
294                         }
295                     }
296
297                     // Skip over one non whitespace key value separators if any
298
if (valueIndex < len) {
299                         if (strictKeyValueSeparators.
300                                 indexOf(line.charAt(valueIndex)) != -1) {
301                             valueIndex++;
302                         }
303                     }
304
305                     // Skip over white space after other separators if any
306
while (valueIndex < len) {
307                         if (whiteSpaceChars.
308                                 indexOf(line.charAt(valueIndex)) == -1) {
309                             break;
310                         }
311                         valueIndex++;
312                     }
313
314                     String JavaDoc key = line.substring(keyStart, separatorIndex);
315                     String JavaDoc value = (separatorIndex < len)
316                         ? line.substring(valueIndex, len)
317                         : "";
318
319                     // Convert then store key and value
320
key = loadConvert(key);
321                     value = loadConvert(value);
322
323                     try {
324                         MimeType JavaDoc mime = new MimeType JavaDoc(value);
325                         if ("text".equals(mime.getPrimaryType())) {
326                             String JavaDoc charset = mime.getParameter("charset");
327                             if (DataTransferer.doesSubtypeSupportCharset
328                                     (mime.getSubType(), charset))
329                             {
330                                 // We need to store the charset and eoln
331
// parameters, if any, so that the
332
// DataTransferer will have this information
333
// for conversion into the native format.
334
DataTransferer transferer =
335                                     DataTransferer.getInstance();
336                                 if (transferer != null) {
337                                     transferer.registerTextFlavorProperties
338                                         (key, charset,
339                                          mime.getParameter("eoln"),
340                                          mime.getParameter("terminators"));
341                                 }
342                             }
343
344                             // But don't store any of these parameters in the
345
// DataFlavor itself for any text natives (even
346
// non-charset ones). The SystemFlavorMap will
347
// synthesize the appropriate mappings later.
348
mime.removeParameter("charset");
349                             mime.removeParameter("class");
350                             mime.removeParameter("eoln");
351                             mime.removeParameter("terminators");
352                             value = mime.toString();
353                         }
354                     } catch (MimeTypeParseException JavaDoc e) {
355                         e.printStackTrace();
356                         continue;
357                     }
358
359                     DataFlavor JavaDoc flavor;
360                     try {
361                         flavor = new DataFlavor JavaDoc(value);
362                     } catch (Exception JavaDoc e) {
363                         try {
364                             flavor = new DataFlavor JavaDoc(value, (String JavaDoc)null);
365                         } catch (Exception JavaDoc ee) {
366                             ee.printStackTrace();
367                             continue;
368                         }
369                     }
370
371                     // For text/* flavors, store mappings in separate maps to
372
// enable dynamic mapping generation at a run-time.
373
if ("text".equals(flavor.getPrimaryType())) {
374                         store(value, key, flavorToNative);
375                         store(key, value, nativeToFlavor);
376                     } else {
377                         store(flavor, key, flavorToNative);
378                         store(key, flavor, nativeToFlavor);
379                     }
380                 }
381             }
382         }
383     }
384
385     /**
386      * Copied from java.util.Properties.
387      */

388     private boolean continueLine (String JavaDoc line) {
389         int slashCount = 0;
390         int index = line.length() - 1;
391         while((index >= 0) && (line.charAt(index--) == '\\')) {
392             slashCount++;
393         }
394         return (slashCount % 2 == 1);
395     }
396
397     /**
398      * Copied from java.util.Properties.
399      */

400     private String JavaDoc loadConvert(String JavaDoc theString) {
401         char aChar;
402         int len = theString.length();
403         StringBuffer JavaDoc outBuffer = new StringBuffer JavaDoc(len);
404
405         for (int x = 0; x < len; ) {
406             aChar = theString.charAt(x++);
407             if (aChar == '\\') {
408                 aChar = theString.charAt(x++);
409                 if (aChar == 'u') {
410                     // Read the xxxx
411
int value = 0;
412                     for (int i = 0; i < 4; i++) {
413                         aChar = theString.charAt(x++);
414                         switch (aChar) {
415                           case '0': case '1': case '2': case '3': case '4':
416                           case '5': case '6': case '7': case '8': case '9': {
417                              value = (value << 4) + aChar - '0';
418                              break;
419                           }
420                           case 'a': case 'b': case 'c':
421                           case 'd': case 'e': case 'f': {
422                              value = (value << 4) + 10 + aChar - 'a';
423                              break;
424                           }
425                           case 'A': case 'B': case 'C':
426                           case 'D': case 'E': case 'F': {
427                              value = (value << 4) + 10 + aChar - 'A';
428                              break;
429                           }
430                           default: {
431                               throw new IllegalArgumentException JavaDoc(
432                                            "Malformed \\uxxxx encoding.");
433                           }
434                         }
435                     }
436                     outBuffer.append((char)value);
437                 } else {
438                     if (aChar == 't') {
439                         aChar = '\t';
440                     } else if (aChar == 'r') {
441                         aChar = '\r';
442                     } else if (aChar == 'n') {
443                         aChar = '\n';
444                     } else if (aChar == 'f') {
445                         aChar = '\f';
446                     }
447                     outBuffer.append(aChar);
448                 }
449             } else {
450                 outBuffer.append(aChar);
451             }
452         }
453         return outBuffer.toString();
454     }
455
456     /**
457      * Stores the listed object under the specified hash key in map. Unlike a
458      * standard map, the listed object will not replace any object already at
459      * the appropriate Map location, but rather will be appended to a List
460      * stored in that location.
461      */

462     private void store(Object JavaDoc hashed, Object JavaDoc listed, Map JavaDoc map) {
463         List JavaDoc list = (List JavaDoc)map.get(hashed);
464         if (list == null) {
465             list = new ArrayList JavaDoc(1);
466             map.put(hashed, list);
467         }
468         if (!list.contains(listed)) {
469             list.add(listed);
470         }
471     }
472
473     /**
474      * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
475      * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
476      * case, a new DataFlavor is synthesized, stored, and returned, if and
477      * only if the specified native is encoded as a Java MIME type.
478      */

479     private List JavaDoc nativeToFlavorLookup(String JavaDoc nat) {
480         List JavaDoc flavors = (List JavaDoc)nativeToFlavor.get(nat);
481
482         if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
483             DataTransferer transferer = DataTransferer.getInstance();
484             if (transferer != null) {
485                 List JavaDoc platformFlavors =
486                     transferer.getPlatformMappingsForNative(nat);
487                 if (!platformFlavors.isEmpty()) {
488                     if (flavors != null) {
489                         platformFlavors.removeAll(new HashSet JavaDoc(flavors));
490                         // Prepending the platform-specific mappings ensures
491
// that the flavors added with
492
// addFlavorForUnencodedNative() are at the end of
493
// list.
494
platformFlavors.addAll(flavors);
495                     }
496                     flavors = platformFlavors;
497                 }
498             }
499         }
500
501         if (flavors == null && isJavaMIMEType(nat)) {
502             String JavaDoc decoded = decodeJavaMIMEType(nat);
503             DataFlavor JavaDoc flavor = null;
504
505             try {
506                 flavor = new DataFlavor JavaDoc(decoded);
507             } catch (Exception JavaDoc e) {
508                 System.err.println("Exception \"" + e.getClass().getName() +
509                                    ": " + e.getMessage() +
510                                    "\"while constructing DataFlavor for: " +
511                                    decoded);
512             }
513
514             if (flavor != null) {
515                 flavors = new ArrayList JavaDoc(1);
516                 nativeToFlavor.put(nat, flavors);
517                 flavors.add(flavor);
518                 getFlavorsForNativeCache.remove(nat);
519                 getFlavorsForNativeCache.remove(null);
520
521                 List JavaDoc natives = (List JavaDoc)flavorToNative.get(flavor);
522                 if (natives == null) {
523                     natives = new ArrayList JavaDoc(1);
524                     flavorToNative.put(flavor, natives);
525                 }
526                 natives.add(nat);
527                 getNativesForFlavorCache.remove(flavor);
528                 getNativesForFlavorCache.remove(null);
529             }
530         }
531
532         return (flavors != null) ? flavors : new ArrayList JavaDoc(0);
533     }
534
535     /**
536      * Semantically equivalent to 'flavorToNative.get(flav)'. This method
537      * handles the case where 'flav' is not found in 'flavorToNative' depending
538      * on the value of passes 'synthesize' parameter. If 'synthesize' is
539      * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
540      * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
541      * and 'flavorToNative' remains unaffected.
542      */

543     private List JavaDoc flavorToNativeLookup(final DataFlavor JavaDoc flav,
544                                       final boolean synthesize) {
545         List JavaDoc natives = (List JavaDoc)flavorToNative.get(flav);
546
547         if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
548             DataTransferer transferer = DataTransferer.getInstance();
549             if (transferer != null) {
550                 List JavaDoc platformNatives =
551                     transferer.getPlatformMappingsForFlavor(flav);
552                 if (!platformNatives.isEmpty()) {
553                     if (natives != null) {
554                         platformNatives.removeAll(new HashSet JavaDoc(natives));
555                         // Prepend the platform-specific mappings to ensure
556
// that the natives added with
557
// addUnencodedNativeForFlavor() are at the end of
558
// list.
559
platformNatives.addAll(natives);
560                     }
561                     natives = platformNatives;
562                 }
563             }
564         }
565
566         if (natives == null) {
567             if (synthesize) {
568                 String JavaDoc encoded = encodeDataFlavor(flav);
569                 natives = new ArrayList JavaDoc(1);
570                 flavorToNative.put(flav, natives);
571                 natives.add(encoded);
572                 getNativesForFlavorCache.remove(flav);
573                 getNativesForFlavorCache.remove(null);
574
575                 List JavaDoc flavors = (List JavaDoc)nativeToFlavor.get(encoded);
576                 if (flavors == null) {
577                     flavors = new ArrayList JavaDoc(1);
578                     nativeToFlavor.put(encoded, flavors);
579                 }
580                 flavors.add(flav);
581                 getFlavorsForNativeCache.remove(encoded);
582                 getFlavorsForNativeCache.remove(null);
583             } else {
584                 natives = new ArrayList JavaDoc(0);
585             }
586         }
587
588         return natives;
589     }
590
591     /**
592      * Returns a <code>List</code> of <code>String</code> natives to which the
593      * specified <code>DataFlavor</code> can be translated by the data transfer
594      * subsystem. The <code>List</code> will be sorted from best native to
595      * worst. That is, the first native will best reflect data in the specified
596      * flavor to the underlying native platform.
597      * <p>
598      * If the specified <code>DataFlavor</code> is previously unknown to the
599      * data transfer subsystem and the data transfer subsystem is unable to
600      * translate this <code>DataFlavor</code> to any existing native, then
601      * invoking this method will establish a
602      * mapping in both directions between the specified <code>DataFlavor</code>
603      * and an encoded version of its MIME type as its native.
604      *
605      * @param flav the <code>DataFlavor</code> whose corresponding natives
606      * should be returned. If <code>null</code> is specified, all
607      * natives currently known to the data transfer subsystem are
608      * returned in a non-deterministic order.
609      * @return a <code>java.util.List</code> of <code>java.lang.String</code>
610      * objects which are platform-specific representations of platform-
611      * specific data formats
612      *
613      * @see #encodeDataFlavor
614      * @since 1.4
615      */

616     public synchronized List JavaDoc<String JavaDoc> getNativesForFlavor(DataFlavor JavaDoc flav) {
617         List JavaDoc retval = null;
618
619         // Check cache, even for null flav
620
SoftReference JavaDoc ref = (SoftReference JavaDoc)getNativesForFlavorCache.get(flav);
621         if (ref != null) {
622             retval = (List JavaDoc)ref.get();
623             if (retval != null) {
624                 // Create a copy, because client code can modify the returned
625
// list.
626
return new ArrayList JavaDoc(retval);
627             }
628         }
629
630         if (flav == null) {
631             retval = new ArrayList JavaDoc(nativeToFlavor.keySet());
632         } else if (disabledMappingGenerationKeys.contains(flav)) {
633             // In this case we shouldn't synthesize a native for this flavor,
634
// since its mappings were explicitly specified.
635
retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
636         } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
637
638             // For text/* flavors, flavor-to-native mappings specified in
639
// flavormap.properties are stored per flavor's base type.
640
if ("text".equals(flav.getPrimaryType())) {
641                 retval = (List JavaDoc)flavorToNative.get(flav.mimeType.getBaseType());
642                 if (retval != null) {
643                     // To prevent the List stored in the map from modification.
644
retval = new ArrayList JavaDoc(retval);
645                 }
646             }
647
648             // Also include text/plain natives, but don't duplicate Strings
649
List JavaDoc textPlainList = (List JavaDoc)flavorToNative.get(TEXT_PLAIN_BASE_TYPE);
650
651             if (textPlainList != null && !textPlainList.isEmpty()) {
652                 // To prevent the List stored in the map from modification.
653
// This also guarantees that removeAll() is supported.
654
textPlainList = new ArrayList JavaDoc(textPlainList);
655                 if (retval != null && !retval.isEmpty()) {
656                     // Use HashSet to get constant-time performance for search.
657
textPlainList.removeAll(new HashSet JavaDoc(retval));
658                     retval.addAll(textPlainList);
659                 } else {
660                     retval = textPlainList;
661                 }
662             }
663
664             if (retval == null || retval.isEmpty()) {
665                 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
666             } else {
667                 // In this branch it is guaranteed that natives explicitly
668
// listed for flav's MIME type were added with
669
// addUnencodedNativeForFlavor(), so they have lower priority.
670
List JavaDoc explicitList =
671                     flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
672
673                 // flavorToNativeLookup() never returns null.
674
// It can return an empty List, however.
675
if (!explicitList.isEmpty()) {
676                     // To prevent the List stored in the map from modification.
677
// This also guarantees that removeAll() is supported.
678
explicitList = new ArrayList JavaDoc(explicitList);
679                     // Use HashSet to get constant-time performance for search.
680
explicitList.removeAll(new HashSet JavaDoc(retval));
681                     retval.addAll(explicitList);
682                 }
683             }
684         } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
685             retval = (List JavaDoc)flavorToNative.get(flav.mimeType.getBaseType());
686
687             if (retval == null || retval.isEmpty()) {
688                 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
689             } else {
690                 // In this branch it is guaranteed that natives explicitly
691
// listed for flav's MIME type were added with
692
// addUnencodedNativeForFlavor(), so they have lower priority.
693
List JavaDoc explicitList =
694                     flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
695
696                 // flavorToNativeLookup() never returns null.
697
// It can return an empty List, however.
698
if (!explicitList.isEmpty()) {
699                     // To prevent the List stored in the map from modification.
700
// This also guarantees that add/removeAll() are supported.
701
retval = new ArrayList JavaDoc(retval);
702                     explicitList = new ArrayList JavaDoc(explicitList);
703                     // Use HashSet to get constant-time performance for search.
704
explicitList.removeAll(new HashSet JavaDoc(retval));
705                     retval.addAll(explicitList);
706                 }
707             }
708         } else {
709             retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
710         }
711
712         getNativesForFlavorCache.put(flav, new SoftReference JavaDoc(retval));
713         // Create a copy, because client code can modify the returned list.
714
return new ArrayList JavaDoc(retval);
715     }
716
717     /**
718      * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
719      * specified <code>String</code> native can be translated by the data
720      * transfer subsystem. The <code>List</code> will be sorted from best
721      * <code>DataFlavor</code> to worst. That is, the first
722      * <code>DataFlavor</code> will best reflect data in the specified
723      * native to a Java application.
724      * <p>
725      * If the specified native is previously unknown to the data transfer
726      * subsystem, and that native has been properly encoded, then invoking this
727      * method will establish a mapping in both directions between the specified
728      * native and a <code>DataFlavor</code> whose MIME type is a decoded
729      * version of the native.
730      * <p>
731      * If the specified native is not a properly encoded native and the
732      * mappings for this native have not been altered with
733      * <code>setFlavorsForNative</code>, then the contents of the
734      * <code>List</code> is platform dependent, but <code>null</code>
735      * cannot be returned.
736      *
737      * @param nat the native whose corresponding <code>DataFlavor</code>s
738      * should be returned. If <code>null</code> is specified, all
739      * <code>DataFlavor</code>s currently known to the data transfer
740      * subsystem are returned in a non-deterministic order.
741      * @return a <code>java.util.List</code> of <code>DataFlavor</code>
742      * objects into which platform-specific data in the specified,
743      * platform-specific native can be translated
744      *
745      * @see #encodeJavaMIMEType
746      * @since 1.4
747      */

748     public synchronized List JavaDoc<DataFlavor JavaDoc> getFlavorsForNative(String JavaDoc nat) {
749
750         // Check cache, even for null nat
751
SoftReference JavaDoc ref = (SoftReference JavaDoc)getFlavorsForNativeCache.get(nat);
752         if (ref != null) {
753             ArrayList JavaDoc retval = (ArrayList JavaDoc)ref.get();
754             if (retval != null) {
755                 return (List JavaDoc)retval.clone();
756             }
757         }
758
759         LinkedList JavaDoc retval = new LinkedList JavaDoc();
760
761         if (nat == null) {
762             List JavaDoc natives = getNativesForFlavor(null);
763             HashSet JavaDoc dups = new HashSet JavaDoc(natives.size());
764
765             for (Iterator JavaDoc natives_iter = natives.iterator();
766                  natives_iter.hasNext(); )
767             {
768                 List JavaDoc flavors =
769                     getFlavorsForNative((String JavaDoc)natives_iter.next());
770                 for (Iterator JavaDoc flavors_iter = flavors.iterator();
771                      flavors_iter.hasNext(); )
772                 {
773                     Object JavaDoc flavor = flavors_iter.next();
774                     if (dups.add(flavor)) {
775                         retval.add(flavor);
776                     }
777                 }
778             }
779         } else {
780             List JavaDoc flavors = nativeToFlavorLookup(nat);
781
782             if (disabledMappingGenerationKeys.contains(nat)) {
783                 return flavors;
784             }
785
786             HashSet JavaDoc dups = new HashSet JavaDoc(flavors.size());
787
788             List JavaDoc flavorsAndbaseTypes = nativeToFlavorLookup(nat);
789
790             for (Iterator JavaDoc flavorsAndbaseTypes_iter =
791                      flavorsAndbaseTypes.iterator();
792                  flavorsAndbaseTypes_iter.hasNext(); )
793             {
794                 Object JavaDoc value = flavorsAndbaseTypes_iter.next();
795                 if (value instanceof String JavaDoc) {
796                     String JavaDoc baseType = (String JavaDoc)value;
797                     String JavaDoc subType = null;
798                     try {
799                         MimeType JavaDoc mimeType = new MimeType JavaDoc(baseType);
800                         subType = mimeType.getSubType();
801                     } catch (MimeTypeParseException JavaDoc mtpe) {
802                         // Cannot happen, since we checked all mappings
803
// on load from flavormap.properties.
804
assert(false);
805                     }
806                     if (DataTransferer.doesSubtypeSupportCharset(subType,
807                                                                  null)) {
808                         if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
809                             dups.add(DataFlavor.stringFlavor))
810                         {
811                             retval.add(DataFlavor.stringFlavor);
812                         }
813
814                         for (int i = 0; i < UNICODE_TEXT_CLASSES.length; i++) {
815                             DataFlavor JavaDoc toAdd = null;
816                             try {
817                                 toAdd = new DataFlavor JavaDoc
818                                     (baseType + ";charset=Unicode;class=" +
819                                      UNICODE_TEXT_CLASSES[i]);
820                             } catch (ClassNotFoundException JavaDoc cannotHappen) {
821                             }
822                             if (dups.add(toAdd)) {
823                                 retval.add(toAdd);
824                             }
825                         }
826
827                         for (Iterator JavaDoc charset_iter =
828                                  DataTransferer.standardEncodings();
829                              charset_iter.hasNext(); )
830                         {
831                             String JavaDoc charset = (String JavaDoc)charset_iter.next();
832
833                             for (int i = 0; i < ENCODED_TEXT_CLASSES.length;
834                                  i++)
835                             {
836                                 DataFlavor JavaDoc toAdd = null;
837                                 try {
838                                     toAdd = new DataFlavor JavaDoc
839                                         (baseType + ";charset=" + charset +
840                                          ";class=" + ENCODED_TEXT_CLASSES[i]);
841                                 } catch (ClassNotFoundException JavaDoc cannotHappen) {
842                                 }
843
844                                 // Check for equality to plainTextFlavor so
845
// that we can ensure that the exact charset of
846
// plainTextFlavor, not the canonical charset
847
// or another equivalent charset with a
848
// different name, is used.
849
if (toAdd.equals(DataFlavor.plainTextFlavor)) {
850                                     toAdd = DataFlavor.plainTextFlavor;
851                                 }
852
853                                 if (dups.add(toAdd)) {
854                                     retval.add(toAdd);
855                                 }
856                             }
857                         }
858
859                         if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
860                             dups.add(DataFlavor.plainTextFlavor))
861                         {
862                             retval.add(DataFlavor.plainTextFlavor);
863                         }
864                     } else {
865                         // Non-charset text natives should be treated as
866
// opaque, 8-bit data in any of its various
867
// representations.
868
for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) {
869                             DataFlavor JavaDoc toAdd = null;
870                             try {
871                                 toAdd = new DataFlavor JavaDoc(baseType +
872                                      ";class=" + ENCODED_TEXT_CLASSES[i]);
873                             } catch (ClassNotFoundException JavaDoc cannotHappen) {
874                             }
875
876                             if (dups.add(toAdd)) {
877                                 retval.add(toAdd);
878                             }
879                         }
880                     }
881                 } else {
882                     DataFlavor JavaDoc flavor = (DataFlavor JavaDoc)value;
883                     if (dups.add(flavor)) {
884                         retval.add(flavor);
885                     }
886                 }
887             }
888         }
889
890         ArrayList JavaDoc arrayList = new ArrayList JavaDoc(retval);
891         getFlavorsForNativeCache.put(nat, new SoftReference JavaDoc(arrayList));
892         return (List JavaDoc)arrayList.clone();
893     }
894
895     /**
896      * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
897      * their most preferred <code>String</code> native. Each native value will
898      * be the same as the first native in the List returned by
899      * <code>getNativesForFlavor</code> for the specified flavor.
900      * <p>
901      * If a specified <code>DataFlavor</code> is previously unknown to the
902      * data transfer subsystem, then invoking this method will establish a
903      * mapping in both directions between the specified <code>DataFlavor</code>
904      * and an encoded version of its MIME type as its native.
905      *
906      * @param flavors an array of <code>DataFlavor</code>s which will be the
907      * key set of the returned <code>Map</code>. If <code>null</code> is
908      * specified, a mapping of all <code>DataFlavor</code>s known to the
909      * data transfer subsystem to their most preferred
910      * <code>String</code> natives will be returned.
911      * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
912      * <code>String</code> natives
913      *
914      * @see #getNativesForFlavor
915      * @see #encodeDataFlavor
916      */

917     public synchronized Map JavaDoc<DataFlavor JavaDoc,String JavaDoc>
918     getNativesForFlavors(DataFlavor JavaDoc[] flavors)
919     {
920         // Use getNativesForFlavor to generate extra natives for text flavors
921
// and stringFlavor
922

923         if (flavors == null) {
924             List JavaDoc flavor_list = getFlavorsForNative(null);
925             flavors = new DataFlavor JavaDoc[flavor_list.size()];
926             flavor_list.toArray(flavors);
927         }
928
929         HashMap JavaDoc retval = new HashMap JavaDoc(flavors.length, 1.0f);
930         for (int i = 0; i < flavors.length; i++) {
931             List JavaDoc natives = getNativesForFlavor(flavors[i]);
932             String JavaDoc nat = (natives.isEmpty()) ? null : (String JavaDoc)natives.get(0);
933             retval.put(flavors[i], nat);
934         }
935
936         return retval;
937     }
938
939     /**
940      * Returns a <code>Map</code> of the specified <code>String</code> natives
941      * to their most preferred <code>DataFlavor</code>. Each
942      * <code>DataFlavor</code> value will be the same as the first
943      * <code>DataFlavor</code> in the List returned by
944      * <code>getFlavorsForNative</code> for the specified native.
945      * <p>
946      * If a specified native is previously unknown to the data transfer
947      * subsystem, and that native has been properly encoded, then invoking this
948      * method will establish a mapping in both directions between the specified
949      * native and a <code>DataFlavor</code> whose MIME type is a decoded
950      * version of the native.
951      *
952      * @param natives an array of <code>String</code>s which will be the
953      * key set of the returned <code>Map</code>. If <code>null</code> is
954      * specified, a mapping of all supported <code>String</code> natives
955      * to their most preferred <code>DataFlavor</code>s will be
956      * returned.
957      * @return a <code>java.util.Map</code> of <code>String</code> natives to
958      * <code>DataFlavor</code>s
959      *
960      * @see #getFlavorsForNative
961      * @see #encodeJavaMIMEType
962      */

963     public synchronized Map JavaDoc<String JavaDoc,DataFlavor JavaDoc>
964     getFlavorsForNatives(String JavaDoc[] natives)
965     {
966         // Use getFlavorsForNative to generate extra flavors for text natives
967

968         if (natives == null) {
969             List JavaDoc native_list = getNativesForFlavor(null);
970             natives = new String JavaDoc[native_list.size()];
971             native_list.toArray(natives);
972         }
973
974         HashMap JavaDoc retval = new HashMap JavaDoc(natives.length, 1.0f);
975         for (int i = 0; i < natives.length; i++) {
976             List JavaDoc flavors = getFlavorsForNative(natives[i]);
977             DataFlavor JavaDoc flav = (flavors.isEmpty())
978                 ? null : (DataFlavor JavaDoc)flavors.get(0);
979             retval.put(natives[i], flav);
980         }
981
982         return retval;
983     }
984
985     /**
986      * Adds a mapping from the specified <code>DataFlavor</code> (and all
987      * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
988      * to the specified <code>String</code> native.
989      * Unlike <code>getNativesForFlavor</code>, the mapping will only be
990      * established in one direction, and the native will not be encoded. To
991      * establish a two-way mapping, call
992      * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
993      * be of lower priority than any existing mapping.
994      * This method has no effect if a mapping from the specified or equal
995      * <code>DataFlavor</code> to the specified <code>String</code> native
996      * already exists.
997      *
998      * @param flav the <code>DataFlavor</code> key for the mapping
999      * @param nat the <code>String</code> native value for the mapping
1000     * @throws NullPointerException if flav or nat is <code>null</code>
1001     *
1002     * @see #addFlavorForUnencodedNative
1003     * @since 1.4
1004     */

1005    public synchronized void addUnencodedNativeForFlavor(DataFlavor JavaDoc flav,
1006                                                         String JavaDoc nat) {
1007        if (flav == null || nat == null) {
1008            throw new NullPointerException JavaDoc("null arguments not permitted");
1009        }
1010        
1011        List JavaDoc natives = (List JavaDoc)flavorToNative.get(flav);
1012        if (natives == null) {
1013            natives = new ArrayList JavaDoc(1);
1014            flavorToNative.put(flav, natives);
1015        } else if (natives.contains(nat)) {
1016            return;
1017        }
1018        natives.add(nat);
1019        getNativesForFlavorCache.remove(flav);
1020        getNativesForFlavorCache.remove(null);
1021    }
1022
1023    /**
1024     * Discards the current mappings for the specified <code>DataFlavor</code>
1025     * and all <code>DataFlavor</code>s equal to the specified
1026     * <code>DataFlavor</code>, and creates new mappings to the
1027     * specified <code>String</code> natives.
1028     * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1029     * established in one direction, and the natives will not be encoded. To
1030     * establish two-way mappings, call <code>setFlavorsForNative</code>
1031     * as well. The first native in the array will represent the highest
1032     * priority mapping. Subsequent natives will represent mappings of
1033     * decreasing priority.
1034     * <p>
1035     * If the array contains several elements that reference equal
1036     * <code>String</code> natives, this method will establish new mappings
1037     * for the first of those elements and ignore the rest of them.
1038     * <p>
1039     * It is recommended that client code not reset mappings established by the
1040     * data transfer subsystem. This method should only be used for
1041     * application-level mappings.
1042     *
1043     * @param flav the <code>DataFlavor</code> key for the mappings
1044     * @param natives the <code>String</code> native values for the mappings
1045     * @throws NullPointerException if flav or natives is <code>null</code>
1046     * or if natives contains <code>null</code> elements
1047     *
1048     * @see #setFlavorsForNative
1049     * @since 1.4
1050     */

1051    public synchronized void setNativesForFlavor(DataFlavor JavaDoc flav,
1052                                                 String JavaDoc[] natives) {
1053        if (flav == null || natives == null) {
1054            throw new NullPointerException JavaDoc("null arguments not permitted");
1055        }
1056
1057        flavorToNative.remove(flav);
1058        for (int i = 0; i < natives.length; i++) {
1059            addUnencodedNativeForFlavor(flav, natives[i]);
1060        }
1061        disabledMappingGenerationKeys.add(flav);
1062        // Clear the cache to handle the case of empty natives.
1063
getNativesForFlavorCache.remove(flav);
1064        getNativesForFlavorCache.remove(null);
1065    }
1066
1067    /**
1068     * Adds a mapping from a single <code>String</code> native to a single
1069     * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1070     * mapping will only be established in one direction, and the native will
1071     * not be encoded. To establish a two-way mapping, call
1072     * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1073     * be of lower priority than any existing mapping.
1074     * This method has no effect if a mapping from the specified
1075     * <code>String</code> native to the specified or equal
1076     * <code>DataFlavor</code> already exists.
1077     *
1078     * @param nat the <code>String</code> native key for the mapping
1079     * @param flav the <code>DataFlavor</code> value for the mapping
1080     * @throws NullPointerException if nat or flav is <code>null</code>
1081     *
1082     * @see #addUnencodedNativeForFlavor
1083     * @since 1.4
1084     */

1085    public synchronized void addFlavorForUnencodedNative(String JavaDoc nat,
1086                                                         DataFlavor JavaDoc flav) {
1087        if (nat == null || flav == null) {
1088            throw new NullPointerException JavaDoc("null arguments not permitted");
1089        }
1090
1091        List JavaDoc flavors = (List JavaDoc)nativeToFlavor.get(nat);
1092        if (flavors == null) {
1093            flavors = new ArrayList JavaDoc(1);
1094            nativeToFlavor.put(nat, flavors);
1095        } else if (flavors.contains(flav)) {
1096            return;
1097        }
1098        flavors.add(flav);
1099        getFlavorsForNativeCache.remove(nat);
1100        getFlavorsForNativeCache.remove(null);
1101    }
1102
1103    /**
1104     * Discards the current mappings for the specified <code>String</code>
1105     * native, and creates new mappings to the specified
1106     * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1107     * mappings will only be established in one direction, and the natives need
1108     * not be encoded. To establish two-way mappings, call
1109     * <code>setNativesForFlavor</code> as well. The first
1110     * <code>DataFlavor</code> in the array will represent the highest priority
1111     * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1112     * decreasing priority.
1113     * <p>
1114     * If the array contains several elements that reference equal
1115     * <code>DataFlavor</code>s, this method will establish new mappings
1116     * for the first of those elements and ignore the rest of them.
1117     * <p>
1118     * It is recommended that client code not reset mappings established by the
1119     * data transfer subsystem. This method should only be used for
1120     * application-level mappings.
1121     *
1122     * @param nat the <code>String</code> native key for the mappings
1123     * @param flavors the <code>DataFlavor</code> values for the mappings
1124     * @throws NullPointerException if nat or flavors is <code>null</code>
1125     * or if flavors contains <code>null</code> elements
1126     *
1127     * @see #setNativesForFlavor
1128     * @since 1.4
1129     */

1130    public synchronized void setFlavorsForNative(String JavaDoc nat,
1131                                                 DataFlavor JavaDoc[] flavors) {
1132        if (nat == null || flavors == null) {
1133            throw new NullPointerException JavaDoc("null arguments not permitted");
1134        }
1135
1136        nativeToFlavor.remove(nat);
1137        for (int i = 0; i < flavors.length; i++) {
1138            addFlavorForUnencodedNative(nat, flavors[i]);
1139        }
1140        disabledMappingGenerationKeys.add(nat);
1141        // Clear the cache to handle the case of empty flavors.
1142
getFlavorsForNativeCache.remove(nat);
1143        getFlavorsForNativeCache.remove(null);
1144    }
1145
1146    /**
1147     * Encodes a MIME type for use as a <code>String</code> native. The format
1148     * of an encoded representation of a MIME type is implementation-dependent.
1149     * The only restrictions are:
1150     * <ul>
1151     * <li>The encoded representation is <code>null</code> if and only if the
1152     * MIME type <code>String</code> is <code>null</code>.</li>
1153     * <li>The encoded representations for two non-<code>null</code> MIME type
1154     * <code>String</code>s are equal if and only if these <code>String</code>s
1155     * are equal according to <code>String.equals(Object)</code>.</li>
1156     * </ul>
1157     * <p>
1158     * Sun's reference implementation of this method returns the specified MIME
1159     * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
1160     *
1161     * @param mimeType the MIME type to encode
1162     * @return the encoded <code>String</code>, or <code>null</code> if
1163     * mimeType is <code>null</code>
1164     */

1165    public static String JavaDoc encodeJavaMIMEType(String JavaDoc mimeType) {
1166        return (mimeType != null)
1167            ? JavaMIME + mimeType
1168            : null;
1169    }
1170
1171    /**
1172     * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
1173     * native. The format of an encoded <code>DataFlavor</code> is
1174     * implementation-dependent. The only restrictions are:
1175     * <ul>
1176     * <li>The encoded representation is <code>null</code> if and only if the
1177     * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
1178     * <code>String</code> is <code>null</code>.</li>
1179     * <li>The encoded representations for two non-<code>null</code>
1180     * <code>DataFlavor</code>s with non-<code>null</code> MIME type
1181     * <code>String</code>s are equal if and only if the MIME type
1182     * <code>String</code>s of these <code>DataFlavor</code>s are equal
1183     * according to <code>String.equals(Object)</code>.</li>
1184     * </ul>
1185     * <p>
1186     * Sun's reference implementation of this method returns the MIME type
1187     * <code>String</code> of the specified <code>DataFlavor</code> prefixed
1188     * with <code>JAVA_DATAFLAVOR:</code>.
1189     *
1190     * @param flav the <code>DataFlavor</code> to encode
1191     * @return the encoded <code>String</code>, or <code>null</code> if
1192     * flav is <code>null</code> or has a <code>null</code> MIME type
1193     */

1194    public static String JavaDoc encodeDataFlavor(DataFlavor JavaDoc flav) {
1195        return (flav != null)
1196            ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
1197            : null;
1198    }
1199
1200    /**
1201     * Returns whether the specified <code>String</code> is an encoded Java
1202     * MIME type.
1203     *
1204     * @param str the <code>String</code> to test
1205     * @return <code>true</code> if the <code>String</code> is encoded;
1206     * <code>false</code> otherwise
1207     */

1208    public static boolean isJavaMIMEType(String JavaDoc str) {
1209        return (str != null && str.startsWith(JavaMIME, 0));
1210    }
1211
1212    /**
1213     * Decodes a <code>String</code> native for use as a Java MIME type.
1214     *
1215     * @param nat the <code>String</code> to decode
1216     * @return the decoded Java MIME type, or <code>null</code> if nat is not
1217     * an encoded <code>String</code> native
1218     */

1219    public static String JavaDoc decodeJavaMIMEType(String JavaDoc nat) {
1220        return (isJavaMIMEType(nat))
1221            ? nat.substring(JavaMIME.length(), nat.length()).trim()
1222            : null;
1223    }
1224
1225    /**
1226     * Decodes a <code>String</code> native for use as a
1227     * <code>DataFlavor</code>.
1228     *
1229     * @param nat the <code>String</code> to decode
1230     * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1231     * nat is not an encoded <code>String</code> native
1232     */

1233    public static DataFlavor JavaDoc decodeDataFlavor(String JavaDoc nat)
1234        throws ClassNotFoundException JavaDoc
1235    {
1236        String JavaDoc retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1237        return (retval_str != null)
1238            ? new DataFlavor JavaDoc(retval_str)
1239            : null;
1240    }
1241}
1242
Popular Tags