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 *
60 * @since 1.2
61 */
62 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
63
64 /**
65 * Constant prefix used to tag Java types converted to native platform
66 * type.
67 */
68 private static String JavaMIME = "JAVA_DATAFLAVOR:";
69
70 /**
71 * System singleton which maps a thread's ClassLoader to a SystemFlavorMap.
72 */
73 private static final WeakHashMap flavorMaps = new WeakHashMap();
74
75 /**
76 * Copied from java.util.Properties.
77 */
78 private static final String keyValueSeparators = "=: \t\r\n\f";
79 private static final String strictKeyValueSeparators = "=:";
80 private static final String whiteSpaceChars = " \t\r\n\f";
81
82 /**
83 * The list of valid, decoded text flavor representation classes, in order
84 * from best to worst.
85 */
86 private static final String[] UNICODE_TEXT_CLASSES = {
87 "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
88 };
89
90 /**
91 * The list of valid, encoded text flavor representation classes, in order
92 * from best to worst.
93 */
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<String, List<DataFlavor>> 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 final 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;
503 } else if (aChar == 'n') {
504 aChar = '\n';
505 } else if (aChar == 'f') {
506 aChar = '\f';
507 }
508 outBuffer.append(aChar);
509 }
510 } else {
511 outBuffer.append(aChar);
512 }
513 }
514 return outBuffer.toString();
515 }
516
517 /**
518 * Stores the listed object under the specified hash key in map. Unlike a
519 * standard map, the listed object will not replace any object already at
520 * the appropriate Map location, but rather will be appended to a List
521 * stored in that location.
522 */
523 private void store(Object hashed, Object listed, Map map) {
524 List list = (List)map.get(hashed);
525 if (list == null) {
526 list = new ArrayList(1);
527 map.put(hashed, list);
528 }
529 if (!list.contains(listed)) {
530 list.add(listed);
531 }
532 }
533
534 /**
535 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
536 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
537 * case, a new DataFlavor is synthesized, stored, and returned, if and
538 * only if the specified native is encoded as a Java MIME type.
539 */
540 private List nativeToFlavorLookup(String nat) {
541 List<DataFlavor> flavors = getNativeToFlavor().get(nat);
542
543 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
544 DataTransferer transferer = DataTransferer.getInstance();
545 if (transferer != null) {
546 List platformFlavors =
547 transferer.getPlatformMappingsForNative(nat);
548 if (!platformFlavors.isEmpty()) {
549 if (flavors != null) {
550 platformFlavors.removeAll(new HashSet(flavors));
551 // Prepending the platform-specific mappings ensures
552 // that the flavors added with
553 // addFlavorForUnencodedNative() are at the end of
554 // list.
555 platformFlavors.addAll(flavors);
556 }
557 flavors = platformFlavors;
558 }
559 }
560 }
561
562 if (flavors == null && isJavaMIMEType(nat)) {
563 String decoded = decodeJavaMIMEType(nat);
564 DataFlavor flavor = null;
565
566 try {
567 flavor = new DataFlavor(decoded);
568 } catch (Exception e) {
569 System.err.println("Exception \"" + e.getClass().getName() +
570 ": " + e.getMessage() +
571 "\"while constructing DataFlavor for: " +
572 decoded);
573 }
574
575 if (flavor != null) {
576 flavors = new ArrayList(1);
577 getNativeToFlavor().put(nat, flavors);
578 flavors.add(flavor);
579 getFlavorsForNativeCache.remove(nat);
580 getFlavorsForNativeCache.remove(null);
581
582 List natives = (List)getFlavorToNative().get(flavor);
583 if (natives == null) {
584 natives = new ArrayList(1);
585 getFlavorToNative().put(flavor, natives);
586 }
587 natives.add(nat);
588 getNativesForFlavorCache.remove(flavor);
589 getNativesForFlavorCache.remove(null);
590 }
591 }
592
593 return (flavors != null) ? flavors : new ArrayList(0);
594 }
595
596 /**
597 * Semantically equivalent to 'flavorToNative.get(flav)'. This method
598 * handles the case where 'flav' is not found in 'flavorToNative' depending
599 * on the value of passes 'synthesize' parameter. If 'synthesize' is
600 * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
601 * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
602 * and 'flavorToNative' remains unaffected.
603 */
604 private List flavorToNativeLookup(final DataFlavor flav,
605 final boolean synthesize) {
606 List natives = (List)getFlavorToNative().get(flav);
607
608 if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
609 DataTransferer transferer = DataTransferer.getInstance();
610 if (transferer != null) {
611 List platformNatives =
612 transferer.getPlatformMappingsForFlavor(flav);
613 if (!platformNatives.isEmpty()) {
614 if (natives != null) {
615 platformNatives.removeAll(new HashSet(natives));
616 // Prepend the platform-specific mappings to ensure
617 // that the natives added with
618 // addUnencodedNativeForFlavor() are at the end of
619 // list.
620 platformNatives.addAll(natives);
621 }
622 natives = platformNatives;
623 }
624 }
625 }
626
627 if (natives == null) {
628 if (synthesize) {
629 String encoded = encodeDataFlavor(flav);
630 natives = new ArrayList(1);
631 getFlavorToNative().put(flav, natives);
632 natives.add(encoded);
633 getNativesForFlavorCache.remove(flav);
634 getNativesForFlavorCache.remove(null);
635
636 List<DataFlavor> flavors = getNativeToFlavor().get(encoded);
637 if (flavors == null) {
638 flavors = new ArrayList(1);
639 getNativeToFlavor().put(encoded, flavors);
640 }
641 flavors.add(flav);
642 getFlavorsForNativeCache.remove(encoded);
643 getFlavorsForNativeCache.remove(null);
644 } else {
645 natives = new ArrayList(0);
646 }
647 }
648
649 return natives;
650 }
651
652 /**
653 * Returns a <code>List</code> of <code>String</code> natives to which the
654 * specified <code>DataFlavor</code> can be translated by the data transfer
655 * subsystem. The <code>List</code> will be sorted from best native to
656 * worst. That is, the first native will best reflect data in the specified
657 * flavor to the underlying native platform.
658 * <p>
659 * If the specified <code>DataFlavor</code> is previously unknown to the
660 * data transfer subsystem and the data transfer subsystem is unable to
661 * translate this <code>DataFlavor</code> to any existing native, then
662 * invoking this method will establish a
663 * mapping in both directions between the specified <code>DataFlavor</code>
664 * and an encoded version of its MIME type as its native.
665 *
666 * @param flav the <code>DataFlavor</code> whose corresponding natives
667 * should be returned. If <code>null</code> is specified, all
668 * natives currently known to the data transfer subsystem are
669 * returned in a non-deterministic order.
670 * @return a <code>java.util.List</code> of <code>java.lang.String</code>
671 * objects which are platform-specific representations of platform-
672 * specific data formats
673 *
674 * @see #encodeDataFlavor
675 * @since 1.4
676 */
677 public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
678 List retval = null;
679
680 // Check cache, even for null flav
681 SoftReference ref = (SoftReference)getNativesForFlavorCache.get(flav);
682 if (ref != null) {
683 retval = (List)ref.get();
684 if (retval != null) {
685 // Create a copy, because client code can modify the returned
686 // list.
687 return new ArrayList(retval);
688 }
689 }
690
691 if (flav == null) {
692 retval = new ArrayList<String>(getNativeToFlavor().keySet());
693 } else if (disabledMappingGenerationKeys.contains(flav)) {
694 // In this case we shouldn't synthesize a native for this flavor,
695 // since its mappings were explicitly specified.
696 retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
697 } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
698
699 // For text/* flavors, flavor-to-native mappings specified in
700 // flavormap.properties are stored per flavor's base type.
701 if ("text".equals(flav.getPrimaryType())) {
702 retval = (List)getFlavorToNative().get(flav.mimeType.getBaseType());
703 if (retval != null) {
704 // To prevent the List stored in the map from modification.
705 retval = new ArrayList(retval);
706 }
707 }
708
709 // Also include text/plain natives, but don't duplicate Strings
710 List textPlainList = (List)getFlavorToNative().get(TEXT_PLAIN_BASE_TYPE);
711
712 if (textPlainList != null && !textPlainList.isEmpty()) {
713 // To prevent the List stored in the map from modification.
714 // This also guarantees that removeAll() is supported.
715 textPlainList = new ArrayList(textPlainList);
716 if (retval != null && !retval.isEmpty()) {
717 // Use HashSet to get constant-time performance for search.
718 textPlainList.removeAll(new HashSet(retval));
719 retval.addAll(textPlainList);
720 } else {
721 retval = textPlainList;
722 }
723 }
724
725 if (retval == null || retval.isEmpty()) {
726 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
727 } else {
728 // In this branch it is guaranteed that natives explicitly
729 // listed for flav's MIME type were added with
730 // addUnencodedNativeForFlavor(), so they have lower priority.
731 List explicitList =
732 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
733
734 // flavorToNativeLookup() never returns null.
735 // It can return an empty List, however.
736 if (!explicitList.isEmpty()) {
737 // To prevent the List stored in the map from modification.
738 // This also guarantees that removeAll() is supported.
739 explicitList = new ArrayList(explicitList);
740 // Use HashSet to get constant-time performance for search.
741 explicitList.removeAll(new HashSet(retval));
742 retval.addAll(explicitList);
743 }
744 }
745 } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
746 retval = (List)getFlavorToNative().get(flav.mimeType.getBaseType());
747
748 if (retval == null || retval.isEmpty()) {
749 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
750 } else {
751 // In this branch it is guaranteed that natives explicitly
752 // listed for flav's MIME type were added with
753 // addUnencodedNativeForFlavor(), so they have lower priority.
754 List explicitList =
755 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
756
757 // flavorToNativeLookup() never returns null.
758 // It can return an empty List, however.
759 if (!explicitList.isEmpty()) {
760 // To prevent the List stored in the map from modification.
761 // This also guarantees that add/removeAll() are supported.
762 retval = new ArrayList(retval);
763 explicitList = new ArrayList(explicitList);
764 // Use HashSet to get constant-time performance for search.
765 explicitList.removeAll(new HashSet(retval));
766 retval.addAll(explicitList);
767 }
768 }
769 } else {
770 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
771 }
772
773 getNativesForFlavorCache.put(flav, new SoftReference(retval));
774 // Create a copy, because client code can modify the returned list.
775 return new ArrayList(retval);
776 }
777
778 /**
779 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
780 * specified <code>String</code> native can be translated by the data
781 * transfer subsystem. The <code>List</code> will be sorted from best
782 * <code>DataFlavor</code> to worst. That is, the first
783 * <code>DataFlavor</code> will best reflect data in the specified
784 * native to a Java application.
785 * <p>
786 * If the specified native is previously unknown to the data transfer
787 * subsystem, and that native has been properly encoded, then invoking this
788 * method will establish a mapping in both directions between the specified
789 * native and a <code>DataFlavor</code> whose MIME type is a decoded
790 * version of the native.
791 * <p>
792 * If the specified native is not a properly encoded native and the
793 * mappings for this native have not been altered with
794 * <code>setFlavorsForNative</code>, then the contents of the
795 * <code>List</code> is platform dependent, but <code>null</code>
796 * cannot be returned.
797 *
798 * @param nat the native whose corresponding <code>DataFlavor</code>s
799 * should be returned. If <code>null</code> is specified, all
800 * <code>DataFlavor</code>s currently known to the data transfer
801 * subsystem are returned in a non-deterministic order.
802 * @return a <code>java.util.List</code> of <code>DataFlavor</code>
803 * objects into which platform-specific data in the specified,
804 * platform-specific native can be translated
805 *
806 * @see #encodeJavaMIMEType
807 * @since 1.4
808 */
809 public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
810
811 // Check cache, even for null nat
812 SoftReference ref = (SoftReference)getFlavorsForNativeCache.get(nat);
813 if (ref != null) {
814 ArrayList retval = (ArrayList)ref.get();
815 if (retval != null) {
816 return (List)retval.clone();
817 }
818 }
819
820 final LinkedHashSet <DataFlavor> returnValue =
821 new LinkedHashSet<>();
822
823 if (nat == null) {
824 final List<String> natives = getNativesForFlavor(null);
825
826 for (String n : natives)
827 {
828 final List<DataFlavor> flavors = getFlavorsForNative(n);
829
830 for (DataFlavor df : flavors)
831 {
832 returnValue.add(df);
833 }
834 }
835 } else {
836
842
843 final List<DataFlavor> flavorsAndBaseTypes =
844 nativeToFlavorLookup(nat);
845
846 for (DataFlavor df : flavorsAndBaseTypes) {
847 returnValue.add(df);
848 if ("text".equals(df.getPrimaryType())) {
849 try {
850 returnValue.addAll(
851 convertMimeTypeToDataFlavors(
852 new MimeType(df.getMimeType()
853 ).getBaseType()));
854 } catch (MimeTypeParseException e) {
855 e.printStackTrace();
856 }
857 }
858 }
859
860 }
861
862 final ArrayList arrayList = new ArrayList(returnValue);
863 getFlavorsForNativeCache.put(nat, new SoftReference(arrayList));
864 return (List)arrayList.clone();
865 }
866
867 private static LinkedHashSet<DataFlavor> convertMimeTypeToDataFlavors(
868 final String baseType) {
869
870 final LinkedHashSet<DataFlavor> returnValue =
871 new LinkedHashSet<DataFlavor>();
872
873 String subType = null;
874
875 try {
876 final MimeType mimeType = new MimeType(baseType);
877 subType = mimeType.getSubType();
878 } catch (MimeTypeParseException mtpe) {
879 // Cannot happen, since we checked all mappings
880 // on load from flavormap.properties.
881 assert(false);
882 }
883
884 if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
885 if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
886 {
887 returnValue.add(DataFlavor.stringFlavor);
888 }
889
890 for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
891 final String mimeType = baseType + ";charset=Unicode;class=" +
992 * data transfer subsystem to their most preferred
993 * <code>String</code> natives will be returned.
994 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
995 * <code>String</code> natives
996 *
997 * @see #getNativesForFlavor
998 * @see #encodeDataFlavor
999 */
1000 public synchronized Map<DataFlavor,String>
1001 getNativesForFlavors(DataFlavor[] flavors)
1002 {
1003 // Use getNativesForFlavor to generate extra natives for text flavors
1004 // and stringFlavor
1005
1006 if (flavors == null) {
1007 List flavor_list = getFlavorsForNative(null);
1008 flavors = new DataFlavor[flavor_list.size()];
1009 flavor_list.toArray(flavors);
1010 }
1011
1012 HashMap retval = new HashMap(flavors.length, 1.0f);
1013 for (int i = 0; i < flavors.length; i++) {
1014 List natives = getNativesForFlavor(flavors[i]);
1015 String nat = (natives.isEmpty()) ? null : (String)natives.get(0);
1016 retval.put(flavors[i], nat);
1017 }
1018
1019 return retval;
1020 }
1021
1022 /**
1023 * Returns a <code>Map</code> of the specified <code>String</code> natives
1024 * to their most preferred <code>DataFlavor</code>. Each
1025 * <code>DataFlavor</code> value will be the same as the first
1026 * <code>DataFlavor</code> in the List returned by
1027 * <code>getFlavorsForNative</code> for the specified native.
1028 * <p>
1029 * If a specified native is previously unknown to the data transfer
1030 * subsystem, and that native has been properly encoded, then invoking this
1031 * method will establish a mapping in both directions between the specified
1032 * native and a <code>DataFlavor</code> whose MIME type is a decoded
1033 * version of the native.
1034 *
1035 * @param natives an array of <code>String</code>s which will be the
1036 * key set of the returned <code>Map</code>. If <code>null</code> is
1037 * specified, a mapping of all supported <code>String</code> natives
1038 * to their most preferred <code>DataFlavor</code>s will be
1039 * returned.
1040 * @return a <code>java.util.Map</code> of <code>String</code> natives to
1041 * <code>DataFlavor</code>s
1042 *
1043 * @see #getFlavorsForNative
1044 * @see #encodeJavaMIMEType
1045 */
1046 public synchronized Map<String,DataFlavor>
1047 getFlavorsForNatives(String[] natives)
1048 {
1049 // Use getFlavorsForNative to generate extra flavors for text natives
1050
1051 if (natives == null) {
1052 List native_list = getNativesForFlavor(null);
1053 natives = new String[native_list.size()];
1054 native_list.toArray(natives);
1055 }
1056
1057 HashMap retval = new HashMap(natives.length, 1.0f);
1058 for (int i = 0; i < natives.length; i++) {
1059 List flavors = getFlavorsForNative(natives[i]);
1060 DataFlavor flav = (flavors.isEmpty())
1061 ? null : (DataFlavor)flavors.get(0);
1062 retval.put(natives[i], flav);
1063 }
1064
1065 return retval;
1066 }
1067
1068 /**
1069 * Adds a mapping from the specified <code>DataFlavor</code> (and all
1070 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
1071 * to the specified <code>String</code> native.
1072 * Unlike <code>getNativesForFlavor</code>, the mapping will only be
1073 * established in one direction, and the native will not be encoded. To
1074 * establish a two-way mapping, call
1075 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1076 * be of lower priority than any existing mapping.
1077 * This method has no effect if a mapping from the specified or equal
1078 * <code>DataFlavor</code> to the specified <code>String</code> native
1079 * already exists.
1080 *
1081 * @param flav the <code>DataFlavor</code> key for the mapping
1082 * @param nat the <code>String</code> native value for the mapping
1083 * @throws NullPointerException if flav or nat is <code>null</code>
1084 *
1085 * @see #addFlavorForUnencodedNative
1086 * @since 1.4
1087 */
1088 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
1089 String nat) {
1090 if (flav == null || nat == null) {
1091 throw new NullPointerException("null arguments not permitted");
1092 }
1093
1094 List natives = (List)getFlavorToNative().get(flav);
1095 if (natives == null) {
1096 natives = new ArrayList(1);
1097 getFlavorToNative().put(flav, natives);
1098 } else if (natives.contains(nat)) {
1099 return;
1100 }
1101 natives.add(nat);
1102 getNativesForFlavorCache.remove(flav);
1103 getNativesForFlavorCache.remove(null);
1104 }
1105
1106 /**
1107 * Discards the current mappings for the specified <code>DataFlavor</code>
1108 * and all <code>DataFlavor</code>s equal to the specified
1109 * <code>DataFlavor</code>, and creates new mappings to the
1110 * specified <code>String</code> natives.
1111 * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1112 * established in one direction, and the natives will not be encoded. To
1113 * establish two-way mappings, call <code>setFlavorsForNative</code>
1114 * as well. The first native in the array will represent the highest
1115 * priority mapping. Subsequent natives will represent mappings of
1116 * decreasing priority.
1121 * <p>
1122 * It is recommended that client code not reset mappings established by the
1123 * data transfer subsystem. This method should only be used for
1124 * application-level mappings.
1125 *
1126 * @param flav the <code>DataFlavor</code> key for the mappings
1127 * @param natives the <code>String</code> native values for the mappings
1128 * @throws NullPointerException if flav or natives is <code>null</code>
1129 * or if natives contains <code>null</code> elements
1130 *
1131 * @see #setFlavorsForNative
1132 * @since 1.4
1133 */
1134 public synchronized void setNativesForFlavor(DataFlavor flav,
1135 String[] natives) {
1136 if (flav == null || natives == null) {
1137 throw new NullPointerException("null arguments not permitted");
1138 }
1139
1140 getFlavorToNative().remove(flav);
1141 for (int i = 0; i < natives.length; i++) {
1142 addUnencodedNativeForFlavor(flav, natives[i]);
1143 }
1144 disabledMappingGenerationKeys.add(flav);
1145 // Clear the cache to handle the case of empty natives.
1146 getNativesForFlavorCache.remove(flav);
1147 getNativesForFlavorCache.remove(null);
1148 }
1149
1150 /**
1151 * Adds a mapping from a single <code>String</code> native to a single
1152 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1153 * mapping will only be established in one direction, and the native will
1154 * not be encoded. To establish a two-way mapping, call
1155 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1156 * be of lower priority than any existing mapping.
1157 * This method has no effect if a mapping from the specified
1158 * <code>String</code> native to the specified or equal
1159 * <code>DataFlavor</code> already exists.
1160 *
1161 * @param nat the <code>String</code> native key for the mapping
1162 * @param flav the <code>DataFlavor</code> value for the mapping
1163 * @throws NullPointerException if nat or flav is <code>null</code>
1164 *
1165 * @see #addUnencodedNativeForFlavor
1166 * @since 1.4
1167 */
1168 public synchronized void addFlavorForUnencodedNative(String nat,
1169 DataFlavor flav) {
1170 if (nat == null || flav == null) {
1171 throw new NullPointerException("null arguments not permitted");
1172 }
1173
1174 List flavors = (List)getNativeToFlavor().get(nat);
1175 if (flavors == null) {
1176 flavors = new ArrayList(1);
1177 getNativeToFlavor().put(nat, flavors);
1178 } else if (flavors.contains(flav)) {
1179 return;
1180 }
1181 flavors.add(flav);
1182 getFlavorsForNativeCache.remove(nat);
1183 getFlavorsForNativeCache.remove(null);
1184 }
1185
1186 /**
1187 * Discards the current mappings for the specified <code>String</code>
1188 * native, and creates new mappings to the specified
1189 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1190 * mappings will only be established in one direction, and the natives need
1191 * not be encoded. To establish two-way mappings, call
1192 * <code>setNativesForFlavor</code> as well. The first
1193 * <code>DataFlavor</code> in the array will represent the highest priority
1194 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1195 * decreasing priority.
1196 * <p>
1200 * <p>
1201 * It is recommended that client code not reset mappings established by the
1202 * data transfer subsystem. This method should only be used for
1203 * application-level mappings.
1204 *
1205 * @param nat the <code>String</code> native key for the mappings
1206 * @param flavors the <code>DataFlavor</code> values for the mappings
1207 * @throws NullPointerException if nat or flavors is <code>null</code>
1208 * or if flavors contains <code>null</code> elements
1209 *
1210 * @see #setNativesForFlavor
1211 * @since 1.4
1212 */
1213 public synchronized void setFlavorsForNative(String nat,
1214 DataFlavor[] flavors) {
1215 if (nat == null || flavors == null) {
1216 throw new NullPointerException("null arguments not permitted");
1217 }
1218
1219 getNativeToFlavor().remove(nat);
1220 for (int i = 0; i < flavors.length; i++) {
1221 addFlavorForUnencodedNative(nat, flavors[i]);
1222 }
1223 disabledMappingGenerationKeys.add(nat);
1224 // Clear the cache to handle the case of empty flavors.
1225 getFlavorsForNativeCache.remove(nat);
1226 getFlavorsForNativeCache.remove(null);
1227 }
1228
1229 /**
1230 * Encodes a MIME type for use as a <code>String</code> native. The format
1231 * of an encoded representation of a MIME type is implementation-dependent.
1232 * The only restrictions are:
1233 * <ul>
1234 * <li>The encoded representation is <code>null</code> if and only if the
1235 * MIME type <code>String</code> is <code>null</code>.</li>
1236 * <li>The encoded representations for two non-<code>null</code> MIME type
1237 * <code>String</code>s are equal if and only if these <code>String</code>s
1238 * are equal according to <code>String.equals(Object)</code>.</li>
1239 * </ul>
1240 * <p>
1241 * The reference implementation of this method returns the specified MIME
1304 ? nat.substring(JavaMIME.length(), nat.length()).trim()
1305 : null;
1306 }
1307
1308 /**
1309 * Decodes a <code>String</code> native for use as a
1310 * <code>DataFlavor</code>.
1311 *
1312 * @param nat the <code>String</code> to decode
1313 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1314 * nat is not an encoded <code>String</code> native
1315 */
1316 public static DataFlavor decodeDataFlavor(String nat)
1317 throws ClassNotFoundException
1318 {
1319 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1320 return (retval_str != null)
1321 ? new DataFlavor(retval_str)
1322 : null;
1323 }
1324 }
|
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 *
60 * @since 1.2
61 */
62 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
63
64 /**
65 * Constant prefix used to tag Java types converted to native platform
66 * type.
67 */
68 private static String JavaMIME = "JAVA_DATAFLAVOR:";
69
70 /**
71 * System singleton which maps a thread's ClassLoader to a SystemFlavorMap.
72 */
73 private static final WeakHashMap<ClassLoader, FlavorMap> flavorMaps = new WeakHashMap<>();
74
75 /**
76 * Copied from java.util.Properties.
77 */
78 private static final String keyValueSeparators = "=: \t\r\n\f";
79 private static final String strictKeyValueSeparators = "=:";
80 private static final String whiteSpaceChars = " \t\r\n\f";
81
82 /**
83 * The list of valid, decoded text flavor representation classes, in order
84 * from best to worst.
85 */
86 private static final String[] UNICODE_TEXT_CLASSES = {
87 "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
88 };
89
90 /**
91 * The list of valid, encoded text flavor representation classes, in order
92 * from best to worst.
93 */
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<String, List<DataFlavor>> 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 final Map<DataFlavor, List<String>> 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<DataFlavor, List<String>> 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<DataFlavor, SoftReference<List<String>>> 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<String, SoftReference<List<DataFlavor>>> 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 = 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;
503 } else if (aChar == 'n') {
504 aChar = '\n';
505 } else if (aChar == 'f') {
506 aChar = '\f';
507 }
508 outBuffer.append(aChar);
509 }
510 } else {
511 outBuffer.append(aChar);
512 }
513 }
514 return outBuffer.toString();
515 }
516
517 /**
518 * Stores the listed object under the specified hash key in map. Unlike a
519 * standard map, the listed object will not replace any object already at
520 * the appropriate Map location, but rather will be appended to a List
521 * stored in that location.
522 */
523 private <H, L> void store(H hashed, L listed, Map<H, List<L>> map) {
524 List<L> list = map.get(hashed);
525 if (list == null) {
526 list = new ArrayList<>(1);
527 map.put(hashed, list);
528 }
529 if (!list.contains(listed)) {
530 list.add(listed);
531 }
532 }
533
534 /**
535 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
536 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
537 * case, a new DataFlavor is synthesized, stored, and returned, if and
538 * only if the specified native is encoded as a Java MIME type.
539 */
540 private List<DataFlavor> nativeToFlavorLookup(String nat) {
541 List<DataFlavor> flavors = getNativeToFlavor().get(nat);
542
543 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
544 DataTransferer transferer = DataTransferer.getInstance();
545 if (transferer != null) {
546 List<DataFlavor> platformFlavors =
547 transferer.getPlatformMappingsForNative(nat);
548 if (!platformFlavors.isEmpty()) {
549 if (flavors != null) {
550 platformFlavors.removeAll(new HashSet<>(flavors));
551 // Prepending the platform-specific mappings ensures
552 // that the flavors added with
553 // addFlavorForUnencodedNative() are at the end of
554 // list.
555 platformFlavors.addAll(flavors);
556 }
557 flavors = platformFlavors;
558 }
559 }
560 }
561
562 if (flavors == null && isJavaMIMEType(nat)) {
563 String decoded = decodeJavaMIMEType(nat);
564 DataFlavor flavor = null;
565
566 try {
567 flavor = new DataFlavor(decoded);
568 } catch (Exception e) {
569 System.err.println("Exception \"" + e.getClass().getName() +
570 ": " + e.getMessage() +
571 "\"while constructing DataFlavor for: " +
572 decoded);
573 }
574
575 if (flavor != null) {
576 flavors = new ArrayList<>(1);
577 getNativeToFlavor().put(nat, flavors);
578 flavors.add(flavor);
579 getFlavorsForNativeCache.remove(nat);
580 getFlavorsForNativeCache.remove(null);
581
582 List<String> natives = getFlavorToNative().get(flavor);
583 if (natives == null) {
584 natives = new ArrayList<>(1);
585 getFlavorToNative().put(flavor, natives);
586 }
587 natives.add(nat);
588 getNativesForFlavorCache.remove(flavor);
589 getNativesForFlavorCache.remove(null);
590 }
591 }
592
593 return (flavors != null) ? flavors : new ArrayList<>(0);
594 }
595
596 /**
597 * Semantically equivalent to 'flavorToNative.get(flav)'. This method
598 * handles the case where 'flav' is not found in 'flavorToNative' depending
599 * on the value of passes 'synthesize' parameter. If 'synthesize' is
600 * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
601 * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
602 * and 'flavorToNative' remains unaffected.
603 */
604 private List<String> flavorToNativeLookup(final DataFlavor flav,
605 final boolean synthesize) {
606 List<String> natives = getFlavorToNative().get(flav);
607
608 if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
609 DataTransferer transferer = DataTransferer.getInstance();
610 if (transferer != null) {
611 List<String> platformNatives =
612 transferer.getPlatformMappingsForFlavor(flav);
613 if (!platformNatives.isEmpty()) {
614 if (natives != null) {
615 platformNatives.removeAll(new HashSet<>(natives));
616 // Prepend the platform-specific mappings to ensure
617 // that the natives added with
618 // addUnencodedNativeForFlavor() are at the end of
619 // list.
620 platformNatives.addAll(natives);
621 }
622 natives = platformNatives;
623 }
624 }
625 }
626
627 if (natives == null) {
628 if (synthesize) {
629 String encoded = encodeDataFlavor(flav);
630 natives = new ArrayList<>(1);
631 getFlavorToNative().put(flav, natives);
632 natives.add(encoded);
633 getNativesForFlavorCache.remove(flav);
634 getNativesForFlavorCache.remove(null);
635
636 List<DataFlavor> flavors = getNativeToFlavor().get(encoded);
637 if (flavors == null) {
638 flavors = new ArrayList<>(1);
639 getNativeToFlavor().put(encoded, flavors);
640 }
641 flavors.add(flav);
642 getFlavorsForNativeCache.remove(encoded);
643 getFlavorsForNativeCache.remove(null);
644 } else {
645 natives = new ArrayList<>(0);
646 }
647 }
648
649 return natives;
650 }
651
652 /**
653 * Returns a <code>List</code> of <code>String</code> natives to which the
654 * specified <code>DataFlavor</code> can be translated by the data transfer
655 * subsystem. The <code>List</code> will be sorted from best native to
656 * worst. That is, the first native will best reflect data in the specified
657 * flavor to the underlying native platform.
658 * <p>
659 * If the specified <code>DataFlavor</code> is previously unknown to the
660 * data transfer subsystem and the data transfer subsystem is unable to
661 * translate this <code>DataFlavor</code> to any existing native, then
662 * invoking this method will establish a
663 * mapping in both directions between the specified <code>DataFlavor</code>
664 * and an encoded version of its MIME type as its native.
665 *
666 * @param flav the <code>DataFlavor</code> whose corresponding natives
667 * should be returned. If <code>null</code> is specified, all
668 * natives currently known to the data transfer subsystem are
669 * returned in a non-deterministic order.
670 * @return a <code>java.util.List</code> of <code>java.lang.String</code>
671 * objects which are platform-specific representations of platform-
672 * specific data formats
673 *
674 * @see #encodeDataFlavor
675 * @since 1.4
676 */
677 public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
678 List<String> retval = null;
679
680 // Check cache, even for null flav
681 SoftReference<List<String>> ref = getNativesForFlavorCache.get(flav);
682 if (ref != null) {
683 retval = ref.get();
684 if (retval != null) {
685 // Create a copy, because client code can modify the returned
686 // list.
687 return new ArrayList<>(retval);
688 }
689 }
690
691 if (flav == null) {
692 retval = new ArrayList<>(getNativeToFlavor().keySet());
693 } else if (disabledMappingGenerationKeys.contains(flav)) {
694 // In this case we shouldn't synthesize a native for this flavor,
695 // since its mappings were explicitly specified.
696 retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
697 } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
698
699 // For text/* flavors, flavor-to-native mappings specified in
700 // flavormap.properties are stored per flavor's base type.
701 if ("text".equals(flav.getPrimaryType())) {
702 retval = getAllNativesForType(flav.mimeType.getBaseType());
703 if (retval != null) {
704 // To prevent the List stored in the map from modification.
705 retval = new ArrayList(retval);
706 }
707 }
708
709 // Also include text/plain natives, but don't duplicate Strings
710 List<String> textPlainList = getAllNativesForType(TEXT_PLAIN_BASE_TYPE);
711
712 if (textPlainList != null && !textPlainList.isEmpty()) {
713 // To prevent the List stored in the map from modification.
714 // This also guarantees that removeAll() is supported.
715 textPlainList = new ArrayList<>(textPlainList);
716 if (retval != null && !retval.isEmpty()) {
717 // Use HashSet to get constant-time performance for search.
718 textPlainList.removeAll(new HashSet<>(retval));
719 retval.addAll(textPlainList);
720 } else {
721 retval = textPlainList;
722 }
723 }
724
725 if (retval == null || retval.isEmpty()) {
726 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
727 } else {
728 // In this branch it is guaranteed that natives explicitly
729 // listed for flav's MIME type were added with
730 // addUnencodedNativeForFlavor(), so they have lower priority.
731 List<String> explicitList =
732 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
733
734 // flavorToNativeLookup() never returns null.
735 // It can return an empty List, however.
736 if (!explicitList.isEmpty()) {
737 // To prevent the List stored in the map from modification.
738 // This also guarantees that removeAll() is supported.
739 explicitList = new ArrayList<>(explicitList);
740 // Use HashSet to get constant-time performance for search.
741 explicitList.removeAll(new HashSet<>(retval));
742 retval.addAll(explicitList);
743 }
744 }
745 } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
746 retval = getAllNativesForType(flav.mimeType.getBaseType());
747
748 if (retval == null || retval.isEmpty()) {
749 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
750 } else {
751 // In this branch it is guaranteed that natives explicitly
752 // listed for flav's MIME type were added with
753 // addUnencodedNativeForFlavor(), so they have lower priority.
754 List<String> explicitList =
755 flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
756
757 // flavorToNativeLookup() never returns null.
758 // It can return an empty List, however.
759 if (!explicitList.isEmpty()) {
760 // To prevent the List stored in the map from modification.
761 // This also guarantees that add/removeAll() are supported.
762 retval = new ArrayList<>(retval);
763 explicitList = new ArrayList<>(explicitList);
764 // Use HashSet to get constant-time performance for search.
765 explicitList.removeAll(new HashSet<>(retval));
766 retval.addAll(explicitList);
767 }
768 }
769 } else {
770 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
771 }
772
773 getNativesForFlavorCache.put(flav, new SoftReference<>(retval));
774 // Create a copy, because client code can modify the returned list.
775 return new ArrayList<>(retval);
776 }
777
778 /**
779 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
780 * specified <code>String</code> native can be translated by the data
781 * transfer subsystem. The <code>List</code> will be sorted from best
782 * <code>DataFlavor</code> to worst. That is, the first
783 * <code>DataFlavor</code> will best reflect data in the specified
784 * native to a Java application.
785 * <p>
786 * If the specified native is previously unknown to the data transfer
787 * subsystem, and that native has been properly encoded, then invoking this
788 * method will establish a mapping in both directions between the specified
789 * native and a <code>DataFlavor</code> whose MIME type is a decoded
790 * version of the native.
791 * <p>
792 * If the specified native is not a properly encoded native and the
793 * mappings for this native have not been altered with
794 * <code>setFlavorsForNative</code>, then the contents of the
795 * <code>List</code> is platform dependent, but <code>null</code>
796 * cannot be returned.
797 *
798 * @param nat the native whose corresponding <code>DataFlavor</code>s
799 * should be returned. If <code>null</code> is specified, all
800 * <code>DataFlavor</code>s currently known to the data transfer
801 * subsystem are returned in a non-deterministic order.
802 * @return a <code>java.util.List</code> of <code>DataFlavor</code>
803 * objects into which platform-specific data in the specified,
804 * platform-specific native can be translated
805 *
806 * @see #encodeJavaMIMEType
807 * @since 1.4
808 */
809 public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
810
811 // Check cache, even for null nat
812 SoftReference<List<DataFlavor>> ref = getFlavorsForNativeCache.get(nat);
813 if (ref != null) {
814 List<DataFlavor> retval = ref.get();
815 if (retval != null) {
816 return new ArrayList<>(retval);
817 }
818 }
819
820 final LinkedHashSet <DataFlavor> returnValue =
821 new LinkedHashSet<>();
822
823 if (nat == null) {
824 final List<String> natives = getNativesForFlavor(null);
825
826 for (String n : natives)
827 {
828 final List<DataFlavor> flavors = getFlavorsForNative(n);
829
830 for (DataFlavor df : flavors)
831 {
832 returnValue.add(df);
833 }
834 }
835 } else {
836
842
843 final List<DataFlavor> flavorsAndBaseTypes =
844 nativeToFlavorLookup(nat);
845
846 for (DataFlavor df : flavorsAndBaseTypes) {
847 returnValue.add(df);
848 if ("text".equals(df.getPrimaryType())) {
849 try {
850 returnValue.addAll(
851 convertMimeTypeToDataFlavors(
852 new MimeType(df.getMimeType()
853 ).getBaseType()));
854 } catch (MimeTypeParseException e) {
855 e.printStackTrace();
856 }
857 }
858 }
859
860 }
861
862 final List<DataFlavor> arrayList = new ArrayList<>(returnValue);
863 getFlavorsForNativeCache.put(nat, new SoftReference<>(arrayList));
864 return new ArrayList<>(arrayList);
865 }
866
867 private static Set<DataFlavor> convertMimeTypeToDataFlavors(
868 final String baseType) {
869
870 final Set<DataFlavor> returnValue = new LinkedHashSet<>();
871
872 String subType = null;
873
874 try {
875 final MimeType mimeType = new MimeType(baseType);
876 subType = mimeType.getSubType();
877 } catch (MimeTypeParseException mtpe) {
878 // Cannot happen, since we checked all mappings
879 // on load from flavormap.properties.
880 assert(false);
881 }
882
883 if (DataTransferer.doesSubtypeSupportCharset(subType, null)) {
884 if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
885 {
886 returnValue.add(DataFlavor.stringFlavor);
887 }
888
889 for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
890 final String mimeType = baseType + ";charset=Unicode;class=" +
991 * data transfer subsystem to their most preferred
992 * <code>String</code> natives will be returned.
993 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
994 * <code>String</code> natives
995 *
996 * @see #getNativesForFlavor
997 * @see #encodeDataFlavor
998 */
999 public synchronized Map<DataFlavor,String>
1000 getNativesForFlavors(DataFlavor[] flavors)
1001 {
1002 // Use getNativesForFlavor to generate extra natives for text flavors
1003 // and stringFlavor
1004
1005 if (flavors == null) {
1006 List flavor_list = getFlavorsForNative(null);
1007 flavors = new DataFlavor[flavor_list.size()];
1008 flavor_list.toArray(flavors);
1009 }
1010
1011 Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
1012 for (DataFlavor flavor : flavors) {
1013 List<String> natives = getNativesForFlavor(flavor);
1014 String nat = (natives.isEmpty()) ? null : natives.get(0);
1015 retval.put(flavor, nat);
1016 }
1017
1018 return retval;
1019 }
1020
1021 /**
1022 * Returns a <code>Map</code> of the specified <code>String</code> natives
1023 * to their most preferred <code>DataFlavor</code>. Each
1024 * <code>DataFlavor</code> value will be the same as the first
1025 * <code>DataFlavor</code> in the List returned by
1026 * <code>getFlavorsForNative</code> for the specified native.
1027 * <p>
1028 * If a specified native is previously unknown to the data transfer
1029 * subsystem, and that native has been properly encoded, then invoking this
1030 * method will establish a mapping in both directions between the specified
1031 * native and a <code>DataFlavor</code> whose MIME type is a decoded
1032 * version of the native.
1033 *
1034 * @param natives an array of <code>String</code>s which will be the
1035 * key set of the returned <code>Map</code>. If <code>null</code> is
1036 * specified, a mapping of all supported <code>String</code> natives
1037 * to their most preferred <code>DataFlavor</code>s will be
1038 * returned.
1039 * @return a <code>java.util.Map</code> of <code>String</code> natives to
1040 * <code>DataFlavor</code>s
1041 *
1042 * @see #getFlavorsForNative
1043 * @see #encodeJavaMIMEType
1044 */
1045 public synchronized Map<String,DataFlavor>
1046 getFlavorsForNatives(String[] natives)
1047 {
1048 // Use getFlavorsForNative to generate extra flavors for text natives
1049
1050 if (natives == null) {
1051 List native_list = getNativesForFlavor(null);
1052 natives = new String[native_list.size()];
1053 native_list.toArray(natives);
1054 }
1055
1056 Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
1057 for (String aNative : natives) {
1058 List<DataFlavor> flavors = getFlavorsForNative(aNative);
1059 DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
1060 retval.put(aNative, flav);
1061 }
1062
1063 return retval;
1064 }
1065
1066 /**
1067 * Adds a mapping from the specified <code>DataFlavor</code> (and all
1068 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
1069 * to the specified <code>String</code> native.
1070 * Unlike <code>getNativesForFlavor</code>, the mapping will only be
1071 * established in one direction, and the native will not be encoded. To
1072 * establish a two-way mapping, call
1073 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1074 * be of lower priority than any existing mapping.
1075 * This method has no effect if a mapping from the specified or equal
1076 * <code>DataFlavor</code> to the specified <code>String</code> native
1077 * already exists.
1078 *
1079 * @param flav the <code>DataFlavor</code> key for the mapping
1080 * @param nat the <code>String</code> native value for the mapping
1081 * @throws NullPointerException if flav or nat is <code>null</code>
1082 *
1083 * @see #addFlavorForUnencodedNative
1084 * @since 1.4
1085 */
1086 public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
1087 String nat) {
1088 if (flav == null || nat == null) {
1089 throw new NullPointerException("null arguments not permitted");
1090 }
1091
1092 List<String> natives = getFlavorToNative().get(flav);
1093 if (natives == null) {
1094 natives = new ArrayList<>(1);
1095 getFlavorToNative().put(flav, natives);
1096 } else if (natives.contains(nat)) {
1097 return;
1098 }
1099 natives.add(nat);
1100 getNativesForFlavorCache.remove(flav);
1101 getNativesForFlavorCache.remove(null);
1102 }
1103
1104 /**
1105 * Discards the current mappings for the specified <code>DataFlavor</code>
1106 * and all <code>DataFlavor</code>s equal to the specified
1107 * <code>DataFlavor</code>, and creates new mappings to the
1108 * specified <code>String</code> natives.
1109 * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1110 * established in one direction, and the natives will not be encoded. To
1111 * establish two-way mappings, call <code>setFlavorsForNative</code>
1112 * as well. The first native in the array will represent the highest
1113 * priority mapping. Subsequent natives will represent mappings of
1114 * decreasing priority.
1119 * <p>
1120 * It is recommended that client code not reset mappings established by the
1121 * data transfer subsystem. This method should only be used for
1122 * application-level mappings.
1123 *
1124 * @param flav the <code>DataFlavor</code> key for the mappings
1125 * @param natives the <code>String</code> native values for the mappings
1126 * @throws NullPointerException if flav or natives is <code>null</code>
1127 * or if natives contains <code>null</code> elements
1128 *
1129 * @see #setFlavorsForNative
1130 * @since 1.4
1131 */
1132 public synchronized void setNativesForFlavor(DataFlavor flav,
1133 String[] natives) {
1134 if (flav == null || natives == null) {
1135 throw new NullPointerException("null arguments not permitted");
1136 }
1137
1138 getFlavorToNative().remove(flav);
1139 for (String aNative : natives) {
1140 addUnencodedNativeForFlavor(flav, aNative);
1141 }
1142 disabledMappingGenerationKeys.add(flav);
1143 // Clear the cache to handle the case of empty natives.
1144 getNativesForFlavorCache.remove(flav);
1145 getNativesForFlavorCache.remove(null);
1146 }
1147
1148 /**
1149 * Adds a mapping from a single <code>String</code> native to a single
1150 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1151 * mapping will only be established in one direction, and the native will
1152 * not be encoded. To establish a two-way mapping, call
1153 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1154 * be of lower priority than any existing mapping.
1155 * This method has no effect if a mapping from the specified
1156 * <code>String</code> native to the specified or equal
1157 * <code>DataFlavor</code> already exists.
1158 *
1159 * @param nat the <code>String</code> native key for the mapping
1160 * @param flav the <code>DataFlavor</code> value for the mapping
1161 * @throws NullPointerException if nat or flav is <code>null</code>
1162 *
1163 * @see #addUnencodedNativeForFlavor
1164 * @since 1.4
1165 */
1166 public synchronized void addFlavorForUnencodedNative(String nat,
1167 DataFlavor flav) {
1168 if (nat == null || flav == null) {
1169 throw new NullPointerException("null arguments not permitted");
1170 }
1171
1172 List<DataFlavor> flavors = getNativeToFlavor().get(nat);
1173 if (flavors == null) {
1174 flavors = new ArrayList<>(1);
1175 getNativeToFlavor().put(nat, flavors);
1176 } else if (flavors.contains(flav)) {
1177 return;
1178 }
1179 flavors.add(flav);
1180 getFlavorsForNativeCache.remove(nat);
1181 getFlavorsForNativeCache.remove(null);
1182 }
1183
1184 /**
1185 * Discards the current mappings for the specified <code>String</code>
1186 * native, and creates new mappings to the specified
1187 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1188 * mappings will only be established in one direction, and the natives need
1189 * not be encoded. To establish two-way mappings, call
1190 * <code>setNativesForFlavor</code> as well. The first
1191 * <code>DataFlavor</code> in the array will represent the highest priority
1192 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1193 * decreasing priority.
1194 * <p>
1198 * <p>
1199 * It is recommended that client code not reset mappings established by the
1200 * data transfer subsystem. This method should only be used for
1201 * application-level mappings.
1202 *
1203 * @param nat the <code>String</code> native key for the mappings
1204 * @param flavors the <code>DataFlavor</code> values for the mappings
1205 * @throws NullPointerException if nat or flavors is <code>null</code>
1206 * or if flavors contains <code>null</code> elements
1207 *
1208 * @see #setNativesForFlavor
1209 * @since 1.4
1210 */
1211 public synchronized void setFlavorsForNative(String nat,
1212 DataFlavor[] flavors) {
1213 if (nat == null || flavors == null) {
1214 throw new NullPointerException("null arguments not permitted");
1215 }
1216
1217 getNativeToFlavor().remove(nat);
1218 for (DataFlavor flavor : flavors) {
1219 addFlavorForUnencodedNative(nat, flavor);
1220 }
1221 disabledMappingGenerationKeys.add(nat);
1222 // Clear the cache to handle the case of empty flavors.
1223 getFlavorsForNativeCache.remove(nat);
1224 getFlavorsForNativeCache.remove(null);
1225 }
1226
1227 /**
1228 * Encodes a MIME type for use as a <code>String</code> native. The format
1229 * of an encoded representation of a MIME type is implementation-dependent.
1230 * The only restrictions are:
1231 * <ul>
1232 * <li>The encoded representation is <code>null</code> if and only if the
1233 * MIME type <code>String</code> is <code>null</code>.</li>
1234 * <li>The encoded representations for two non-<code>null</code> MIME type
1235 * <code>String</code>s are equal if and only if these <code>String</code>s
1236 * are equal according to <code>String.equals(Object)</code>.</li>
1237 * </ul>
1238 * <p>
1239 * The reference implementation of this method returns the specified MIME
1302 ? nat.substring(JavaMIME.length(), nat.length()).trim()
1303 : null;
1304 }
1305
1306 /**
1307 * Decodes a <code>String</code> native for use as a
1308 * <code>DataFlavor</code>.
1309 *
1310 * @param nat the <code>String</code> to decode
1311 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1312 * nat is not an encoded <code>String</code> native
1313 */
1314 public static DataFlavor decodeDataFlavor(String nat)
1315 throws ClassNotFoundException
1316 {
1317 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1318 return (retval_str != null)
1319 ? new DataFlavor(retval_str)
1320 : null;
1321 }
1322
1323 private List<String> getAllNativesForType(String type) {
1324 List<String> retval = null;
1325 for (DataFlavor dataFlavor : convertMimeTypeToDataFlavors(type)) {
1326 List<String> natives = getFlavorToNative().get(dataFlavor);
1327 if (!natives.isEmpty()) {
1328 if (retval == null) {
1329 retval = new ArrayList<>();
1330 }
1331 retval.addAll(natives);
1332 }
1333 }
1334 return retval;
1335 }
1336 }
|