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