1 /* SystemFlavorMap.java -- Maps between native flavor names and MIME types.
2    Copyright (C) 2001, 2004  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.awt.datatransfer;
40 
41 import java.awt.Toolkit;
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.net.URL;
47 import java.security.AccessController;
48 import java.security.PrivilegedAction;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.Enumeration;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Properties;
56 import java.util.WeakHashMap;
57 
58 /**
59   * This class maps between native platform type names and DataFlavors.
60   *
61   * XXX - The current implementation does no mapping at all.
62   *
63   * @author Mark Wielaard (mark@klomp.org)
64   *
65   * @since 1.2
66   */
67 public final class SystemFlavorMap implements FlavorMap, FlavorTable
68 {
69   /**
70    * The map which maps the thread's <code>ClassLoaders</code> to
71    * <code>SystemFlavorMaps</code>.
72    */
73   private static final Map systemFlavorMaps = new WeakHashMap();
74 
75   /**
76    * Constant which is used to prefix encode Java MIME types.
77    */
78   private static final String GNU_JAVA_MIME_PREFIX = "gnu.java:";
79 
80   /**
81    * This map maps native <code>String</code>s to lists of
82    * <code>DataFlavor</code>s
83    */
84   private HashMap<String,List<DataFlavor>> nativeToFlavorMap =
85     new HashMap<String,List<DataFlavor>>();
86 
87   /**
88    * This map maps <code>DataFlavor</code>s to lists of native
89    * <code>String</code>s
90    */
91   private HashMap<DataFlavor, List<String>> flavorToNativeMap =
92     new HashMap<DataFlavor, List<String>>();
93 
94   /**
95    * Private constructor.
96    */
SystemFlavorMap()97   private SystemFlavorMap ()
98   {
99     AccessController.doPrivileged
100     (new PrivilegedAction<Object>()
101      {
102        public Object run()
103        {
104          try
105            {
106              // Load installed flavormap.properties first.
107              String sep = File.separator;
108              File propsFile =
109                new File(System.getProperty("gnu.classpath.home.url")
110                         + sep + "accessibility.properties");
111              InputStream in = new FileInputStream(propsFile);
112              Properties props = new Properties();
113              props.load(in);
114              in.close();
115 
116              String augmented = Toolkit.getProperty("AWT.DnD.flavorMapFileURL",
117                                                     null);
118              if (augmented != null)
119                {
120                  URL url = new URL(augmented);
121                  in = url.openStream();
122                  props.load(in);
123                }
124              setupMapping(props);
125            }
126          catch (IOException ex)
127            {
128              // Can't do anything about it.
129            }
130          return null;
131        }
132      });
133   }
134 
135   /**
136    * Sets up the mapping from native to mime types and vice versa as specified
137    * in the flavormap.properties file.
138    *
139    * This is package private to avoid an accessor method.
140    *
141    * @param props the properties file
142    */
setupMapping(Properties props)143   void setupMapping(Properties props)
144   {
145     Enumeration propNames = props.propertyNames();
146     while (propNames.hasMoreElements())
147       {
148         try
149           {
150             String nat = (String) propNames.nextElement();
151             String mime = (String) props.getProperty(nat);
152             // Check valid mime type.
153             MimeType type = new MimeType(mime);
154             DataFlavor flav = new DataFlavor(mime);
155 
156             List<DataFlavor> flavs = nativeToFlavorMap.get(nat);
157             if (flavs == null)
158               {
159                 flavs = new ArrayList<DataFlavor>();
160                 nativeToFlavorMap.put(nat, flavs);
161               }
162             List<String> nats = flavorToNativeMap.get(flav);
163             if (nats == null)
164               {
165                 nats = new ArrayList<String>();
166                 flavorToNativeMap.put(flav, nats);
167               }
168             flavs.add(flav);
169             nats.add(nat);
170           }
171         catch (ClassNotFoundException ex)
172           {
173             // Skip.
174           }
175         catch (MimeTypeParseException ex)
176           {
177             // Skip.
178           }
179       }
180   }
181 
182   /**
183    * Maps the specified <code>DataFlavor</code> objects to the native
184    * data type name.  The returned <code>Map</code> has keys that are
185    * the data flavors and values that are strings.  The returned map
186    * may be modified.  This can be useful for implementing nested mappings.
187    *
188    * @param flavors An array of data flavors to map
189    *                or null for all data flavors.
190    *
191    * @return A <code>Map</code> of native data types to data flavors.
192    */
getNativesForFlavors(DataFlavor[] flavors)193   public Map<DataFlavor, String> getNativesForFlavors (DataFlavor[] flavors)
194   {
195     return new HashMap<DataFlavor, String>();
196   }
197 
198   /**
199    * Maps the specified native type names to <code>DataFlavor</code>'s.
200    * The returned <code>Map</code> has keys that are strings and values
201    * that are <code>DataFlavor</code>'s.  The returned map may be
202    * modified.  This can be useful for implementing nested mappings.
203    *
204    * @param natives An array of native types to map
205    *                or null for all native types.
206    *
207    * @return A <code>Map</code> of data flavors to native type names.
208    */
getFlavorsForNatives(String[] natives)209   public Map<String, DataFlavor> getFlavorsForNatives (String[] natives)
210   {
211     return new HashMap<String, DataFlavor>();
212   }
213 
214   /**
215    * Returns the (System)FlavorMap for the current thread's
216    * ClassLoader.
217    */
getDefaultFlavorMap()218   public static FlavorMap getDefaultFlavorMap ()
219   {
220     ClassLoader classLoader = Thread.currentThread()
221         .getContextClassLoader();
222 
223     //if ContextClassLoader not set, use system default
224     if (classLoader == null)
225       {
226         classLoader = ClassLoader.getSystemClassLoader();
227       }
228 
229     synchronized(systemFlavorMaps)
230       {
231         FlavorMap map = (FlavorMap)
232             systemFlavorMaps.get(classLoader);
233         if (map == null)
234           {
235             map = new SystemFlavorMap();
236             systemFlavorMaps.put(classLoader, map);
237           }
238         return map;
239       }
240   }
241 
242   /**
243    * Encodes a MIME type for use as a <code>String</code> native. The format
244    * of an encoded representation of a MIME type is implementation-dependent.
245    * The only restrictions are:
246    * <ul>
247    * <li>The encoded representation is <code>null</code> if and only if the
248    * MIME type <code>String</code> is <code>null</code>.</li>
249    * <li>The encoded representations for two non-<code>null</code> MIME type
250    * <code>String</code>s are equal if and only if these <code>String</code>s
251    * are equal according to <code>String.equals(Object)</code>.</li>
252    * </ul>
253    * <p>
254    * The present implementation of this method returns the specified MIME
255    * type <code>String</code> prefixed with <code>gnu.java:</code>.
256    *
257    * @param mime the MIME type to encode
258    * @return the encoded <code>String</code>, or <code>null</code> if
259    *         mimeType is <code>null</code>
260    */
encodeJavaMIMEType(String mime)261   public static String encodeJavaMIMEType (String mime)
262   {
263     if (mime != null)
264       return GNU_JAVA_MIME_PREFIX + mime;
265     else
266       return null;
267   }
268 
269   /**
270    * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
271    * native. The format of an encoded <code>DataFlavor</code> is
272    * implementation-dependent. The only restrictions are:
273    * <ul>
274    * <li>The encoded representation is <code>null</code> if and only if the
275    * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
276    * <code>String</code> is <code>null</code>.</li>
277    * <li>The encoded representations for two non-<code>null</code>
278    * <code>DataFlavor</code>s with non-<code>null</code> MIME type
279    * <code>String</code>s are equal if and only if the MIME type
280    * <code>String</code>s of these <code>DataFlavor</code>s are equal
281    * according to <code>String.equals(Object)</code>.</li>
282    * </ul>
283    * <p>
284    * The present implementation of this method returns the MIME type
285    * <code>String</code> of the specified <code>DataFlavor</code> prefixed
286    * with <code>gnu.java:</code>.
287    *
288    * @param df the <code>DataFlavor</code> to encode
289    * @return the encoded <code>String</code>, or <code>null</code> if
290    *         flav is <code>null</code> or has a <code>null</code> MIME type
291    */
encodeDataFlavor(DataFlavor df)292   public static String encodeDataFlavor (DataFlavor df)
293   {
294     if (df != null)
295       {
296         return encodeJavaMIMEType(df.getMimeType());
297       }
298     else
299       return null;
300   }
301 
302   /**
303    * Returns true if the native type name can be represented as
304    * a java mime type. Returns <code>false</code> if parameter is
305    * <code>null</code>.
306    */
isJavaMIMEType(String name)307   public static boolean isJavaMIMEType (String name)
308   {
309     return (name != null && name.startsWith(GNU_JAVA_MIME_PREFIX));
310   }
311 
312   /**
313    * Decodes a <code>String</code> native for use as a Java MIME type.
314    *
315    * @param name the <code>String</code> to decode
316    * @return the decoded Java MIME type, or <code>null</code> if nat
317    *         is not an encoded <code>String</code> native
318    */
decodeJavaMIMEType(String name)319   public static String decodeJavaMIMEType (String name)
320   {
321     if (isJavaMIMEType(name))
322       {
323         return name.substring(GNU_JAVA_MIME_PREFIX.length());
324       }
325     else
326       return null;
327   }
328 
329   /**
330    * Returns the data flavor given the native type name
331    * or null when no such data flavor exists.
332    */
decodeDataFlavor(String name)333   public static DataFlavor decodeDataFlavor (String name)
334     throws ClassNotFoundException
335   {
336     String javaMIMEType = decodeJavaMIMEType (name);
337 
338     if (javaMIMEType != null)
339       return new DataFlavor (javaMIMEType);
340     else
341       return null;
342   }
343 
344   /**
345    * Returns a List of <code>DataFlavors</code> to which the specified
346    * <code>String</code> native can be translated by the data transfer
347    * subsystem. The <code>List</code> will be sorted from best
348    * <code>DataFlavor</code> to worst. That is, the first <code>DataFlavor
349    * </code> will best reflect data in the specified native to a Java
350    * application.
351    * <p>
352    * If the specified native is previously unknown to the data transfer
353    * subsystem, and that native has been properly encoded, then invoking
354    * this method will establish a mapping in both directions between the
355    * specified native and a DataFlavor whose MIME type is a decoded
356    * version of the native.
357    */
getFlavorsForNative(String nat)358   public List<DataFlavor> getFlavorsForNative(String nat)
359   {
360     List<DataFlavor> ret = new ArrayList<DataFlavor>();
361     if (nat == null)
362       {
363         Collection<List<DataFlavor>> all = nativeToFlavorMap.values();
364         for (List<DataFlavor> list : all)
365           {
366             for (DataFlavor flav : list)
367               {
368                 if (! ret.contains(flav))
369                   ret.add(flav);
370               }
371           }
372       }
373     else
374       {
375         List<DataFlavor> list = nativeToFlavorMap.get(nat);
376         if (list != null)
377           ret.addAll(list);
378       }
379     return ret;
380   }
381 
getNativesForFlavor(DataFlavor flav)382   public List<String> getNativesForFlavor (DataFlavor flav)
383   {
384     List<String> ret = new ArrayList<String>();
385     if (flav == null)
386       {
387         Collection<List<String>> all = flavorToNativeMap.values();
388         for (List<String> list : all)
389           {
390             for (String nat : list)
391               {
392                 if (! ret.contains(nat))
393                   ret.add(nat);
394               }
395           }
396       }
397     else
398       {
399         List<String> list = flavorToNativeMap.get(flav);
400         if (list != null)
401           ret.addAll(list);
402       }
403     return ret;
404   }
405 
406   /**
407    * Adds a mapping from a single <code>String</code> native to a single
408    * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
409    * mapping will only be established in one direction, and the native will
410    * not be encoded. To establish a two-way mapping, call
411    * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
412    * be of lower priority than any existing mapping.
413    * This method has no effect if a mapping from the specified
414    * <code>String</code> native to the specified or equal
415    * <code>DataFlavor</code> already exists.
416    *
417    * @param nativeStr the <code>String</code> native key for the mapping
418    * @param flavor the <code>DataFlavor</code> value for the mapping
419    * @throws NullPointerException if nat or flav is <code>null</code>
420    *
421    * @see #addUnencodedNativeForFlavor
422    * @since 1.4
423    */
addFlavorForUnencodedNative(String nativeStr, DataFlavor flavor)424   public synchronized void addFlavorForUnencodedNative(String nativeStr,
425                                                        DataFlavor flavor)
426   {
427     if ((nativeStr == null) || (flavor == null))
428       throw new NullPointerException();
429     List<DataFlavor> flavors = nativeToFlavorMap.get(nativeStr);
430     if (flavors == null)
431       {
432         flavors = new ArrayList<DataFlavor>();
433         nativeToFlavorMap.put(nativeStr, flavors);
434       }
435     else
436       {
437         if (! flavors.contains(flavor))
438           flavors.add(flavor);
439       }
440   }
441 
442   /**
443    * Adds a mapping from the specified <code>DataFlavor</code> (and all
444    * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
445    * to the specified <code>String</code> native.
446    * Unlike <code>getNativesForFlavor</code>, the mapping will only be
447    * established in one direction, and the native will not be encoded. To
448    * establish a two-way mapping, call
449    * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
450    * be of lower priority than any existing mapping.
451    * This method has no effect if a mapping from the specified or equal
452    * <code>DataFlavor</code> to the specified <code>String</code> native
453    * already exists.
454    *
455    * @param flavor the <code>DataFlavor</code> key for the mapping
456    * @param nativeStr the <code>String</code> native value for the mapping
457    * @throws NullPointerException if flav or nat is <code>null</code>
458    *
459    * @see #addFlavorForUnencodedNative
460    * @since 1.4
461    */
addUnencodedNativeForFlavor(DataFlavor flavor, String nativeStr)462   public synchronized void addUnencodedNativeForFlavor(DataFlavor flavor,
463                                                        String nativeStr)
464   {
465     if ((nativeStr == null) || (flavor == null))
466       throw new NullPointerException();
467     List<String> natives = flavorToNativeMap.get(flavor);
468     if (natives == null)
469       {
470         natives = new ArrayList<String>();
471         flavorToNativeMap.put(flavor, natives);
472       }
473     else
474       {
475         if (! natives.contains(nativeStr))
476           natives.add(nativeStr);
477       }
478   }
479 
480   /**
481    * Discards the current mappings for the specified <code>DataFlavor</code>
482    * and all <code>DataFlavor</code>s equal to the specified
483    * <code>DataFlavor</code>, and creates new mappings to the
484    * specified <code>String</code> natives.
485    * Unlike <code>getNativesForFlavor</code>, the mappings will only be
486    * established in one direction, and the natives will not be encoded. To
487    * establish two-way mappings, call <code>setFlavorsForNative</code>
488    * as well. The first native in the array will represent the highest
489    * priority mapping. Subsequent natives will represent mappings of
490    * decreasing priority.
491    * <p>
492    * If the array contains several elements that reference equal
493    * <code>String</code> natives, this method will establish new mappings
494    * for the first of those elements and ignore the rest of them.
495    * <p>
496    * It is recommended that client code not reset mappings established by the
497    * data transfer subsystem. This method should only be used for
498    * application-level mappings.
499    *
500    * @param flavor the <code>DataFlavor</code> key for the mappings
501    * @param natives the <code>String</code> native values for the mappings
502    * @throws NullPointerException if flav or natives is <code>null</code>
503    *         or if natives contains <code>null</code> elements
504    *
505    * @see #setFlavorsForNative
506    * @since 1.4
507    */
setNativesForFlavor(DataFlavor flavor, String[] natives)508   public synchronized void setNativesForFlavor(DataFlavor flavor,
509                                                String[] natives)
510   {
511     if ((natives == null) || (flavor == null))
512       throw new NullPointerException();
513 
514     flavorToNativeMap.remove(flavor);
515     for (int i = 0; i < natives.length; i++)
516       {
517         addUnencodedNativeForFlavor(flavor, natives[i]);
518       }
519   }
520 
521   /**
522    * Discards the current mappings for the specified <code>String</code>
523    * native, and creates new mappings to the specified
524    * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
525    * mappings will only be established in one direction, and the natives need
526    * not be encoded. To establish two-way mappings, call
527    * <code>setNativesForFlavor</code> as well. The first
528    * <code>DataFlavor</code> in the array will represent the highest priority
529    * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
530    * decreasing priority.
531    * <p>
532    * If the array contains several elements that reference equal
533    * <code>DataFlavor</code>s, this method will establish new mappings
534    * for the first of those elements and ignore the rest of them.
535    * <p>
536    * It is recommended that client code not reset mappings established by the
537    * data transfer subsystem. This method should only be used for
538    * application-level mappings.
539    *
540    * @param nativeStr the <code>String</code> native key for the mappings
541    * @param flavors the <code>DataFlavor</code> values for the mappings
542    * @throws NullPointerException if nat or flavors is <code>null</code>
543    *         or if flavors contains <code>null</code> elements
544    *
545    * @see #setNativesForFlavor
546    * @since 1.4
547    */
setFlavorsForNative(String nativeStr, DataFlavor[] flavors)548   public synchronized void setFlavorsForNative(String nativeStr,
549                                                DataFlavor[] flavors)
550   {
551     if ((nativeStr == null) || (flavors == null))
552       throw new NullPointerException();
553 
554     nativeToFlavorMap.remove(nativeStr);
555     for (int i = 0; i < flavors.length; i++)
556       {
557         addFlavorForUnencodedNative(nativeStr, flavors[i]);
558       }
559   }
560 
561 } // class SystemFlavorMap
562