0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package java.awt.datatransfer;
0027
0028 import java.awt.Toolkit;
0029
0030 import java.lang.ref.SoftReference;
0031
0032 import java.io.BufferedReader;
0033 import java.io.File;
0034 import java.io.InputStreamReader;
0035 import java.io.IOException;
0036
0037 import java.net.URL;
0038 import java.net.MalformedURLException;
0039
0040 import java.util.ArrayList;
0041 import java.util.HashMap;
0042 import java.util.HashSet;
0043 import java.util.Iterator;
0044 import java.util.LinkedList;
0045 import java.util.List;
0046 import java.util.Map;
0047 import java.util.Set;
0048 import java.util.WeakHashMap;
0049
0050 import sun.awt.datatransfer.DataTransferer;
0051
0052 /**
0053 * The SystemFlavorMap is a configurable map between "natives" (Strings), which
0054 * correspond to platform-specific data formats, and "flavors" (DataFlavors),
0055 * which correspond to platform-independent MIME types. This mapping is used
0056 * by the data transfer subsystem to transfer data between Java and native
0057 * applications, and between Java applications in separate VMs.
0058 * <p>
0059 * In the Sun reference implementation, the default SystemFlavorMap is
0060 * initialized by the file <code>jre/lib/flavormap.properties</code> and the
0061 * contents of the URL referenced by the AWT property
0062 * <code>AWT.DnD.flavorMapFileURL</code>. See <code>flavormap.properties</code>
0063 * for details.
0064 *
0065 * @version 1.44, 05/05/07
0066 * @since 1.2
0067 */
0068 public final class SystemFlavorMap implements FlavorMap, FlavorTable {
0069
0070 /**
0071 * Constant prefix used to tag Java types converted to native platform
0072 * type.
0073 */
0074 private static String JavaMIME = "JAVA_DATAFLAVOR:";
0075
0076 /**
0077 * System singleton which maps a thread's ClassLoader to a SystemFlavorMap.
0078 */
0079 private static final WeakHashMap flavorMaps = new WeakHashMap();
0080
0081 /**
0082 * Copied from java.util.Properties.
0083 */
0084 private static final String keyValueSeparators = "=: \t\r\n\f";
0085 private static final String strictKeyValueSeparators = "=:";
0086 private static final String whiteSpaceChars = " \t\r\n\f";
0087
0088 /**
0089 * The list of valid, decoded text flavor representation classes, in order
0090 * from best to worst.
0091 */
0092 private static final String[] UNICODE_TEXT_CLASSES = {
0093 "java.io.Reader", "java.lang.String",
0094 "java.nio.CharBuffer", "\"[C\"" };
0095
0096 /**
0097 * The list of valid, encoded text flavor representation classes, in order
0098 * from best to worst.
0099 */
0100 private static final String[] ENCODED_TEXT_CLASSES = {
0101 "java.io.InputStream", "java.nio.ByteBuffer", "\"[B\"" };
0102
0103 /**
0104 * A String representing text/plain MIME type.
0105 */
0106 private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
0107
0108 /**
0109 * This constant is passed to flavorToNativeLookup() to indicate that a
0110 * a native should be synthesized, stored, and returned by encoding the
0111 * DataFlavor's MIME type in case if the DataFlavor is not found in
0112 * 'flavorToNative' map.
0113 */
0114 private static final boolean SYNTHESIZE_IF_NOT_FOUND = true;
0115
0116 /**
0117 * Maps native Strings to Lists of DataFlavors (or base type Strings for
0118 * text DataFlavors).
0119 */
0120 private Map nativeToFlavor = new HashMap();
0121
0122 /**
0123 * Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
0124 * native Strings.
0125 */
0126 private Map flavorToNative = new HashMap();
0127
0128 /**
0129 * Caches the result of getNativesForFlavor(). Maps DataFlavors to
0130 * SoftReferences which reference Lists of String natives.
0131 */
0132 private Map getNativesForFlavorCache = new HashMap();
0133
0134 /**
0135 * Caches the result getFlavorsForNative(). Maps String natives to
0136 * SoftReferences which reference Lists of DataFlavors.
0137 */
0138 private Map getFlavorsForNativeCache = new HashMap();
0139
0140 /**
0141 * Dynamic mapping generation used for text mappings should not be applied
0142 * to the DataFlavors and String natives for which the mappings have been
0143 * explicitly specified with setFlavorsForNative() or
0144 * setNativesForFlavor(). This keeps all such keys.
0145 */
0146 private Set disabledMappingGenerationKeys = new HashSet();
0147
0148 /**
0149 * Returns the default FlavorMap for this thread's ClassLoader.
0150 */
0151 public static FlavorMap getDefaultFlavorMap() {
0152 ClassLoader contextClassLoader = Thread.currentThread()
0153 .getContextClassLoader();
0154 if (contextClassLoader == null) {
0155 contextClassLoader = ClassLoader.getSystemClassLoader();
0156 }
0157
0158 FlavorMap fm;
0159
0160 synchronized (flavorMaps) {
0161 fm = (FlavorMap) flavorMaps.get(contextClassLoader);
0162 if (fm == null) {
0163 fm = new SystemFlavorMap();
0164 flavorMaps.put(contextClassLoader, fm);
0165 }
0166 }
0167
0168 return fm;
0169 }
0170
0171 /**
0172 * Constructs a SystemFlavorMap by reading flavormap.properties and
0173 * AWT.DnD.flavorMapFileURL.
0174 */
0175 private SystemFlavorMap() {
0176 BufferedReader flavormapDotProperties = (BufferedReader) java.security.AccessController
0177 .doPrivileged(new java.security.PrivilegedAction() {
0178 public Object run() {
0179 String fileName = System
0180 .getProperty("java.home")
0181 + File.separator
0182 + "lib"
0183 + File.separator
0184 + "flavormap.properties";
0185 try {
0186 return new BufferedReader(
0187 new InputStreamReader(new File(
0188 fileName).toURI().toURL()
0189 .openStream(), "ISO-8859-1"));
0190 } catch (MalformedURLException e) {
0191 System.err
0192 .println("MalformedURLException:"
0193 + e
0194 + " while loading default flavormap.properties file:"
0195 + fileName);
0196 } catch (IOException e) {
0197 System.err
0198 .println("IOException:"
0199 + e
0200 + " while loading default flavormap.properties file:"
0201 + fileName);
0202 }
0203 return null;
0204 }
0205 });
0206
0207 BufferedReader flavormapURL = (BufferedReader) java.security.AccessController
0208 .doPrivileged(new java.security.PrivilegedAction() {
0209 public Object run() {
0210 String url = Toolkit.getDefaultToolkit()
0211 .getProperty(
0212 "AWT.DnD.flavorMapFileURL",
0213 null);
0214
0215 if (url == null) {
0216 return null;
0217 }
0218
0219 try {
0220 return new BufferedReader(
0221 new InputStreamReader(new URL(url)
0222 .openStream(), "ISO-8859-1"));
0223 } catch (MalformedURLException e) {
0224 System.err
0225 .println("MalformedURLException:"
0226 + e
0227 + " while reading AWT.DnD.flavorMapFileURL:"
0228 + url);
0229 } catch (IOException e) {
0230 System.err
0231 .println("IOException:"
0232 + e
0233 + " while reading AWT.DnD.flavorMapFileURL:"
0234 + url);
0235 }
0236 return null;
0237 }
0238 });
0239
0240 if (flavormapDotProperties != null) {
0241 try {
0242 parseAndStoreReader(flavormapDotProperties);
0243 } catch (IOException e) {
0244 System.err
0245 .println("IOException:"
0246 + e
0247 + " while parsing default flavormap.properties file");
0248 }
0249 }
0250
0251 if (flavormapURL != null) {
0252 try {
0253 parseAndStoreReader(flavormapURL);
0254 } catch (IOException e) {
0255 System.err.println("IOException:" + e
0256 + " while parsing AWT.DnD.flavorMapFileURL");
0257 }
0258 }
0259 }
0260
0261 /**
0262 * Copied code from java.util.Properties. Parsing the data ourselves is the
0263 * only way to handle duplicate keys and values.
0264 */
0265 private void parseAndStoreReader(BufferedReader in)
0266 throws IOException {
0267 while (true) {
0268 // Get next line
0269 String line = in.readLine();
0270 if (line == null) {
0271 return;
0272 }
0273
0274 if (line.length() > 0) {
0275 // Continue lines that end in slashes if they are not comments
0276 char firstChar = line.charAt(0);
0277 if (firstChar != '#' && firstChar != '!') {
0278 while (continueLine(line)) {
0279 String nextLine = in.readLine();
0280 if (nextLine == null) {
0281 nextLine = new String("");
0282 }
0283 String loppedLine = line.substring(0, line
0284 .length() - 1);
0285 // Advance beyond whitespace on new line
0286 int startIndex = 0;
0287 for (; startIndex < nextLine.length(); startIndex++) {
0288 if (whiteSpaceChars.indexOf(nextLine
0289 .charAt(startIndex)) == -1) {
0290 break;
0291 }
0292 }
0293 nextLine = nextLine.substring(startIndex,
0294 nextLine.length());
0295 line = new String(loppedLine + nextLine);
0296 }
0297
0298 // Find start of key
0299 int len = line.length();
0300 int keyStart = 0;
0301 for (; keyStart < len; keyStart++) {
0302 if (whiteSpaceChars.indexOf(line
0303 .charAt(keyStart)) == -1) {
0304 break;
0305 }
0306 }
0307
0308 // Blank lines are ignored
0309 if (keyStart == len) {
0310 continue;
0311 }
0312
0313 // Find separation between key and value
0314 int separatorIndex = keyStart;
0315 for (; separatorIndex < len; separatorIndex++) {
0316 char currentChar = line.charAt(separatorIndex);
0317 if (currentChar == '\\') {
0318 separatorIndex++;
0319 } else if (keyValueSeparators
0320 .indexOf(currentChar) != -1) {
0321 break;
0322 }
0323 }
0324
0325 // Skip over whitespace after key if any
0326 int valueIndex = separatorIndex;
0327 for (; valueIndex < len; valueIndex++) {
0328 if (whiteSpaceChars.indexOf(line
0329 .charAt(valueIndex)) == -1) {
0330 break;
0331 }
0332 }
0333
0334 // Skip over one non whitespace key value separators if any
0335 if (valueIndex < len) {
0336 if (strictKeyValueSeparators.indexOf(line
0337 .charAt(valueIndex)) != -1) {
0338 valueIndex++;
0339 }
0340 }
0341
0342 // Skip over white space after other separators if any
0343 while (valueIndex < len) {
0344 if (whiteSpaceChars.indexOf(line
0345 .charAt(valueIndex)) == -1) {
0346 break;
0347 }
0348 valueIndex++;
0349 }
0350
0351 String key = line.substring(keyStart,
0352 separatorIndex);
0353 String value = (separatorIndex < len) ? line
0354 .substring(valueIndex, len) : "";
0355
0356 // Convert then store key and value
0357 key = loadConvert(key);
0358 value = loadConvert(value);
0359
0360 try {
0361 MimeType mime = new MimeType(value);
0362 if ("text".equals(mime.getPrimaryType())) {
0363 String charset = mime
0364 .getParameter("charset");
0365 if (DataTransferer
0366 .doesSubtypeSupportCharset(mime
0367 .getSubType(), charset)) {
0368 // We need to store the charset and eoln
0369 // parameters, if any, so that the
0370 // DataTransferer will have this information
0371 // for conversion into the native format.
0372 DataTransferer transferer = DataTransferer
0373 .getInstance();
0374 if (transferer != null) {
0375 transferer
0376 .registerTextFlavorProperties(
0377 key,
0378 charset,
0379 mime
0380 .getParameter("eoln"),
0381 mime
0382 .getParameter("terminators"));
0383 }
0384 }
0385
0386 // But don't store any of these parameters in the
0387 // DataFlavor itself for any text natives (even
0388 // non-charset ones). The SystemFlavorMap will
0389 // synthesize the appropriate mappings later.
0390 mime.removeParameter("charset");
0391 mime.removeParameter("class");
0392 mime.removeParameter("eoln");
0393 mime.removeParameter("terminators");
0394 value = mime.toString();
0395 }
0396 } catch (MimeTypeParseException e) {
0397 e.printStackTrace();
0398 continue;
0399 }
0400
0401 DataFlavor flavor;
0402 try {
0403 flavor = new DataFlavor(value);
0404 } catch (Exception e) {
0405 try {
0406 flavor = new DataFlavor(value,
0407 (String) null);
0408 } catch (Exception ee) {
0409 ee.printStackTrace();
0410 continue;
0411 }
0412 }
0413
0414 // For text/* flavors, store mappings in separate maps to
0415 // enable dynamic mapping generation at a run-time.
0416 if ("text".equals(flavor.getPrimaryType())) {
0417 store(value, key, flavorToNative);
0418 store(key, value, nativeToFlavor);
0419 } else {
0420 store(flavor, key, flavorToNative);
0421 store(key, flavor, nativeToFlavor);
0422 }
0423 }
0424 }
0425 }
0426 }
0427
0428 /**
0429 * Copied from java.util.Properties.
0430 */
0431 private boolean continueLine(String line) {
0432 int slashCount = 0;
0433 int index = line.length() - 1;
0434 while ((index >= 0) && (line.charAt(index--) == '\\')) {
0435 slashCount++;
0436 }
0437 return (slashCount % 2 == 1);
0438 }
0439
0440 /**
0441 * Copied from java.util.Properties.
0442 */
0443 private String loadConvert(String theString) {
0444 char aChar;
0445 int len = theString.length();
0446 StringBuilder outBuffer = new StringBuilder(len);
0447
0448 for (int x = 0; x < len;) {
0449 aChar = theString.charAt(x++);
0450 if (aChar == '\\') {
0451 aChar = theString.charAt(x++);
0452 if (aChar == 'u') {
0453 // Read the xxxx
0454 int value = 0;
0455 for (int i = 0; i < 4; i++) {
0456 aChar = theString.charAt(x++);
0457 switch (aChar) {
0458 case '0':
0459 case '1':
0460 case '2':
0461 case '3':
0462 case '4':
0463 case '5':
0464 case '6':
0465 case '7':
0466 case '8':
0467 case '9': {
0468 value = (value << 4) + aChar - '0';
0469 break;
0470 }
0471 case 'a':
0472 case 'b':
0473 case 'c':
0474 case 'd':
0475 case 'e':
0476 case 'f': {
0477 value = (value << 4) + 10 + aChar - 'a';
0478 break;
0479 }
0480 case 'A':
0481 case 'B':
0482 case 'C':
0483 case 'D':
0484 case 'E':
0485 case 'F': {
0486 value = (value << 4) + 10 + aChar - 'A';
0487 break;
0488 }
0489 default: {
0490 throw new IllegalArgumentException(
0491 "Malformed \\uxxxx encoding.");
0492 }
0493 }
0494 }
0495 outBuffer.append((char) value);
0496 } else {
0497 if (aChar == 't') {
0498 aChar = '\t';
0499 } else if (aChar == 'r') {
0500 aChar = '\r';
0501 } else if (aChar == 'n') {
0502 aChar = '\n';
0503 } else if (aChar == 'f') {
0504 aChar = '\f';
0505 }
0506 outBuffer.append(aChar);
0507 }
0508 } else {
0509 outBuffer.append(aChar);
0510 }
0511 }
0512 return outBuffer.toString();
0513 }
0514
0515 /**
0516 * Stores the listed object under the specified hash key in map. Unlike a
0517 * standard map, the listed object will not replace any object already at
0518 * the appropriate Map location, but rather will be appended to a List
0519 * stored in that location.
0520 */
0521 private void store(Object hashed, Object listed, Map map) {
0522 List list = (List) map.get(hashed);
0523 if (list == null) {
0524 list = new ArrayList(1);
0525 map.put(hashed, list);
0526 }
0527 if (!list.contains(listed)) {
0528 list.add(listed);
0529 }
0530 }
0531
0532 /**
0533 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method
0534 * handles the case where 'nat' is not found in 'nativeToFlavor'. In that
0535 * case, a new DataFlavor is synthesized, stored, and returned, if and
0536 * only if the specified native is encoded as a Java MIME type.
0537 */
0538 private List nativeToFlavorLookup(String nat) {
0539 List flavors = (List) nativeToFlavor.get(nat);
0540
0541 if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
0542 DataTransferer transferer = DataTransferer.getInstance();
0543 if (transferer != null) {
0544 List platformFlavors = transferer
0545 .getPlatformMappingsForNative(nat);
0546 if (!platformFlavors.isEmpty()) {
0547 if (flavors != null) {
0548 platformFlavors.removeAll(new HashSet(flavors));
0549 // Prepending the platform-specific mappings ensures
0550 // that the flavors added with
0551 // addFlavorForUnencodedNative() are at the end of
0552 // list.
0553 platformFlavors.addAll(flavors);
0554 }
0555 flavors = platformFlavors;
0556 }
0557 }
0558 }
0559
0560 if (flavors == null && isJavaMIMEType(nat)) {
0561 String decoded = decodeJavaMIMEType(nat);
0562 DataFlavor flavor = null;
0563
0564 try {
0565 flavor = new DataFlavor(decoded);
0566 } catch (Exception e) {
0567 System.err.println("Exception \""
0568 + e.getClass().getName() + ": "
0569 + e.getMessage()
0570 + "\"while constructing DataFlavor for: "
0571 + decoded);
0572 }
0573
0574 if (flavor != null) {
0575 flavors = new ArrayList(1);
0576 nativeToFlavor.put(nat, flavors);
0577 flavors.add(flavor);
0578 getFlavorsForNativeCache.remove(nat);
0579 getFlavorsForNativeCache.remove(null);
0580
0581 List natives = (List) flavorToNative.get(flavor);
0582 if (natives == null) {
0583 natives = new ArrayList(1);
0584 flavorToNative.put(flavor, natives);
0585 }
0586 natives.add(nat);
0587 getNativesForFlavorCache.remove(flavor);
0588 getNativesForFlavorCache.remove(null);
0589 }
0590 }
0591
0592 return (flavors != null) ? flavors : new ArrayList(0);
0593 }
0594
0595 /**
0596 * Semantically equivalent to 'flavorToNative.get(flav)'. This method
0597 * handles the case where 'flav' is not found in 'flavorToNative' depending
0598 * on the value of passes 'synthesize' parameter. If 'synthesize' is
0599 * SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
0600 * encoding the DataFlavor's MIME type. Otherwise an empty List is returned
0601 * and 'flavorToNative' remains unaffected.
0602 */
0603 private List flavorToNativeLookup(final DataFlavor flav,
0604 final boolean synthesize) {
0605 List natives = (List) flavorToNative.get(flav);
0606
0607 if (flav != null
0608 && !disabledMappingGenerationKeys.contains(flav)) {
0609 DataTransferer transferer = DataTransferer.getInstance();
0610 if (transferer != null) {
0611 List platformNatives = transferer
0612 .getPlatformMappingsForFlavor(flav);
0613 if (!platformNatives.isEmpty()) {
0614 if (natives != null) {
0615 platformNatives.removeAll(new HashSet(natives));
0616 // Prepend the platform-specific mappings to ensure
0617 // that the natives added with
0618 // addUnencodedNativeForFlavor() are at the end of
0619 // list.
0620 platformNatives.addAll(natives);
0621 }
0622 natives = platformNatives;
0623 }
0624 }
0625 }
0626
0627 if (natives == null) {
0628 if (synthesize) {
0629 String encoded = encodeDataFlavor(flav);
0630 natives = new ArrayList(1);
0631 flavorToNative.put(flav, natives);
0632 natives.add(encoded);
0633 getNativesForFlavorCache.remove(flav);
0634 getNativesForFlavorCache.remove(null);
0635
0636 List flavors = (List) nativeToFlavor.get(encoded);
0637 if (flavors == null) {
0638 flavors = new ArrayList(1);
0639 nativeToFlavor.put(encoded, flavors);
0640 }
0641 flavors.add(flav);
0642 getFlavorsForNativeCache.remove(encoded);
0643 getFlavorsForNativeCache.remove(null);
0644 } else {
0645 natives = new ArrayList(0);
0646 }
0647 }
0648
0649 return natives;
0650 }
0651
0652 /**
0653 * Returns a <code>List</code> of <code>String</code> natives to which the
0654 * specified <code>DataFlavor</code> can be translated by the data transfer
0655 * subsystem. The <code>List</code> will be sorted from best native to
0656 * worst. That is, the first native will best reflect data in the specified
0657 * flavor to the underlying native platform.
0658 * <p>
0659 * If the specified <code>DataFlavor</code> is previously unknown to the
0660 * data transfer subsystem and the data transfer subsystem is unable to
0661 * translate this <code>DataFlavor</code> to any existing native, then
0662 * invoking this method will establish a
0663 * mapping in both directions between the specified <code>DataFlavor</code>
0664 * and an encoded version of its MIME type as its native.
0665 *
0666 * @param flav the <code>DataFlavor</code> whose corresponding natives
0667 * should be returned. If <code>null</code> is specified, all
0668 * natives currently known to the data transfer subsystem are
0669 * returned in a non-deterministic order.
0670 * @return a <code>java.util.List</code> of <code>java.lang.String</code>
0671 * objects which are platform-specific representations of platform-
0672 * specific data formats
0673 *
0674 * @see #encodeDataFlavor
0675 * @since 1.4
0676 */
0677 public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
0678 List retval = null;
0679
0680 // Check cache, even for null flav
0681 SoftReference ref = (SoftReference) getNativesForFlavorCache
0682 .get(flav);
0683 if (ref != null) {
0684 retval = (List) ref.get();
0685 if (retval != null) {
0686 // Create a copy, because client code can modify the returned
0687 // list.
0688 return new ArrayList(retval);
0689 }
0690 }
0691
0692 if (flav == null) {
0693 retval = new ArrayList(nativeToFlavor.keySet());
0694 } else if (disabledMappingGenerationKeys.contains(flav)) {
0695 // In this case we shouldn't synthesize a native for this flavor,
0696 // since its mappings were explicitly specified.
0697 retval = flavorToNativeLookup(flav,
0698 !SYNTHESIZE_IF_NOT_FOUND);
0699 } else if (DataTransferer.isFlavorCharsetTextType(flav)) {
0700
0701 // For text/* flavors, flavor-to-native mappings specified in
0702 // flavormap.properties are stored per flavor's base type.
0703 if ("text".equals(flav.getPrimaryType())) {
0704 retval = (List) flavorToNative.get(flav.mimeType
0705 .getBaseType());
0706 if (retval != null) {
0707 // To prevent the List stored in the map from modification.
0708 retval = new ArrayList(retval);
0709 }
0710 }
0711
0712 // Also include text/plain natives, but don't duplicate Strings
0713 List textPlainList = (List) flavorToNative
0714 .get(TEXT_PLAIN_BASE_TYPE);
0715
0716 if (textPlainList != null && !textPlainList.isEmpty()) {
0717 // To prevent the List stored in the map from modification.
0718 // This also guarantees that removeAll() is supported.
0719 textPlainList = new ArrayList(textPlainList);
0720 if (retval != null && !retval.isEmpty()) {
0721 // Use HashSet to get constant-time performance for search.
0722 textPlainList.removeAll(new HashSet(retval));
0723 retval.addAll(textPlainList);
0724 } else {
0725 retval = textPlainList;
0726 }
0727 }
0728
0729 if (retval == null || retval.isEmpty()) {
0730 retval = flavorToNativeLookup(flav,
0731 SYNTHESIZE_IF_NOT_FOUND);
0732 } else {
0733 // In this branch it is guaranteed that natives explicitly
0734 // listed for flav's MIME type were added with
0735 // addUnencodedNativeForFlavor(), so they have lower priority.
0736 List explicitList = flavorToNativeLookup(flav,
0737 !SYNTHESIZE_IF_NOT_FOUND);
0738
0739 // flavorToNativeLookup() never returns null.
0740 // It can return an empty List, however.
0741 if (!explicitList.isEmpty()) {
0742 // To prevent the List stored in the map from modification.
0743 // This also guarantees that removeAll() is supported.
0744 explicitList = new ArrayList(explicitList);
0745 // Use HashSet to get constant-time performance for search.
0746 explicitList.removeAll(new HashSet(retval));
0747 retval.addAll(explicitList);
0748 }
0749 }
0750 } else if (DataTransferer.isFlavorNoncharsetTextType(flav)) {
0751 retval = (List) flavorToNative.get(flav.mimeType
0752 .getBaseType());
0753
0754 if (retval == null || retval.isEmpty()) {
0755 retval = flavorToNativeLookup(flav,
0756 SYNTHESIZE_IF_NOT_FOUND);
0757 } else {
0758 // In this branch it is guaranteed that natives explicitly
0759 // listed for flav's MIME type were added with
0760 // addUnencodedNativeForFlavor(), so they have lower priority.
0761 List explicitList = flavorToNativeLookup(flav,
0762 !SYNTHESIZE_IF_NOT_FOUND);
0763
0764 // flavorToNativeLookup() never returns null.
0765 // It can return an empty List, however.
0766 if (!explicitList.isEmpty()) {
0767 // To prevent the List stored in the map from modification.
0768 // This also guarantees that add/removeAll() are supported.
0769 retval = new ArrayList(retval);
0770 explicitList = new ArrayList(explicitList);
0771 // Use HashSet to get constant-time performance for search.
0772 explicitList.removeAll(new HashSet(retval));
0773 retval.addAll(explicitList);
0774 }
0775 }
0776 } else {
0777 retval = flavorToNativeLookup(flav, SYNTHESIZE_IF_NOT_FOUND);
0778 }
0779
0780 getNativesForFlavorCache.put(flav, new SoftReference(retval));
0781 // Create a copy, because client code can modify the returned list.
0782 return new ArrayList(retval);
0783 }
0784
0785 /**
0786 * Returns a <code>List</code> of <code>DataFlavor</code>s to which the
0787 * specified <code>String</code> native can be translated by the data
0788 * transfer subsystem. The <code>List</code> will be sorted from best
0789 * <code>DataFlavor</code> to worst. That is, the first
0790 * <code>DataFlavor</code> will best reflect data in the specified
0791 * native to a Java application.
0792 * <p>
0793 * If the specified native is previously unknown to the data transfer
0794 * subsystem, and that native has been properly encoded, then invoking this
0795 * method will establish a mapping in both directions between the specified
0796 * native and a <code>DataFlavor</code> whose MIME type is a decoded
0797 * version of the native.
0798 * <p>
0799 * If the specified native is not a properly encoded native and the
0800 * mappings for this native have not been altered with
0801 * <code>setFlavorsForNative</code>, then the contents of the
0802 * <code>List</code> is platform dependent, but <code>null</code>
0803 * cannot be returned.
0804 *
0805 * @param nat the native whose corresponding <code>DataFlavor</code>s
0806 * should be returned. If <code>null</code> is specified, all
0807 * <code>DataFlavor</code>s currently known to the data transfer
0808 * subsystem are returned in a non-deterministic order.
0809 * @return a <code>java.util.List</code> of <code>DataFlavor</code>
0810 * objects into which platform-specific data in the specified,
0811 * platform-specific native can be translated
0812 *
0813 * @see #encodeJavaMIMEType
0814 * @since 1.4
0815 */
0816 public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
0817
0818 // Check cache, even for null nat
0819 SoftReference ref = (SoftReference) getFlavorsForNativeCache
0820 .get(nat);
0821 if (ref != null) {
0822 ArrayList retval = (ArrayList) ref.get();
0823 if (retval != null) {
0824 return (List) retval.clone();
0825 }
0826 }
0827
0828 LinkedList retval = new LinkedList();
0829
0830 if (nat == null) {
0831 List natives = getNativesForFlavor(null);
0832 HashSet dups = new HashSet(natives.size());
0833
0834 for (Iterator natives_iter = natives.iterator(); natives_iter
0835 .hasNext();) {
0836 List flavors = getFlavorsForNative((String) natives_iter
0837 .next());
0838 for (Iterator flavors_iter = flavors.iterator(); flavors_iter
0839 .hasNext();) {
0840 Object flavor = flavors_iter.next();
0841 if (dups.add(flavor)) {
0842 retval.add(flavor);
0843 }
0844 }
0845 }
0846 } else {
0847 List flavors = nativeToFlavorLookup(nat);
0848
0849 if (disabledMappingGenerationKeys.contains(nat)) {
0850 return flavors;
0851 }
0852
0853 HashSet dups = new HashSet(flavors.size());
0854
0855 List flavorsAndbaseTypes = nativeToFlavorLookup(nat);
0856
0857 for (Iterator flavorsAndbaseTypes_iter = flavorsAndbaseTypes
0858 .iterator(); flavorsAndbaseTypes_iter.hasNext();) {
0859 Object value = flavorsAndbaseTypes_iter.next();
0860 if (value instanceof String) {
0861 String baseType = (String) value;
0862 String subType = null;
0863 try {
0864 MimeType mimeType = new MimeType(baseType);
0865 subType = mimeType.getSubType();
0866 } catch (MimeTypeParseException mtpe) {
0867 // Cannot happen, since we checked all mappings
0868 // on load from flavormap.properties.
0869 assert (false);
0870 }
0871 if (DataTransferer.doesSubtypeSupportCharset(
0872 subType, null)) {
0873 if (TEXT_PLAIN_BASE_TYPE.equals(baseType)
0874 && dups.add(DataFlavor.stringFlavor)) {
0875 retval.add(DataFlavor.stringFlavor);
0876 }
0877
0878 for (int i = 0; i < UNICODE_TEXT_CLASSES.length; i++) {
0879 DataFlavor toAdd = null;
0880 try {
0881 toAdd = new DataFlavor(baseType
0882 + ";charset=Unicode;class="
0883 + UNICODE_TEXT_CLASSES[i]);
0884 } catch (ClassNotFoundException cannotHappen) {
0885 }
0886 if (dups.add(toAdd)) {
0887 retval.add(toAdd);
0888 }
0889 }
0890
0891 for (Iterator charset_iter = DataTransferer
0892 .standardEncodings(); charset_iter
0893 .hasNext();) {
0894 String charset = (String) charset_iter
0895 .next();
0896
0897 for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) {
0898 DataFlavor toAdd = null;
0899 try {
0900 toAdd = new DataFlavor(baseType
0901 + ";charset=" + charset
0902 + ";class="
0903 + ENCODED_TEXT_CLASSES[i]);
0904 } catch (ClassNotFoundException cannotHappen) {
0905 }
0906
0907 // Check for equality to plainTextFlavor so
0908 // that we can ensure that the exact charset of
0909 // plainTextFlavor, not the canonical charset
0910 // or another equivalent charset with a
0911 // different name, is used.
0912 if (toAdd
0913 .equals(DataFlavor.plainTextFlavor)) {
0914 toAdd = DataFlavor.plainTextFlavor;
0915 }
0916
0917 if (dups.add(toAdd)) {
0918 retval.add(toAdd);
0919 }
0920 }
0921 }
0922
0923 if (TEXT_PLAIN_BASE_TYPE.equals(baseType)
0924 && dups.add(DataFlavor.plainTextFlavor)) {
0925 retval.add(DataFlavor.plainTextFlavor);
0926 }
0927 } else {
0928 // Non-charset text natives should be treated as
0929 // opaque, 8-bit data in any of its various
0930 // representations.
0931 for (int i = 0; i < ENCODED_TEXT_CLASSES.length; i++) {
0932 DataFlavor toAdd = null;
0933 try {
0934 toAdd = new DataFlavor(baseType
0935 + ";class="
0936 + ENCODED_TEXT_CLASSES[i]);
0937 } catch (ClassNotFoundException cannotHappen) {
0938 }
0939
0940 if (dups.add(toAdd)) {
0941 retval.add(toAdd);
0942 }
0943 }
0944 }
0945 } else {
0946 DataFlavor flavor = (DataFlavor) value;
0947 if (dups.add(flavor)) {
0948 retval.add(flavor);
0949 }
0950 }
0951 }
0952 }
0953
0954 ArrayList arrayList = new ArrayList(retval);
0955 getFlavorsForNativeCache.put(nat, new SoftReference(arrayList));
0956 return (List) arrayList.clone();
0957 }
0958
0959 /**
0960 * Returns a <code>Map</code> of the specified <code>DataFlavor</code>s to
0961 * their most preferred <code>String</code> native. Each native value will
0962 * be the same as the first native in the List returned by
0963 * <code>getNativesForFlavor</code> for the specified flavor.
0964 * <p>
0965 * If a specified <code>DataFlavor</code> is previously unknown to the
0966 * data transfer subsystem, then invoking this method will establish a
0967 * mapping in both directions between the specified <code>DataFlavor</code>
0968 * and an encoded version of its MIME type as its native.
0969 *
0970 * @param flavors an array of <code>DataFlavor</code>s which will be the
0971 * key set of the returned <code>Map</code>. If <code>null</code> is
0972 * specified, a mapping of all <code>DataFlavor</code>s known to the
0973 * data transfer subsystem to their most preferred
0974 * <code>String</code> natives will be returned.
0975 * @return a <code>java.util.Map</code> of <code>DataFlavor</code>s to
0976 * <code>String</code> natives
0977 *
0978 * @see #getNativesForFlavor
0979 * @see #encodeDataFlavor
0980 */
0981 public synchronized Map<DataFlavor, String> getNativesForFlavors(
0982 DataFlavor[] flavors) {
0983 // Use getNativesForFlavor to generate extra natives for text flavors
0984 // and stringFlavor
0985
0986 if (flavors == null) {
0987 List flavor_list = getFlavorsForNative(null);
0988 flavors = new DataFlavor[flavor_list.size()];
0989 flavor_list.toArray(flavors);
0990 }
0991
0992 HashMap retval = new HashMap(flavors.length, 1.0f);
0993 for (int i = 0; i < flavors.length; i++) {
0994 List natives = getNativesForFlavor(flavors[i]);
0995 String nat = (natives.isEmpty()) ? null : (String) natives
0996 .get(0);
0997 retval.put(flavors[i], nat);
0998 }
0999
1000 return retval;
1001 }
1002
1003 /**
1004 * Returns a <code>Map</code> of the specified <code>String</code> natives
1005 * to their most preferred <code>DataFlavor</code>. Each
1006 * <code>DataFlavor</code> value will be the same as the first
1007 * <code>DataFlavor</code> in the List returned by
1008 * <code>getFlavorsForNative</code> for the specified native.
1009 * <p>
1010 * If a specified native is previously unknown to the data transfer
1011 * subsystem, and that native has been properly encoded, then invoking this
1012 * method will establish a mapping in both directions between the specified
1013 * native and a <code>DataFlavor</code> whose MIME type is a decoded
1014 * version of the native.
1015 *
1016 * @param natives an array of <code>String</code>s which will be the
1017 * key set of the returned <code>Map</code>. If <code>null</code> is
1018 * specified, a mapping of all supported <code>String</code> natives
1019 * to their most preferred <code>DataFlavor</code>s will be
1020 * returned.
1021 * @return a <code>java.util.Map</code> of <code>String</code> natives to
1022 * <code>DataFlavor</code>s
1023 *
1024 * @see #getFlavorsForNative
1025 * @see #encodeJavaMIMEType
1026 */
1027 public synchronized Map<String, DataFlavor> getFlavorsForNatives(
1028 String[] natives) {
1029 // Use getFlavorsForNative to generate extra flavors for text natives
1030
1031 if (natives == null) {
1032 List native_list = getNativesForFlavor(null);
1033 natives = new String[native_list.size()];
1034 native_list.toArray(natives);
1035 }
1036
1037 HashMap retval = new HashMap(natives.length, 1.0f);
1038 for (int i = 0; i < natives.length; i++) {
1039 List flavors = getFlavorsForNative(natives[i]);
1040 DataFlavor flav = (flavors.isEmpty()) ? null
1041 : (DataFlavor) flavors.get(0);
1042 retval.put(natives[i], flav);
1043 }
1044
1045 return retval;
1046 }
1047
1048 /**
1049 * Adds a mapping from the specified <code>DataFlavor</code> (and all
1050 * <code>DataFlavor</code>s equal to the specified <code>DataFlavor</code>)
1051 * to the specified <code>String</code> native.
1052 * Unlike <code>getNativesForFlavor</code>, the mapping will only be
1053 * established in one direction, and the native will not be encoded. To
1054 * establish a two-way mapping, call
1055 * <code>addFlavorForUnencodedNative</code> as well. The new mapping will
1056 * be of lower priority than any existing mapping.
1057 * This method has no effect if a mapping from the specified or equal
1058 * <code>DataFlavor</code> to the specified <code>String</code> native
1059 * already exists.
1060 *
1061 * @param flav the <code>DataFlavor</code> key for the mapping
1062 * @param nat the <code>String</code> native value for the mapping
1063 * @throws NullPointerException if flav or nat is <code>null</code>
1064 *
1065 * @see #addFlavorForUnencodedNative
1066 * @since 1.4
1067 */
1068 public synchronized void addUnencodedNativeForFlavor(
1069 DataFlavor flav, String nat) {
1070 if (flav == null || nat == null) {
1071 throw new NullPointerException(
1072 "null arguments not permitted");
1073 }
1074
1075 List natives = (List) flavorToNative.get(flav);
1076 if (natives == null) {
1077 natives = new ArrayList(1);
1078 flavorToNative.put(flav, natives);
1079 } else if (natives.contains(nat)) {
1080 return;
1081 }
1082 natives.add(nat);
1083 getNativesForFlavorCache.remove(flav);
1084 getNativesForFlavorCache.remove(null);
1085 }
1086
1087 /**
1088 * Discards the current mappings for the specified <code>DataFlavor</code>
1089 * and all <code>DataFlavor</code>s equal to the specified
1090 * <code>DataFlavor</code>, and creates new mappings to the
1091 * specified <code>String</code> natives.
1092 * Unlike <code>getNativesForFlavor</code>, the mappings will only be
1093 * established in one direction, and the natives will not be encoded. To
1094 * establish two-way mappings, call <code>setFlavorsForNative</code>
1095 * as well. The first native in the array will represent the highest
1096 * priority mapping. Subsequent natives will represent mappings of
1097 * decreasing priority.
1098 * <p>
1099 * If the array contains several elements that reference equal
1100 * <code>String</code> natives, this method will establish new mappings
1101 * for the first of those elements and ignore the rest of them.
1102 * <p>
1103 * It is recommended that client code not reset mappings established by the
1104 * data transfer subsystem. This method should only be used for
1105 * application-level mappings.
1106 *
1107 * @param flav the <code>DataFlavor</code> key for the mappings
1108 * @param natives the <code>String</code> native values for the mappings
1109 * @throws NullPointerException if flav or natives is <code>null</code>
1110 * or if natives contains <code>null</code> elements
1111 *
1112 * @see #setFlavorsForNative
1113 * @since 1.4
1114 */
1115 public synchronized void setNativesForFlavor(DataFlavor flav,
1116 String[] natives) {
1117 if (flav == null || natives == null) {
1118 throw new NullPointerException(
1119 "null arguments not permitted");
1120 }
1121
1122 flavorToNative.remove(flav);
1123 for (int i = 0; i < natives.length; i++) {
1124 addUnencodedNativeForFlavor(flav, natives[i]);
1125 }
1126 disabledMappingGenerationKeys.add(flav);
1127 // Clear the cache to handle the case of empty natives.
1128 getNativesForFlavorCache.remove(flav);
1129 getNativesForFlavorCache.remove(null);
1130 }
1131
1132 /**
1133 * Adds a mapping from a single <code>String</code> native to a single
1134 * <code>DataFlavor</code>. Unlike <code>getFlavorsForNative</code>, the
1135 * mapping will only be established in one direction, and the native will
1136 * not be encoded. To establish a two-way mapping, call
1137 * <code>addUnencodedNativeForFlavor</code> as well. The new mapping will
1138 * be of lower priority than any existing mapping.
1139 * This method has no effect if a mapping from the specified
1140 * <code>String</code> native to the specified or equal
1141 * <code>DataFlavor</code> already exists.
1142 *
1143 * @param nat the <code>String</code> native key for the mapping
1144 * @param flav the <code>DataFlavor</code> value for the mapping
1145 * @throws NullPointerException if nat or flav is <code>null</code>
1146 *
1147 * @see #addUnencodedNativeForFlavor
1148 * @since 1.4
1149 */
1150 public synchronized void addFlavorForUnencodedNative(String nat,
1151 DataFlavor flav) {
1152 if (nat == null || flav == null) {
1153 throw new NullPointerException(
1154 "null arguments not permitted");
1155 }
1156
1157 List flavors = (List) nativeToFlavor.get(nat);
1158 if (flavors == null) {
1159 flavors = new ArrayList(1);
1160 nativeToFlavor.put(nat, flavors);
1161 } else if (flavors.contains(flav)) {
1162 return;
1163 }
1164 flavors.add(flav);
1165 getFlavorsForNativeCache.remove(nat);
1166 getFlavorsForNativeCache.remove(null);
1167 }
1168
1169 /**
1170 * Discards the current mappings for the specified <code>String</code>
1171 * native, and creates new mappings to the specified
1172 * <code>DataFlavor</code>s. Unlike <code>getFlavorsForNative</code>, the
1173 * mappings will only be established in one direction, and the natives need
1174 * not be encoded. To establish two-way mappings, call
1175 * <code>setNativesForFlavor</code> as well. The first
1176 * <code>DataFlavor</code> in the array will represent the highest priority
1177 * mapping. Subsequent <code>DataFlavor</code>s will represent mappings of
1178 * decreasing priority.
1179 * <p>
1180 * If the array contains several elements that reference equal
1181 * <code>DataFlavor</code>s, this method will establish new mappings
1182 * for the first of those elements and ignore the rest of them.
1183 * <p>
1184 * It is recommended that client code not reset mappings established by the
1185 * data transfer subsystem. This method should only be used for
1186 * application-level mappings.
1187 *
1188 * @param nat the <code>String</code> native key for the mappings
1189 * @param flavors the <code>DataFlavor</code> values for the mappings
1190 * @throws NullPointerException if nat or flavors is <code>null</code>
1191 * or if flavors contains <code>null</code> elements
1192 *
1193 * @see #setNativesForFlavor
1194 * @since 1.4
1195 */
1196 public synchronized void setFlavorsForNative(String nat,
1197 DataFlavor[] flavors) {
1198 if (nat == null || flavors == null) {
1199 throw new NullPointerException(
1200 "null arguments not permitted");
1201 }
1202
1203 nativeToFlavor.remove(nat);
1204 for (int i = 0; i < flavors.length; i++) {
1205 addFlavorForUnencodedNative(nat, flavors[i]);
1206 }
1207 disabledMappingGenerationKeys.add(nat);
1208 // Clear the cache to handle the case of empty flavors.
1209 getFlavorsForNativeCache.remove(nat);
1210 getFlavorsForNativeCache.remove(null);
1211 }
1212
1213 /**
1214 * Encodes a MIME type for use as a <code>String</code> native. The format
1215 * of an encoded representation of a MIME type is implementation-dependent.
1216 * The only restrictions are:
1217 * <ul>
1218 * <li>The encoded representation is <code>null</code> if and only if the
1219 * MIME type <code>String</code> is <code>null</code>.</li>
1220 * <li>The encoded representations for two non-<code>null</code> MIME type
1221 * <code>String</code>s are equal if and only if these <code>String</code>s
1222 * are equal according to <code>String.equals(Object)</code>.</li>
1223 * </ul>
1224 * <p>
1225 * Sun's reference implementation of this method returns the specified MIME
1226 * type <code>String</code> prefixed with <code>JAVA_DATAFLAVOR:</code>.
1227 *
1228 * @param mimeType the MIME type to encode
1229 * @return the encoded <code>String</code>, or <code>null</code> if
1230 * mimeType is <code>null</code>
1231 */
1232 public static String encodeJavaMIMEType(String mimeType) {
1233 return (mimeType != null) ? JavaMIME + mimeType : null;
1234 }
1235
1236 /**
1237 * Encodes a <code>DataFlavor</code> for use as a <code>String</code>
1238 * native. The format of an encoded <code>DataFlavor</code> is
1239 * implementation-dependent. The only restrictions are:
1240 * <ul>
1241 * <li>The encoded representation is <code>null</code> if and only if the
1242 * specified <code>DataFlavor</code> is <code>null</code> or its MIME type
1243 * <code>String</code> is <code>null</code>.</li>
1244 * <li>The encoded representations for two non-<code>null</code>
1245 * <code>DataFlavor</code>s with non-<code>null</code> MIME type
1246 * <code>String</code>s are equal if and only if the MIME type
1247 * <code>String</code>s of these <code>DataFlavor</code>s are equal
1248 * according to <code>String.equals(Object)</code>.</li>
1249 * </ul>
1250 * <p>
1251 * Sun's reference implementation of this method returns the MIME type
1252 * <code>String</code> of the specified <code>DataFlavor</code> prefixed
1253 * with <code>JAVA_DATAFLAVOR:</code>.
1254 *
1255 * @param flav the <code>DataFlavor</code> to encode
1256 * @return the encoded <code>String</code>, or <code>null</code> if
1257 * flav is <code>null</code> or has a <code>null</code> MIME type
1258 */
1259 public static String encodeDataFlavor(DataFlavor flav) {
1260 return (flav != null) ? SystemFlavorMap.encodeJavaMIMEType(flav
1261 .getMimeType()) : null;
1262 }
1263
1264 /**
1265 * Returns whether the specified <code>String</code> is an encoded Java
1266 * MIME type.
1267 *
1268 * @param str the <code>String</code> to test
1269 * @return <code>true</code> if the <code>String</code> is encoded;
1270 * <code>false</code> otherwise
1271 */
1272 public static boolean isJavaMIMEType(String str) {
1273 return (str != null && str.startsWith(JavaMIME, 0));
1274 }
1275
1276 /**
1277 * Decodes a <code>String</code> native for use as a Java MIME type.
1278 *
1279 * @param nat the <code>String</code> to decode
1280 * @return the decoded Java MIME type, or <code>null</code> if nat is not
1281 * an encoded <code>String</code> native
1282 */
1283 public static String decodeJavaMIMEType(String nat) {
1284 return (isJavaMIMEType(nat)) ? nat.substring(JavaMIME.length(),
1285 nat.length()).trim() : null;
1286 }
1287
1288 /**
1289 * Decodes a <code>String</code> native for use as a
1290 * <code>DataFlavor</code>.
1291 *
1292 * @param nat the <code>String</code> to decode
1293 * @return the decoded <code>DataFlavor</code>, or <code>null</code> if
1294 * nat is not an encoded <code>String</code> native
1295 */
1296 public static DataFlavor decodeDataFlavor(String nat)
1297 throws ClassNotFoundException {
1298 String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1299 return (retval_str != null) ? new DataFlavor(retval_str) : null;
1300 }
1301 }
|