Frames | No Frames |
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