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