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