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