1. /*
  2. * @(#)SystemFlavorMap.java 1.31 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.datatransfer;
  8. import java.awt.Toolkit;
  9. import java.lang.ref.SoftReference;
  10. import java.io.BufferedReader;
  11. import java.io.File;
  12. import java.io.InputStreamReader;
  13. import java.io.IOException;
  14. import java.net.URL;
  15. import java.net.MalformedURLException;
  16. import java.util.ArrayList;
  17. import java.util.HashMap;
  18. import java.util.HashSet;
  19. import java.util.Iterator;
  20. import java.util.LinkedList;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import java.util.WeakHashMap;
  25. import sun.awt.datatransfer.DataTransferer;
  26. /**
  27. * The SystemFlavorMap is a configurable map between "natives" (Strings), which
  28. * correspond to platform-specific data formats, and "flavors" (DataFlavors),
  29. * which correspond to platform-independent MIME types. This mapping is used
  30. * by the data transfer subsystem to transfer data between Java and native
  31. * applications, and between Java applications in separate VMs.
  32. * <p>
  33. * In the Sun reference implementation, the default SystemFlavorMap is
  34. * initialized by the file <code>jre/lib/flavormap.properties</code> and the
  35. * contents of the URL referenced by the AWT property
  36. * <code>AWT.DnD.flavorMapFileURL</code>. See <code>flavormap.properties</code>
  37. * for details.
  38. *
  39. * @version 1.31, 01/23/03
  40. * @since 1.2
  41. */
  42. public final class SystemFlavorMap implements FlavorMap, FlavorTable {
  43. /**
  44. * Constant prefix used to tag Java types converted to native platform
  45. * type.
  46. */
  47. private static String JavaMIME = "JAVA_DATAFLAVOR:";
  48. /**
  49. * System singleton which maps a thread's ClassLoader to a SystemFlavorMap.
  50. */
  51. private static final WeakHashMap flavorMaps = new WeakHashMap();
  52. /**
  53. * Copied from java.util.Properties.
  54. */
  55. private static final String keyValueSeparators = "=: \t\r\n\f";
  56. private static final String strictKeyValueSeparators = "=:";
  57. private static final String whiteSpaceChars = " \t\r\n\f";
  58. /**
  59. * The list of valid, decoded text flavor representation classes, in order
  60. * from best to worst.
  61. */
  62. private static final String[] UNICODE_TEXT_CLASSES = {
  63. "java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
  64. };
  65. /**
  66. * The list of valid, encoded text flavor representation classes, in order
  67. * from best to worst.
  68. */
  69. private static final String[] ENCODED_TEXT_CLASSES = {
  70. "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
  71. };
  72. /**
  73. * A String representing text/plain MIME type.
  74. */
  75. private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
  76. /**
  77. * This constant is passed to flavorToNativeLookup() to indicate that a
  78. * a native should be synthesized, stored, and returned by encoding the
  79. * DataFlavor's MIME type in case if the DataFlavor is not found in
  80. * 'flavorToNative' map.
  81. */
  82. private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
  83. /**
  84. * Maps native Strings to Lists of DataFlavors (or base type Strings for
  85. * text DataFlavors).
  86. */
  87. private Map nativeToFlavor = new HashMap();
  88. /**
  89. * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
  90. * native Strings.
  91. */
  92. private Map flavorToNative = new HashMap();
  93. /**
  94. * Caches the result of getNativesForFlavor(). Maps DataFlavors to
  95. * SoftReferences which reference Lists of String natives.
  96. */
  97. private Map getNativesForFlavorCache = new HashMap();
  98. /**
  99. * Caches the result getFlavorsForNative(). Maps String natives to
  100. * SoftReferences which reference Lists of DataFlavors.
  101. */
  102. private Map getFlavorsForNativeCache = new HashMap();
  103. /**
  104. * Dynamic mapping generation used for text mappings should not be applied
  105. * to the DataFlavors and String natives for which the mappings have been
  106. * explicitly specified with setFlavorsForNative() or
  107. * setNativesForFlavor(). This keeps all such keys.
  108. */
  109. private Set disabledMappingGenerationKeys = new HashSet();
  110. /**
  111. * Returns the default FlavorMap for this thread's ClassLoader.
  112. */
  113. public static FlavorMap getDefaultFlavorMap() {
  114. ClassLoader contextClassLoader =
  115. Thread.currentThread().getContextClassLoader();
  116. if (contextClassLoader == null) {
  117. contextClassLoader = ClassLoader.getSystemClassLoader();
  118. }
  119. FlavorMap fm;
  120. synchronized(flavorMaps) {
  121. fm = (FlavorMap)flavorMaps.get(contextClassLoader);
  122. if (fm == null) {
  123. fm = new SystemFlavorMap();
  124. flavorMaps.put(contextClassLoader, fm);
  125. }
  126. }
  127. return fm;
  128. }
  129. /**
  130. * Constructs a SystemFlavorMap by reading flavormap.properties and
  131. * AWT.DnD.flavorMapFileURL.
  132. */
  133. private SystemFlavorMap() {
  134. BufferedReader flavormapDotProperties = (BufferedReader)
  135. java.security.AccessController.doPrivileged(
  136. new java.security.PrivilegedAction() {
  137. public Object run() {
  138. String fileName =
  139. System.getProperty("java.home") +
  140. File.separator +
  141. "lib" +
  142. File.separator +
  143. "flavormap.properties";
  144. try {
  145. return new BufferedReader
  146. (new InputStreamReader
  147. (new File(fileName).toURI().toURL().openStream(), "ISO-8859-1"));
  148. } catch (MalformedURLException e) {
  149. System.err.println("MalformedURLException:" + e + " while loading default flavormap.properties file:" + fileName);
  150. } catch (IOException e) {
  151. System.err.println("IOException:" + e + " while loading default flavormap.properties file:" + fileName);
  152. }
  153. return null;
  154. }
  155. });
  156. BufferedReader flavormapURL = (BufferedReader)
  157. java.security.AccessController.doPrivileged(
  158. new java.security.PrivilegedAction() {
  159. public Object run() {
  160. String url = Toolkit.getDefaultToolkit().getProperty
  161. ("AWT.DnD.flavorMapFileURL", null);
  162. if (url == null) {
  163. return null;
  164. }
  165. try {
  166. return new BufferedReader
  167. (new InputStreamReader
  168. (new URL(url).openStream(), "ISO-8859-1"));
  169. } catch (MalformedURLException e) {
  170. System.err.println("MalformedURLException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
  171. } catch (IOException e) {
  172. System.err.println("IOException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url);
  173. }
  174. return null;
  175. }
  176. });
  177. if (flavormapDotProperties != null) {
  178. try {
  179. parseAndStoreReader(flavormapDotProperties);
  180. } catch (IOException e) {
  181. System.err.println("IOException:" + e + " while parsing default flavormap.properties file");
  182. }
  183. }
  184. if (flavormapURL != null) {
  185. try {
  186. parseAndStoreReader(flavormapURL);
  187. } catch (IOException e) {
  188. System.err.println("IOException:" + e + " while parsing AWT.DnD.flavorMapFileURL");
  189. }
  190. }
  191. }
  192. /**
  193. * Copied code from java.util.Properties. Parsing the data ourselves is the
  194. * only way to handle duplicate keys and values.
  195. */
  196. private void parseAndStoreReader(BufferedReader in) throws IOException {
  197. while (true) {
  198. // Get next line
  199. String line = in.readLine();
  200. if (line == null) {
  201. return;
  202. }
  203. if (line.length() > 0) {
  204. // Continue lines that end in slashes if they are not comments
  205. char firstChar = line.charAt(0);
  206. if (firstChar != '#' && firstChar != '!') {
  207. while (continueLine(line)) {
  208. String nextLine = in.readLine();
  209. if (nextLine == null) {
  210. nextLine = new String("");
  211. }
  212. String loppedLine =
  213. line.substring(0, line.length() - 1);
  214. // Advance beyond whitespace on new line
  215. int startIndex = 0;
  216. for(; startIndex < nextLine.length(); startIndex++) {
  217. if (whiteSpaceChars.
  218. indexOf(nextLine.charAt(startIndex)) == -1)
  219. {
  220. break;
  221. }
  222. }
  223. nextLine = nextLine.substring(startIndex,
  224. nextLine.length());
  225. line = new String(loppedLine+nextLine);
  226. }
  227. // Find start of key
  228. int len = line.length();
  229. int keyStart = 0;
  230. for(; keyStart < len; keyStart++) {
  231. if(whiteSpaceChars.
  232. indexOf(line.charAt(keyStart)) == -1) {
  233. break;
  234. }
  235. }
  236. // Blank lines are ignored
  237. if (keyStart == len) {
  238. continue;
  239. }
  240. // Find separation between key and value
  241. int separatorIndex = keyStart;
  242. for(; separatorIndex < len; separatorIndex++) {
  243. char currentChar = line.charAt(separatorIndex);
  244. if (currentChar == '\\') {
  245. separatorIndex++;
  246. } else if (keyValueSeparators.
  247. indexOf(currentChar) != -1) {
  248. break;
  249. }
  250. }
  251. // Skip over whitespace after key if any
  252. int valueIndex = separatorIndex;
  253. for (; valueIndex < len; valueIndex++) {
  254. if (whiteSpaceChars.
  255. indexOf(line.charAt(valueIndex)) == -1) {
  256. break;
  257. }
  258. }
  259. // Skip over one non whitespace key value separators if any
  260. if (valueIndex < len) {
  261. if (strictKeyValueSeparators.
  262. indexOf(line.charAt(valueIndex)) != -1) {
  263. valueIndex++;
  264. }
  265. }
  266. // Skip over white space after other separators if any
  267. while (valueIndex < len) {
  268. if (whiteSpaceChars.
  269. indexOf(line.charAt(valueIndex)) == -1) {
  270. break;
  271. }
  272. valueIndex++;
  273. }
  274. String key = line.substring(keyStart, separatorIndex);
  275. String value = (separatorIndex < len)
  276. ? line.substring(valueIndex, len)
  277. : "";
  278. // Convert then store key and value
  279. key = loadConvert(key);
  280. value = loadConvert(value);
  281. try {
  282. MimeType mime = new MimeType(value);
  283. if ("text".equals(mime.getPrimaryType())) {
  284. String charset = mime.getParameter("charset");
  285. if (DataTransferer.doesSubtypeSupportCharset
  286. (mime.getSubType(), charset))
  287. {
  288. // We need to store the charset and eoln
  289. // parameters, if any, so that the
  290. // DataTransferer will have this information
  291. // for conversion into the native format.
  292. DataTransferer transferer =
  293. DataTransferer.getInstance();
  294. if (transferer != null) {
  295. transferer.registerTextFlavorProperties
  296. (key, charset,
  297. mime.getParameter("eoln"),
  298. mime.getParameter("terminators"));
  299. }
  300. }
  301. // But don't store any of these parameters in the
  302. // DataFlavor itself for any text natives (even
  303. // non-charset ones). The SystemFlavorMap will
  304. // synthesize the appropriate mappings later.
  305. mime.removeParameter("charset");
  306. mime.removeParameter("class");
  307. mime.removeParameter("eoln");
  308. mime.removeParameter("terminators");
  309. value = mime.toString();
  310. }
  311. } catch (MimeTypeParseException e) {
  312. e.printStackTrace();
  313. continue;
  314. }
  315. DataFlavor flavor;
  316. try {
  317. flavor = new DataFlavor(value);
  318. } catch (Exception e) {
  319. try {
  320. flavor = new DataFlavor(value, (String)null);
  321. } catch (Exception ee) {
  322. ee.printStackTrace();
  323. continue;
  324. }
  325. }
  326. // For text/* flavors, store mappings in separate maps to
  327. // enable dynamic mapping generation at a run-time.
  328. if ("text".equals(flavor.getPrimaryType())) {
  329. store(value, key, flavorToNative);
  330. store(key, value, nativeToFlavor);
  331. } else {
  332. store(flavor, key, flavorToNative);
  333. store(key, flavor, nativeToFlavor);
  334. }
  335. }
  336. }
  337. }
  338. }
  339. /**
  340. * Copied from java.util.Properties.
  341. */
  342. private boolean continueLine (String line) {
  343. int slashCount = 0;
  344. int index = line.length() - 1;
  345. while((index >= 0) && (line.charAt(index--) == '\\')) {
  346. slashCount++;
  347. }
  348. return (slashCount % 2 == 1);
  349. }
  350. /**
  351. * Copied from java.util.Properties.
  352. */
  353. private String loadConvert(String theString) {
  354. char aChar;
  355. int len = theString.length();
  356. StringBuffer outBuffer = new StringBuffer(len);
  357. for (int x = 0; x < len; ) {
  358. aChar = theString.charAt(x++);
  359. if (aChar == '\\') {
  360. aChar = theString.charAt(x++);
  361. if (aChar == 'u') {
  362. // Read the xxxx
  363. int value = 0;
  364. for (int i = 0; i < 4; i++) {
  365. aChar = theString.charAt(x++);
  366. switch (aChar) {
  367. case '0': case '1': case '2': case '3': case '4':
  368. case '5': case '6': case '7': case '8': case '9': {
  369. value = (value << 4) + aChar - '0';
  370. break;
  371. }
  372. case 'a': case 'b': case 'c':
  373. case 'd': case 'e': case 'f': {
  374. value = (value << 4) + 10 + aChar - 'a';
  375. break;
  376. }
  377. case 'A': case 'B': case 'C':
  378. case 'D': case 'E': case 'F': {
  379. value = (value << 4) + 10 + aChar - 'A';
  380. break;
  381. }
  382. default: {
  383. throw new IllegalArgumentException(
  384. "Malformed \\uxxxx encoding.");
  385. }
  386. }
  387. }
  388. outBuffer.append((char)value);
  389. } else {
  390. if (aChar == 't') {
  391. aChar = '\t';
  392. } else if (aChar == 'r') {
  393. aChar = '\r';
  394. } else if (aChar == 'n') {
  395. aChar = '\n';
  396. } else if (aChar == 'f') {
  397. aChar = '\f';
  398. }
  399. outBuffer.append(aChar);
  400. }
  401. } else {
  402. outBuffer.append(aChar);
  403. }
  404. }
  405. return outBuffer.toString();
  406. }
  407. /**
  408. * Stores the listed object under the specified hash key in map. Unlike a
  409. * standard map, the listed object will not replace any object already at
  410. * the appropriate Map location, but rather will be appended to a List
  411. * stored in that location.
  412. */
  413. private void store(Object hashed, Object listed, Map map) {
  414. List list = (List)map.get(hashed);
  415. if (list == null) {
  416. list = new ArrayList(1);
  417. map.put(hashed, list);
  418. }
  419. if (!list.contains(listed)) {
  420. list.add(listed);
  421. }
  422. }
  423. /**
  424. * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
  425. * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
  426. * case, a new DataFlavor is synthesized, stored, and returned, if and
  427. * only if the specified native is encoded as a Java MIME type.
  428. */
  429. private List nativeToFlavorLookup(String nat) {
  430. List flavors = (List)nativeToFlavor.get(nat);
  431. if (flavors == null && isJavaMIMEType(nat)) {
  432. String decoded = decodeJavaMIMEType(nat);
  433. DataFlavor flavor = null;
  434. try {
  435. flavor = new DataFlavor(decoded);
  436. } catch (Exception e) {
  437. System.err.println("Exception \"" + e.getClass().getName() +
  438. ": " + e.getMessage() +
  439. "\"while constructing DataFlavor for: " +
  440. decoded);
  441. }
  442. if (flavor != null) {
  443. flavors = new ArrayList(1);
  444. nativeToFlavor.put(nat, flavors);
  445. flavors.add(flavor);
  446. getFlavorsForNativeCache.remove(nat);
  447. getFlavorsForNativeCache.remove(null);
  448. List natives = (List)flavorToNative.get(flavor);
  449. if (natives == null) {
  450. natives = new ArrayList(1);
  451. flavorToNative.put(flavor, natives);
  452. }
  453. natives.add(nat);
  454. getNativesForFlavorCache.remove(flavor);
  455. getNativesForFlavorCache.remove(null);
  456. }
  457. }
  458. return (flavors != null) ? flavors : new ArrayList(0);
  459. }
  460. /**
  461. * Semantically equivalent to 'flavorToNative.get(flav)'. This method
  462. * handles the case where 'flav' is not found in 'flavorToNative' depending
  463. * on the value of passes 'synthesize' parameter. If 'synthesize' is
  464. * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
  465. * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
  466. * and 'flavorToNative' remains unaffected.
  467. */
  468. private List flavorToNativeLookup(final DataFlavor flav,
  469. final boolean synthesize) {
  470. List natives = (List)flavorToNative.get(flav);
  471. if (natives == null) {
  472. if (synthesize) {
  473. String encoded = encodeDataFlavor(flav);
  474. natives = new ArrayList(1);
  475. flavorToNative.put(flav, natives);
  476. natives.add(encoded);
  477. getNativesForFlavorCache.remove(flav);
  478. getNativesForFlavorCache.remove(null);
  479. List flavors = (List)nativeToFlavor.get(encoded);
  480. if (flavors == null) {
  481. flavors = new ArrayList(1);
  482. nativeToFlavor.put(encoded, flavors);
  483. }
  484. flavors.add(flav);
  485. getFlavorsForNativeCache.remove(encoded);
  486. getFlavorsForNativeCache.remove(null);
  487. } else {
  488. natives = new ArrayList(0);
  489. }
  490. }
  491. return natives;
  492. }
  493. /**
  494. * Returns a <code>List</code> of <code>String</code> natives to which the
  495. * specified <code>DataFlavor</code> can be translated by the data transfer
  496. * subsystem. The <code>List</code> will be sorted from best native to
  497. * worst. That is, the first native will best reflect data in the specified
  498. * flavor to the underlying native platform.
  499. * <p>
  500. * If the specified <code>DataFlavor</code> is previously unknown to the
  501. * data transfer subsystem, then invoking this method will establish a
  502. * mapping in both directions between the specified <code>DataFlavor</code>
  503. * and an encoded version of its MIME type as its native.
  504. *
  505. * @param flav the <code>DataFlavor</code> whose corresponding natives
  506. * should be returned. If <code>null</code> is specified, all
  507. * natives currently known to the data transfer subsystem are
  508. * returned in a non-deterministic order.
  509. * @return a <code>java.util.List</code> of <code>java.lang.String</code>
  510. * objects which are platform-specific representations of platform-
  511. * specific data formats
  512. *
  513. * @see #encodeDataFlavor
  514. * @since 1.4
  515. */
  516. public synchronized List getNativesForFlavor(DataFlavor flav) {
  517. List retval = null;
  518. // Check cache, even for null flav
  519. SoftReference ref = (SoftReference)getNativesForFlavorCache.get(flav);
  520. if (ref != null) {
  521. retval = (List)ref.get();
  522. if (retval != null) {
  523. // Create a copy, because client code can modify the returned
  524. // list.
  525. return new ArrayList(retval);
  526. }
  527. }
  528. if (flav == null) {
  529. retval = new ArrayList(nativeToFlavor.keySet());
  530. } else if (disabledMappingGenerationKeys.contains(flav)) {
  531. // In this case we shouldn't synthesize a native for this flavor,
  532. // since its mappings were explicitly specified.
  533. retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
  534. } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
  535. // For text/* flavors, flavor-to-native mappings specified in
  536. // flavormap.properties are stored per flavor's base type.
  537. if ("text".equals(flav.getPrimaryType())) {
  538. retval = (List)flavorToNative.get(flav.mimeType.getBaseType());
  539. if (retval != null) {
  540. // To prevent the List stored in the map from modification.
  541. retval = new ArrayList(retval);
  542. }
  543. }
  544. // Also include text/plain natives, but don't duplicate Strings
  545. List textPlainList = (List)flavorToNative.get(TEXT_PLAIN_BASE_TYPE);
  546. if (textPlainList != null && !textPlainList.isEmpty()) {
  547. // To prevent the List stored in the map from modification.
  548. // This also guarantees that removeAll() is supported.
  549. textPlainList = new ArrayList(textPlainList);
  550. if (retval != null && !retval.isEmpty()) {
  551. // Use HashSet to get constant-time performance for search.
  552. textPlainList.removeAll(new HashSet(retval));
  553. retval.addAll(textPlainList);
  554. } else {
  555. retval = textPlainList;
  556. }
  557. }
  558. if (retval == null || retval.isEmpty()) {
  559. retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
  560. } else {
  561. // In this branch it is guaranteed that natives explicitly
  562. // listed for flav's MIME type were added with
  563. // addUnencodedNativeForFlavor(), so they have lower priority.
  564. List explicitList =
  565. flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
  566. // flavorToNativeLookup() never returns null.
  567. // It can return an empty List, however.
  568. if (!explicitList.isEmpty()) {
  569. // To prevent the List stored in the map from modification.
  570. // This also guarantees that removeAll() is supported.
  571. explicitList = new ArrayList(explicitList);
  572. // Use HashSet to get constant-time performance for search.
  573. explicitList.removeAll(new HashSet(retval));
  574. retval.addAll(explicitList);
  575. }
  576. }
  577. } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
  578. retval = (List)flavorToNative.get(flav.mimeType.getBaseType());
  579. if (retval == null || retval.isEmpty()) {
  580. retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
  581. } else {
  582. // In this branch it is guaranteed that natives explicitly
  583. // listed for flav's MIME type were added with
  584. // addUnencodedNativeForFlavor(), so they have lower priority.
  585. List explicitList =
  586. flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
  587. // flavorToNativeLookup() never returns null.
  588. // It can return an empty List, however.
  589. if (!explicitList.isEmpty()) {
  590. // To prevent the List stored in the map from modification.
  591. // This also guarantees that add/removeAll() are supported.
  592. retval = new ArrayList(retval);
  593. explicitList = new ArrayList(explicitList);
  594. // Use HashSet to get constant-time performance for search.
  595. explicitList.removeAll(new HashSet(retval));
  596. retval.addAll(explicitList);
  597. }
  598. }
  599. } else {
  600. retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
  601. }
  602. getNativesForFlavorCache.put(flav, new SoftReference(retval));
  603. // Create a copy, because client code can modify the returned list.
  604. return new ArrayList(retval);
  605. }
  606. /**
  607. * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
  608. * specified <code>String</code> native can be translated by the data
  609. * transfer subsystem. The <code>List</code> will be sorted from best
  610. * <code>DataFlavor</code> to worst. That is, the first
  611. * <code>DataFlavor</code> will best reflect data in the specified
  612. * native to a Java application.
  613. * <p>
  614. * If the specified native is previously unknown to the data transfer
  615. * subsystem, and that native has been properly encoded, then invoking this
  616. * method will establish a mapping in both directions between the specified
  617. * native and a <code>DataFlavor</code> whose MIME type is a decoded
  618. * version of the native.
  619. *
  620. * @param nat the native whose corresponding <code>DataFlavor</code>s
  621. * should be returned. If <code>null</code> is specified, all
  622. * <code>DataFlavor</code>s currently known to the data transfer
  623. * subsystem are returned in a non-deterministic order.
  624. * @return a <code>java.util.List</code> of <code>DataFlavor</code>
  625. * objects into which platform-specific data in the specified,
  626. * platform-specific native can be translated
  627. *
  628. * @see #encodeJavaMIMEType
  629. * @since 1.4
  630. */
  631. public synchronized List getFlavorsForNative(String nat) {
  632. // Check cache, even for null nat
  633. SoftReference ref = (SoftReference)getFlavorsForNativeCache.get(nat);
  634. if (ref != null) {
  635. ArrayList retval = (ArrayList)ref.get();
  636. if (retval != null) {
  637. return (List)retval.clone();
  638. }
  639. }
  640. LinkedList retval = new LinkedList();
  641. if (nat == null) {
  642. List natives = getNativesForFlavor(null);
  643. HashSet dups = new HashSet(natives.size());
  644. for (Iterator natives_iter = natives.iterator();
  645. natives_iter.hasNext(); )
  646. {
  647. List flavors =
  648. getFlavorsForNative((String)natives_iter.next());
  649. for (Iterator flavors_iter = flavors.iterator();
  650. flavors_iter.hasNext(); )
  651. {
  652. Object flavor = flavors_iter.next();
  653. if (dups.add(flavor)) {
  654. retval.add(flavor);
  655. }
  656. }
  657. }
  658. } else {
  659. List flavors = nativeToFlavorLookup(nat);
  660. if (disabledMappingGenerationKeys.contains(nat)) {
  661. return flavors;
  662. }
  663. HashSet dups = new HashSet(flavors.size());
  664. List flavorsAndbaseTypes = nativeToFlavorLookup(nat);
  665. for (Iterator flavorsAndbaseTypes_iter =
  666. flavorsAndbaseTypes.iterator();
  667. flavorsAndbaseTypes_iter.hasNext(); )
  668. {
  669. Object value = flavorsAndbaseTypes_iter.next();
  670. if (value instanceof String) {
  671. String baseType = (String)value;
  672. String subType = null;
  673. try {
  674. MimeType mimeType = new MimeType(baseType);
  675. subType = mimeType.getSubType();
  676. } catch (MimeTypeParseException mtpe) {
  677. // Cannot happen, since we checked all mappings
  678. // on load from flavormap.properties.
  679. assert(false);
  680. }
  681. if (DataTransferer.doesSubtypeSupportCharset(subType,
  682. null)) {
  683. if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
  684. dups.add(DataFlavor.stringFlavor))
  685. {
  686. retval.add(DataFlavor.stringFlavor);
  687. }
  688. for (int i = 0; i < UNICODE_TEXT_CLASSES.length; i++) {
  689. DataFlavor toAdd = null;
  690. try {
  691. toAdd = new DataFlavor
  692. (baseType + ";charset=Unicode;class=" +
  693. UNICODE_TEXT_CLASSES[i]);
  694. } catch (ClassNotFoundException cannotHappen) {
  695. }
  696. if (dups.add(toAdd)) {
  697. retval.add(toAdd);
  698. }
  699. }
  700. for (Iterator charset_iter =
  701. DataTransferer.standardEncodings();
  702. charset_iter.hasNext(); )
  703. {
  704. String charset = (String)charset_iter.next();
  705. for (int i = 0; i < ENCODED_TEXT_CLASSES.length;
  706. i++)
  707. {
  708. DataFlavor toAdd = null;
  709. try {
  710. toAdd = new DataFlavor
  711. (baseType + ";charset=" + charset +
  712. ";class=" + ENCODED_TEXT_CLASSES[i]);
  713. } catch (ClassNotFoundException cannotHappen) {
  714. }
  715. // Check for equality to plainTextFlavor so
  716. // that we can ensure that the exact charset of
  717. // plainTextFlavor, not the canonical charset
  718. // or another equivalent charset with a
  719. // different name, is used.
  720. if (toAdd.equals(DataFlavor.plainTextFlavor)) {
  721. toAdd = DataFlavor.plainTextFlavor;
  722. }
  723. if (dups.add(toAdd)) {
  724. retval.add(toAdd);
  725. }
  726. }
  727. }
  728. if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
  729. dups.add(DataFlavor.plainTextFlavor))
  730. {
  731. retval.add(DataFlavor.plainTextFlavor);
  732. }
  733. } else {
  734. // Non-charset text natives should be treated as
  735. // opaque, 8-bit data in any of its various
  736. // representations.
  737. for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) {
  738. DataFlavor toAdd = null;
  739. try {
  740. toAdd = new DataFlavor(baseType +
  741. ";class=" + ENCODED_TEXT_CLASSES[i]);
  742. } catch (ClassNotFoundException cannotHappen) {
  743. }
  744. if (dups.add(toAdd)) {
  745. retval.add(toAdd);
  746. }
  747. }
  748. }
  749. } else {
  750. DataFlavor flavor = (DataFlavor)value;
  751. if (dups.add(flavor)) {
  752. retval.add(flavor);
  753. }
  754. }
  755. }
  756. }
  757. ArrayList arrayList = new ArrayList(retval);
  758. getFlavorsForNativeCache.put(nat, new SoftReference(arrayList));
  759. return (List)arrayList.clone();
  760. }
  761. /**
  762. * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
  763. * their most preferred <code>String</code> native. Each native value will
  764. * be the same as the first native in the List returned by
  765. * <code>getNativesForFlavor</code> for the specified flavor.
  766. * <p>
  767. * If a specified <code>DataFlavor</code> is previously unknown to the
  768. * data transfer subsystem, then invoking this method will establish a
  769. * mapping in both directions between the specified <code>DataFlavor</code>
  770. * and an encoded version of its MIME type as its native.
  771. *
  772. * @param flavors an array of <code>DataFlavor</code>s which will be the
  773. * key set of the returned <code>Map</code>. If <code>null</code> is
  774. * specified, a mapping of all <code>DataFlavor</code>s known to the
  775. * data transfer subsystem to their most preferred
  776. * <code>String</code> natives will be returned.
  777. * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
  778. * <code>String</code> natives
  779. *
  780. * @see #getNativesForFlavor
  781. * @see #encodeDataFlavor
  782. */
  783. public synchronized Map getNativesForFlavors(DataFlavor[] flavors) {
  784. // Use getNativesForFlavor to generate extra natives for text flavors
  785. // and stringFlavor
  786. if (flavors == null) {
  787. List flavor_list = getFlavorsForNative(null);
  788. flavors = new DataFlavor[flavor_list.size()];
  789. flavor_list.toArray(flavors);
  790. }
  791. HashMap retval = new HashMap(flavors.length, 1.0f);
  792. for (int i = 0; i < flavors.length; i++) {
  793. List natives = getNativesForFlavor(flavors[i]);
  794. String nat = (natives.isEmpty()) ? null : (String)natives.get(0);
  795. retval.put(flavors[i], nat);
  796. }
  797. return retval;
  798. }
  799. /**
  800. * Returns a <code>Map</code> of the specified <code>String</code> natives
  801. * to their most preferred <code>DataFlavor</code>. Each
  802. * <code>DataFlavor</code> value will be the same as the first
  803. * <code>DataFlavor</code> in the List returned by
  804. * <code>getFlavorsForNative</code> for the specified native.
  805. * <p>
  806. * If a specified native is previously unknown to the data transfer
  807. * subsystem, and that native has been properly encoded, then invoking this
  808. * method will establish a mapping in both directions between the specified
  809. * native and a <code>DataFlavor</code> whose MIME type is a decoded
  810. * version of the native.
  811. *
  812. * @param natives an array of <code>String</code>s which will be the
  813. * key set of the returned <code>Map</code>. If <code>null</code> is
  814. * specified, a mapping of all supported <code>String</code> natives
  815. * to their most preferred <code>DataFlavor</code>s will be
  816. * returned.
  817. * @return a <code>java.util.Map</code> of <code>String</code> natives to
  818. * <code>DataFlavor</code>s
  819. *
  820. * @see #getFlavorsForNative
  821. * @see #encodeJavaMIMEType
  822. */
  823. public synchronized Map getFlavorsForNatives(String[] natives) {
  824. // Use getFlavorsForNative to generate extra flavors for text natives
  825. if (natives == null) {
  826. List native_list = getNativesForFlavor(null);
  827. natives = new String[native_list.size()];
  828. native_list.toArray(natives);
  829. }
  830. HashMap retval = new HashMap(natives.length, 1.0f);
  831. for (int i = 0; i < natives.length; i++) {
  832. List flavors = getFlavorsForNative(natives[i]);
  833. DataFlavor flav = (flavors.isEmpty())
  834. ? null : (DataFlavor)flavors.get(0);
  835. retval.put(natives[i], flav);
  836. }
  837. return retval;
  838. }
  839. /**
  840. * Adds a mapping from the specified <code>DataFlavor</code> (and all
  841. * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
  842. * to the specified <code>String</code> native.
  843. * Unlike <code>getNativesForFlavor</code>, the mapping will only be
  844. * established in one direction, and the native will not be encoded. To
  845. * establish a two-way mapping, call
  846. * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
  847. * be of lower priority than any existing mapping.
  848. * This method has no effect if a mapping from the specified or equal
  849. * <code>DataFlavor</code> to the specified <code>String</code> native
  850. * already exists.
  851. *
  852. * @param flav the <code>DataFlavor</code> key for the mapping
  853. * @param nat the <code>String</code> native value for the mapping
  854. * @throws NullPointerException if flav or nat is <code>null</code>
  855. *
  856. * @see #addFlavorForUnencodedNative
  857. * @since 1.4
  858. */
  859. public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
  860. String nat) {
  861. if (flav == null || nat == null) {
  862. throw new NullPointerException("null arguments not permitted");
  863. }
  864. List natives = (List)flavorToNative.get(flav);
  865. if (natives == null) {
  866. natives = new ArrayList(1);
  867. flavorToNative.put(flav, natives);
  868. } else if (natives.contains(nat)) {
  869. return;
  870. }
  871. natives.add(nat);
  872. getNativesForFlavorCache.remove(flav);
  873. getNativesForFlavorCache.remove(null);
  874. }
  875. /**
  876. * Discards the current mappings for the specified <code>DataFlavor</code>
  877. * and all <code>DataFlavor</code>s equal to the specified
  878. * <code>DataFlavor</code>, and creates new mappings to the
  879. * specified <code>String</code> natives.
  880. * Unlike <code>getNativesForFlavor</code>, the mappings will only be
  881. * established in one direction, and the natives will not be encoded. To
  882. * establish two-way mappings, call <code>setFlavorsForNative</code>
  883. * as well. The first native in the array will represent the highest
  884. * priority mapping. Subsequent natives will represent mappings of
  885. * decreasing priority.
  886. * <p>
  887. * If the array contains several elements that reference equal
  888. * <code>String</code> natives, this method will establish new mappings
  889. * for the first of those elements and ignore the rest of them.
  890. * <p>
  891. * It is recommended that client code not reset mappings established by the
  892. * data transfer subsystem. This method should only be used for
  893. * application-level mappings.
  894. *
  895. * @param flav the <code>DataFlavor</code> key for the mappings
  896. * @param natives the <code>String</code> native values for the mappings
  897. * @throws NullPointerException if flav or natives is <code>null</code>
  898. * or if natives contains <code>null</code> elements
  899. *
  900. * @see #setFlavorsForNative
  901. * @since 1.4
  902. */
  903. public synchronized void setNativesForFlavor(DataFlavor flav,
  904. String[] natives) {
  905. if (flav == null || natives == null) {
  906. throw new NullPointerException("null arguments not permitted");
  907. }
  908. flavorToNative.remove(flav);
  909. for (int i = 0; i < natives.length; i++) {
  910. addUnencodedNativeForFlavor(flav, natives[i]);
  911. }
  912. disabledMappingGenerationKeys.add(flav);
  913. // Clear the cache to handle the case of empty natives.
  914. getNativesForFlavorCache.remove(flav);
  915. getNativesForFlavorCache.remove(null);
  916. }
  917. /**
  918. * Adds a mapping from a single <code>String</code> native to a single
  919. * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
  920. * mapping will only be established in one direction, and the native will
  921. * not be encoded. To establish a two-way mapping, call
  922. * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
  923. * be of lower priority than any existing mapping.
  924. * This method has no effect if a mapping from the specified
  925. * <code>String</code> native to the specified or equal
  926. * <code>DataFlavor</code> already exists.
  927. *
  928. * @param nat the <code>String</code> native key for the mapping
  929. * @param flav the <code>DataFlavor</code> value for the mapping
  930. * @throws NullPointerException if nat or flav is <code>null</code>
  931. *
  932. * @see #addUnencodedNativeForFlavor
  933. * @since 1.4
  934. */
  935. public synchronized void addFlavorForUnencodedNative(String nat,
  936. DataFlavor flav) {
  937. if (nat == null || flav == null) {
  938. throw new NullPointerException("null arguments not permitted");
  939. }
  940. List flavors = (List)nativeToFlavor.get(nat);
  941. if (flavors == null) {
  942. flavors = new ArrayList(1);
  943. nativeToFlavor.put(nat, flavors);
  944. } else if (flavors.contains(flav)) {
  945. return;
  946. }
  947. flavors.add(flav);
  948. getFlavorsForNativeCache.remove(nat);
  949. getFlavorsForNativeCache.remove(null);
  950. }
  951. /**
  952. * Discards the current mappings for the specified <code>String</code>
  953. * native, and creates new mappings to the specified
  954. * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
  955. * mappings will only be established in one direction, and the natives need
  956. * not be encoded. To establish two-way mappings, call
  957. * <code>setNativesForFlavor</code> as well. The first
  958. * <code>DataFlavor</code> in the array will represent the highest priority
  959. * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
  960. * decreasing priority.
  961. * <p>
  962. * If the array contains several elements that reference equal
  963. * <code>DataFlavor</code>s, this method will establish new mappings
  964. * for the first of those elements and ignore the rest of them.
  965. * <p>
  966. * It is recommended that client code not reset mappings established by the
  967. * data transfer subsystem. This method should only be used for
  968. * application-level mappings.
  969. *
  970. * @param nat the <code>String</code> native key for the mappings
  971. * @param flavors the <code>DataFlavor</code> values for the mappings
  972. * @throws NullPointerException if nat or flavors is <code>null</code>
  973. * or if flavors contains <code>null</code> elements
  974. *
  975. * @see #setNativesForFlavor
  976. * @since 1.4
  977. */
  978. public synchronized void setFlavorsForNative(String nat,
  979. DataFlavor[] flavors) {
  980. if (nat == null || flavors == null) {
  981. throw new NullPointerException("null arguments not permitted");
  982. }
  983. nativeToFlavor.remove(nat);
  984. for (int i = 0; i < flavors.length; i++) {
  985. addFlavorForUnencodedNative(nat, flavors[i]);
  986. }
  987. disabledMappingGenerationKeys.add(nat);
  988. // Clear the cache to handle the case of empty flavors.
  989. getFlavorsForNativeCache.remove(nat);
  990. getFlavorsForNativeCache.remove(null);
  991. }
  992. /**
  993. * Encodes a MIME type for use as a <code>String</code> native. The format
  994. * of an encoded representation of a MIME type is implementation-dependent.
  995. * The only restrictions are:
  996. * <ul>
  997. * <li>The encoded representation is <code>null</code> if and only if the
  998. * MIME type <code>String</code> is <code>null</code>.</li>
  999. * <li>The encoded representations for two non-<code>null</code> MIME type
  1000. * <code>String</code>s are equal if and only if these <code>String</code>s
  1001. * are equal according to <code>String.equals(Object)</code>.</li>
  1002. * </ul>
  1003. * <p>
  1004. * Sun's reference implementation of this method returns the specified MIME
  1005. * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
  1006. *
  1007. * @param mimeType the MIME type to encode
  1008. * @return the encoded <code>String</code>, or <code>null</code> if
  1009. * mimeType is <code>null</code>
  1010. */
  1011. public static String encodeJavaMIMEType(String mimeType) {
  1012. return (mimeType != null)
  1013. ? JavaMIME + mimeType
  1014. : null;
  1015. }
  1016. /**
  1017. * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
  1018. * native. The format of an encoded <code>DataFlavor</code> is
  1019. * implementation-dependent. The only restrictions are:
  1020. * <ul>
  1021. * <li>The encoded representation is <code>null</code> if and only if the
  1022. * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
  1023. * <code>String</code> is <code>null</code>.</li>
  1024. * <li>The encoded representations for two non-<code>null</code>
  1025. * <code>DataFlavor</code>s with non-<code>null</code> MIME type
  1026. * <code>String</code>s are equal if and only if the MIME type
  1027. * <code>String</code>s of these <code>DataFlavor</code>s are equal
  1028. * according to <code>String.equals(Object)</code>.</li>
  1029. * </ul>
  1030. * <p>
  1031. * Sun's reference implementation of this method returns the MIME type
  1032. * <code>String</code> of the specified <code>DataFlavor</code> prefixed
  1033. * with <code>JAVA_DATAFLAVOR:</code>.
  1034. *
  1035. * @param flav the <code>DataFlavor</code> to encode
  1036. * @return the encoded <code>String</code>, or <code>null</code> if
  1037. * flav is <code>null</code> or has a <code>null</code> MIME type
  1038. */
  1039. public static String encodeDataFlavor(DataFlavor flav) {
  1040. return (flav != null)
  1041. ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
  1042. : null;
  1043. }
  1044. /**
  1045. * Returns whether the specified <code>String</code> is an encoded Java
  1046. * MIME type.
  1047. *
  1048. * @param str the <code>String</code> to test
  1049. * @return <code>true</code> if the <code>String</code> is encoded;
  1050. * <code>false</code> otherwise
  1051. */
  1052. public static boolean isJavaMIMEType(String str) {
  1053. return (str != null && str.startsWith(JavaMIME, 0));
  1054. }
  1055. /**
  1056. * Decodes a <code>String</code> native for use as a Java MIME type.
  1057. *
  1058. * @param nat the <code>String</code> to decode
  1059. * @return the decoded Java MIME type, or <code>null</code> if nat is not
  1060. * an encoded <code>String</code> native
  1061. */
  1062. public static String decodeJavaMIMEType(String nat) {
  1063. return (isJavaMIMEType(nat))
  1064. ? nat.substring(JavaMIME.length(), nat.length()).trim()
  1065. : null;
  1066. }
  1067. /**
  1068. * Decodes a <code>String</code> native for use as a
  1069. * <code>DataFlavor</code>.
  1070. *
  1071. * @param nat the <code>String</code> to decode
  1072. * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
  1073. * nat is not an encoded <code>String</code> native
  1074. */
  1075. public static DataFlavor decodeDataFlavor(String nat)
  1076. throws ClassNotFoundException
  1077. {
  1078. String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
  1079. return (retval_str != null)
  1080. ? new DataFlavor(retval_str)
  1081. : null;
  1082. }
  1083. }