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