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