10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt.datatransfer; 27 28 import java.awt.Toolkit; 29 30 import java.lang.ref.SoftReference; 31 32 import java.io.BufferedReader; 33 import java.io.File; 34 import java.io.InputStreamReader; 35 import java.io.IOException; 36 37 import java.net.URL; 38 import java.net.MalformedURLException; 39 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.LinkedHashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Objects; 48 import java.util.Set; 49 50 import sun.awt.AppContext; 51 import sun.awt.datatransfer.DataTransferer; 52 53 /** 54 * The SystemFlavorMap is a configurable map between "natives" (Strings), which 55 * correspond to platform-specific data formats, and "flavors" (DataFlavors), 56 * which correspond to platform-independent MIME types. This mapping is used 57 * by the data transfer subsystem to transfer data between Java and native 58 * applications, and between Java applications in separate VMs. 59 * 60 * @since 1.2 61 */ 62 public final class SystemFlavorMap implements FlavorMap, FlavorTable { 63 64 /** 65 * Constant prefix used to tag Java types converted to native platform 66 * type. 67 */ 193 private Set<Object> disabledMappingGenerationKeys = new HashSet<>(); 194 195 /** 196 * Returns the default FlavorMap for this thread's ClassLoader. 197 * @return the default FlavorMap for this thread's ClassLoader 198 */ 199 public static FlavorMap getDefaultFlavorMap() { 200 AppContext context = AppContext.getAppContext(); 201 FlavorMap fm = (FlavorMap) context.get(FLAVOR_MAP_KEY); 202 if (fm == null) { 203 fm = new SystemFlavorMap(); 204 context.put(FLAVOR_MAP_KEY, fm); 205 } 206 return fm; 207 } 208 209 private SystemFlavorMap() { 210 } 211 212 /** 213 * Initializes a SystemFlavorMap by reading flavormap.properties and 214 * AWT.DnD.flavorMapFileURL. 215 * For thread-safety must be called under lock on this. 216 */ 217 private void initSystemFlavorMap() { 218 if (isMapInitialized) { 219 return; 220 } 221 222 isMapInitialized = true; 223 BufferedReader flavormapDotProperties = 224 java.security.AccessController.doPrivileged( 225 new java.security.PrivilegedAction<BufferedReader>() { 226 public BufferedReader run() { 227 String fileName = 228 System.getProperty("java.home") + 229 File.separator + 230 "lib" + 231 File.separator + 232 "flavormap.properties"; 233 try { 234 return new BufferedReader 235 (new InputStreamReader 236 (new File(fileName).toURI().toURL().openStream(), "ISO-8859-1")); 237 } catch (MalformedURLException e) { 238 System.err.println("MalformedURLException:" + e + " while loading default flavormap.properties file:" + fileName); 239 } catch (IOException e) { 240 System.err.println("IOException:" + e + " while loading default flavormap.properties file:" + fileName); 241 } 242 return null; 243 } 244 }); 245 246 String url = 247 java.security.AccessController.doPrivileged( 248 new java.security.PrivilegedAction<String>() { 249 public String run() { 250 return Toolkit.getProperty("AWT.DnD.flavorMapFileURL", null); 251 } 252 }); 253 254 if (flavormapDotProperties != null) { 255 try { 256 parseAndStoreReader(flavormapDotProperties); 257 } catch (IOException e) { 258 System.err.println("IOException:" + e + " while parsing default flavormap.properties file"); 259 } 260 } 261 262 BufferedReader flavormapURL = null; 263 if (url != null) { 264 try { 265 flavormapURL = new BufferedReader(new InputStreamReader(new URL(url).openStream(), "ISO-8859-1")); 266 } catch (MalformedURLException e) { 267 System.err.println("MalformedURLException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url); 268 } catch (IOException e) { 269 System.err.println("IOException:" + e + " while reading AWT.DnD.flavorMapFileURL:" + url); 270 } catch (SecurityException e) { 271 // ignored 272 } 273 } 274 275 if (flavormapURL != null) { 276 try { 277 parseAndStoreReader(flavormapURL); 278 } catch (IOException e) { 279 System.err.println("IOException:" + e + " while parsing AWT.DnD.flavorMapFileURL"); 280 } 281 } 282 } 283 /** 284 * Copied code from java.util.Properties. Parsing the data ourselves is the 285 * only way to handle duplicate keys and values. 286 */ 287 private void parseAndStoreReader(BufferedReader in) throws IOException { 288 while (true) { 289 // Get next line 290 String line = in.readLine(); 291 if (line == null) { 292 return; 293 } 294 295 if (line.length() > 0) { 296 // Continue lines that end in slashes if they are not comments 297 char firstChar = line.charAt(0); 298 if (firstChar != '#' && firstChar != '!') { 299 while (continueLine(line)) { 300 String nextLine = in.readLine(); 301 if (nextLine == null) { 302 nextLine = ""; 303 } 304 String loppedLine = 305 line.substring(0, line.length() - 1); 306 // Advance beyond whitespace on new line 307 int startIndex = 0; 308 for(; startIndex < nextLine.length(); startIndex++) { 309 if (whiteSpaceChars. 310 indexOf(nextLine.charAt(startIndex)) == -1) 311 { 312 break; 313 } 314 } 315 nextLine = nextLine.substring(startIndex, 316 nextLine.length()); 317 line = loppedLine+nextLine; 318 } 319 320 // Find start of key 321 int len = line.length(); 322 int keyStart = 0; 323 for(; keyStart < len; keyStart++) { 324 if(whiteSpaceChars. 325 indexOf(line.charAt(keyStart)) == -1) { 326 break; 327 } 328 } 329 330 // Blank lines are ignored 331 if (keyStart == len) { 332 continue; 333 } 334 335 // Find separation between key and value 336 int separatorIndex = keyStart; 337 for(; separatorIndex < len; separatorIndex++) { 338 char currentChar = line.charAt(separatorIndex); 339 if (currentChar == '\\') { 340 separatorIndex++; 341 } else if (keyValueSeparators. 342 indexOf(currentChar) != -1) { 343 break; 344 } 345 } 346 347 // Skip over whitespace after key if any 348 int valueIndex = separatorIndex; 349 for (; valueIndex < len; valueIndex++) { 350 if (whiteSpaceChars. 351 indexOf(line.charAt(valueIndex)) == -1) { 352 break; 353 } 354 } 355 356 // Skip over one non whitespace key value separators if any 357 if (valueIndex < len) { 358 if (strictKeyValueSeparators. 359 indexOf(line.charAt(valueIndex)) != -1) { 360 valueIndex++; 361 } 362 } 363 364 // Skip over white space after other separators if any 365 while (valueIndex < len) { 366 if (whiteSpaceChars. 367 indexOf(line.charAt(valueIndex)) == -1) { 368 break; 369 } 370 valueIndex++; 371 } 372 373 String key = line.substring(keyStart, separatorIndex); 374 String value = (separatorIndex < len) 375 ? line.substring(valueIndex, len) 376 : ""; 377 378 // Convert then store key and value 379 key = loadConvert(key); 380 value = loadConvert(value); 381 382 try { 383 MimeType mime = new MimeType(value); 384 if ("text".equals(mime.getPrimaryType())) { 385 String charset = mime.getParameter("charset"); 386 if (DataTransferer.doesSubtypeSupportCharset 387 (mime.getSubType(), charset)) 388 { 389 // We need to store the charset and eoln 390 // parameters, if any, so that the 391 // DataTransferer will have this information 392 // for conversion into the native format. 393 DataTransferer transferer = 394 DataTransferer.getInstance(); 395 if (transferer != null) { 396 transferer.registerTextFlavorProperties 397 (key, charset, 398 mime.getParameter("eoln"), 399 mime.getParameter("terminators")); 400 } 401 } 402 403 // But don't store any of these parameters in the 404 // DataFlavor itself for any text natives (even 405 // non-charset ones). The SystemFlavorMap will 406 // synthesize the appropriate mappings later. 407 mime.removeParameter("charset"); 408 mime.removeParameter("class"); 409 mime.removeParameter("eoln"); 410 mime.removeParameter("terminators"); 411 value = mime.toString(); 412 } 413 } catch (MimeTypeParseException e) { 414 e.printStackTrace(); 415 continue; 416 } 417 425 ee.printStackTrace(); 426 continue; 427 } 428 } 429 430 final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>(); 431 dfs.add(flavor); 432 433 if ("text".equals(flavor.getPrimaryType())) { 434 dfs.addAll(convertMimeTypeToDataFlavors(value)); 435 store(flavor.mimeType.getBaseType(), key, getTextTypeToNative()); 436 } 437 438 for (DataFlavor df : dfs) { 439 store(df, key, getFlavorToNative()); 440 store(key, df, getNativeToFlavor()); 441 } 442 } 443 } 444 } 445 } 446 447 /** 448 * Copied from java.util.Properties. 449 */ 450 private boolean continueLine (String line) { 451 int slashCount = 0; 452 int index = line.length() - 1; 453 while((index >= 0) && (line.charAt(index--) == '\\')) { 454 slashCount++; 455 } 456 return (slashCount % 2 == 1); 457 } 458 459 /** 460 * Copied from java.util.Properties. 461 */ 462 private String loadConvert(String theString) { 463 char aChar; 464 int len = theString.length(); 465 StringBuilder outBuffer = new StringBuilder(len); 466 467 for (int x = 0; x < len; ) { 468 aChar = theString.charAt(x++); 469 if (aChar == '\\') { 470 aChar = theString.charAt(x++); 471 if (aChar == 'u') { 472 // Read the xxxx 473 int value = 0; 474 for (int i = 0; i < 4; i++) { 475 aChar = theString.charAt(x++); 476 switch (aChar) { 477 case '0': case '1': case '2': case '3': case '4': 478 case '5': case '6': case '7': case '8': case '9': { 479 value = (value << 4) + aChar - '0'; 480 break; 481 } 482 case 'a': case 'b': case 'c': 483 case 'd': case 'e': case 'f': { 484 value = (value << 4) + 10 + aChar - 'a'; 485 break; 486 } 487 case 'A': case 'B': case 'C': 488 case 'D': case 'E': case 'F': { 489 value = (value << 4) + 10 + aChar - 'A'; 490 break; 491 } 492 default: { 493 throw new IllegalArgumentException( 494 "Malformed \\uxxxx encoding."); 495 } 496 } 497 } 498 outBuffer.append((char)value); 499 } else { 500 if (aChar == 't') { 501 aChar = '\t'; 502 } else if (aChar == 'r') { 503 aChar = '\r'; 504 } else if (aChar == 'n') { 505 aChar = '\n'; 506 } else if (aChar == 'f') { 507 aChar = '\f'; 508 } 509 outBuffer.append(aChar); 510 } 511 } else { 512 outBuffer.append(aChar); 513 } 514 } 515 return outBuffer.toString(); 516 } 517 518 /** 519 * Stores the listed object under the specified hash key in map. Unlike a 520 * standard map, the listed object will not replace any object already at 521 * the appropriate Map location, but rather will be appended to a List 522 * stored in that location. 523 */ 524 private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) { 525 LinkedHashSet<L> list = map.get(hashed); 526 if (list == null) { 527 list = new LinkedHashSet<>(1); 528 map.put(hashed, list); 529 } 530 if (!list.contains(listed)) { 531 list.add(listed); 532 } 533 } 534 535 /** 536 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method | 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt.datatransfer; 27 28 import java.awt.Toolkit; 29 30 import java.io.BufferedInputStream; 31 import java.io.InputStream; 32 import java.lang.ref.SoftReference; 33 34 import java.io.BufferedReader; 35 import java.io.File; 36 import java.io.InputStreamReader; 37 import java.io.IOException; 38 39 import java.net.URL; 40 import java.net.MalformedURLException; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Collections; 45 import java.util.HashMap; 46 import java.util.HashSet; 47 import java.util.LinkedHashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.Properties; 52 import java.util.Set; 53 54 import sun.awt.AppContext; 55 import sun.awt.datatransfer.DataTransferer; 56 57 /** 58 * The SystemFlavorMap is a configurable map between "natives" (Strings), which 59 * correspond to platform-specific data formats, and "flavors" (DataFlavors), 60 * which correspond to platform-independent MIME types. This mapping is used 61 * by the data transfer subsystem to transfer data between Java and native 62 * applications, and between Java applications in separate VMs. 63 * 64 * @since 1.2 65 */ 66 public final class SystemFlavorMap implements FlavorMap, FlavorTable { 67 68 /** 69 * Constant prefix used to tag Java types converted to native platform 70 * type. 71 */ 197 private Set<Object> disabledMappingGenerationKeys = new HashSet<>(); 198 199 /** 200 * Returns the default FlavorMap for this thread's ClassLoader. 201 * @return the default FlavorMap for this thread's ClassLoader 202 */ 203 public static FlavorMap getDefaultFlavorMap() { 204 AppContext context = AppContext.getAppContext(); 205 FlavorMap fm = (FlavorMap) context.get(FLAVOR_MAP_KEY); 206 if (fm == null) { 207 fm = new SystemFlavorMap(); 208 context.put(FLAVOR_MAP_KEY, fm); 209 } 210 return fm; 211 } 212 213 private SystemFlavorMap() { 214 } 215 216 /** 217 * Initializes a SystemFlavorMap by reading flavormap.properties 218 * For thread-safety must be called under lock on this. 219 */ 220 private void initSystemFlavorMap() { 221 if (isMapInitialized) { 222 return; 223 } 224 isMapInitialized = true; 225 226 InputStream is = SystemFlavorMap.class.getResourceAsStream("/sun/awt/datatransfer/flavormap.properties"); 227 if (is == null) { 228 throw new InternalError("Default flavor mapping not found"); 229 } 230 231 Properties flavorProperties = new Properties(); 232 try (BufferedInputStream bis = new BufferedInputStream(is)) { 233 flavorProperties.load(bis); 234 } catch (IOException e) { 235 throw new InternalError("Error reading default flavor mapping", e); 236 } 237 238 for (String key : flavorProperties.stringPropertyNames()) { 239 String[] values = flavorProperties.getProperty(key).split(","); 240 for (String value : values) { 241 try { 242 MimeType mime = new MimeType(value); 243 if ("text".equals(mime.getPrimaryType())) { 244 String charset = mime.getParameter("charset"); 245 if (DataTransferer.doesSubtypeSupportCharset(mime.getSubType(), charset)) 246 { 247 // We need to store the charset and eoln 248 // parameters, if any, so that the 249 // DataTransferer will have this information 250 // for conversion into the native format. 251 DataTransferer transferer = DataTransferer.getInstance(); 252 if (transferer != null) { 253 transferer.registerTextFlavorProperties(key, charset, 254 mime.getParameter("eoln"), 255 mime.getParameter("terminators")); 256 } 257 } 258 259 // But don't store any of these parameters in the 260 // DataFlavor itself for any text natives (even 261 // non-charset ones). The SystemFlavorMap will 262 // synthesize the appropriate mappings later. 263 mime.removeParameter("charset"); 264 mime.removeParameter("class"); 265 mime.removeParameter("eoln"); 266 mime.removeParameter("terminators"); 267 value = mime.toString(); 268 } 269 } catch (MimeTypeParseException e) { 270 e.printStackTrace(); 271 continue; 272 } 273 281 ee.printStackTrace(); 282 continue; 283 } 284 } 285 286 final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>(); 287 dfs.add(flavor); 288 289 if ("text".equals(flavor.getPrimaryType())) { 290 dfs.addAll(convertMimeTypeToDataFlavors(value)); 291 store(flavor.mimeType.getBaseType(), key, getTextTypeToNative()); 292 } 293 294 for (DataFlavor df : dfs) { 295 store(df, key, getFlavorToNative()); 296 store(key, df, getNativeToFlavor()); 297 } 298 } 299 } 300 } 301 302 /** 303 * Stores the listed object under the specified hash key in map. Unlike a 304 * standard map, the listed object will not replace any object already at 305 * the appropriate Map location, but rather will be appended to a List 306 * stored in that location. 307 */ 308 private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) { 309 LinkedHashSet<L> list = map.get(hashed); 310 if (list == null) { 311 list = new LinkedHashSet<>(1); 312 map.put(hashed, list); 313 } 314 if (!list.contains(listed)) { 315 list.add(listed); 316 } 317 } 318 319 /** 320 * Semantically equivalent to 'nativeToFlavor.get(nat)'. This method |