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