001    /* SystemFlavorMap.java -- Maps between native flavor names and MIME types.
002       Copyright (C) 2001, 2004  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt.datatransfer;
040    
041    import java.awt.Toolkit;
042    import java.io.File;
043    import java.io.FileInputStream;
044    import java.io.IOException;
045    import java.io.InputStream;
046    import java.net.URL;
047    import java.security.AccessController;
048    import java.security.PrivilegedAction;
049    import java.util.ArrayList;
050    import java.util.Collection;
051    import java.util.Enumeration;
052    import java.util.HashMap;
053    import java.util.List;
054    import java.util.Map;
055    import java.util.Properties;
056    import java.util.WeakHashMap;
057    
058    /**
059      * This class maps between native platform type names and DataFlavors.
060      *
061      * XXX - The current implementation does no mapping at all.
062      *
063      * @author Mark Wielaard (mark@klomp.org)
064      *
065      * @since 1.2
066      */
067    public final class SystemFlavorMap implements FlavorMap, FlavorTable
068    {
069      /**
070       * The map which maps the thread's <code>ClassLoaders</code> to 
071       * <code>SystemFlavorMaps</code>.
072       */
073      private static final Map systemFlavorMaps = new WeakHashMap();
074      
075      /**
076       * Constant which is used to prefix encode Java MIME types.
077       */
078      private static final String GNU_JAVA_MIME_PREFIX = "gnu.java:";
079      
080      /**
081       * This map maps native <code>String</code>s to lists of 
082       * <code>DataFlavor</code>s
083       */
084      private HashMap<String,List<DataFlavor>> nativeToFlavorMap =
085        new HashMap<String,List<DataFlavor>>();
086      
087      /**
088       * This map maps <code>DataFlavor</code>s to lists of native 
089       * <code>String</code>s
090       */
091      private HashMap<DataFlavor, List<String>> flavorToNativeMap =
092        new HashMap<DataFlavor, List<String>>();
093      
094      /**
095       * Private constructor.
096       */
097      private SystemFlavorMap ()
098      {
099        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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */
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       */ 
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    
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       */
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       */
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       */
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       */
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