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