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