001/* SystemFlavorMap.java -- Maps between native flavor names and MIME types.
002   Copyright (C) 2001, 2004  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt.datatransfer;
040
041import java.awt.Toolkit;
042import java.io.File;
043import java.io.FileInputStream;
044import java.io.IOException;
045import java.io.InputStream;
046import java.net.URL;
047import java.security.AccessController;
048import java.security.PrivilegedAction;
049import java.util.ArrayList;
050import java.util.Collection;
051import java.util.Enumeration;
052import java.util.HashMap;
053import java.util.List;
054import java.util.Map;
055import java.util.Properties;
056import 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  */
067public 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