1. /*
  2. * @(#)SystemFlavorMap.java 1.36 04/05/05
  3. *
  4. * Copyright 2004 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.36, 05/05/04
  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 (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
  432. DataTransferer transferer = DataTransferer.getInstance();
  433. if (transferer != null) {
  434. List platformFlavors =
  435. transferer.getPlatformMappingsForNative(nat);
  436. if (!platformFlavors.isEmpty()) {
  437. if (flavors != null) {
  438. platformFlavors.removeAll(new HashSet(flavors));
  439. // Prepending the platform-specific mappings ensures
  440. // that the flavors added with
  441. // addFlavorForUnencodedNative() are at the end of
  442. // list.
  443. platformFlavors.addAll(flavors);
  444. }
  445. flavors = platformFlavors;
  446. }
  447. }
  448. }
  449. if (flavors == null && isJavaMIMEType(nat)) {
  450. String decoded = decodeJavaMIMEType(nat);
  451. DataFlavor flavor = null;
  452. try {
  453. flavor = new DataFlavor(decoded);
  454. } catch (Exception e) {
  455. System.err.println("Exception \"" + e.getClass().getName() +
  456. ": " + e.getMessage() +
  457. "\"while constructing DataFlavor for: " +
  458. decoded);
  459. }
  460. if (flavor != null) {
  461. flavors = new ArrayList(1);
  462. nativeToFlavor.put(nat, flavors);
  463. flavors.add(flavor);
  464. getFlavorsForNativeCache.remove(nat);
  465. getFlavorsForNativeCache.remove(null);
  466. List natives = (List)flavorToNative.get(flavor);
  467. if (natives == null) {
  468. natives = new ArrayList(1);
  469. flavorToNative.put(flavor, natives);
  470. }
  471. natives.add(nat);
  472. getNativesForFlavorCache.remove(flavor);
  473. getNativesForFlavorCache.remove(null);
  474. }
  475. }
  476. return (flavors != null) ? flavors : new ArrayList(0);
  477. }
  478. /**
  479. * Semantically equivalent to 'flavorToNative.get(flav)'. This method
  480. * handles the case where 'flav' is not found in 'flavorToNative' depending
  481. * on the value of passes 'synthesize' parameter. If 'synthesize' is
  482. * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
  483. * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
  484. * and 'flavorToNative' remains unaffected.
  485. */
  486. private List flavorToNativeLookup(final DataFlavor flav,
  487. final boolean synthesize) {
  488. List natives = (List)flavorToNative.get(flav);
  489. if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
  490. DataTransferer transferer = DataTransferer.getInstance();
  491. if (transferer != null) {
  492. List platformNatives =
  493. transferer.getPlatformMappingsForFlavor(flav);
  494. if (!platformNatives.isEmpty()) {
  495. if (natives != null) {
  496. platformNatives.removeAll(new HashSet(natives));
  497. // Prepend the platform-specific mappings to ensure
  498. // that the natives added with
  499. // addUnencodedNativeForFlavor() are at the end of
  500. // list.
  501. platformNatives.addAll(natives);
  502. }
  503. natives = platformNatives;
  504. }
  505. }
  506. }
  507. if (natives == null) {
  508. if (synthesize) {
  509. String encoded = encodeDataFlavor(flav);
  510. natives = new ArrayList(1);
  511. flavorToNative.put(flav, natives);
  512. natives.add(encoded);
  513. getNativesForFlavorCache.remove(flav);
  514. getNativesForFlavorCache.remove(null);
  515. List flavors = (List)nativeToFlavor.get(encoded);
  516. if (flavors == null) {
  517. flavors = new ArrayList(1);
  518. nativeToFlavor.put(encoded, flavors);
  519. }
  520. flavors.add(flav);
  521. getFlavorsForNativeCache.remove(encoded);
  522. getFlavorsForNativeCache.remove(null);
  523. } else {
  524. natives = new ArrayList(0);
  525. }
  526. }
  527. return natives;
  528. }
  529. /**
  530. * Returns a <code>List</code> of <code>String</code> natives to which the
  531. * specified <code>DataFlavor</code> can be translated by the data transfer
  532. * subsystem. The <code>List</code> will be sorted from best native to
  533. * worst. That is, the first native will best reflect data in the specified
  534. * flavor to the underlying native platform.
  535. * <p>
  536. * If the specified <code>DataFlavor</code> is previously unknown to the
  537. * data transfer subsystem and the data transfer subsystem is unable to
  538. * translate this <code>DataFlavor</code> to any existing native, then
  539. * invoking this method will establish a
  540. * mapping in both directions between the specified <code>DataFlavor</code>
  541. * and an encoded version of its MIME type as its native.
  542. *
  543. * @param flav the <code>DataFlavor</code> whose corresponding natives
  544. * should be returned. If <code>null</code> is specified, all
  545. * natives currently known to the data transfer subsystem are
  546. * returned in a non-deterministic order.
  547. * @return a <code>java.util.List</code> of <code>java.lang.String</code>
  548. * objects which are platform-specific representations of platform-
  549. * specific data formats
  550. *
  551. * @see #encodeDataFlavor
  552. * @since 1.4
  553. */
  554. public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
  555. List retval = null;
  556. // Check cache, even for null flav
  557. SoftReference ref = (SoftReference)getNativesForFlavorCache.get(flav);
  558. if (ref != null) {
  559. retval = (List)ref.get();
  560. if (retval != null) {
  561. // Create a copy, because client code can modify the returned
  562. // list.
  563. return new ArrayList(retval);
  564. }
  565. }
  566. if (flav == null) {
  567. retval = new ArrayList(nativeToFlavor.keySet());
  568. } else if (disabledMappingGenerationKeys.contains(flav)) {
  569. // In this case we shouldn't synthesize a native for this flavor,
  570. // since its mappings were explicitly specified.
  571. retval = flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
  572. } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
  573. // For text/* flavors, flavor-to-native mappings specified in
  574. // flavormap.properties are stored per flavor's base type.
  575. if ("text".equals(flav.getPrimaryType())) {
  576. retval = (List)flavorToNative.get(flav.mimeType.getBaseType());
  577. if (retval != null) {
  578. // To prevent the List stored in the map from modification.
  579. retval = new ArrayList(retval);
  580. }
  581. }
  582. // Also include text/plain natives, but don't duplicate Strings
  583. List textPlainList = (List)flavorToNative.get(TEXT_PLAIN_BASE_TYPE);
  584. if (textPlainList != null && !textPlainList.isEmpty()) {
  585. // To prevent the List stored in the map from modification.
  586. // This also guarantees that removeAll() is supported.
  587. textPlainList = new ArrayList(textPlainList);
  588. if (retval != null && !retval.isEmpty()) {
  589. // Use HashSet to get constant-time performance for search.
  590. textPlainList.removeAll(new HashSet(retval));
  591. retval.addAll(textPlainList);
  592. } else {
  593. retval = textPlainList;
  594. }
  595. }
  596. if (retval == null || retval.isEmpty()) {
  597. retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
  598. } else {
  599. // In this branch it is guaranteed that natives explicitly
  600. // listed for flav's MIME type were added with
  601. // addUnencodedNativeForFlavor(), so they have lower priority.
  602. List explicitList =
  603. flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
  604. // flavorToNativeLookup() never returns null.
  605. // It can return an empty List, however.
  606. if (!explicitList.isEmpty()) {
  607. // To prevent the List stored in the map from modification.
  608. // This also guarantees that removeAll() is supported.
  609. explicitList = new ArrayList(explicitList);
  610. // Use HashSet to get constant-time performance for search.
  611. explicitList.removeAll(new HashSet(retval));
  612. retval.addAll(explicitList);
  613. }
  614. }
  615. } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
  616. retval = (List)flavorToNative.get(flav.mimeType.getBaseType());
  617. if (retval == null || retval.isEmpty()) {
  618. retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
  619. } else {
  620. // In this branch it is guaranteed that natives explicitly
  621. // listed for flav's MIME type were added with
  622. // addUnencodedNativeForFlavor(), so they have lower priority.
  623. List explicitList =
  624. flavorToNativeLookup(flav, !SYNTHESIZE_IF_NOT_FOUND);
  625. // flavorToNativeLookup() never returns null.
  626. // It can return an empty List, however.
  627. if (!explicitList.isEmpty()) {
  628. // To prevent the List stored in the map from modification.
  629. // This also guarantees that add/removeAll() are supported.
  630. retval = new ArrayList(retval);
  631. explicitList = new ArrayList(explicitList);
  632. // Use HashSet to get constant-time performance for search.
  633. explicitList.removeAll(new HashSet(retval));
  634. retval.addAll(explicitList);
  635. }
  636. }
  637. } else {
  638. retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
  639. }
  640. getNativesForFlavorCache.put(flav, new SoftReference(retval));
  641. // Create a copy, because client code can modify the returned list.
  642. return new ArrayList(retval);
  643. }
  644. /**
  645. * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
  646. * specified <code>String</code> native can be translated by the data
  647. * transfer subsystem. The <code>List</code> will be sorted from best
  648. * <code>DataFlavor</code> to worst. That is, the first
  649. * <code>DataFlavor</code> will best reflect data in the specified
  650. * native to a Java application.
  651. * <p>
  652. * If the specified native is previously unknown to the data transfer
  653. * subsystem, and that native has been properly encoded, then invoking this
  654. * method will establish a mapping in both directions between the specified
  655. * native and a <code>DataFlavor</code> whose MIME type is a decoded
  656. * version of the native.
  657. * <p>
  658. * If the specified native is not a properly encoded native and the
  659. * mappings for this native have not been altered with
  660. * <code>setFlavorsForNative</code>, then the contents of the
  661. * <code>List</code> is platform dependent, but <code>null</code>
  662. * cannot be returned.
  663. *
  664. * @param nat the native whose corresponding <code>DataFlavor</code>s
  665. * should be returned. If <code>null</code> is specified, all
  666. * <code>DataFlavor</code>s currently known to the data transfer
  667. * subsystem are returned in a non-deterministic order.
  668. * @return a <code>java.util.List</code> of <code>DataFlavor</code>
  669. * objects into which platform-specific data in the specified,
  670. * platform-specific native can be translated
  671. *
  672. * @see #encodeJavaMIMEType
  673. * @since 1.4
  674. */
  675. public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
  676. // Check cache, even for null nat
  677. SoftReference ref = (SoftReference)getFlavorsForNativeCache.get(nat);
  678. if (ref != null) {
  679. ArrayList retval = (ArrayList)ref.get();
  680. if (retval != null) {
  681. return (List)retval.clone();
  682. }
  683. }
  684. LinkedList retval = new LinkedList();
  685. if (nat == null) {
  686. List natives = getNativesForFlavor(null);
  687. HashSet dups = new HashSet(natives.size());
  688. for (Iterator natives_iter = natives.iterator();
  689. natives_iter.hasNext(); )
  690. {
  691. List flavors =
  692. getFlavorsForNative((String)natives_iter.next());
  693. for (Iterator flavors_iter = flavors.iterator();
  694. flavors_iter.hasNext(); )
  695. {
  696. Object flavor = flavors_iter.next();
  697. if (dups.add(flavor)) {
  698. retval.add(flavor);
  699. }
  700. }
  701. }
  702. } else {
  703. List flavors = nativeToFlavorLookup(nat);
  704. if (disabledMappingGenerationKeys.contains(nat)) {
  705. return flavors;
  706. }
  707. HashSet dups = new HashSet(flavors.size());
  708. List flavorsAndbaseTypes = nativeToFlavorLookup(nat);
  709. for (Iterator flavorsAndbaseTypes_iter =
  710. flavorsAndbaseTypes.iterator();
  711. flavorsAndbaseTypes_iter.hasNext(); )
  712. {
  713. Object value = flavorsAndbaseTypes_iter.next();
  714. if (value instanceof String) {
  715. String baseType = (String)value;
  716. String subType = null;
  717. try {
  718. MimeType mimeType = new MimeType(baseType);
  719. subType = mimeType.getSubType();
  720. } catch (MimeTypeParseException mtpe) {
  721. // Cannot happen, since we checked all mappings
  722. // on load from flavormap.properties.
  723. assert(false);
  724. }
  725. if (DataTransferer.doesSubtypeSupportCharset(subType,
  726. null)) {
  727. if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
  728. dups.add(DataFlavor.stringFlavor))
  729. {
  730. retval.add(DataFlavor.stringFlavor);
  731. }
  732. for (int i = 0; i < UNICODE_TEXT_CLASSES.length; i++) {
  733. DataFlavor toAdd = null;
  734. try {
  735. toAdd = new DataFlavor
  736. (baseType + ";charset=Unicode;class=" +
  737. UNICODE_TEXT_CLASSES[i]);
  738. } catch (ClassNotFoundException cannotHappen) {
  739. }
  740. if (dups.add(toAdd)) {
  741. retval.add(toAdd);
  742. }
  743. }
  744. for (Iterator charset_iter =
  745. DataTransferer.standardEncodings();
  746. charset_iter.hasNext(); )
  747. {
  748. String charset = (String)charset_iter.next();
  749. for (int i = 0; i < ENCODED_TEXT_CLASSES.length;
  750. i++)
  751. {
  752. DataFlavor toAdd = null;
  753. try {
  754. toAdd = new DataFlavor
  755. (baseType + ";charset=" + charset +
  756. ";class=" + ENCODED_TEXT_CLASSES[i]);
  757. } catch (ClassNotFoundException cannotHappen) {
  758. }
  759. // Check for equality to plainTextFlavor so
  760. // that we can ensure that the exact charset of
  761. // plainTextFlavor, not the canonical charset
  762. // or another equivalent charset with a
  763. // different name, is used.
  764. if (toAdd.equals(DataFlavor.plainTextFlavor)) {
  765. toAdd = DataFlavor.plainTextFlavor;
  766. }
  767. if (dups.add(toAdd)) {
  768. retval.add(toAdd);
  769. }
  770. }
  771. }
  772. if (TEXT_PLAIN_BASE_TYPE.equals(baseType) &&
  773. dups.add(DataFlavor.plainTextFlavor))
  774. {
  775. retval.add(DataFlavor.plainTextFlavor);
  776. }
  777. } else {
  778. // Non-charset text natives should be treated as
  779. // opaque, 8-bit data in any of its various
  780. // representations.
  781. for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) {
  782. DataFlavor toAdd = null;
  783. try {
  784. toAdd = new DataFlavor(baseType +
  785. ";class=" + ENCODED_TEXT_CLASSES[i]);
  786. } catch (ClassNotFoundException cannotHappen) {
  787. }
  788. if (dups.add(toAdd)) {
  789. retval.add(toAdd);
  790. }
  791. }
  792. }
  793. } else {
  794. DataFlavor flavor = (DataFlavor)value;
  795. if (dups.add(flavor)) {
  796. retval.add(flavor);
  797. }
  798. }
  799. }
  800. }
  801. ArrayList arrayList = new ArrayList(retval);
  802. getFlavorsForNativeCache.put(nat, new SoftReference(arrayList));
  803. return (List)arrayList.clone();
  804. }
  805. /**
  806. * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
  807. * their most preferred <code>String</code> native. Each native value will
  808. * be the same as the first native in the List returned by
  809. * <code>getNativesForFlavor</code> for the specified flavor.
  810. * <p>
  811. * If a specified <code>DataFlavor</code> is previously unknown to the
  812. * data transfer subsystem, then invoking this method will establish a
  813. * mapping in both directions between the specified <code>DataFlavor</code>
  814. * and an encoded version of its MIME type as its native.
  815. *
  816. * @param flavors an array of <code>DataFlavor</code>s which will be the
  817. * key set of the returned <code>Map</code>. If <code>null</code> is
  818. * specified, a mapping of all <code>DataFlavor</code>s known to the
  819. * data transfer subsystem to their most preferred
  820. * <code>String</code> natives will be returned.
  821. * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
  822. * <code>String</code> natives
  823. *
  824. * @see #getNativesForFlavor
  825. * @see #encodeDataFlavor
  826. */
  827. public synchronized Map<DataFlavor,String>
  828. getNativesForFlavors(DataFlavor[] flavors)
  829. {
  830. // Use getNativesForFlavor to generate extra natives for text flavors
  831. // and stringFlavor
  832. if (flavors == null) {
  833. List flavor_list = getFlavorsForNative(null);
  834. flavors = new DataFlavor[flavor_list.size()];
  835. flavor_list.toArray(flavors);
  836. }
  837. HashMap retval = new HashMap(flavors.length, 1.0f);
  838. for (int i = 0; i < flavors.length; i++) {
  839. List natives = getNativesForFlavor(flavors[i]);
  840. String nat = (natives.isEmpty()) ? null : (String)natives.get(0);
  841. retval.put(flavors[i], nat);
  842. }
  843. return retval;
  844. }
  845. /**
  846. * Returns a <code>Map</code> of the specified <code>String</code> natives
  847. * to their most preferred <code>DataFlavor</code>. Each
  848. * <code>DataFlavor</code> value will be the same as the first
  849. * <code>DataFlavor</code> in the List returned by
  850. * <code>getFlavorsForNative</code> for the specified native.
  851. * <p>
  852. * If a specified native is previously unknown to the data transfer
  853. * subsystem, and that native has been properly encoded, then invoking this
  854. * method will establish a mapping in both directions between the specified
  855. * native and a <code>DataFlavor</code> whose MIME type is a decoded
  856. * version of the native.
  857. *
  858. * @param natives an array of <code>String</code>s which will be the
  859. * key set of the returned <code>Map</code>. If <code>null</code> is
  860. * specified, a mapping of all supported <code>String</code> natives
  861. * to their most preferred <code>DataFlavor</code>s will be
  862. * returned.
  863. * @return a <code>java.util.Map</code> of <code>String</code> natives to
  864. * <code>DataFlavor</code>s
  865. *
  866. * @see #getFlavorsForNative
  867. * @see #encodeJavaMIMEType
  868. */
  869. public synchronized Map<String,DataFlavor>
  870. getFlavorsForNatives(String[] natives)
  871. {
  872. // Use getFlavorsForNative to generate extra flavors for text natives
  873. if (natives == null) {
  874. List native_list = getNativesForFlavor(null);
  875. natives = new String[native_list.size()];
  876. native_list.toArray(natives);
  877. }
  878. HashMap retval = new HashMap(natives.length, 1.0f);
  879. for (int i = 0; i < natives.length; i++) {
  880. List flavors = getFlavorsForNative(natives[i]);
  881. DataFlavor flav = (flavors.isEmpty())
  882. ? null : (DataFlavor)flavors.get(0);
  883. retval.put(natives[i], flav);
  884. }
  885. return retval;
  886. }
  887. /**
  888. * Adds a mapping from the specified <code>DataFlavor</code> (and all
  889. * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
  890. * to the specified <code>String</code> native.
  891. * Unlike <code>getNativesForFlavor</code>, the mapping will only be
  892. * established in one direction, and the native will not be encoded. To
  893. * establish a two-way mapping, call
  894. * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
  895. * be of lower priority than any existing mapping.
  896. * This method has no effect if a mapping from the specified or equal
  897. * <code>DataFlavor</code> to the specified <code>String</code> native
  898. * already exists.
  899. *
  900. * @param flav the <code>DataFlavor</code> key for the mapping
  901. * @param nat the <code>String</code> native value for the mapping
  902. * @throws NullPointerException if flav or nat is <code>null</code>
  903. *
  904. * @see #addFlavorForUnencodedNative
  905. * @since 1.4
  906. */
  907. public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
  908. String nat) {
  909. if (flav == null || nat == null) {
  910. throw new NullPointerException("null arguments not permitted");
  911. }
  912. List natives = (List)flavorToNative.get(flav);
  913. if (natives == null) {
  914. natives = new ArrayList(1);
  915. flavorToNative.put(flav, natives);
  916. } else if (natives.contains(nat)) {
  917. return;
  918. }
  919. natives.add(nat);
  920. getNativesForFlavorCache.remove(flav);
  921. getNativesForFlavorCache.remove(null);
  922. }
  923. /**
  924. * Discards the current mappings for the specified <code>DataFlavor</code>
  925. * and all <code>DataFlavor</code>s equal to the specified
  926. * <code>DataFlavor</code>, and creates new mappings to the
  927. * specified <code>String</code> natives.
  928. * Unlike <code>getNativesForFlavor</code>, the mappings will only be
  929. * established in one direction, and the natives will not be encoded. To
  930. * establish two-way mappings, call <code>setFlavorsForNative</code>
  931. * as well. The first native in the array will represent the highest
  932. * priority mapping. Subsequent natives will represent mappings of
  933. * decreasing priority.
  934. * <p>
  935. * If the array contains several elements that reference equal
  936. * <code>String</code> natives, this method will establish new mappings
  937. * for the first of those elements and ignore the rest of them.
  938. * <p>
  939. * It is recommended that client code not reset mappings established by the
  940. * data transfer subsystem. This method should only be used for
  941. * application-level mappings.
  942. *
  943. * @param flav the <code>DataFlavor</code> key for the mappings
  944. * @param natives the <code>String</code> native values for the mappings
  945. * @throws NullPointerException if flav or natives is <code>null</code>
  946. * or if natives contains <code>null</code> elements
  947. *
  948. * @see #setFlavorsForNative
  949. * @since 1.4
  950. */
  951. public synchronized void setNativesForFlavor(DataFlavor flav,
  952. String[] natives) {
  953. if (flav == null || natives == null) {
  954. throw new NullPointerException("null arguments not permitted");
  955. }
  956. flavorToNative.remove(flav);
  957. for (int i = 0; i < natives.length; i++) {
  958. addUnencodedNativeForFlavor(flav, natives[i]);
  959. }
  960. disabledMappingGenerationKeys.add(flav);
  961. // Clear the cache to handle the case of empty natives.
  962. getNativesForFlavorCache.remove(flav);
  963. getNativesForFlavorCache.remove(null);
  964. }
  965. /**
  966. * Adds a mapping from a single <code>String</code> native to a single
  967. * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
  968. * mapping will only be established in one direction, and the native will
  969. * not be encoded. To establish a two-way mapping, call
  970. * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
  971. * be of lower priority than any existing mapping.
  972. * This method has no effect if a mapping from the specified
  973. * <code>String</code> native to the specified or equal
  974. * <code>DataFlavor</code> already exists.
  975. *
  976. * @param nat the <code>String</code> native key for the mapping
  977. * @param flav the <code>DataFlavor</code> value for the mapping
  978. * @throws NullPointerException if nat or flav is <code>null</code>
  979. *
  980. * @see #addUnencodedNativeForFlavor
  981. * @since 1.4
  982. */
  983. public synchronized void addFlavorForUnencodedNative(String nat,
  984. DataFlavor flav) {
  985. if (nat == null || flav == null) {
  986. throw new NullPointerException("null arguments not permitted");
  987. }
  988. List flavors = (List)nativeToFlavor.get(nat);
  989. if (flavors == null) {
  990. flavors = new ArrayList(1);
  991. nativeToFlavor.put(nat, flavors);
  992. } else if (flavors.contains(flav)) {
  993. return;
  994. }
  995. flavors.add(flav);
  996. getFlavorsForNativeCache.remove(nat);
  997. getFlavorsForNativeCache.remove(null);
  998. }
  999. /**
  1000. * Discards the current mappings for the specified <code>String</code>
  1001. * native, and creates new mappings to the specified
  1002. * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
  1003. * mappings will only be established in one direction, and the natives need
  1004. * not be encoded. To establish two-way mappings, call
  1005. * <code>setNativesForFlavor</code> as well. The first
  1006. * <code>DataFlavor</code> in the array will represent the highest priority
  1007. * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
  1008. * decreasing priority.
  1009. * <p>
  1010. * If the array contains several elements that reference equal
  1011. * <code>DataFlavor</code>s, this method will establish new mappings
  1012. * for the first of those elements and ignore the rest of them.
  1013. * <p>
  1014. * It is recommended that client code not reset mappings established by the
  1015. * data transfer subsystem. This method should only be used for
  1016. * application-level mappings.
  1017. *
  1018. * @param nat the <code>String</code> native key for the mappings
  1019. * @param flavors the <code>DataFlavor</code> values for the mappings
  1020. * @throws NullPointerException if nat or flavors is <code>null</code>
  1021. * or if flavors contains <code>null</code> elements
  1022. *
  1023. * @see #setNativesForFlavor
  1024. * @since 1.4
  1025. */
  1026. public synchronized void setFlavorsForNative(String nat,
  1027. DataFlavor[] flavors) {
  1028. if (nat == null || flavors == null) {
  1029. throw new NullPointerException("null arguments not permitted");
  1030. }
  1031. nativeToFlavor.remove(nat);
  1032. for (int i = 0; i < flavors.length; i++) {
  1033. addFlavorForUnencodedNative(nat, flavors[i]);
  1034. }
  1035. disabledMappingGenerationKeys.add(nat);
  1036. // Clear the cache to handle the case of empty flavors.
  1037. getFlavorsForNativeCache.remove(nat);
  1038. getFlavorsForNativeCache.remove(null);
  1039. }
  1040. /**
  1041. * Encodes a MIME type for use as a <code>String</code> native. The format
  1042. * of an encoded representation of a MIME type is implementation-dependent.
  1043. * The only restrictions are:
  1044. * <ul>
  1045. * <li>The encoded representation is <code>null</code> if and only if the
  1046. * MIME type <code>String</code> is <code>null</code>.</li>
  1047. * <li>The encoded representations for two non-<code>null</code> MIME type
  1048. * <code>String</code>s are equal if and only if these <code>String</code>s
  1049. * are equal according to <code>String.equals(Object)</code>.</li>
  1050. * </ul>
  1051. * <p>
  1052. * Sun's reference implementation of this method returns the specified MIME
  1053. * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
  1054. *
  1055. * @param mimeType the MIME type to encode
  1056. * @return the encoded <code>String</code>, or <code>null</code> if
  1057. * mimeType is <code>null</code>
  1058. */
  1059. public static String encodeJavaMIMEType(String mimeType) {
  1060. return (mimeType != null)
  1061. ? JavaMIME + mimeType
  1062. : null;
  1063. }
  1064. /**
  1065. * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
  1066. * native. The format of an encoded <code>DataFlavor</code> is
  1067. * implementation-dependent. The only restrictions are:
  1068. * <ul>
  1069. * <li>The encoded representation is <code>null</code> if and only if the
  1070. * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
  1071. * <code>String</code> is <code>null</code>.</li>
  1072. * <li>The encoded representations for two non-<code>null</code>
  1073. * <code>DataFlavor</code>s with non-<code>null</code> MIME type
  1074. * <code>String</code>s are equal if and only if the MIME type
  1075. * <code>String</code>s of these <code>DataFlavor</code>s are equal
  1076. * according to <code>String.equals(Object)</code>.</li>
  1077. * </ul>
  1078. * <p>
  1079. * Sun's reference implementation of this method returns the MIME type
  1080. * <code>String</code> of the specified <code>DataFlavor</code> prefixed
  1081. * with <code>JAVA_DATAFLAVOR:</code>.
  1082. *
  1083. * @param flav the <code>DataFlavor</code> to encode
  1084. * @return the encoded <code>String</code>, or <code>null</code> if
  1085. * flav is <code>null</code> or has a <code>null</code> MIME type
  1086. */
  1087. public static String encodeDataFlavor(DataFlavor flav) {
  1088. return (flav != null)
  1089. ? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
  1090. : null;
  1091. }
  1092. /**
  1093. * Returns whether the specified <code>String</code> is an encoded Java
  1094. * MIME type.
  1095. *
  1096. * @param str the <code>String</code> to test
  1097. * @return <code>true</code> if the <code>String</code> is encoded;
  1098. * <code>false</code> otherwise
  1099. */
  1100. public static boolean isJavaMIMEType(String str) {
  1101. return (str != null && str.startsWith(JavaMIME, 0));
  1102. }
  1103. /**
  1104. * Decodes a <code>String</code> native for use as a Java MIME type.
  1105. *
  1106. * @param nat the <code>String</code> to decode
  1107. * @return the decoded Java MIME type, or <code>null</code> if nat is not
  1108. * an encoded <code>String</code> native
  1109. */
  1110. public static String decodeJavaMIMEType(String nat) {
  1111. return (isJavaMIMEType(nat))
  1112. ? nat.substring(JavaMIME.length(), nat.length()).trim()
  1113. : null;
  1114. }
  1115. /**
  1116. * Decodes a <code>String</code> native for use as a
  1117. * <code>DataFlavor</code>.
  1118. *
  1119. * @param nat the <code>String</code> to decode
  1120. * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
  1121. * nat is not an encoded <code>String</code> native
  1122. */
  1123. public static DataFlavor decodeDataFlavor(String nat)
  1124. throws ClassNotFoundException
  1125. {
  1126. String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
  1127. return (retval_str != null)
  1128. ? new DataFlavor(retval_str)
  1129. : null;
  1130. }
  1131. }