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