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