1 /* 2 * Copyright (c) 2000, 2014, 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.awt.windows; 27 28 import java.awt.Image; 29 import java.awt.Graphics2D; 30 import java.awt.Transparency; 31 32 import java.awt.color.ColorSpace; 33 34 import java.awt.datatransfer.DataFlavor; 35 import java.awt.datatransfer.FlavorTable; 36 import java.awt.datatransfer.Transferable; 37 import java.awt.datatransfer.UnsupportedFlavorException; 38 39 import java.awt.geom.AffineTransform; 40 41 import java.awt.image.BufferedImage; 42 import java.awt.image.ColorModel; 43 import java.awt.image.ComponentColorModel; 44 import java.awt.image.DataBuffer; 45 import java.awt.image.DataBufferByte; 46 import java.awt.image.DataBufferInt; 47 import java.awt.image.DirectColorModel; 48 import java.awt.image.ImageObserver; 49 import java.awt.image.Raster; 50 import java.awt.image.WritableRaster; 51 52 import java.io.BufferedInputStream; 53 import java.io.BufferedReader; 54 import java.io.InputStream; 55 import java.io.InputStreamReader; 56 import java.io.IOException; 57 import java.io.UnsupportedEncodingException; 58 import java.io.File; 59 60 import java.net.URL; 61 62 import java.util.Arrays; 63 import java.util.Collections; 64 import java.util.HashMap; 65 import java.util.Map; 66 import java.util.SortedMap; 67 68 import sun.awt.Mutex; 69 import sun.awt.datatransfer.DataTransferer; 70 import sun.awt.datatransfer.ToolkitThreadBlockedHandler; 71 72 import sun.awt.image.ImageRepresentation; 73 import sun.awt.image.ToolkitImage; 74 75 import java.util.ArrayList; 76 77 import java.io.ByteArrayOutputStream; 78 79 /** 80 * Platform-specific support for the data transfer subsystem. 81 * 82 * @author David Mendenhall 83 * @author Danila Sinopalnikov 84 * 85 * @since 1.3.1 86 */ 87 final class WDataTransferer extends DataTransferer { 88 private static final String[] predefinedClipboardNames = { 89 "", 90 "TEXT", 91 "BITMAP", 92 "METAFILEPICT", 93 "SYLK", 94 "DIF", 95 "TIFF", 96 "OEM TEXT", 97 "DIB", 98 "PALETTE", 99 "PENDATA", 100 "RIFF", 101 "WAVE", 102 "UNICODE TEXT", 103 "ENHMETAFILE", 104 "HDROP", 105 "LOCALE", 106 "DIBV5" 107 }; 108 109 private static final Map <String, Long> predefinedClipboardNameMap; 110 static { 111 Map <String,Long> tempMap = 112 new HashMap <> (predefinedClipboardNames.length, 1.0f); 113 for (int i = 1; i < predefinedClipboardNames.length; i++) { tempMap.put(predefinedClipboardNames[i], Long.valueOf(i))114 tempMap.put(predefinedClipboardNames[i], Long.valueOf(i)); 115 } 116 predefinedClipboardNameMap = 117 Collections.synchronizedMap(tempMap); 118 } 119 120 /** 121 * from winuser.h 122 */ 123 public static final int CF_TEXT = 1; 124 public static final int CF_METAFILEPICT = 3; 125 public static final int CF_DIB = 8; 126 public static final int CF_ENHMETAFILE = 14; 127 public static final int CF_HDROP = 15; 128 public static final int CF_LOCALE = 16; 129 130 public static final long CF_HTML = registerClipboardFormat("HTML Format"); 131 public static final long CFSTR_INETURL = registerClipboardFormat("UniformResourceLocator"); 132 public static final long CF_PNG = registerClipboardFormat("PNG"); 133 public static final long CF_JFIF = registerClipboardFormat("JFIF"); 134 135 public static final long CF_FILEGROUPDESCRIPTORW = registerClipboardFormat("FileGroupDescriptorW"); 136 public static final long CF_FILEGROUPDESCRIPTORA = registerClipboardFormat("FileGroupDescriptor"); 137 //CF_FILECONTENTS supported as mandatory associated clipboard 138 139 private static final Long L_CF_LOCALE = 140 predefinedClipboardNameMap.get(predefinedClipboardNames[CF_LOCALE]); 141 142 private static final DirectColorModel directColorModel = 143 new DirectColorModel(24, 144 0x00FF0000, /* red mask */ 145 0x0000FF00, /* green mask */ 146 0x000000FF); /* blue mask */ 147 148 private static final int[] bandmasks = new int[] { 149 directColorModel.getRedMask(), 150 directColorModel.getGreenMask(), 151 directColorModel.getBlueMask() }; 152 153 /** 154 * Singleton constructor 155 */ WDataTransferer()156 private WDataTransferer() { 157 } 158 159 private static WDataTransferer transferer; 160 getInstanceImpl()161 static synchronized WDataTransferer getInstanceImpl() { 162 if (transferer == null) { 163 transferer = new WDataTransferer(); 164 } 165 return transferer; 166 } 167 168 @Override getFormatsForFlavors( DataFlavor[] flavors, FlavorTable map)169 public SortedMap <Long, DataFlavor> getFormatsForFlavors( 170 DataFlavor[] flavors, FlavorTable map) 171 { 172 SortedMap <Long, DataFlavor> retval = 173 super.getFormatsForFlavors(flavors, map); 174 175 // The Win32 native code does not support exporting LOCALE data, nor 176 // should it. 177 retval.remove(L_CF_LOCALE); 178 179 return retval; 180 } 181 182 @Override getDefaultUnicodeEncoding()183 public String getDefaultUnicodeEncoding() { 184 return "utf-16le"; 185 } 186 187 @Override translateTransferable(Transferable contents, DataFlavor flavor, long format)188 public byte[] translateTransferable(Transferable contents, 189 DataFlavor flavor, 190 long format) throws IOException 191 { 192 byte[] bytes = null; 193 if (format == CF_HTML) { 194 if (contents.isDataFlavorSupported(DataFlavor.selectionHtmlFlavor)) { 195 // if a user provides data represented by 196 // DataFlavor.selectionHtmlFlavor format, we use this 197 // type to store the data in the native clipboard 198 bytes = super.translateTransferable(contents, 199 DataFlavor.selectionHtmlFlavor, 200 format); 201 } else if (contents.isDataFlavorSupported(DataFlavor.allHtmlFlavor)) { 202 // if we cannot get data represented by the 203 // DataFlavor.selectionHtmlFlavor format 204 // but the DataFlavor.allHtmlFlavor format is avialable 205 // we belive that the user knows how to represent 206 // the data and how to mark up selection in a 207 // system specific manner. Therefor, we use this data 208 bytes = super.translateTransferable(contents, 209 DataFlavor.allHtmlFlavor, 210 format); 211 } else { 212 // handle other html flavor types, including custom and 213 // fragment ones 214 bytes = HTMLCodec.convertToHTMLFormat(super.translateTransferable(contents, flavor, format)); 215 } 216 } else { 217 // we handle non-html types basing on their 218 // flavors 219 bytes = super.translateTransferable(contents, flavor, format); 220 } 221 return bytes; 222 } 223 224 // The stream is closed as a closable object 225 @Override translateStream(InputStream str, DataFlavor flavor, long format, Transferable localeTransferable)226 public Object translateStream(InputStream str, 227 DataFlavor flavor, long format, 228 Transferable localeTransferable) 229 throws IOException 230 { 231 if (format == CF_HTML && flavor.isFlavorTextType()) { 232 str = new HTMLCodec(str, 233 EHTMLReadMode.getEHTMLReadMode(flavor)); 234 235 } 236 return super.translateStream(str, flavor, format, 237 localeTransferable); 238 239 } 240 241 @Override translateBytes(byte[] bytes, DataFlavor flavor, long format, Transferable localeTransferable)242 public Object translateBytes(byte[] bytes, DataFlavor flavor, long format, 243 Transferable localeTransferable) throws IOException 244 { 245 246 247 if (format == CF_FILEGROUPDESCRIPTORA || format == CF_FILEGROUPDESCRIPTORW) { 248 if (bytes == null || !DataFlavor.javaFileListFlavor.equals(flavor)) { 249 throw new IOException("data translation failed"); 250 } 251 String st = new String(bytes, 0, bytes.length, "UTF-16LE"); 252 String[] filenames = st.split("\0"); 253 if( 0 == filenames.length ){ 254 return null; 255 } 256 257 // Convert the strings to File objects 258 File[] files = new File[filenames.length]; 259 for (int i = 0; i < filenames.length; ++i) { 260 files[i] = new File(filenames[i]); 261 //They are temp-files from memory Stream, so they have to be removed on exit 262 files[i].deleteOnExit(); 263 } 264 // Turn the list of Files into a List and return 265 return Arrays.asList(files); 266 } 267 268 if (format == CFSTR_INETURL && 269 URL.class.equals(flavor.getRepresentationClass())) 270 { 271 String charset = getDefaultTextCharset(); 272 if (localeTransferable != null && localeTransferable. 273 isDataFlavorSupported(javaTextEncodingFlavor)) 274 { 275 try { 276 charset = new String((byte[])localeTransferable. 277 getTransferData(javaTextEncodingFlavor), "UTF-8"); 278 } catch (UnsupportedFlavorException cannotHappen) { 279 } 280 } 281 return new URL(new String(bytes, charset)); 282 } 283 284 return super.translateBytes(bytes , flavor, format, 285 localeTransferable); 286 287 } 288 289 @Override isLocaleDependentTextFormat(long format)290 public boolean isLocaleDependentTextFormat(long format) { 291 return format == CF_TEXT || format == CFSTR_INETURL; 292 } 293 294 @Override isFileFormat(long format)295 public boolean isFileFormat(long format) { 296 return format == CF_HDROP || format == CF_FILEGROUPDESCRIPTORA || format == CF_FILEGROUPDESCRIPTORW; 297 } 298 299 @Override getFormatForNativeAsLong(String str)300 protected Long getFormatForNativeAsLong(String str) { 301 Long format = predefinedClipboardNameMap.get(str); 302 if (format == null) { 303 format = Long.valueOf(registerClipboardFormat(str)); 304 } 305 return format; 306 } 307 308 @Override getNativeForFormat(long format)309 protected String getNativeForFormat(long format) { 310 return (format < predefinedClipboardNames.length) 311 ? predefinedClipboardNames[(int)format] 312 : getClipboardFormatName(format); 313 } 314 315 private final ToolkitThreadBlockedHandler handler = 316 new WToolkitThreadBlockedHandler(); 317 318 @Override getToolkitThreadBlockedHandler()319 public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() { 320 return handler; 321 } 322 323 /** 324 * Calls the Win32 RegisterClipboardFormat function to register 325 * a non-standard format. 326 */ registerClipboardFormat(String str)327 private static native long registerClipboardFormat(String str); 328 329 /** 330 * Calls the Win32 GetClipboardFormatName function which is 331 * the reverse operation of RegisterClipboardFormat. 332 */ getClipboardFormatName(long format)333 private static native String getClipboardFormatName(long format); 334 335 @Override isImageFormat(long format)336 public boolean isImageFormat(long format) { 337 return format == CF_DIB || format == CF_ENHMETAFILE || 338 format == CF_METAFILEPICT || format == CF_PNG || 339 format == CF_JFIF; 340 } 341 342 @Override imageToPlatformBytes(Image image, long format)343 protected byte[] imageToPlatformBytes(Image image, long format) 344 throws IOException { 345 String mimeType = null; 346 if (format == CF_PNG) { 347 mimeType = "image/png"; 348 } else if (format == CF_JFIF) { 349 mimeType = "image/jpeg"; 350 } 351 if (mimeType != null) { 352 return imageToStandardBytes(image, mimeType); 353 } 354 355 int width = 0; 356 int height = 0; 357 358 if (image instanceof ToolkitImage) { 359 ImageRepresentation ir = ((ToolkitImage)image).getImageRep(); 360 ir.reconstruct(ImageObserver.ALLBITS); 361 width = ir.getWidth(); 362 height = ir.getHeight(); 363 } else { 364 width = image.getWidth(null); 365 height = image.getHeight(null); 366 } 367 368 // Fix for 4919639. 369 // Some Windows native applications (e.g. clipbrd.exe) do not handle 370 // 32-bpp DIBs correctly. 371 // As a workaround we switched to 24-bpp DIBs. 372 // MSDN prescribes that the bitmap array for a 24-bpp should consist of 373 // 3-byte triplets representing blue, green and red components of a 374 // pixel respectively. Additionally each scan line must be padded with 375 // zeroes to end on a LONG data-type boundary. LONG is always 32-bit. 376 // We render the given Image to a BufferedImage of type TYPE_3BYTE_BGR 377 // with non-default scanline stride and pass the resulting data buffer 378 // to the native code to fill the BITMAPINFO structure. 379 int mod = (width * 3) % 4; 380 int pad = mod > 0 ? 4 - mod : 0; 381 382 ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); 383 int[] nBits = {8, 8, 8}; 384 int[] bOffs = {2, 1, 0}; 385 ColorModel colorModel = 386 new ComponentColorModel(cs, nBits, false, false, 387 Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 388 WritableRaster raster = 389 Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, 390 width * 3 + pad, 3, bOffs, null); 391 392 BufferedImage bimage = new BufferedImage(colorModel, raster, false, null); 393 394 // Some Windows native applications (e.g. clipbrd.exe) do not understand 395 // top-down DIBs. 396 // So we flip the image vertically and create a bottom-up DIB. 397 AffineTransform imageFlipTransform = 398 new AffineTransform(1, 0, 0, -1, 0, height); 399 400 Graphics2D g2d = bimage.createGraphics(); 401 402 try { 403 g2d.drawImage(image, imageFlipTransform, null); 404 } finally { 405 g2d.dispose(); 406 } 407 408 DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer(); 409 410 byte[] imageData = buffer.getData(); 411 return imageDataToPlatformImageBytes(imageData, width, height, format); 412 } 413 414 private static final byte [] UNICODE_NULL_TERMINATOR = new byte [] {0,0}; 415 416 @Override convertFileListToBytes(ArrayList<String> fileList)417 protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) 418 throws IOException 419 { 420 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 421 422 if(fileList.isEmpty()) { 423 //store empty unicode string (null terminator) 424 bos.write(UNICODE_NULL_TERMINATOR); 425 } else { 426 for (int i = 0; i < fileList.size(); i++) { 427 byte[] bytes = fileList.get(i).getBytes(getDefaultUnicodeEncoding()); 428 //store unicode string with null terminator 429 bos.write(bytes, 0, bytes.length); 430 bos.write(UNICODE_NULL_TERMINATOR); 431 } 432 } 433 434 // According to MSDN the byte array have to be double NULL-terminated. 435 // The array contains Unicode characters, so each NULL-terminator is 436 // a pair of bytes 437 438 bos.write(UNICODE_NULL_TERMINATOR); 439 return bos; 440 } 441 442 /** 443 * Returns a byte array which contains data special for the given format 444 * and for the given image data. 445 */ imageDataToPlatformImageBytes(byte[] imageData, int width, int height, long format)446 private native byte[] imageDataToPlatformImageBytes(byte[] imageData, 447 int width, int height, 448 long format); 449 450 /** 451 * Translates either a byte array or an input stream which contain 452 * platform-specific image data in the given format into an Image. 453 */ 454 @Override platformImageBytesToImage(byte[] bytes, long format)455 protected Image platformImageBytesToImage(byte[] bytes, long format) 456 throws IOException { 457 String mimeType = null; 458 if (format == CF_PNG) { 459 mimeType = "image/png"; 460 } else if (format == CF_JFIF) { 461 mimeType = "image/jpeg"; 462 } 463 if (mimeType != null) { 464 return standardImageBytesToImage(bytes, mimeType); 465 } 466 467 int[] imageData = platformImageBytesToImageData(bytes, format); 468 if (imageData == null) { 469 throw new IOException("data translation failed"); 470 } 471 472 int len = imageData.length - 2; 473 int width = imageData[len]; 474 int height = imageData[len + 1]; 475 476 DataBufferInt buffer = new DataBufferInt(imageData, len); 477 WritableRaster raster = Raster.createPackedRaster(buffer, width, 478 height, width, 479 bandmasks, null); 480 481 return new BufferedImage(directColorModel, raster, false, null); 482 } 483 484 /** 485 * Translates a byte array which contains platform-specific image data in 486 * the given format into an integer array which contains pixel values in 487 * ARGB format. The two last elements in the array specify width and 488 * height of the image respectively. 489 */ platformImageBytesToImageData(byte[] bytes, long format)490 private native int[] platformImageBytesToImageData(byte[] bytes, 491 long format) 492 throws IOException; 493 494 @Override dragQueryFile(byte[] bytes)495 protected native String[] dragQueryFile(byte[] bytes); 496 } 497 498 final class WToolkitThreadBlockedHandler extends Mutex 499 implements ToolkitThreadBlockedHandler { 500 501 @Override enter()502 public void enter() { 503 if (!isOwned()) { 504 throw new IllegalMonitorStateException(); 505 } 506 unlock(); 507 startSecondaryEventLoop(); 508 lock(); 509 } 510 511 @Override exit()512 public void exit() { 513 if (!isOwned()) { 514 throw new IllegalMonitorStateException(); 515 } 516 WToolkit.quitSecondaryEventLoop(); 517 } 518 startSecondaryEventLoop()519 private native void startSecondaryEventLoop(); 520 } 521 522 enum EHTMLReadMode { 523 HTML_READ_ALL, 524 HTML_READ_FRAGMENT, 525 HTML_READ_SELECTION; 526 getEHTMLReadMode(DataFlavor df)527 public static EHTMLReadMode getEHTMLReadMode (DataFlavor df) { 528 529 EHTMLReadMode mode = HTML_READ_SELECTION; 530 531 String parameter = df.getParameter("document"); 532 533 if ("all".equals(parameter)) { 534 mode = HTML_READ_ALL; 535 } else if ("fragment".equals(parameter)) { 536 mode = HTML_READ_FRAGMENT; 537 } 538 539 return mode; 540 } 541 } 542 543 /** 544 * on decode: This stream takes an InputStream which provides data in CF_HTML format, 545 * strips off the description and context to extract the original HTML data. 546 * 547 * on encode: static convertToHTMLFormat is responsible for HTML clipboard header creation 548 */ 549 class HTMLCodec extends InputStream { 550 //static section 551 public static final String ENCODING = "UTF-8"; 552 553 public static final String VERSION = "Version:"; 554 public static final String START_HTML = "StartHTML:"; 555 public static final String END_HTML = "EndHTML:"; 556 public static final String START_FRAGMENT = "StartFragment:"; 557 public static final String END_FRAGMENT = "EndFragment:"; 558 public static final String START_SELECTION = "StartSelection:"; //optional 559 public static final String END_SELECTION = "EndSelection:"; //optional 560 561 public static final String START_FRAGMENT_CMT = "<!--StartFragment-->"; 562 public static final String END_FRAGMENT_CMT = "<!--EndFragment-->"; 563 public static final String SOURCE_URL = "SourceURL:"; 564 public static final String DEF_SOURCE_URL = "about:blank"; 565 566 public static final String EOLN = "\r\n"; 567 568 private static final String VERSION_NUM = "1.0"; 569 private static final int PADDED_WIDTH = 10; 570 toPaddedString(int n, int width)571 private static String toPaddedString(int n, int width) { 572 String string = "" + n; 573 int len = string.length(); 574 if (n >= 0 && len < width) { 575 char[] array = new char[width - len]; 576 Arrays.fill(array, '0'); 577 StringBuffer buffer = new StringBuffer(width); 578 buffer.append(array); 579 buffer.append(string); 580 string = buffer.toString(); 581 } 582 return string; 583 } 584 585 /** 586 * convertToHTMLFormat adds the MS HTML clipboard header to byte array that 587 * contains the parameters pairs. 588 * 589 * The consequence of parameters is fixed, but some or all of them could be 590 * omitted. One parameter per one text line. 591 * It looks like that: 592 * 593 * Version:1.0\r\n -- current supported version 594 * StartHTML:000000192\r\n -- shift in array to the first byte after the header 595 * EndHTML:000000757\r\n -- shift in array of last byte for HTML syntax analysis 596 * StartFragment:000000396\r\n -- shift in array jast after <!--StartFragment--> 597 * EndFragment:000000694\r\n -- shift in array before start <!--EndFragment--> 598 * StartSelection:000000398\r\n -- shift in array of the first char in copied selection 599 * EndSelection:000000692\r\n -- shift in array of the last char in copied selection 600 * SourceURL:http://sun.com/\r\n -- base URL for related referenses 601 * <HTML>...<BODY>...<!--StartFragment-->.....................<!--EndFragment-->...</BODY><HTML> 602 * ^ ^ ^ ^^ ^ 603 * \ StartHTML | \-StartSelection | \EndFragment EndHTML/ 604 * \-StartFragment \EndSelection 605 * 606 *Combinations with tags sequence 607 *<!--StartFragment--><HTML>...<BODY>...</BODY><HTML><!--EndFragment--> 608 * or 609 *<HTML>...<!--StartFragment-->...<BODY>...</BODY><!--EndFragment--><HTML> 610 * are vailid too. 611 */ convertToHTMLFormat(byte[] bytes)612 public static byte[] convertToHTMLFormat(byte[] bytes) { 613 // Calculate section offsets 614 String htmlPrefix = ""; 615 String htmlSuffix = ""; 616 { 617 //we have extend the fragment to full HTML document correctly 618 //to avoid HTML and BODY tags doubling 619 String stContext = new String(bytes); 620 String stUpContext = stContext.toUpperCase(); 621 if( -1 == stUpContext.indexOf("<HTML") ) { 622 htmlPrefix = "<HTML>"; 623 htmlSuffix = "</HTML>"; 624 if( -1 == stUpContext.indexOf("<BODY") ) { 625 htmlPrefix = htmlPrefix +"<BODY>"; 626 htmlSuffix = "</BODY>" + htmlSuffix; 627 }; 628 }; 629 } 630 631 String stBaseUrl = DEF_SOURCE_URL; 632 int nStartHTML = 633 VERSION.length() + VERSION_NUM.length() + EOLN.length() 634 + START_HTML.length() + PADDED_WIDTH + EOLN.length() 635 + END_HTML.length() + PADDED_WIDTH + EOLN.length() 636 + START_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() 637 + END_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() 638 + SOURCE_URL.length() + stBaseUrl.length() + EOLN.length() 639 ; 640 int nStartFragment = nStartHTML + htmlPrefix.length(); 641 int nEndFragment = nStartFragment + bytes.length - 1; 642 int nEndHTML = nEndFragment + htmlSuffix.length(); 643 644 StringBuilder header = new StringBuilder( 645 nStartFragment 646 + START_FRAGMENT_CMT.length() 647 ); 648 //header 649 header.append(VERSION); 650 header.append(VERSION_NUM); 651 header.append(EOLN); 652 653 header.append(START_HTML); 654 header.append(toPaddedString(nStartHTML, PADDED_WIDTH)); 655 header.append(EOLN); 656 657 header.append(END_HTML); 658 header.append(toPaddedString(nEndHTML, PADDED_WIDTH)); 659 header.append(EOLN); 660 661 header.append(START_FRAGMENT); 662 header.append(toPaddedString(nStartFragment, PADDED_WIDTH)); 663 header.append(EOLN); 664 665 header.append(END_FRAGMENT); 666 header.append(toPaddedString(nEndFragment, PADDED_WIDTH)); 667 header.append(EOLN); 668 669 header.append(SOURCE_URL); 670 header.append(stBaseUrl); 671 header.append(EOLN); 672 673 //HTML 674 header.append(htmlPrefix); 675 676 byte[] headerBytes = null, trailerBytes = null; 677 678 try { 679 headerBytes = header.toString().getBytes(ENCODING); 680 trailerBytes = htmlSuffix.getBytes(ENCODING); 681 } catch (UnsupportedEncodingException cannotHappen) { 682 } 683 684 byte[] retval = new byte[headerBytes.length + bytes.length + 685 trailerBytes.length]; 686 687 System.arraycopy(headerBytes, 0, retval, 0, headerBytes.length); 688 System.arraycopy(bytes, 0, retval, headerBytes.length, 689 bytes.length - 1); 690 System.arraycopy(trailerBytes, 0, retval, 691 headerBytes.length + bytes.length - 1, 692 trailerBytes.length); 693 retval[retval.length-1] = 0; 694 695 return retval; 696 } 697 698 //////////////////////////////////// 699 //decoder instance data and methods: 700 701 private final BufferedInputStream bufferedStream; 702 private boolean descriptionParsed = false; 703 private boolean closed = false; 704 705 // InputStreamReader uses an 8K buffer. The size is not customizable. 706 public static final int BYTE_BUFFER_LEN = 8192; 707 708 // CharToByteUTF8.getMaxBytesPerChar returns 3, so we should not buffer 709 // more chars than 3 times the number of bytes we can buffer. 710 public static final int CHAR_BUFFER_LEN = BYTE_BUFFER_LEN / 3; 711 712 private static final String FAILURE_MSG = 713 "Unable to parse HTML description: "; 714 private static final String INVALID_MSG = 715 " invalid"; 716 717 //HTML header mapping: 718 private long iHTMLStart,// StartHTML -- shift in array to the first byte after the header 719 iHTMLEnd, // EndHTML -- shift in array of last byte for HTML syntax analysis 720 iFragStart,// StartFragment -- shift in array jast after <!--StartFragment--> 721 iFragEnd, // EndFragment -- shift in array before start <!--EndFragment--> 722 iSelStart, // StartSelection -- shift in array of the first char in copied selection 723 iSelEnd; // EndSelection -- shift in array of the last char in copied selection 724 private String stBaseURL; // SourceURL -- base URL for related referenses 725 private String stVersion; // Version -- current supported version 726 727 //Stream reader markers: 728 private long iStartOffset, 729 iEndOffset, 730 iReadCount; 731 732 private EHTMLReadMode readMode; 733 HTMLCodec( InputStream _bytestream, EHTMLReadMode _readMode)734 public HTMLCodec( 735 InputStream _bytestream, 736 EHTMLReadMode _readMode) throws IOException 737 { 738 bufferedStream = new BufferedInputStream(_bytestream, BYTE_BUFFER_LEN); 739 readMode = _readMode; 740 } 741 getBaseURL()742 public synchronized String getBaseURL() throws IOException 743 { 744 if( !descriptionParsed ) { 745 parseDescription(); 746 } 747 return stBaseURL; 748 } getVersion()749 public synchronized String getVersion() throws IOException 750 { 751 if( !descriptionParsed ) { 752 parseDescription(); 753 } 754 return stVersion; 755 } 756 757 /** 758 * parseDescription parsing HTML clipboard header as it described in 759 * comment to convertToHTMLFormat 760 */ parseDescription()761 private void parseDescription() throws IOException 762 { 763 stBaseURL = null; 764 stVersion = null; 765 766 // initialization of array offset pointers 767 // to the same "uninitialized" state. 768 iHTMLEnd = 769 iHTMLStart = 770 iFragEnd = 771 iFragStart = 772 iSelEnd = 773 iSelStart = -1; 774 775 bufferedStream.mark(BYTE_BUFFER_LEN); 776 String astEntries[] = new String[] { 777 //common 778 VERSION, 779 START_HTML, 780 END_HTML, 781 START_FRAGMENT, 782 END_FRAGMENT, 783 //ver 1.0 784 START_SELECTION, 785 END_SELECTION, 786 SOURCE_URL 787 }; 788 BufferedReader bufferedReader = new BufferedReader( 789 new InputStreamReader( 790 bufferedStream, 791 ENCODING 792 ), 793 CHAR_BUFFER_LEN 794 ); 795 long iHeadSize = 0; 796 long iCRSize = EOLN.length(); 797 int iEntCount = astEntries.length; 798 boolean bContinue = true; 799 800 for( int iEntry = 0; iEntry < iEntCount; ++iEntry ){ 801 String stLine = bufferedReader.readLine(); 802 if( null==stLine ) { 803 break; 804 } 805 //some header entries are optional, but the order is fixed. 806 for( ; iEntry < iEntCount; ++iEntry ){ 807 if( !stLine.startsWith(astEntries[iEntry]) ) { 808 continue; 809 } 810 iHeadSize += stLine.length() + iCRSize; 811 String stValue = stLine.substring(astEntries[iEntry].length()).trim(); 812 if( null!=stValue ) { 813 try{ 814 switch( iEntry ){ 815 case 0: 816 stVersion = stValue; 817 break; 818 case 1: 819 iHTMLStart = Integer.parseInt(stValue); 820 break; 821 case 2: 822 iHTMLEnd = Integer.parseInt(stValue); 823 break; 824 case 3: 825 iFragStart = Integer.parseInt(stValue); 826 break; 827 case 4: 828 iFragEnd = Integer.parseInt(stValue); 829 break; 830 case 5: 831 iSelStart = Integer.parseInt(stValue); 832 break; 833 case 6: 834 iSelEnd = Integer.parseInt(stValue); 835 break; 836 case 7: 837 stBaseURL = stValue; 838 break; 839 }; 840 } catch ( NumberFormatException e ) { 841 throw new IOException(FAILURE_MSG + astEntries[iEntry]+ " value " + e + INVALID_MSG); 842 } 843 } 844 break; 845 } 846 } 847 //some entries could absent in HTML header, 848 //so we have find they by another way. 849 if( -1 == iHTMLStart ) 850 iHTMLStart = iHeadSize; 851 if( -1 == iFragStart ) 852 iFragStart = iHTMLStart; 853 if( -1 == iFragEnd ) 854 iFragEnd = iHTMLEnd; 855 if( -1 == iSelStart ) 856 iSelStart = iFragStart; 857 if( -1 == iSelEnd ) 858 iSelEnd = iFragEnd; 859 860 //one of possible modes 861 switch( readMode ){ 862 case HTML_READ_ALL: 863 iStartOffset = iHTMLStart; 864 iEndOffset = iHTMLEnd; 865 break; 866 case HTML_READ_FRAGMENT: 867 iStartOffset = iFragStart; 868 iEndOffset = iFragEnd; 869 break; 870 case HTML_READ_SELECTION: 871 default: 872 iStartOffset = iSelStart; 873 iEndOffset = iSelEnd; 874 break; 875 } 876 877 bufferedStream.reset(); 878 if( -1 == iStartOffset ){ 879 throw new IOException(FAILURE_MSG + "invalid HTML format."); 880 } 881 882 int curOffset = 0; 883 while (curOffset < iStartOffset){ 884 curOffset += bufferedStream.skip(iStartOffset - curOffset); 885 } 886 887 iReadCount = curOffset; 888 889 if( iStartOffset != iReadCount ){ 890 throw new IOException(FAILURE_MSG + "Byte stream ends in description."); 891 } 892 descriptionParsed = true; 893 } 894 895 @Override read()896 public synchronized int read() throws IOException { 897 if( closed ){ 898 throw new IOException("Stream closed"); 899 } 900 901 if( !descriptionParsed ){ 902 parseDescription(); 903 } 904 if( -1 != iEndOffset && iReadCount >= iEndOffset ) { 905 return -1; 906 } 907 908 int retval = bufferedStream.read(); 909 if( retval == -1 ) { 910 return -1; 911 } 912 ++iReadCount; 913 return retval; 914 } 915 916 @Override close()917 public synchronized void close() throws IOException { 918 if( !closed ){ 919 closed = true; 920 bufferedStream.close(); 921 } 922 } 923 } 924