1 /* 2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 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 sun.lwawt.macosx; 27 28 import java.awt.*; 29 import java.awt.image.*; 30 import sun.awt.image.ImageRepresentation; 31 32 import java.io.*; 33 import java.net.URL; 34 import java.text.Normalizer; 35 import java.text.Normalizer.Form; 36 import java.util.*; 37 38 import java.awt.datatransfer.*; 39 import sun.awt.datatransfer.*; 40 41 public class CDataTransferer extends DataTransferer { 42 private static final Map<String, Long> predefinedClipboardNameMap; 43 private static final Map<Long, String> predefinedClipboardFormatMap; 44 45 // See SystemFlavorMap, or the flavormap.properties file: 46 // We should define a few more types in flavormap.properties, it's rather slim now. 47 private static final String[] predefinedClipboardNames = { 48 "", 49 "STRING", 50 "FILE_NAME", 51 "TIFF", 52 "RICH_TEXT", 53 "HTML", 54 "PDF", 55 "URL" 56 }; 57 58 static { 59 Map<String, Long> nameMap = new HashMap<String, Long>(predefinedClipboardNames.length, 1.0f); 60 Map<Long, String> formatMap = new HashMap<Long, String>(predefinedClipboardNames.length, 1.0f); 61 for (int i = 1; i < predefinedClipboardNames.length; i++) { 62 nameMap.put(predefinedClipboardNames[i], new Long(i)); 63 formatMap.put(new Long(i), predefinedClipboardNames[i]); 64 } 65 predefinedClipboardNameMap = Collections.synchronizedMap(nameMap); 66 predefinedClipboardFormatMap = Collections.synchronizedMap(formatMap); 67 } 68 69 public static final int CF_UNSUPPORTED = 0; 70 public static final int CF_STRING = 1; 71 public static final int CF_FILE = 2; 72 public static final int CF_TIFF = 3; 73 public static final int CF_RICH_TEXT = 4; 74 public static final int CF_HTML = 5; 75 public static final int CF_PDF = 6; 76 public static final int CF_URL = 7; 77 public static final int CF_PNG = 10; 78 public static final int CF_JPEG = 11; 79 80 public static final Long L_CF_TIFF = predefinedClipboardNameMap.get(predefinedClipboardNames[CF_TIFF]); 81 82 // Image file formats with java.awt.Image representation: 83 private static final Long[] imageFormats = new Long[] { 84 L_CF_TIFF 85 }; 86 87 88 private CDataTransferer() {} 89 90 private static CDataTransferer fTransferer; 91 92 public static synchronized CDataTransferer getInstanceImpl() { 93 if (fTransferer == null) { 94 fTransferer = new CDataTransferer(); 95 } 96 97 return fTransferer; 98 } 99 100 public String getDefaultUnicodeEncoding() { 101 return "utf-16le"; 102 } 103 104 public boolean isLocaleDependentTextFormat(long format) { 105 return format == CF_STRING; 106 } 107 108 public boolean isFileFormat(long format) { 109 return format == CF_FILE; 110 } 111 112 public boolean isImageFormat(long format) { 113 int ifmt = (int)format; 114 switch(ifmt) { 115 case CF_TIFF: 116 case CF_PDF: 117 case CF_PNG: 118 case CF_JPEG: 119 return true; 120 default: 121 return false; 122 } 123 } 124 125 protected Long[] getImageFormatsAsLongArray() { 126 return imageFormats; 127 } 128 129 public byte[] translateTransferable(Transferable contents, DataFlavor flavor, long format) throws IOException 130 { 131 byte[] bytes = super.translateTransferable(contents, flavor, format); 132 133 // 9-12-02 VL: we may need to do something like Windows here. 134 //if (format == CF_HTML) { 135 // bytes = HTMLSupport.convertToHTMLFormat(bytes); 136 //} 137 138 return bytes; 139 } 140 141 protected Object translateBytesOrStream(InputStream stream, byte[] bytes, DataFlavor flavor, long format, 142 Transferable transferable) throws IOException 143 { 144 // 5-28-03 VL: [Radar 3266030] 145 // We need to do like Windows does here. 146 if (format == CF_HTML && flavor.isFlavorTextType()) { 147 if (stream == null) { 148 stream = new ByteArrayInputStream(bytes); 149 bytes = null; 150 } 151 152 stream = new HTMLDecodingInputStream(stream); 153 } 154 155 if (format == CF_URL && URL.class.equals(flavor.getRepresentationClass())) 156 { 157 if (bytes == null) { 158 bytes = inputStreamToByteArray(stream); 159 stream = null; 160 } 161 162 String charset = getDefaultTextCharset(); 163 if (transferable != null && transferable.isDataFlavorSupported(javaTextEncodingFlavor)) { 164 try { 165 charset = new String((byte[])transferable.getTransferData(javaTextEncodingFlavor), "UTF-8"); 166 } catch (UnsupportedFlavorException cannotHappen) { 167 } 168 } 169 170 return new URL(new String(bytes, charset)); 171 } 172 173 if (format == CF_STRING) { 174 bytes = Normalizer.normalize(new String(bytes, "UTF8"), Form.NFC).getBytes("UTF8"); 175 } 176 177 return super.translateBytesOrStream(stream, bytes, flavor, format, transferable); 178 } 179 180 181 synchronized protected Long getFormatForNativeAsLong(String str) { 182 Long format = predefinedClipboardNameMap.get(str); 183 184 if (format == null) { 185 if (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) { 186 // Do not try to access GUI manager for unknown format 187 return new Long(-1); 188 } 189 format = new Long(registerFormatWithPasteboard(str)); 190 predefinedClipboardNameMap.put(str, format); 191 predefinedClipboardFormatMap.put(format, str); 192 } 193 194 return format; 195 } 196 197 /* 198 * Adds type to native mapping NSDictionary. 199 */ 200 private native long registerFormatWithPasteboard(String type); 201 202 // Get registered native format string for an index, return null if unknown: 203 private native String formatForIndex(long index); 204 205 protected String getNativeForFormat(long format) { 206 String returnValue = null; 207 208 // The most common case - just index the array of predefined names: 209 if (format >= 0 && format < predefinedClipboardNames.length) { 210 returnValue = predefinedClipboardNames[(int) format]; 211 } else { 212 Long formatObj = new Long(format); 213 returnValue = predefinedClipboardFormatMap.get(formatObj); 214 215 // predefinedClipboardFormatMap may not know this format: 216 if (returnValue == null) { 217 returnValue = formatForIndex(format); 218 219 // Native clipboard may not know this format either: 220 if (returnValue != null) { 221 predefinedClipboardNameMap.put(returnValue, formatObj); 222 predefinedClipboardFormatMap.put(formatObj, returnValue); 223 } 224 } 225 } 226 227 if (returnValue == null) { 228 returnValue = predefinedClipboardNames[CF_UNSUPPORTED]; 229 } 230 231 return returnValue; 232 } 233 234 private final ToolkitThreadBlockedHandler handler = new CToolkitThreadBlockedHandler(); 235 236 public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() { 237 return handler; 238 } 239 240 protected byte[] imageToPlatformBytes(Image image, long format) { 241 int w = image.getWidth(null); 242 int h = image.getHeight(null); 243 BufferedImage bimage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); 244 Graphics g = bimage.getGraphics(); 245 g.drawImage(image, 0, 0, w, h, null); 246 g.dispose(); 247 Raster raster = bimage.getRaster(); 248 DataBuffer buffer = raster.getDataBuffer(); 249 return imageDataToPlatformImageBytes(((DataBufferInt)buffer).getData(), 250 raster.getWidth(), 251 raster.getHeight()); 252 } 253 254 private static native String[] nativeDragQueryFile(final byte[] bytes); 255 protected String[] dragQueryFile(final byte[] bytes) { 256 if (bytes == null) return null; 257 if (new String(bytes).startsWith("Unsupported type")) return null; 258 return nativeDragQueryFile(bytes); 259 } 260 261 private native byte[] imageDataToPlatformImageBytes(int[] rData, int nW, int nH); 262 263 /** 264 * Translates either a byte array or an input stream which contain 265 * platform-specific image data in the given format into an Image. 266 */ 267 protected Image platformImageBytesOrStreamToImage(InputStream stream, byte[] bytes, long format) throws IOException { 268 byte[] imageData = bytes; 269 270 if (imageData == null) 271 imageData = inputStreamToByteArray(stream); 272 273 return getImageForByteStream(imageData); 274 } 275 276 private native Image getImageForByteStream(byte[] bytes); 277 278 @Override 279 protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException { 280 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 281 for (int i = 0; i < fileList.size(); i++) 282 { 283 byte[] bytes = fileList.get(i).getBytes(); 284 bos.write(bytes, 0, bytes.length); 285 bos.write(0); 286 } 287 return bos; 288 } 289 290 @Override 291 protected boolean isURIListFormat(long format) { 292 String nat = getNativeForFormat(format); 293 if (nat == null) { 294 return false; 295 } 296 try { 297 DataFlavor df = new DataFlavor(nat); 298 if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) { 299 return true; 300 } 301 } catch (Exception e) { 302 // Not a MIME format. 303 } 304 return false; 305 } 306 } 307 308 309 // ---- Code borrowed from WDataTransferer: ---- 310 // This will come handy for supporting HTML data. 311 312 final class HTMLSupport { 313 public static final String ENCODING = "UTF-8"; 314 315 public static final String VERSION = "Version:"; 316 public static final String START_HTML = "StartHTML:"; 317 public static final String END_HTML = "EndHTML:"; 318 public static final String START_FRAGMENT = "StartFragment:"; 319 public static final String END_FRAGMENT = "EndFragment:"; 320 public static final String START_FRAGMENT_CMT = "<!--StartFragment-->"; 321 public static final String END_FRAGMENT_CMT = "<!--EndFragment-->"; 322 public static final String EOLN = "\r\n"; 323 324 private static final String VERSION_NUM = "0.9"; 325 private static final String HTML_START_END = "-1"; 326 327 private static final int PADDED_WIDTH = 10; 328 329 private static final int HEADER_LEN = 330 VERSION.length() + VERSION_NUM.length() + EOLN.length() + 331 START_HTML.length() + HTML_START_END.length() + EOLN.length() + 332 END_HTML.length() + HTML_START_END.length() + EOLN.length() + 333 START_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() + 334 END_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() + 335 START_FRAGMENT_CMT.length() + EOLN.length(); 336 private static final String HEADER_LEN_STR = 337 toPaddedString(HEADER_LEN, PADDED_WIDTH); 338 339 private static final String TRAILER = END_FRAGMENT_CMT + EOLN + '\0'; 340 341 private static String toPaddedString(int n, int width) { 342 String string = "" + n; 343 int len = string.length(); 344 if (n >= 0 && len < width) { 345 char[] array = new char[width - len]; 346 Arrays.fill(array, '0'); 347 StringBuffer buffer = new StringBuffer(); 348 buffer.append(array); 349 buffer.append(string); 350 string = buffer.toString(); 351 } 352 return string; 353 } 354 355 public static byte[] convertToHTMLFormat(byte[] bytes) { 356 StringBuffer header = new StringBuffer(HEADER_LEN); 357 header.append(VERSION); 358 header.append(VERSION_NUM); 359 header.append(EOLN); 360 header.append(START_HTML); 361 header.append(HTML_START_END); 362 header.append(EOLN); 363 header.append(END_HTML); 364 header.append(HTML_START_END); 365 header.append(EOLN); 366 header.append(START_FRAGMENT); 367 header.append(HEADER_LEN_STR); 368 header.append(EOLN); 369 header.append(END_FRAGMENT); 370 // Strip terminating NUL byte from array 371 header.append(toPaddedString(HEADER_LEN + bytes.length - 1, 372 PADDED_WIDTH)); 373 header.append(EOLN); 374 header.append(START_FRAGMENT_CMT); 375 header.append(EOLN); 376 377 byte[] headerBytes = null, trailerBytes = null; 378 379 try { 380 headerBytes = new String(header).getBytes(ENCODING); 381 trailerBytes = TRAILER.getBytes(ENCODING); 382 } catch (UnsupportedEncodingException cannotHappen) { 383 } 384 385 byte[] retval = new byte[headerBytes.length + bytes.length - 1 + 386 trailerBytes.length]; 387 388 System.arraycopy(headerBytes, 0, retval, 0, headerBytes.length); 389 System.arraycopy(bytes, 0, retval, headerBytes.length, 390 bytes.length - 1); 391 System.arraycopy(trailerBytes, 0, retval, 392 headerBytes.length + bytes.length - 1, 393 trailerBytes.length); 394 395 return retval; 396 } 397 } 398 399 /** 400 * This stream takes an InputStream which provides data in CF_HTML format, 401 * strips off the description and context to extract the original HTML data. 402 */ 403 class HTMLDecodingInputStream extends InputStream { 404 405 private final BufferedInputStream bufferedStream; 406 private boolean descriptionParsed = false; 407 private boolean closed = false; 408 private int index; 409 private int end; 410 411 // InputStreamReader uses an 8K buffer. The size is not customizable. 412 public static final int BYTE_BUFFER_LEN = 8192; 413 414 // CharToByteUTF8.getMaxBytesPerChar returns 3, so we should not buffer 415 // more chars than 3 times the number of bytes we can buffer. 416 public static final int CHAR_BUFFER_LEN = BYTE_BUFFER_LEN / 3; 417 418 private static final String FAILURE_MSG = 419 "Unable to parse HTML description: "; 420 private static final String INVALID_MSG = " invalid"; 421 422 public HTMLDecodingInputStream(InputStream bytestream) throws IOException { 423 bufferedStream = new BufferedInputStream(bytestream, BYTE_BUFFER_LEN); 424 } 425 426 private void parseDescription() throws IOException { 427 bufferedStream.mark(BYTE_BUFFER_LEN); 428 429 BufferedReader bufferedReader = new BufferedReader 430 (new InputStreamReader(bufferedStream, HTMLSupport.ENCODING), 431 CHAR_BUFFER_LEN); 432 String version = bufferedReader.readLine().trim(); 433 if (version == null || !version.startsWith(HTMLSupport.VERSION)) { 434 // Not MS-compliant HTML text. Return raw text from read(). 435 index = 0; 436 end = -1; 437 bufferedStream.reset(); 438 return; 439 } 440 441 String input; 442 boolean startHTML, endHTML, startFragment, endFragment; 443 startHTML = endHTML = startFragment = endFragment = false; 444 445 try { 446 do { 447 input = bufferedReader.readLine().trim(); 448 if (input == null) { 449 close(); 450 throw new IOException(FAILURE_MSG); 451 } else if (input.startsWith(HTMLSupport.START_HTML)) { 452 int val = Integer.parseInt 453 (input.substring(HTMLSupport.START_HTML.length(), 454 input.length()).trim()); 455 if (val >= 0) { 456 index = val; 457 startHTML = true; 458 } else if (val != -1) { 459 close(); 460 throw new IOException(FAILURE_MSG + 461 HTMLSupport.START_HTML + 462 INVALID_MSG); 463 } 464 } else if (input.startsWith(HTMLSupport.END_HTML)) { 465 int val = Integer.parseInt 466 (input.substring(HTMLSupport.END_HTML.length(), 467 input.length()).trim()); 468 if (val >= 0) { 469 end = val; 470 endHTML = true; 471 } else if (val != -1) { 472 close(); 473 throw new IOException(FAILURE_MSG + 474 HTMLSupport.END_HTML + 475 INVALID_MSG); 476 } 477 } else if (!startHTML && !endHTML && 478 input.startsWith(HTMLSupport.START_FRAGMENT)) { 479 index = Integer.parseInt 480 (input.substring(HTMLSupport.START_FRAGMENT.length(), 481 input.length()).trim()); 482 if (index < 0) { 483 close(); 484 throw new IOException(FAILURE_MSG + 485 HTMLSupport.START_FRAGMENT + 486 INVALID_MSG); 487 } 488 startFragment = true; 489 } else if (!startHTML && !endHTML && 490 input.startsWith(HTMLSupport.END_FRAGMENT)) { 491 end = Integer.parseInt 492 (input.substring(HTMLSupport.END_FRAGMENT.length(), 493 input.length()).trim()); 494 if (end < 0) { 495 close(); 496 throw new IOException(FAILURE_MSG + 497 HTMLSupport.END_FRAGMENT + 498 INVALID_MSG); 499 } 500 endFragment = true; 501 } 502 } while (!((startHTML && endHTML) || 503 (startFragment && endFragment))); 504 } catch (NumberFormatException e) { 505 close(); 506 throw new IOException(FAILURE_MSG + e); 507 } 508 509 bufferedStream.reset(); 510 511 for (int i = 0; i < index; i++) { 512 if (bufferedStream.read() == -1) { 513 close(); 514 throw new IOException(FAILURE_MSG + 515 "Byte stream ends in description."); 516 } 517 } 518 } 519 520 public int read() throws IOException { 521 if (closed) { 522 throw new IOException("Stream closed"); 523 } 524 525 if (!descriptionParsed) { 526 parseDescription(); // initializes 'index' and 'end' 527 descriptionParsed = true; 528 } 529 530 if (end != -1 && index >= end) { 531 return -1; 532 } 533 534 int retval = bufferedStream.read(); 535 if (retval == -1) { 536 index = end = 0; // so future read() calls will fail quickly 537 return -1; 538 } 539 540 index++; 541 // System.out.print((char)retval); 542 return retval; 543 } 544 545 public void close() throws IOException { 546 if (!closed) { 547 closed = true; 548 bufferedStream.close(); 549 } 550 } 551 }