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.LinkedHashSet;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47 import java.util.WeakHashMap;
48
49 import sun.awt.AppContext;
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 *
59 * @since 1.2
60 */
61 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
62
63 /**
64 * Constant prefix used to tag Java types converted to native platform
65 * type.
66 */
67 private static String JavaMIME = "JAVA_DATAFLAVOR:";
85
86 /**
87 * The list of valid, encoded text flavor representation classes, in order
88 * from best to worst.
89 */
90 private static final String[] ENCODED_TEXT_CLASSES = {
91 "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
92 };
93
94 /**
95 * A String representing text/plain MIME type.
96 */
97 private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
98
99 /**
100 * A String representing text/html MIME type.
101 */
102 private static final String HTML_TEXT_BASE_TYPE = "text/html";
103
104 /**
105 * This constant is passed to flavorToNativeLookup() to indicate that a
106 * a native should be synthesized, stored, and returned by encoding the
107 * DataFlavor's MIME type in case if the DataFlavor is not found in
108 * 'flavorToNative' map.
109 */
110 private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
111
112 /**
113 * Maps native Strings to Lists of DataFlavors (or base type Strings for
114 * text DataFlavors).
115 * Do not use the field directly, use getNativeToFlavor() instead.
116 */
117 private final Map<String, List<DataFlavor>> nativeToFlavor = new HashMap<>();
118
119 /**
120 * Accessor to nativeToFlavor map. Since we use lazy initialization we must
121 * use this accessor instead of direct access to the field which may not be
122 * initialized yet. This method will initialize the field if needed.
123 *
124 * @return nativeToFlavor
125 */
126 private Map<String, List<DataFlavor>> getNativeToFlavor() {
127 if (!isMapInitialized) {
128 initSystemFlavorMap();
129 }
130 return nativeToFlavor;
131 }
132
133 /**
134 * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
135 * native Strings.
136 * Do not use the field directly, use getFlavorToNative() instead.
137 */
138 private final Map<DataFlavor, List<String>> flavorToNative = new HashMap<>();
139
140 /**
141 * Accessor to flavorToNative map. Since we use lazy initialization we must
142 * use this accessor instead of direct access to the field which may not be
143 * initialized yet. This method will initialize the field if needed.
144 *
145 * @return flavorToNative
146 */
147 private synchronized Map<DataFlavor, List<String>> getFlavorToNative() {
148 if (!isMapInitialized) {
149 initSystemFlavorMap();
150 }
151 return flavorToNative;
152 }
153
154 /**
155 * Shows if the object has been initialized.
156 */
157 private boolean isMapInitialized = false;
158
159 /**
160 * Caches the result of getNativesForFlavor(). Maps DataFlavors to
161 * SoftReferences which reference Lists of String natives.
162 */
163 private Map<DataFlavor, SoftReference<List<String>>> getNativesForFlavorCache = new HashMap<>();
164
165 /**
166 * Caches the result getFlavorsForNative(). Maps String natives to
167 * SoftReferences which reference Lists of DataFlavors.
168 */
169 private Map<String, SoftReference<List<DataFlavor>>> getFlavorsForNativeCache = new HashMap<>();
170
171 /**
172 * Dynamic mapping generation used for text mappings should not be applied
173 * to the DataFlavors and String natives for which the mappings have been
174 * explicitly specified with setFlavorsForNative() or
175 * setNativesForFlavor(). This keeps all such keys.
176 */
177 private Set disabledMappingGenerationKeys = new HashSet();
178
179 /**
180 * Returns the default FlavorMap for this thread's ClassLoader.
181 * @return the default FlavorMap for this thread's ClassLoader
182 */
183 public static FlavorMap getDefaultFlavorMap() {
184 AppContext context = AppContext.getAppContext();
185 FlavorMap fm = (FlavorMap) context.get(FLAVOR_MAP_KEY);
186 if (fm == null) {
187 fm = new SystemFlavorMap();
188 context.put(FLAVOR_MAP_KEY, fm);
189 }
190 return fm;
191 }
192
193 private SystemFlavorMap() {
194 }
195
196 /**
197 * Initializes a SystemFlavorMap by reading flavormap.properties and
387 // But don't store any of these parameters in the
388 // DataFlavor itself for any text natives (even
389 // non-charset ones). The SystemFlavorMap will
390 // synthesize the appropriate mappings later.
391 mime.removeParameter("charset");
392 mime.removeParameter("class");
393 mime.removeParameter("eoln");
394 mime.removeParameter("terminators");
395 value = mime.toString();
396 }
397 } catch (MimeTypeParseException e) {
398 e.printStackTrace();
399 continue;
400 }
401
402 DataFlavor flavor;
403 try {
404 flavor = new DataFlavor(value);
405 } catch (Exception e) {
406 try {
407 flavor = new DataFlavor(value, (String)null);
408 } catch (Exception ee) {
409 ee.printStackTrace();
410 continue;
411 }
412 }
413
414 final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();
415
416 dfs.add(flavor);
417
418 if ("text".equals(flavor.getPrimaryType())) {
419 dfs.addAll(convertMimeTypeToDataFlavors(value));
420 }
421
422 for (DataFlavor df : dfs) {
423 store(df, key, getFlavorToNative());
424 store(key, df, getNativeToFlavor());
425 }
426 }
427 }
428 }
429 }
430
431 /**
432 * Copied from java.util.Properties.
433 */
434 private boolean continueLine (String line) {
435 int slashCount = 0;
436 int index = line.length() - 1;
437 while((index >= 0) && (line.charAt(index--) == '\\')) {
438 slashCount++;
439 }
488 } else if (aChar == 'n') {
489 aChar = '\n';
490 } else if (aChar == 'f') {
491 aChar = '\f';
492 }
493 outBuffer.append(aChar);
494 }
495 } else {
496 outBuffer.append(aChar);
497 }
498 }
499 return outBuffer.toString();
500 }
501
502 /**
503 * Stores the listed object under the specified hash key in map. Unlike a
504 * standard map, the listed object will not replace any object already at
505 * the appropriate Map location, but rather will be appended to a List
506 * stored in that location.
507 */
508 private <H, L> void store(H hashed, L listed, Map<H, List<L>> map) {
509 List<L> list = map.get(hashed);
510 if (list == null) {
511 list = new ArrayList<>(1);
512 map.put(hashed, list);
513 }
514 if (!list.contains(listed)) {
515 list.add(listed);
516 }
517 }
518
519 /**
520 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
521 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
522 * case, a new DataFlavor is synthesized, stored, and returned, if and
523 * only if the specified native is encoded as a Java MIME type.
524 */
525 private List<DataFlavor> nativeToFlavorLookup(String nat) {
526 List<DataFlavor> flavors = getNativeToFlavor().get(nat);
527
528 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
529 DataTransferer transferer = DataTransferer.getInstance();
530 if (transferer != null) {
531 List<DataFlavor> platformFlavors =
532 transferer.getPlatformMappingsForNative(nat);
533 if (!platformFlavors.isEmpty()) {
534 if (flavors != null) {
535 platformFlavors.removeAll(new HashSet<>(flavors));
536 // Prepending the platform-specific mappings ensures
537 // that the flavors added with
538 // addFlavorForUnencodedNative() are at the end of
539 // list.
540 platformFlavors.addAll(flavors);
541 }
542 flavors = platformFlavors;
543 }
544 }
545 }
546
547 if (flavors == null && isJavaMIMEType(nat)) {
548 String decoded = decodeJavaMIMEType(nat);
549 DataFlavor flavor = null;
550
551 try {
552 flavor = new DataFlavor(decoded);
553 } catch (Exception e) {
554 System.err.println("Exception \"" + e.getClass().getName() +
555 ": " + e.getMessage() +
556 "\"while constructing DataFlavor for: " +
557 decoded);
558 }
559
560 if (flavor != null) {
561 flavors = new ArrayList<>(1);
562 getNativeToFlavor().put(nat, flavors);
563 flavors.add(flavor);
564 getFlavorsForNativeCache.remove(nat);
565 getFlavorsForNativeCache.remove(null);
566
567 List<String> natives = getFlavorToNative().get(flavor);
568 if (natives == null) {
569 natives = new ArrayList<>(1);
570 getFlavorToNative().put(flavor, natives);
571 }
572 natives.add(nat);
573 getNativesForFlavorCache.remove(flavor);
574 getNativesForFlavorCache.remove(null);
575 }
576 }
577
578 return (flavors != null) ? flavors : new ArrayList<>(0);
579 }
580
581 /**
582 * Semantically equivalent to 'flavorToNative.get(flav)'. This method
583 * handles the case where 'flav' is not found in 'flavorToNative' depending
584 * on the value of passes 'synthesize' parameter. If 'synthesize' is
585 * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
586 * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
587 * and 'flavorToNative' remains unaffected.
588 */
589 private List<String> flavorToNativeLookup(final DataFlavor flav,
590 final boolean synthesize) {
591 List<String> natives = getFlavorToNative().get(flav);
592
593 if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
594 DataTransferer transferer = DataTransferer.getInstance();
595 if (transferer != null) {
596 List<String> platformNatives =
597 transferer.getPlatformMappingsForFlavor(flav);
598 if (!platformNatives.isEmpty()) {
599 if (natives != null) {
600 platformNatives.removeAll(new HashSet<>(natives));
601 // Prepend the platform-specific mappings to ensure
602 // that the natives added with
603 // addUnencodedNativeForFlavor() are at the end of
604 // list.
605 platformNatives.addAll(natives);
606 }
607 natives = platformNatives;
608 }
609 }
610 }
611
612 if (natives == null) {
613 if (synthesize) {
614 String encoded = encodeDataFlavor(flav);
615 natives = new ArrayList<>(1);
616 getFlavorToNative().put(flav, natives);
617 natives.add(encoded);
618 getNativesForFlavorCache.remove(flav);
619 getNativesForFlavorCache.remove(null);
620
621 List<DataFlavor> flavors = getNativeToFlavor().get(encoded);
622 if (flavors == null) {
623 flavors = new ArrayList<>(1);
624 getNativeToFlavor().put(encoded, flavors);
625 }
626 flavors.add(flav);
627 getFlavorsForNativeCache.remove(encoded);
628 getFlavorsForNativeCache.remove(null);
629 } else {
630 natives = new ArrayList<>(0);
631 }
632 }
633
634 return natives;
635 }
636
637 /**
638 * Returns a <code>List</code> of <code>String</code> natives to which the
639 * specified <code>DataFlavor</code> can be translated by the data transfer
640 * subsystem. The <code>List</code> will be sorted from best native to
641 * worst. That is, the first native will best reflect data in the specified
642 * flavor to the underlying native platform.
643 * <p>
644 * If the specified <code>DataFlavor</code> is previously unknown to the
645 * data transfer subsystem and the data transfer subsystem is unable to
646 * translate this <code>DataFlavor</code> to any existing native, then
647 * invoking this method will establish a
648 * mapping in both directions between the specified <code>DataFlavor</code>
649 * and an encoded version of its MIME type as its native.
650 *
651 * @param flav the <code>DataFlavor</code> whose corresponding natives
652 * should be returned. If <code>null</code> is specified, all
653 * natives currently known to the data transfer subsystem are
654 * returned in a non-deterministic order.
655 * @return a <code>java.util.List</code> of <code>java.lang.String</code>
656 * objects which are platform-specific representations of platform-
657 * specific data formats
658 *
659 * @see #encodeDataFlavor
660 * @since 1.4
661 */
662 public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
663 List<String> retval = null;
664
665 // Check cache, even for null flav
666 SoftReference<List<String>> ref = getNativesForFlavorCache.get(flav);
667 if (ref != null) {
668 retval = ref.get();
669 if (retval != null) {
670 // Create a copy, because client code can modify the returned
671 // list.
672 return new ArrayList<>(retval);
673 }
674 }
675
676 if (flav == null) {
677 retval = new ArrayList<>(getNativeToFlavor().keySet());
678 } else if (disabledMappingGenerationKeys.contains(flav)) {
679 // In this case we shouldn't synthesize a native for this flavor,
680 // since its mappings were explicitly specified.
681 retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
682 } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
683
684 // For text/* flavors, flavor-to-native mappings specified in
685 // flavormap.properties are stored per flavor's base type.
686 if ("text".equals(flav.getPrimaryType())) {
687 retval = getAllNativesForType(flav.mimeType.getBaseType());
688 if (retval != null) {
689 // To prevent the List stored in the map from modification.
690 retval = new ArrayList(retval);
691 }
692 }
693
694 // Also include text/plain natives, but don't duplicate Strings
695 List<String> textPlainList = getAllNativesForType(TEXT_PLAIN_BASE_TYPE);
696
697 if (textPlainList != null && !textPlainList.isEmpty()) {
698 // To prevent the List stored in the map from modification.
699 // This also guarantees that removeAll() is supported.
700 textPlainList = new ArrayList<>(textPlainList);
701 if (retval != null && !retval.isEmpty()) {
702 // Use HashSet to get constant-time performance for search.
703 textPlainList.removeAll(new HashSet<>(retval));
704 retval.addAll(textPlainList);
705 } else {
706 retval = textPlainList;
707 }
708 }
709
710 if (retval == null || retval.isEmpty()) {
711 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
712 } else {
713 // In this branch it is guaranteed that natives explicitly
714 // listed for flav's MIME type were added with
715 // addUnencodedNativeForFlavor(), so they have lower priority.
716 List<String> explicitList =
717 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
718
719 // flavorToNativeLookup() never returns null.
720 // It can return an empty List, however.
721 if (!explicitList.isEmpty()) {
722 // To prevent the List stored in the map from modification.
723 // This also guarantees that removeAll() is supported.
724 explicitList = new ArrayList<>(explicitList);
725 // Use HashSet to get constant-time performance for search.
726 explicitList.removeAll(new HashSet<>(retval));
727 retval.addAll(explicitList);
728 }
729 }
730 } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
731 retval = getAllNativesForType(flav.mimeType.getBaseType());
732
733 if (retval == null || retval.isEmpty()) {
734 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
735 } else {
736 // In this branch it is guaranteed that natives explicitly
737 // listed for flav's MIME type were added with
738 // addUnencodedNativeForFlavor(), so they have lower priority.
739 List<String> explicitList =
740 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
741
742 // flavorToNativeLookup() never returns null.
743 // It can return an empty List, however.
744 if (!explicitList.isEmpty()) {
745 // To prevent the List stored in the map from modification.
746 // This also guarantees that add/removeAll() are supported.
747 retval = new ArrayList<>(retval);
748 explicitList = new ArrayList<>(explicitList);
749 // Use HashSet to get constant-time performance for search.
750 explicitList.removeAll(new HashSet<>(retval));
751 retval.addAll(explicitList);
752 }
753 }
754 } else {
755 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
756 }
757
758 getNativesForFlavorCache.put(flav, new SoftReference<>(retval));
759 // Create a copy, because client code can modify the returned list.
760 return new ArrayList<>(retval);
761 }
762
763 /**
764 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
765 * specified <code>String</code> native can be translated by the data
766 * transfer subsystem. The <code>List</code> will be sorted from best
767 * <code>DataFlavor</code> to worst. That is, the first
768 * <code>DataFlavor</code> will best reflect data in the specified
769 * native to a Java application.
770 * <p>
771 * If the specified native is previously unknown to the data transfer
772 * subsystem, and that native has been properly encoded, then invoking this
773 * method will establish a mapping in both directions between the specified
774 * native and a <code>DataFlavor</code> whose MIME type is a decoded
775 * version of the native.
776 * <p>
777 * If the specified native is not a properly encoded native and the
778 * mappings for this native have not been altered with
779 * <code>setFlavorsForNative</code>, then the contents of the
780 * <code>List</code> is platform dependent, but <code>null</code>
781 * cannot be returned.
782 *
783 * @param nat the native whose corresponding <code>DataFlavor</code>s
784 * should be returned. If <code>null</code> is specified, all
785 * <code>DataFlavor</code>s currently known to the data transfer
786 * subsystem are returned in a non-deterministic order.
787 * @return a <code>java.util.List</code> of <code>DataFlavor</code>
788 * objects into which platform-specific data in the specified,
789 * platform-specific native can be translated
790 *
791 * @see #encodeJavaMIMEType
792 * @since 1.4
793 */
794 public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
795
796 // Check cache, even for null nat
797 SoftReference<List<DataFlavor>> ref = getFlavorsForNativeCache.get(nat);
798 if (ref != null) {
799 List<DataFlavor> retval = ref.get();
800 if (retval != null) {
801 return new ArrayList<>(retval);
802 }
803 }
804
805 final LinkedHashSet <DataFlavor> returnValue =
806 new LinkedHashSet<>();
807
808 if (nat == null) {
809 final List<String> natives = getNativesForFlavor(null);
810
811 for (String n : natives)
812 {
813 final List<DataFlavor> flavors = getFlavorsForNative(n);
814
815 for (DataFlavor df : flavors)
816 {
817 returnValue.add(df);
818 }
819 }
820 } else {
821
822 final List<DataFlavor> flavors = nativeToFlavorLookup(nat);
823
824 if (disabledMappingGenerationKeys.contains(nat)) {
825 return flavors;
826 }
827
828 final List<DataFlavor> flavorsAndBaseTypes =
829 nativeToFlavorLookup(nat);
830
831 for (DataFlavor df : flavorsAndBaseTypes) {
832 returnValue.add(df);
833 if ("text".equals(df.getPrimaryType())) {
834 try {
835 returnValue.addAll(
836 convertMimeTypeToDataFlavors(
837 new MimeType(df.getMimeType()
838 ).getBaseType()));
839 } catch (MimeTypeParseException e) {
840 e.printStackTrace();
841 }
842 }
843 }
844
845 }
846
847 final List<DataFlavor> arrayList = new ArrayList<>(returnValue);
848 getFlavorsForNativeCache.put(nat, new SoftReference<>(arrayList));
849 return new ArrayList<>(arrayList);
850 }
851
852 private static Set<DataFlavor> convertMimeTypeToDataFlavors(
853 final String baseType) {
854
855 final Set<DataFlavor> returnValue = new LinkedHashSet<>();
856
857 String subType = null;
858
859 try {
860 final MimeType mimeType = new MimeType(baseType);
861 subType = mimeType.getSubType();
862 } catch (MimeTypeParseException mtpe) {
863 // Cannot happen, since we checked all mappings
864 // on load from flavormap.properties.
865 assert(false);
866 }
867
868 if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
869 if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
870 {
871 returnValue.add(DataFlavor.stringFlavor);
872 }
873
874 for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
875 final String mimeType = baseType + ";charset=Unicode;class=" +
876 unicodeClassName;
877
878 final LinkedHashSet<String> mimeTypes =
879 handleHtmlMimeTypes(baseType, mimeType);
880 for (String mt : mimeTypes) {
881 DataFlavor toAdd = null;
882 try {
883 toAdd = new DataFlavor(mt);
884 } catch (ClassNotFoundException cannotHappen) {
885 }
923 {
924 returnValue.add(DataFlavor.plainTextFlavor);
925 }
926 } else {
927 // Non-charset text natives should be treated as
928 // opaque, 8-bit data in any of its various
929 // representations.
930 for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {
931 DataFlavor toAdd = null;
932 try {
933 toAdd = new DataFlavor(baseType +
934 ";class=" + encodedTextClassName);
935 } catch (ClassNotFoundException cannotHappen) {
936 }
937 returnValue.add(toAdd);
938 }
939 }
940 return returnValue;
941 }
942
943 private static final String [] htmlDocumntTypes =
944 new String [] {"all", "selection", "fragment"};
945
946 private static LinkedHashSet<String> handleHtmlMimeTypes(
947 String baseType, String mimeType) {
948
949 LinkedHashSet<String> returnValues = new LinkedHashSet<>();
950
951 if (HTML_TEXT_BASE_TYPE.equals(baseType)) {
952 for (String documentType : htmlDocumntTypes) {
953 returnValues.add(mimeType + ";document=" + documentType);
954 }
955 } else {
956 returnValues.add(mimeType);
957 }
958
959 return returnValues;
960 }
961
962 /**
963 * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
964 * their most preferred <code>String</code> native. Each native value will
965 * be the same as the first native in the List returned by
966 * <code>getNativesForFlavor</code> for the specified flavor.
967 * <p>
968 * If a specified <code>DataFlavor</code> is previously unknown to the
969 * data transfer subsystem, then invoking this method will establish a
970 * mapping in both directions between the specified <code>DataFlavor</code>
971 * and an encoded version of its MIME type as its native.
972 *
973 * @param flavors an array of <code>DataFlavor</code>s which will be the
974 * key set of the returned <code>Map</code>. If <code>null</code> is
975 * specified, a mapping of all <code>DataFlavor</code>s known to the
976 * data transfer subsystem to their most preferred
977 * <code>String</code> natives will be returned.
978 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
979 * <code>String</code> natives
980 *
981 * @see #getNativesForFlavor
982 * @see #encodeDataFlavor
983 */
984 public synchronized Map<DataFlavor,String>
985 getNativesForFlavors(DataFlavor[] flavors)
986 {
987 // Use getNativesForFlavor to generate extra natives for text flavors
988 // and stringFlavor
989
990 if (flavors == null) {
991 List flavor_list = getFlavorsForNative(null);
992 flavors = new DataFlavor[flavor_list.size()];
993 flavor_list.toArray(flavors);
994 }
995
996 Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
997 for (DataFlavor flavor : flavors) {
998 List<String> natives = getNativesForFlavor(flavor);
999 String nat = (natives.isEmpty()) ? null : natives.get(0);
1000 retval.put(flavor, nat);
1001 }
1002
1003 return retval;
1004 }
1005
1006 /**
1007 * Returns a <code>Map</code> of the specified <code>String</code> natives
1008 * to their most preferred <code>DataFlavor</code>. Each
1009 * <code>DataFlavor</code> value will be the same as the first
1010 * <code>DataFlavor</code> in the List returned by
1011 * <code>getFlavorsForNative</code> for the specified native.
1012 * <p>
1013 * If a specified native is previously unknown to the data transfer
1014 * subsystem, and that native has been properly encoded, then invoking this
1015 * method will establish a mapping in both directions between the specified
1016 * native and a <code>DataFlavor</code> whose MIME type is a decoded
1017 * version of the native.
1018 *
1019 * @param natives an array of <code>String</code>s which will be the
1020 * key set of the returned <code>Map</code>. If <code>null</code> is
1021 * specified, a mapping of all supported <code>String</code> natives
1022 * to their most preferred <code>DataFlavor</code>s will be
1023 * returned.
1024 * @return a <code>java.util.Map</code> of <code>String</code> natives to
1025 * <code>DataFlavor</code>s
1026 *
1027 * @see #getFlavorsForNative
1028 * @see #encodeJavaMIMEType
1029 */
1030 public synchronized Map<String,DataFlavor>
1031 getFlavorsForNatives(String[] natives)
1032 {
1033 // Use getFlavorsForNative to generate extra flavors for text natives
1034
1035 if (natives == null) {
1036 List native_list = getNativesForFlavor(null);
1037 natives = new String[native_list.size()];
1038 native_list.toArray(natives);
1039 }
1040
1041 Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
1042 for (String aNative : natives) {
1043 List<DataFlavor> flavors = getFlavorsForNative(aNative);
1044 DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
1045 retval.put(aNative, flav);
1046 }
1047
1048 return retval;
1049 }
1050
1051 /**
1052 * Adds a mapping from the specified <code>DataFlavor</code> (and all
1053 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
1054 * to the specified <code>String</code> native.
1055 * Unlike <code>getNativesForFlavor</code>, the mapping will only be
1056 * established in one direction, and the native will not be encoded. To
1057 * establish a two-way mapping, call
1058 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1059 * be of lower priority than any existing mapping.
1060 * This method has no effect if a mapping from the specified or equal
1061 * <code>DataFlavor</code> to the specified <code>String</code> native
1062 * already exists.
1063 *
1064 * @param flav the <code>DataFlavor</code> key for the mapping
1065 * @param nat the <code>String</code> native value for the mapping
1066 * @throws NullPointerException if flav or nat is <code>null</code>
1067 *
1068 * @see #addFlavorForUnencodedNative
1069 * @since 1.4
1070 */
1071 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
1072 String nat) {
1073 if (flav == null || nat == null) {
1074 throw new NullPointerException("null arguments not permitted");
1075 }
1076
1077 List<String> natives = getFlavorToNative().get(flav);
1078 if (natives == null) {
1079 natives = new ArrayList<>(1);
1080 getFlavorToNative().put(flav, natives);
1081 } else if (natives.contains(nat)) {
1082 return;
1083 }
1084 natives.add(nat);
1085 getNativesForFlavorCache.remove(flav);
1086 getNativesForFlavorCache.remove(null);
1087 }
1088
1089 /**
1090 * Discards the current mappings for the specified <code>DataFlavor</code>
1091 * and all <code>DataFlavor</code>s equal to the specified
1092 * <code>DataFlavor</code>, and creates new mappings to the
1093 * specified <code>String</code> natives.
1094 * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1095 * established in one direction, and the natives will not be encoded. To
1096 * establish two-way mappings, call <code>setFlavorsForNative</code>
1097 * as well. The first native in the array will represent the highest
1098 * priority mapping. Subsequent natives will represent mappings of
1099 * decreasing priority.
1100 * <p>
1101 * If the array contains several elements that reference equal
1102 * <code>String</code> natives, this method will establish new mappings
1103 * for the first of those elements and ignore the rest of them.
1104 * <p>
1105 * It is recommended that client code not reset mappings established by the
1106 * data transfer subsystem. This method should only be used for
1107 * application-level mappings.
1108 *
1109 * @param flav the <code>DataFlavor</code> key for the mappings
1110 * @param natives the <code>String</code> native values for the mappings
1111 * @throws NullPointerException if flav or natives is <code>null</code>
1112 * or if natives contains <code>null</code> elements
1113 *
1114 * @see #setFlavorsForNative
1115 * @since 1.4
1116 */
1117 public synchronized void setNativesForFlavor(DataFlavor flav,
1118 String[] natives) {
1119 if (flav == null || natives == null) {
1120 throw new NullPointerException("null arguments not permitted");
1121 }
1122
1123 getFlavorToNative().remove(flav);
1124 for (String aNative : natives) {
1125 addUnencodedNativeForFlavor(flav, aNative);
1126 }
1127 disabledMappingGenerationKeys.add(flav);
1128 // Clear the cache to handle the case of empty natives.
1129 getNativesForFlavorCache.remove(flav);
1130 getNativesForFlavorCache.remove(null);
1131 }
1132
1133 /**
1134 * Adds a mapping from a single <code>String</code> native to a single
1135 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1136 * mapping will only be established in one direction, and the native will
1137 * not be encoded. To establish a two-way mapping, call
1138 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1139 * be of lower priority than any existing mapping.
1140 * This method has no effect if a mapping from the specified
1141 * <code>String</code> native to the specified or equal
1142 * <code>DataFlavor</code> already exists.
1143 *
1144 * @param nat the <code>String</code> native key for the mapping
1145 * @param flav the <code>DataFlavor</code> value for the mapping
1146 * @throws NullPointerException if nat or flav is <code>null</code>
1147 *
1148 * @see #addUnencodedNativeForFlavor
1149 * @since 1.4
1150 */
1151 public synchronized void addFlavorForUnencodedNative(String nat,
1152 DataFlavor flav) {
1153 if (nat == null || flav == null) {
1154 throw new NullPointerException("null arguments not permitted");
1155 }
1156
1157 List<DataFlavor> flavors = getNativeToFlavor().get(nat);
1158 if (flavors == null) {
1159 flavors = new ArrayList<>(1);
1160 getNativeToFlavor().put(nat, flavors);
1161 } else if (flavors.contains(flav)) {
1162 return;
1163 }
1164 flavors.add(flav);
1165 getFlavorsForNativeCache.remove(nat);
1166 getFlavorsForNativeCache.remove(null);
1167 }
1168
1169 /**
1170 * Discards the current mappings for the specified <code>String</code>
1171 * native, and creates new mappings to the specified
1172 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1173 * mappings will only be established in one direction, and the natives need
1174 * not be encoded. To establish two-way mappings, call
1175 * <code>setNativesForFlavor</code> as well. The first
1176 * <code>DataFlavor</code> in the array will represent the highest priority
1177 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1178 * decreasing priority.
1179 * <p>
1180 * If the array contains several elements that reference equal
1181 * <code>DataFlavor</code>s, this method will establish new mappings
1182 * for the first of those elements and ignore the rest of them.
1183 * <p>
1184 * It is recommended that client code not reset mappings established by the
1185 * data transfer subsystem. This method should only be used for
1186 * application-level mappings.
1187 *
1188 * @param nat the <code>String</code> native key for the mappings
1189 * @param flavors the <code>DataFlavor</code> values for the mappings
1190 * @throws NullPointerException if nat or flavors is <code>null</code>
1191 * or if flavors contains <code>null</code> elements
1192 *
1193 * @see #setNativesForFlavor
1194 * @since 1.4
1195 */
1196 public synchronized void setFlavorsForNative(String nat,
1197 DataFlavor[] flavors) {
1198 if (nat == null || flavors == null) {
1199 throw new NullPointerException("null arguments not permitted");
1200 }
1201
1202 getNativeToFlavor().remove(nat);
1203 for (DataFlavor flavor : flavors) {
1204 addFlavorForUnencodedNative(nat, flavor);
1205 }
1206 disabledMappingGenerationKeys.add(nat);
1207 // Clear the cache to handle the case of empty flavors.
1208 getFlavorsForNativeCache.remove(nat);
1209 getFlavorsForNativeCache.remove(null);
1210 }
1211
1212 /**
1213 * Encodes a MIME type for use as a <code>String</code> native. The format
1214 * of an encoded representation of a MIME type is implementation-dependent.
1215 * The only restrictions are:
1216 * <ul>
1217 * <li>The encoded representation is <code>null</code> if and only if the
1218 * MIME type <code>String</code> is <code>null</code>.</li>
1219 * <li>The encoded representations for two non-<code>null</code> MIME type
1220 * <code>String</code>s are equal if and only if these <code>String</code>s
1221 * are equal according to <code>String.equals(Object)</code>.</li>
1222 * </ul>
1223 * <p>
1224 * The reference implementation of this method returns the specified MIME
1225 * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
1226 *
1227 * @param mimeType the MIME type to encode
1228 * @return the encoded <code>String</code>, or <code>null</code> if
1229 * mimeType is <code>null</code>
1290
1291 /**
1292 * Decodes a <code>String</code> native for use as a
1293 * <code>DataFlavor</code>.
1294 *
1295 * @param nat the <code>String</code> to decode
1296 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1297 * nat is not an encoded <code>String</code> native
1298 * @throws ClassNotFoundException if the class of the data flavor
1299 * is not loaded
1300 */
1301 public static DataFlavor decodeDataFlavor(String nat)
1302 throws ClassNotFoundException
1303 {
1304 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1305 return (retval_str != null)
1306 ? new DataFlavor(retval_str)
1307 : null;
1308 }
1309
1310 private List<String> getAllNativesForType(String type) {
1311 Set<String> retval = null;
1312 for (DataFlavor dataFlavor : convertMimeTypeToDataFlavors(type)) {
1313 List<String> natives = getFlavorToNative().get(dataFlavor);
1314 if (natives != null && !natives.isEmpty()) {
1315 if (retval == null) {
1316 retval = new LinkedHashSet<>();
1317 }
1318 retval.addAll(natives);
1319 }
1320 }
1321 return retval == null ? null : new ArrayList<>(retval);
1322 }
1323 }
|
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.LinkedHashSet;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 import java.util.Set;
48
49 import sun.awt.AppContext;
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 *
59 * @since 1.2
60 */
61 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
62
63 /**
64 * Constant prefix used to tag Java types converted to native platform
65 * type.
66 */
67 private static String JavaMIME = "JAVA_DATAFLAVOR:";
85
86 /**
87 * The list of valid, encoded text flavor representation classes, in order
88 * from best to worst.
89 */
90 private static final String[] ENCODED_TEXT_CLASSES = {
91 "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
92 };
93
94 /**
95 * A String representing text/plain MIME type.
96 */
97 private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
98
99 /**
100 * A String representing text/html MIME type.
101 */
102 private static final String HTML_TEXT_BASE_TYPE = "text/html";
103
104 /**
105 * Maps native Strings to Lists of DataFlavors (or base type Strings for
106 * text DataFlavors).
107 * Do not use the field directly, use getNativeToFlavor() instead.
108 */
109 private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>();
110
111 /**
112 * Accessor to nativeToFlavor map. Since we use lazy initialization we must
113 * use this accessor instead of direct access to the field which may not be
114 * initialized yet. This method will initialize the field if needed.
115 *
116 * @return nativeToFlavor
117 */
118 private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() {
119 if (!isMapInitialized) {
120 initSystemFlavorMap();
121 }
122 return nativeToFlavor;
123 }
124
125 /**
126 * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
127 * native Strings.
128 * Do not use the field directly, use getFlavorToNative() instead.
129 */
130 private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>();
131
132 /**
133 * Accessor to flavorToNative map. Since we use lazy initialization we must
134 * use this accessor instead of direct access to the field which may not be
135 * initialized yet. This method will initialize the field if needed.
136 *
137 * @return flavorToNative
138 */
139 private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() {
140 if (!isMapInitialized) {
141 initSystemFlavorMap();
142 }
143 return flavorToNative;
144 }
145
146 /**
147 * Maps a text DataFlavor primary mime-type to the native. Used only to store
148 * standard mappings registered in the flavormap.properties
149 * Do not use this field directly, use getTextTypeToNative() instead.
150 */
151 private final Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>();
152
153 /**
154 * An accessor to textTypeToNative map. Since we use lazy initialization we
155 * must use this accessor instead of direct access to the field which may not
156 * be initialized yet. This method will initialize the field if needed.
157 *
158 * @return textTypeToNative
159 */
160 private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() {
161 if (!isMapInitialized) {
162 initSystemFlavorMap();
163 }
164 return textTypeToNative;
165 }
166
167 /**
168 * Shows if the object has been initialized.
169 */
170 private boolean isMapInitialized = false;
171
172 /**
173 * Caches the result of getNativesForFlavor(). Maps DataFlavors to
174 * SoftReferences which reference LinkedHashSet of String natives.
175 */
176 private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>();
177
178 /**
179 * Caches the result getFlavorsForNative(). Maps String natives to
180 * SoftReferences which reference LinkedHashSet of DataFlavors.
181 */
182 private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>();
183
184 /**
185 * Dynamic mapping generation used for text mappings should not be applied
186 * to the DataFlavors and String natives for which the mappings have been
187 * explicitly specified with setFlavorsForNative() or
188 * setNativesForFlavor(). This keeps all such keys.
189 */
190 private Set<Object> disabledMappingGenerationKeys = new HashSet<>();
191
192 /**
193 * Returns the default FlavorMap for this thread's ClassLoader.
194 * @return the default FlavorMap for this thread's ClassLoader
195 */
196 public static FlavorMap getDefaultFlavorMap() {
197 AppContext context = AppContext.getAppContext();
198 FlavorMap fm = (FlavorMap) context.get(FLAVOR_MAP_KEY);
199 if (fm == null) {
200 fm = new SystemFlavorMap();
201 context.put(FLAVOR_MAP_KEY, fm);
202 }
203 return fm;
204 }
205
206 private SystemFlavorMap() {
207 }
208
209 /**
210 * Initializes a SystemFlavorMap by reading flavormap.properties and
400 // But don't store any of these parameters in the
401 // DataFlavor itself for any text natives (even
402 // non-charset ones). The SystemFlavorMap will
403 // synthesize the appropriate mappings later.
404 mime.removeParameter("charset");
405 mime.removeParameter("class");
406 mime.removeParameter("eoln");
407 mime.removeParameter("terminators");
408 value = mime.toString();
409 }
410 } catch (MimeTypeParseException e) {
411 e.printStackTrace();
412 continue;
413 }
414
415 DataFlavor flavor;
416 try {
417 flavor = new DataFlavor(value);
418 } catch (Exception e) {
419 try {
420 flavor = new DataFlavor(value, null);
421 } catch (Exception ee) {
422 ee.printStackTrace();
423 continue;
424 }
425 }
426
427 final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();
428 dfs.add(flavor);
429
430 if ("text".equals(flavor.getPrimaryType())) {
431 dfs.addAll(convertMimeTypeToDataFlavors(value));
432 store(flavor.mimeType.getBaseType(), key, getTextTypeToNative());
433 }
434
435 for (DataFlavor df : dfs) {
436 store(df, key, getFlavorToNative());
437 store(key, df, getNativeToFlavor());
438 }
439 }
440 }
441 }
442 }
443
444 /**
445 * Copied from java.util.Properties.
446 */
447 private boolean continueLine (String line) {
448 int slashCount = 0;
449 int index = line.length() - 1;
450 while((index >= 0) && (line.charAt(index--) == '\\')) {
451 slashCount++;
452 }
501 } else if (aChar == 'n') {
502 aChar = '\n';
503 } else if (aChar == 'f') {
504 aChar = '\f';
505 }
506 outBuffer.append(aChar);
507 }
508 } else {
509 outBuffer.append(aChar);
510 }
511 }
512 return outBuffer.toString();
513 }
514
515 /**
516 * Stores the listed object under the specified hash key in map. Unlike a
517 * standard map, the listed object will not replace any object already at
518 * the appropriate Map location, but rather will be appended to a List
519 * stored in that location.
520 */
521 private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) {
522 LinkedHashSet<L> list = map.get(hashed);
523 if (list == null) {
524 list = new LinkedHashSet<>(1);
525 map.put(hashed, list);
526 }
527 if (!list.contains(listed)) {
528 list.add(listed);
529 }
530 }
531
532 /**
533 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
534 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
535 * case, a new DataFlavor is synthesized, stored, and returned, if and
536 * only if the specified native is encoded as a Java MIME type.
537 */
538 private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) {
539 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
540
541 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
542 DataTransferer transferer = DataTransferer.getInstance();
543 if (transferer != null) {
544 LinkedHashSet<DataFlavor> platformFlavors =
545 transferer.getPlatformMappingsForNative(nat);
546 if (!platformFlavors.isEmpty()) {
547 if (flavors != null) {
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 LinkedHashSet<>(1);
574 getNativeToFlavor().put(nat, flavors);
575 flavors.add(flavor);
576 flavorsForNativeCache.remove(nat);
577
578 LinkedHashSet<String> natives = getFlavorToNative().get(flavor);
579 if (natives == null) {
580 natives = new LinkedHashSet<>(1);
581 getFlavorToNative().put(flavor, natives);
582 }
583 natives.add(nat);
584 nativesForFlavorCache.remove(flavor);
585 }
586 }
587
588 return (flavors != null) ? flavors : new LinkedHashSet<>(0);
589 }
590
591 /**
592 * Semantically equivalent to 'flavorToNative.get(flav)'. This method
593 * handles the case where 'flav' is not found in 'flavorToNative' depending
594 * on the value of passes 'synthesize' parameter. If 'synthesize' is
595 * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
596 * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
597 * and 'flavorToNative' remains unaffected.
598 */
599 private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav,
600 final boolean synthesize) {
601
602 LinkedHashSet<String> natives = getFlavorToNative().get(flav);
603
604 if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
605 DataTransferer transferer = DataTransferer.getInstance();
606 if (transferer != null) {
607 LinkedHashSet<String> platformNatives =
608 transferer.getPlatformMappingsForFlavor(flav);
609 if (!platformNatives.isEmpty()) {
610 if (natives != null) {
611 // Prepend the platform-specific mappings to ensure
612 // that the natives added with
613 // addUnencodedNativeForFlavor() are at the end of
614 // list.
615 platformNatives.addAll(natives);
616 }
617 natives = platformNatives;
618 }
619 }
620 }
621
622 if (natives == null) {
623 if (synthesize) {
624 String encoded = encodeDataFlavor(flav);
625 natives = new LinkedHashSet<>(1);
626 getFlavorToNative().put(flav, natives);
627 natives.add(encoded);
628
629 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded);
630 if (flavors == null) {
631 flavors = new LinkedHashSet<>(1);
632 getNativeToFlavor().put(encoded, flavors);
633 }
634 flavors.add(flav);
635
636 nativesForFlavorCache.remove(flav);
637 flavorsForNativeCache.remove(encoded);
638 } else {
639 natives = new LinkedHashSet<>(0);
640 }
641 }
642
643 return new LinkedHashSet<>(natives);
644 }
645
646 /**
647 * Returns a <code>List</code> of <code>String</code> natives to which the
648 * specified <code>DataFlavor</code> can be translated by the data transfer
649 * subsystem. The <code>List</code> will be sorted from best native to
650 * worst. That is, the first native will best reflect data in the specified
651 * flavor to the underlying native platform.
652 * <p>
653 * If the specified <code>DataFlavor</code> is previously unknown to the
654 * data transfer subsystem and the data transfer subsystem is unable to
655 * translate this <code>DataFlavor</code> to any existing native, then
656 * invoking this method will establish a
657 * mapping in both directions between the specified <code>DataFlavor</code>
658 * and an encoded version of its MIME type as its native.
659 *
660 * @param flav the <code>DataFlavor</code> whose corresponding natives
661 * should be returned. If <code>null</code> is specified, all
662 * natives currently known to the data transfer subsystem are
663 * returned in a non-deterministic order.
664 * @return a <code>java.util.List</code> of <code>java.lang.String</code>
665 * objects which are platform-specific representations of platform-
666 * specific data formats
667 *
668 * @see #encodeDataFlavor
669 * @since 1.4
670 */
671 @Override
672 public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
673 LinkedHashSet<String> retval = nativesForFlavorCache.check(flav);
674 if (retval != null) {
675 return new ArrayList<>(retval);
676 }
677
678 if (flav == null) {
679 retval = new LinkedHashSet<>(getNativeToFlavor().keySet());
680 } else if (disabledMappingGenerationKeys.contains(flav)) {
681 // In this case we shouldn't synthesize a native for this flavor,
682 // since its mappings were explicitly specified.
683 retval = flavorToNativeLookup(flav, false);
684 } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
685 retval = new LinkedHashSet<>(0);
686
687 // For text/* flavors, flavor-to-native mappings specified in
688 // flavormap.properties are stored per flavor's base type.
689 if ("text".equals(flav.getPrimaryType())) {
690 LinkedHashSet<String> textTypeNatives =
691 getTextTypeToNative().get(flav.mimeType.getBaseType());
692 if (textTypeNatives != null) {
693 retval.addAll(textTypeNatives);
694 }
695 }
696
697 // Also include text/plain natives, but don't duplicate Strings
698 LinkedHashSet<String> textTypeNatives =
699 getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE);
700 if (textTypeNatives != null) {
701 retval.addAll(textTypeNatives);
702 }
703
704 if (retval.isEmpty()) {
705 retval = flavorToNativeLookup(flav, true);
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 retval.addAll(flavorToNativeLookup(flav, false));
711 }
712 } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
713 retval = getTextTypeToNative().get(flav.mimeType.getBaseType());
714
715 if (retval == null || retval.isEmpty()) {
716 retval = flavorToNativeLookup(flav, true);
717 } else {
718 // In this branch it is guaranteed that natives explicitly
719 // listed for flav's MIME type were added with
720 // addUnencodedNativeForFlavor(), so they have lower priority.
721 retval.addAll(flavorToNativeLookup(flav, false));
722 }
723 } else {
724 retval = flavorToNativeLookup(flav, true);
725 }
726
727 nativesForFlavorCache.put(flav, retval);
728 // Create a copy, because client code can modify the returned list.
729 return new ArrayList<>(retval);
730 }
731
732 /**
733 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
734 * specified <code>String</code> native can be translated by the data
735 * transfer subsystem. The <code>List</code> will be sorted from best
736 * <code>DataFlavor</code> to worst. That is, the first
737 * <code>DataFlavor</code> will best reflect data in the specified
738 * native to a Java application.
739 * <p>
740 * If the specified native is previously unknown to the data transfer
741 * subsystem, and that native has been properly encoded, then invoking this
742 * method will establish a mapping in both directions between the specified
743 * native and a <code>DataFlavor</code> whose MIME type is a decoded
744 * version of the native.
745 * <p>
746 * If the specified native is not a properly encoded native and the
747 * mappings for this native have not been altered with
748 * <code>setFlavorsForNative</code>, then the contents of the
749 * <code>List</code> is platform dependent, but <code>null</code>
750 * cannot be returned.
751 *
752 * @param nat the native whose corresponding <code>DataFlavor</code>s
753 * should be returned. If <code>null</code> is specified, all
754 * <code>DataFlavor</code>s currently known to the data transfer
755 * subsystem are returned in a non-deterministic order.
756 * @return a <code>java.util.List</code> of <code>DataFlavor</code>
757 * objects into which platform-specific data in the specified,
758 * platform-specific native can be translated
759 *
760 * @see #encodeJavaMIMEType
761 * @since 1.4
762 */
763 @Override
764 public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
765 LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat);
766 if (returnValue != null) {
767 return new ArrayList<>(returnValue);
768 } else {
769 returnValue = new LinkedHashSet<>();
770 }
771
772 if (nat == null) {
773 for (String n : getNativesForFlavor(null)) {
774 returnValue.addAll(getFlavorsForNative(n));
775 }
776 } else {
777 final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat);
778 if (disabledMappingGenerationKeys.contains(nat)) {
779 return new ArrayList<>(flavors);
780 }
781
782 final LinkedHashSet<DataFlavor> flavorsWithSynthesized =
783 nativeToFlavorLookup(nat);
784
785 for (DataFlavor df : flavorsWithSynthesized) {
786 returnValue.add(df);
787 if ("text".equals(df.getPrimaryType())) {
788 String baseType = df.mimeType.getBaseType();
789 returnValue.addAll(convertMimeTypeToDataFlavors(baseType));
790 }
791 }
792 }
793 flavorsForNativeCache.put(nat, returnValue);
794 return new ArrayList<>(returnValue);
795 }
796
797 private static Set<DataFlavor> convertMimeTypeToDataFlavors(
798 final String baseType) {
799
800 final Set<DataFlavor> returnValue = new LinkedHashSet<>();
801
802 String subType = null;
803
804 try {
805 final MimeType mimeType = new MimeType(baseType);
806 subType = mimeType.getSubType();
807 } catch (MimeTypeParseException mtpe) {
808 // Cannot happen, since we checked all mappings
809 // on load from flavormap.properties.
810 }
811
812 if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
813 if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
814 {
815 returnValue.add(DataFlavor.stringFlavor);
816 }
817
818 for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
819 final String mimeType = baseType + ";charset=Unicode;class=" +
820 unicodeClassName;
821
822 final LinkedHashSet<String> mimeTypes =
823 handleHtmlMimeTypes(baseType, mimeType);
824 for (String mt : mimeTypes) {
825 DataFlavor toAdd = null;
826 try {
827 toAdd = new DataFlavor(mt);
828 } catch (ClassNotFoundException cannotHappen) {
829 }
867 {
868 returnValue.add(DataFlavor.plainTextFlavor);
869 }
870 } else {
871 // Non-charset text natives should be treated as
872 // opaque, 8-bit data in any of its various
873 // representations.
874 for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {
875 DataFlavor toAdd = null;
876 try {
877 toAdd = new DataFlavor(baseType +
878 ";class=" + encodedTextClassName);
879 } catch (ClassNotFoundException cannotHappen) {
880 }
881 returnValue.add(toAdd);
882 }
883 }
884 return returnValue;
885 }
886
887 private static final String [] htmlDocumntTypes = new String [] {"all",
888 "selection",
889 "fragment"};
890
891 private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType,
892 String mimeType) {
893
894 LinkedHashSet<String> returnValues = new LinkedHashSet<>();
895
896 if (HTML_TEXT_BASE_TYPE.equals(baseType)) {
897 for (String documentType : htmlDocumntTypes) {
898 returnValues.add(mimeType + ";document=" + documentType);
899 }
900 } else {
901 returnValues.add(mimeType);
902 }
903
904 return returnValues;
905 }
906
907 /**
908 * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
909 * their most preferred <code>String</code> native. Each native value will
910 * be the same as the first native in the List returned by
911 * <code>getNativesForFlavor</code> for the specified flavor.
912 * <p>
913 * If a specified <code>DataFlavor</code> is previously unknown to the
914 * data transfer subsystem, then invoking this method will establish a
915 * mapping in both directions between the specified <code>DataFlavor</code>
916 * and an encoded version of its MIME type as its native.
917 *
918 * @param flavors an array of <code>DataFlavor</code>s which will be the
919 * key set of the returned <code>Map</code>. If <code>null</code> is
920 * specified, a mapping of all <code>DataFlavor</code>s known to the
921 * data transfer subsystem to their most preferred
922 * <code>String</code> natives will be returned.
923 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
924 * <code>String</code> natives
925 *
926 * @see #getNativesForFlavor
927 * @see #encodeDataFlavor
928 */
929 @Override
930 public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors)
931 {
932 // Use getNativesForFlavor to generate extra natives for text flavors
933 // and stringFlavor
934
935 if (flavors == null) {
936 List<DataFlavor> flavor_list = getFlavorsForNative(null);
937 flavors = new DataFlavor[flavor_list.size()];
938 flavor_list.toArray(flavors);
939 }
940
941 Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
942 for (DataFlavor flavor : flavors) {
943 List<String> natives = getNativesForFlavor(flavor);
944 String nat = (natives.isEmpty()) ? null : natives.get(0);
945 retval.put(flavor, nat);
946 }
947
948 return retval;
949 }
950
951 /**
952 * Returns a <code>Map</code> of the specified <code>String</code> natives
953 * to their most preferred <code>DataFlavor</code>. Each
954 * <code>DataFlavor</code> value will be the same as the first
955 * <code>DataFlavor</code> in the List returned by
956 * <code>getFlavorsForNative</code> for the specified native.
957 * <p>
958 * If a specified native is previously unknown to the data transfer
959 * subsystem, and that native has been properly encoded, then invoking this
960 * method will establish a mapping in both directions between the specified
961 * native and a <code>DataFlavor</code> whose MIME type is a decoded
962 * version of the native.
963 *
964 * @param natives an array of <code>String</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 supported <code>String</code> natives
967 * to their most preferred <code>DataFlavor</code>s will be
968 * returned.
969 * @return a <code>java.util.Map</code> of <code>String</code> natives to
970 * <code>DataFlavor</code>s
971 *
972 * @see #getFlavorsForNative
973 * @see #encodeJavaMIMEType
974 */
975 @Override
976 public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives)
977 {
978 // Use getFlavorsForNative to generate extra flavors for text natives
979 if (natives == null) {
980 List<String> nativesList = getNativesForFlavor(null);
981 natives = new String[nativesList.size()];
982 nativesList.toArray(natives);
983 }
984
985 Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
986 for (String aNative : natives) {
987 List<DataFlavor> flavors = getFlavorsForNative(aNative);
988 DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
989 retval.put(aNative, flav);
990 }
991 return retval;
992 }
993
994 /**
995 * Adds a mapping from the specified <code>DataFlavor</code> (and all
996 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
997 * to the specified <code>String</code> native.
998 * Unlike <code>getNativesForFlavor</code>, the mapping will only be
999 * established in one direction, and the native will not be encoded. To
1000 * establish a two-way mapping, call
1001 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1002 * be of lower priority than any existing mapping.
1003 * This method has no effect if a mapping from the specified or equal
1004 * <code>DataFlavor</code> to the specified <code>String</code> native
1005 * already exists.
1006 *
1007 * @param flav the <code>DataFlavor</code> key for the mapping
1008 * @param nat the <code>String</code> native value for the mapping
1009 * @throws NullPointerException if flav or nat is <code>null</code>
1010 *
1011 * @see #addFlavorForUnencodedNative
1012 * @since 1.4
1013 */
1014 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
1015 String nat) {
1016 Objects.requireNonNull(nat, "Null native not permitted");
1017 Objects.requireNonNull(flav, "Null flavor not permitted");
1018
1019 LinkedHashSet<String> natives = getFlavorToNative().get(flav);
1020 if (natives == null) {
1021 natives = new LinkedHashSet<>(1);
1022 getFlavorToNative().put(flav, natives);
1023 }
1024 natives.add(nat);
1025 nativesForFlavorCache.remove(flav);
1026 }
1027
1028 /**
1029 * Discards the current mappings for the specified <code>DataFlavor</code>
1030 * and all <code>DataFlavor</code>s equal to the specified
1031 * <code>DataFlavor</code>, and creates new mappings to the
1032 * specified <code>String</code> natives.
1033 * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1034 * established in one direction, and the natives will not be encoded. To
1035 * establish two-way mappings, call <code>setFlavorsForNative</code>
1036 * as well. The first native in the array will represent the highest
1037 * priority mapping. Subsequent natives will represent mappings of
1038 * decreasing priority.
1039 * <p>
1040 * If the array contains several elements that reference equal
1041 * <code>String</code> natives, this method will establish new mappings
1042 * for the first of those elements and ignore the rest of them.
1043 * <p>
1044 * It is recommended that client code not reset mappings established by the
1045 * data transfer subsystem. This method should only be used for
1046 * application-level mappings.
1047 *
1048 * @param flav the <code>DataFlavor</code> key for the mappings
1049 * @param natives the <code>String</code> native values for the mappings
1050 * @throws NullPointerException if flav or natives is <code>null</code>
1051 * or if natives contains <code>null</code> elements
1052 *
1053 * @see #setFlavorsForNative
1054 * @since 1.4
1055 */
1056 public synchronized void setNativesForFlavor(DataFlavor flav,
1057 String[] natives) {
1058 Objects.requireNonNull(natives, "Null natives not permitted");
1059 Objects.requireNonNull(flav, "Null flavors not permitted");
1060
1061 getFlavorToNative().remove(flav);
1062 for (String aNative : natives) {
1063 addUnencodedNativeForFlavor(flav, aNative);
1064 }
1065 disabledMappingGenerationKeys.add(flav);
1066 nativesForFlavorCache.remove(flav);
1067 }
1068
1069 /**
1070 * Adds a mapping from a single <code>String</code> native to a single
1071 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1072 * mapping will only be established in one direction, and the native will
1073 * not be encoded. To establish a two-way mapping, call
1074 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1075 * be of lower priority than any existing mapping.
1076 * This method has no effect if a mapping from the specified
1077 * <code>String</code> native to the specified or equal
1078 * <code>DataFlavor</code> already exists.
1079 *
1080 * @param nat the <code>String</code> native key for the mapping
1081 * @param flav the <code>DataFlavor</code> value for the mapping
1082 * @throws NullPointerException if nat or flav is <code>null</code>
1083 *
1084 * @see #addUnencodedNativeForFlavor
1085 * @since 1.4
1086 */
1087 public synchronized void addFlavorForUnencodedNative(String nat,
1088 DataFlavor flav) {
1089 Objects.requireNonNull(nat, "Null native not permitted");
1090 Objects.requireNonNull(flav, "Null flavor not permitted");
1091
1092 LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
1093 if (flavors == null) {
1094 flavors = new LinkedHashSet<>(1);
1095 getNativeToFlavor().put(nat, flavors);
1096 }
1097 flavors.add(flav);
1098 flavorsForNativeCache.remove(nat);
1099 }
1100
1101 /**
1102 * Discards the current mappings for the specified <code>String</code>
1103 * native, and creates new mappings to the specified
1104 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1105 * mappings will only be established in one direction, and the natives need
1106 * not be encoded. To establish two-way mappings, call
1107 * <code>setNativesForFlavor</code> as well. The first
1108 * <code>DataFlavor</code> in the array will represent the highest priority
1109 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1110 * decreasing priority.
1111 * <p>
1112 * If the array contains several elements that reference equal
1113 * <code>DataFlavor</code>s, this method will establish new mappings
1114 * for the first of those elements and ignore the rest of them.
1115 * <p>
1116 * It is recommended that client code not reset mappings established by the
1117 * data transfer subsystem. This method should only be used for
1118 * application-level mappings.
1119 *
1120 * @param nat the <code>String</code> native key for the mappings
1121 * @param flavors the <code>DataFlavor</code> values for the mappings
1122 * @throws NullPointerException if nat or flavors is <code>null</code>
1123 * or if flavors contains <code>null</code> elements
1124 *
1125 * @see #setNativesForFlavor
1126 * @since 1.4
1127 */
1128 public synchronized void setFlavorsForNative(String nat,
1129 DataFlavor[] flavors) {
1130 Objects.requireNonNull(nat, "Null native not permitted");
1131 Objects.requireNonNull(flavors, "Null flavors not permitted");
1132
1133 getNativeToFlavor().remove(nat);
1134 for (DataFlavor flavor : flavors) {
1135 addFlavorForUnencodedNative(nat, flavor);
1136 }
1137 disabledMappingGenerationKeys.add(nat);
1138 flavorsForNativeCache.remove(nat);
1139 }
1140
1141 /**
1142 * Encodes a MIME type for use as a <code>String</code> native. The format
1143 * of an encoded representation of a MIME type is implementation-dependent.
1144 * The only restrictions are:
1145 * <ul>
1146 * <li>The encoded representation is <code>null</code> if and only if the
1147 * MIME type <code>String</code> is <code>null</code>.</li>
1148 * <li>The encoded representations for two non-<code>null</code> MIME type
1149 * <code>String</code>s are equal if and only if these <code>String</code>s
1150 * are equal according to <code>String.equals(Object)</code>.</li>
1151 * </ul>
1152 * <p>
1153 * The reference implementation of this method returns the specified MIME
1154 * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
1155 *
1156 * @param mimeType the MIME type to encode
1157 * @return the encoded <code>String</code>, or <code>null</code> if
1158 * mimeType is <code>null</code>
1219
1220 /**
1221 * Decodes a <code>String</code> native for use as a
1222 * <code>DataFlavor</code>.
1223 *
1224 * @param nat the <code>String</code> to decode
1225 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1226 * nat is not an encoded <code>String</code> native
1227 * @throws ClassNotFoundException if the class of the data flavor
1228 * is not loaded
1229 */
1230 public static DataFlavor decodeDataFlavor(String nat)
1231 throws ClassNotFoundException
1232 {
1233 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1234 return (retval_str != null)
1235 ? new DataFlavor(retval_str)
1236 : null;
1237 }
1238
1239 private static final class SoftCache<K, V> {
1240 Map<K, SoftReference<LinkedHashSet<V>>> cache;
1241
1242 public void put(K key, LinkedHashSet<V> value) {
1243 if (cache == null) {
1244 cache = new HashMap<>(1);
1245 }
1246 cache.put(key, new SoftReference<>(value));
1247 }
1248
1249 public void remove(K key) {
1250 if (cache == null) return;
1251 cache.remove(null);
1252 cache.remove(key);
1253 }
1254
1255 public LinkedHashSet<V> check(K key) {
1256 if (cache == null) return null;
1257 SoftReference<LinkedHashSet<V>> ref = cache.get(key);
1258 if (ref != null) {
1259 return ref.get();
1260 }
1261 return null;
1262 }
1263 }
1264 }
|