Source for java.awt.datatransfer.SystemFlavorMap

   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:    */
  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:    */
 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