| /* |
| * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.awt.windows; |
| |
| import java.awt.Image; |
| import java.awt.Graphics2D; |
| import java.awt.Transparency; |
| |
| import java.awt.color.ColorSpace; |
| |
| import java.awt.datatransfer.DataFlavor; |
| import java.awt.datatransfer.FlavorTable; |
| import java.awt.datatransfer.Transferable; |
| import java.awt.datatransfer.UnsupportedFlavorException; |
| |
| import java.awt.geom.AffineTransform; |
| |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ColorModel; |
| import java.awt.image.ComponentColorModel; |
| import java.awt.image.DataBuffer; |
| import java.awt.image.DataBufferByte; |
| import java.awt.image.DataBufferInt; |
| import java.awt.image.DirectColorModel; |
| import java.awt.image.ImageObserver; |
| import java.awt.image.Raster; |
| import java.awt.image.WritableRaster; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedReader; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.io.File; |
| |
| import java.net.URL; |
| |
| import java.nio.charset.Charset; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.SortedMap; |
| |
| import sun.awt.Mutex; |
| import sun.awt.datatransfer.DataTransferer; |
| import sun.awt.datatransfer.ToolkitThreadBlockedHandler; |
| |
| import sun.awt.image.ImageRepresentation; |
| import sun.awt.image.ToolkitImage; |
| |
| import java.util.ArrayList; |
| |
| import java.io.ByteArrayOutputStream; |
| |
| /** |
| * Platform-specific support for the data transfer subsystem. |
| * |
| * @author David Mendenhall |
| * @author Danila Sinopalnikov |
| * |
| * @since 1.3.1 |
| */ |
| final class WDataTransferer extends DataTransferer { |
| private static final String[] predefinedClipboardNames = { |
| "", |
| "TEXT", |
| "BITMAP", |
| "METAFILEPICT", |
| "SYLK", |
| "DIF", |
| "TIFF", |
| "OEM TEXT", |
| "DIB", |
| "PALETTE", |
| "PENDATA", |
| "RIFF", |
| "WAVE", |
| "UNICODE TEXT", |
| "ENHMETAFILE", |
| "HDROP", |
| "LOCALE", |
| "DIBV5" |
| }; |
| |
| private static final Map <String, Long> predefinedClipboardNameMap; |
| static { |
| Map <String,Long> tempMap = |
| new HashMap <> (predefinedClipboardNames.length, 1.0f); |
| for (int i = 1; i < predefinedClipboardNames.length; i++) { |
| tempMap.put(predefinedClipboardNames[i], Long.valueOf(i)); |
| } |
| predefinedClipboardNameMap = |
| Collections.synchronizedMap(tempMap); |
| } |
| |
| /** |
| * from winuser.h |
| */ |
| public static final int CF_TEXT = 1; |
| public static final int CF_METAFILEPICT = 3; |
| public static final int CF_DIB = 8; |
| public static final int CF_ENHMETAFILE = 14; |
| public static final int CF_HDROP = 15; |
| public static final int CF_LOCALE = 16; |
| |
| public static final long CF_HTML = registerClipboardFormat("HTML Format"); |
| public static final long CFSTR_INETURL = registerClipboardFormat("UniformResourceLocator"); |
| public static final long CF_PNG = registerClipboardFormat("PNG"); |
| public static final long CF_JFIF = registerClipboardFormat("JFIF"); |
| |
| public static final long CF_FILEGROUPDESCRIPTORW = registerClipboardFormat("FileGroupDescriptorW"); |
| public static final long CF_FILEGROUPDESCRIPTORA = registerClipboardFormat("FileGroupDescriptor"); |
| //CF_FILECONTENTS supported as mandatory associated clipboard |
| |
| private static final Long L_CF_LOCALE = |
| predefinedClipboardNameMap.get(predefinedClipboardNames[CF_LOCALE]); |
| |
| private static final DirectColorModel directColorModel = |
| new DirectColorModel(24, |
| 0x00FF0000, /* red mask */ |
| 0x0000FF00, /* green mask */ |
| 0x000000FF); /* blue mask */ |
| |
| private static final int[] bandmasks = new int[] { |
| directColorModel.getRedMask(), |
| directColorModel.getGreenMask(), |
| directColorModel.getBlueMask() }; |
| |
| /** |
| * Singleton constructor |
| */ |
| private WDataTransferer() { |
| } |
| |
| private static WDataTransferer transferer; |
| |
| static synchronized WDataTransferer getInstanceImpl() { |
| if (transferer == null) { |
| transferer = new WDataTransferer(); |
| } |
| return transferer; |
| } |
| |
| @Override |
| public SortedMap <Long, DataFlavor> getFormatsForFlavors( |
| DataFlavor[] flavors, FlavorTable map) |
| { |
| SortedMap <Long, DataFlavor> retval = |
| super.getFormatsForFlavors(flavors, map); |
| |
| // The Win32 native code does not support exporting LOCALE data, nor |
| // should it. |
| retval.remove(L_CF_LOCALE); |
| |
| return retval; |
| } |
| |
| @Override |
| public String getDefaultUnicodeEncoding() { |
| return "utf-16le"; |
| } |
| |
| @Override |
| public byte[] translateTransferable(Transferable contents, |
| DataFlavor flavor, |
| long format) throws IOException |
| { |
| byte[] bytes = null; |
| if (format == CF_HTML) { |
| if (contents.isDataFlavorSupported(DataFlavor.selectionHtmlFlavor)) { |
| // if a user provides data represented by |
| // DataFlavor.selectionHtmlFlavor format, we use this |
| // type to store the data in the native clipboard |
| bytes = super.translateTransferable(contents, |
| DataFlavor.selectionHtmlFlavor, |
| format); |
| } else if (contents.isDataFlavorSupported(DataFlavor.allHtmlFlavor)) { |
| // if we cannot get data represented by the |
| // DataFlavor.selectionHtmlFlavor format |
| // but the DataFlavor.allHtmlFlavor format is avialable |
| // we belive that the user knows how to represent |
| // the data and how to mark up selection in a |
| // system specific manner. Therefor, we use this data |
| bytes = super.translateTransferable(contents, |
| DataFlavor.allHtmlFlavor, |
| format); |
| } else { |
| // handle other html flavor types, including custom and |
| // fragment ones |
| bytes = HTMLCodec.convertToHTMLFormat(super.translateTransferable(contents, flavor, format)); |
| } |
| } else { |
| // we handle non-html types basing on their |
| // flavors |
| bytes = super.translateTransferable(contents, flavor, format); |
| } |
| return bytes; |
| } |
| |
| // The stream is closed as a closable object |
| @Override |
| public Object translateStream(InputStream str, |
| DataFlavor flavor, long format, |
| Transferable localeTransferable) |
| throws IOException |
| { |
| if (format == CF_HTML && flavor.isFlavorTextType()) { |
| str = new HTMLCodec(str, |
| EHTMLReadMode.getEHTMLReadMode(flavor)); |
| |
| } |
| return super.translateStream(str, flavor, format, |
| localeTransferable); |
| |
| } |
| |
| @Override |
| public Object translateBytes(byte[] bytes, DataFlavor flavor, long format, |
| Transferable localeTransferable) throws IOException |
| { |
| |
| |
| if (format == CF_FILEGROUPDESCRIPTORA || format == CF_FILEGROUPDESCRIPTORW) { |
| if (bytes == null || !DataFlavor.javaFileListFlavor.equals(flavor)) { |
| throw new IOException("data translation failed"); |
| } |
| String st = new String(bytes, 0, bytes.length, "UTF-16LE"); |
| String[] filenames = st.split("\0"); |
| if( 0 == filenames.length ){ |
| return null; |
| } |
| |
| // Convert the strings to File objects |
| File[] files = new File[filenames.length]; |
| for (int i = 0; i < filenames.length; ++i) { |
| files[i] = new File(filenames[i]); |
| //They are temp-files from memory Stream, so they have to be removed on exit |
| files[i].deleteOnExit(); |
| } |
| // Turn the list of Files into a List and return |
| return Arrays.asList(files); |
| } |
| |
| if (format == CFSTR_INETURL && |
| URL.class.equals(flavor.getRepresentationClass())) |
| { |
| String charset = Charset.defaultCharset().name(); |
| if (localeTransferable != null |
| && localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) |
| { |
| try { |
| charset = new String((byte[])localeTransferable. |
| getTransferData(javaTextEncodingFlavor), "UTF-8"); |
| } catch (UnsupportedFlavorException cannotHappen) { |
| } |
| } |
| return new URL(new String(bytes, charset)); |
| } |
| |
| return super.translateBytes(bytes , flavor, format, |
| localeTransferable); |
| |
| } |
| |
| @Override |
| public boolean isLocaleDependentTextFormat(long format) { |
| return format == CF_TEXT || format == CFSTR_INETURL; |
| } |
| |
| @Override |
| public boolean isFileFormat(long format) { |
| return format == CF_HDROP || format == CF_FILEGROUPDESCRIPTORA || format == CF_FILEGROUPDESCRIPTORW; |
| } |
| |
| @Override |
| protected Long getFormatForNativeAsLong(String str) { |
| Long format = predefinedClipboardNameMap.get(str); |
| if (format == null) { |
| format = Long.valueOf(registerClipboardFormat(str)); |
| } |
| return format; |
| } |
| |
| @Override |
| protected String getNativeForFormat(long format) { |
| return (format < predefinedClipboardNames.length) |
| ? predefinedClipboardNames[(int)format] |
| : getClipboardFormatName(format); |
| } |
| |
| private final ToolkitThreadBlockedHandler handler = |
| new WToolkitThreadBlockedHandler(); |
| |
| @Override |
| public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() { |
| return handler; |
| } |
| |
| /** |
| * Calls the Win32 RegisterClipboardFormat function to register |
| * a non-standard format. |
| */ |
| private static native long registerClipboardFormat(String str); |
| |
| /** |
| * Calls the Win32 GetClipboardFormatName function which is |
| * the reverse operation of RegisterClipboardFormat. |
| */ |
| private static native String getClipboardFormatName(long format); |
| |
| @Override |
| public boolean isImageFormat(long format) { |
| return format == CF_DIB || format == CF_ENHMETAFILE || |
| format == CF_METAFILEPICT || format == CF_PNG || |
| format == CF_JFIF; |
| } |
| |
| @Override |
| protected byte[] imageToPlatformBytes(Image image, long format) |
| throws IOException { |
| String mimeType = null; |
| if (format == CF_PNG) { |
| mimeType = "image/png"; |
| } else if (format == CF_JFIF) { |
| mimeType = "image/jpeg"; |
| } |
| if (mimeType != null) { |
| return imageToStandardBytes(image, mimeType); |
| } |
| |
| int width = 0; |
| int height = 0; |
| |
| if (image instanceof ToolkitImage) { |
| ImageRepresentation ir = ((ToolkitImage)image).getImageRep(); |
| ir.reconstruct(ImageObserver.ALLBITS); |
| width = ir.getWidth(); |
| height = ir.getHeight(); |
| } else { |
| width = image.getWidth(null); |
| height = image.getHeight(null); |
| } |
| |
| // Fix for 4919639. |
| // Some Windows native applications (e.g. clipbrd.exe) do not handle |
| // 32-bpp DIBs correctly. |
| // As a workaround we switched to 24-bpp DIBs. |
| // MSDN prescribes that the bitmap array for a 24-bpp should consist of |
| // 3-byte triplets representing blue, green and red components of a |
| // pixel respectively. Additionally each scan line must be padded with |
| // zeroes to end on a LONG data-type boundary. LONG is always 32-bit. |
| // We render the given Image to a BufferedImage of type TYPE_3BYTE_BGR |
| // with non-default scanline stride and pass the resulting data buffer |
| // to the native code to fill the BITMAPINFO structure. |
| int mod = (width * 3) % 4; |
| int pad = mod > 0 ? 4 - mod : 0; |
| |
| ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); |
| int[] nBits = {8, 8, 8}; |
| int[] bOffs = {2, 1, 0}; |
| ColorModel colorModel = |
| new ComponentColorModel(cs, nBits, false, false, |
| Transparency.OPAQUE, DataBuffer.TYPE_BYTE); |
| WritableRaster raster = |
| Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, |
| width * 3 + pad, 3, bOffs, null); |
| |
| BufferedImage bimage = new BufferedImage(colorModel, raster, false, null); |
| |
| // Some Windows native applications (e.g. clipbrd.exe) do not understand |
| // top-down DIBs. |
| // So we flip the image vertically and create a bottom-up DIB. |
| AffineTransform imageFlipTransform = |
| new AffineTransform(1, 0, 0, -1, 0, height); |
| |
| Graphics2D g2d = bimage.createGraphics(); |
| |
| try { |
| g2d.drawImage(image, imageFlipTransform, null); |
| } finally { |
| g2d.dispose(); |
| } |
| |
| DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer(); |
| |
| byte[] imageData = buffer.getData(); |
| return imageDataToPlatformImageBytes(imageData, width, height, format); |
| } |
| |
| private static final byte [] UNICODE_NULL_TERMINATOR = new byte [] {0,0}; |
| |
| @Override |
| protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) |
| throws IOException |
| { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| |
| if(fileList.isEmpty()) { |
| //store empty unicode string (null terminator) |
| bos.write(UNICODE_NULL_TERMINATOR); |
| } else { |
| for (int i = 0; i < fileList.size(); i++) { |
| byte[] bytes = fileList.get(i).getBytes(getDefaultUnicodeEncoding()); |
| //store unicode string with null terminator |
| bos.write(bytes, 0, bytes.length); |
| bos.write(UNICODE_NULL_TERMINATOR); |
| } |
| } |
| |
| // According to MSDN the byte array have to be double NULL-terminated. |
| // The array contains Unicode characters, so each NULL-terminator is |
| // a pair of bytes |
| |
| bos.write(UNICODE_NULL_TERMINATOR); |
| return bos; |
| } |
| |
| /** |
| * Returns a byte array which contains data special for the given format |
| * and for the given image data. |
| */ |
| private native byte[] imageDataToPlatformImageBytes(byte[] imageData, |
| int width, int height, |
| long format); |
| |
| /** |
| * Translates either a byte array or an input stream which contain |
| * platform-specific image data in the given format into an Image. |
| */ |
| @Override |
| protected Image platformImageBytesToImage(byte[] bytes, long format) |
| throws IOException { |
| String mimeType = null; |
| if (format == CF_PNG) { |
| mimeType = "image/png"; |
| } else if (format == CF_JFIF) { |
| mimeType = "image/jpeg"; |
| } |
| if (mimeType != null) { |
| return standardImageBytesToImage(bytes, mimeType); |
| } |
| |
| int[] imageData = platformImageBytesToImageData(bytes, format); |
| if (imageData == null) { |
| throw new IOException("data translation failed"); |
| } |
| |
| int len = imageData.length - 2; |
| int width = imageData[len]; |
| int height = imageData[len + 1]; |
| |
| DataBufferInt buffer = new DataBufferInt(imageData, len); |
| WritableRaster raster = Raster.createPackedRaster(buffer, width, |
| height, width, |
| bandmasks, null); |
| |
| return new BufferedImage(directColorModel, raster, false, null); |
| } |
| |
| /** |
| * Translates a byte array which contains platform-specific image data in |
| * the given format into an integer array which contains pixel values in |
| * ARGB format. The two last elements in the array specify width and |
| * height of the image respectively. |
| */ |
| private native int[] platformImageBytesToImageData(byte[] bytes, |
| long format) |
| throws IOException; |
| |
| @Override |
| protected native String[] dragQueryFile(byte[] bytes); |
| } |
| |
| final class WToolkitThreadBlockedHandler extends Mutex |
| implements ToolkitThreadBlockedHandler { |
| |
| @Override |
| public void enter() { |
| if (!isOwned()) { |
| throw new IllegalMonitorStateException(); |
| } |
| unlock(); |
| startSecondaryEventLoop(); |
| lock(); |
| } |
| |
| @Override |
| public void exit() { |
| if (!isOwned()) { |
| throw new IllegalMonitorStateException(); |
| } |
| WToolkit.quitSecondaryEventLoop(); |
| } |
| |
| private native void startSecondaryEventLoop(); |
| } |
| |
| enum EHTMLReadMode { |
| HTML_READ_ALL, |
| HTML_READ_FRAGMENT, |
| HTML_READ_SELECTION; |
| |
| public static EHTMLReadMode getEHTMLReadMode (DataFlavor df) { |
| |
| EHTMLReadMode mode = HTML_READ_SELECTION; |
| |
| String parameter = df.getParameter("document"); |
| |
| if ("all".equals(parameter)) { |
| mode = HTML_READ_ALL; |
| } else if ("fragment".equals(parameter)) { |
| mode = HTML_READ_FRAGMENT; |
| } |
| |
| return mode; |
| } |
| } |
| |
| /** |
| * on decode: This stream takes an InputStream which provides data in CF_HTML format, |
| * strips off the description and context to extract the original HTML data. |
| * |
| * on encode: static convertToHTMLFormat is responsible for HTML clipboard header creation |
| */ |
| class HTMLCodec extends InputStream { |
| //static section |
| public static final String ENCODING = "UTF-8"; |
| |
| public static final String VERSION = "Version:"; |
| public static final String START_HTML = "StartHTML:"; |
| public static final String END_HTML = "EndHTML:"; |
| public static final String START_FRAGMENT = "StartFragment:"; |
| public static final String END_FRAGMENT = "EndFragment:"; |
| public static final String START_SELECTION = "StartSelection:"; //optional |
| public static final String END_SELECTION = "EndSelection:"; //optional |
| |
| public static final String START_FRAGMENT_CMT = "<!--StartFragment-->"; |
| public static final String END_FRAGMENT_CMT = "<!--EndFragment-->"; |
| public static final String SOURCE_URL = "SourceURL:"; |
| public static final String DEF_SOURCE_URL = "about:blank"; |
| |
| public static final String EOLN = "\r\n"; |
| |
| private static final String VERSION_NUM = "1.0"; |
| private static final int PADDED_WIDTH = 10; |
| |
| private static String toPaddedString(int n, int width) { |
| String string = "" + n; |
| int len = string.length(); |
| if (n >= 0 && len < width) { |
| char[] array = new char[width - len]; |
| Arrays.fill(array, '0'); |
| StringBuffer buffer = new StringBuffer(width); |
| buffer.append(array); |
| buffer.append(string); |
| string = buffer.toString(); |
| } |
| return string; |
| } |
| |
| /** |
| * convertToHTMLFormat adds the MS HTML clipboard header to byte array that |
| * contains the parameters pairs. |
| * |
| * The consequence of parameters is fixed, but some or all of them could be |
| * omitted. One parameter per one text line. |
| * It looks like that: |
| * |
| * Version:1.0\r\n -- current supported version |
| * StartHTML:000000192\r\n -- shift in array to the first byte after the header |
| * EndHTML:000000757\r\n -- shift in array of last byte for HTML syntax analysis |
| * StartFragment:000000396\r\n -- shift in array jast after <!--StartFragment--> |
| * EndFragment:000000694\r\n -- shift in array before start <!--EndFragment--> |
| * StartSelection:000000398\r\n -- shift in array of the first char in copied selection |
| * EndSelection:000000692\r\n -- shift in array of the last char in copied selection |
| * SourceURL:http://sun.com/\r\n -- base URL for related referenses |
| * <HTML>...<BODY>...<!--StartFragment-->.....................<!--EndFragment-->...</BODY><HTML> |
| * ^ ^ ^ ^^ ^ |
| * \ StartHTML | \-StartSelection | \EndFragment EndHTML/ |
| * \-StartFragment \EndSelection |
| * |
| *Combinations with tags sequence |
| *<!--StartFragment--><HTML>...<BODY>...</BODY><HTML><!--EndFragment--> |
| * or |
| *<HTML>...<!--StartFragment-->...<BODY>...</BODY><!--EndFragment--><HTML> |
| * are vailid too. |
| */ |
| public static byte[] convertToHTMLFormat(byte[] bytes) { |
| // Calculate section offsets |
| String htmlPrefix = ""; |
| String htmlSuffix = ""; |
| { |
| //we have extend the fragment to full HTML document correctly |
| //to avoid HTML and BODY tags doubling |
| String stContext = new String(bytes); |
| String stUpContext = stContext.toUpperCase(); |
| if( -1 == stUpContext.indexOf("<HTML") ) { |
| htmlPrefix = "<HTML>"; |
| htmlSuffix = "</HTML>"; |
| if( -1 == stUpContext.indexOf("<BODY") ) { |
| htmlPrefix = htmlPrefix +"<BODY>"; |
| htmlSuffix = "</BODY>" + htmlSuffix; |
| }; |
| }; |
| } |
| |
| String stBaseUrl = DEF_SOURCE_URL; |
| int nStartHTML = |
| VERSION.length() + VERSION_NUM.length() + EOLN.length() |
| + START_HTML.length() + PADDED_WIDTH + EOLN.length() |
| + END_HTML.length() + PADDED_WIDTH + EOLN.length() |
| + START_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() |
| + END_FRAGMENT.length() + PADDED_WIDTH + EOLN.length() |
| + SOURCE_URL.length() + stBaseUrl.length() + EOLN.length() |
| ; |
| int nStartFragment = nStartHTML + htmlPrefix.length(); |
| int nEndFragment = nStartFragment + bytes.length - 1; |
| int nEndHTML = nEndFragment + htmlSuffix.length(); |
| |
| StringBuilder header = new StringBuilder( |
| nStartFragment |
| + START_FRAGMENT_CMT.length() |
| ); |
| //header |
| header.append(VERSION); |
| header.append(VERSION_NUM); |
| header.append(EOLN); |
| |
| header.append(START_HTML); |
| header.append(toPaddedString(nStartHTML, PADDED_WIDTH)); |
| header.append(EOLN); |
| |
| header.append(END_HTML); |
| header.append(toPaddedString(nEndHTML, PADDED_WIDTH)); |
| header.append(EOLN); |
| |
| header.append(START_FRAGMENT); |
| header.append(toPaddedString(nStartFragment, PADDED_WIDTH)); |
| header.append(EOLN); |
| |
| header.append(END_FRAGMENT); |
| header.append(toPaddedString(nEndFragment, PADDED_WIDTH)); |
| header.append(EOLN); |
| |
| header.append(SOURCE_URL); |
| header.append(stBaseUrl); |
| header.append(EOLN); |
| |
| //HTML |
| header.append(htmlPrefix); |
| |
| byte[] headerBytes = null, trailerBytes = null; |
| |
| try { |
| headerBytes = header.toString().getBytes(ENCODING); |
| trailerBytes = htmlSuffix.getBytes(ENCODING); |
| } catch (UnsupportedEncodingException cannotHappen) { |
| } |
| |
| byte[] retval = new byte[headerBytes.length + bytes.length + |
| trailerBytes.length]; |
| |
| System.arraycopy(headerBytes, 0, retval, 0, headerBytes.length); |
| System.arraycopy(bytes, 0, retval, headerBytes.length, |
| bytes.length - 1); |
| System.arraycopy(trailerBytes, 0, retval, |
| headerBytes.length + bytes.length - 1, |
| trailerBytes.length); |
| retval[retval.length-1] = 0; |
| |
| return retval; |
| } |
| |
| //////////////////////////////////// |
| //decoder instance data and methods: |
| |
| private final BufferedInputStream bufferedStream; |
| private boolean descriptionParsed = false; |
| private boolean closed = false; |
| |
| // InputStreamReader uses an 8K buffer. The size is not customizable. |
| public static final int BYTE_BUFFER_LEN = 8192; |
| |
| // CharToByteUTF8.getMaxBytesPerChar returns 3, so we should not buffer |
| // more chars than 3 times the number of bytes we can buffer. |
| public static final int CHAR_BUFFER_LEN = BYTE_BUFFER_LEN / 3; |
| |
| private static final String FAILURE_MSG = |
| "Unable to parse HTML description: "; |
| private static final String INVALID_MSG = |
| " invalid"; |
| |
| //HTML header mapping: |
| private long iHTMLStart,// StartHTML -- shift in array to the first byte after the header |
| iHTMLEnd, // EndHTML -- shift in array of last byte for HTML syntax analysis |
| iFragStart,// StartFragment -- shift in array jast after <!--StartFragment--> |
| iFragEnd, // EndFragment -- shift in array before start <!--EndFragment--> |
| iSelStart, // StartSelection -- shift in array of the first char in copied selection |
| iSelEnd; // EndSelection -- shift in array of the last char in copied selection |
| private String stBaseURL; // SourceURL -- base URL for related referenses |
| private String stVersion; // Version -- current supported version |
| |
| //Stream reader markers: |
| private long iStartOffset, |
| iEndOffset, |
| iReadCount; |
| |
| private EHTMLReadMode readMode; |
| |
| public HTMLCodec( |
| InputStream _bytestream, |
| EHTMLReadMode _readMode) throws IOException |
| { |
| bufferedStream = new BufferedInputStream(_bytestream, BYTE_BUFFER_LEN); |
| readMode = _readMode; |
| } |
| |
| public synchronized String getBaseURL() throws IOException |
| { |
| if( !descriptionParsed ) { |
| parseDescription(); |
| } |
| return stBaseURL; |
| } |
| public synchronized String getVersion() throws IOException |
| { |
| if( !descriptionParsed ) { |
| parseDescription(); |
| } |
| return stVersion; |
| } |
| |
| /** |
| * parseDescription parsing HTML clipboard header as it described in |
| * comment to convertToHTMLFormat |
| */ |
| private void parseDescription() throws IOException |
| { |
| stBaseURL = null; |
| stVersion = null; |
| |
| // initialization of array offset pointers |
| // to the same "uninitialized" state. |
| iHTMLEnd = |
| iHTMLStart = |
| iFragEnd = |
| iFragStart = |
| iSelEnd = |
| iSelStart = -1; |
| |
| bufferedStream.mark(BYTE_BUFFER_LEN); |
| String astEntries[] = new String[] { |
| //common |
| VERSION, |
| START_HTML, |
| END_HTML, |
| START_FRAGMENT, |
| END_FRAGMENT, |
| //ver 1.0 |
| START_SELECTION, |
| END_SELECTION, |
| SOURCE_URL |
| }; |
| BufferedReader bufferedReader = new BufferedReader( |
| new InputStreamReader( |
| bufferedStream, |
| ENCODING |
| ), |
| CHAR_BUFFER_LEN |
| ); |
| long iHeadSize = 0; |
| long iCRSize = EOLN.length(); |
| int iEntCount = astEntries.length; |
| boolean bContinue = true; |
| |
| for( int iEntry = 0; iEntry < iEntCount; ++iEntry ){ |
| String stLine = bufferedReader.readLine(); |
| if( null==stLine ) { |
| break; |
| } |
| //some header entries are optional, but the order is fixed. |
| for( ; iEntry < iEntCount; ++iEntry ){ |
| if( !stLine.startsWith(astEntries[iEntry]) ) { |
| continue; |
| } |
| iHeadSize += stLine.length() + iCRSize; |
| String stValue = stLine.substring(astEntries[iEntry].length()).trim(); |
| if( null!=stValue ) { |
| try{ |
| switch( iEntry ){ |
| case 0: |
| stVersion = stValue; |
| break; |
| case 1: |
| iHTMLStart = Integer.parseInt(stValue); |
| break; |
| case 2: |
| iHTMLEnd = Integer.parseInt(stValue); |
| break; |
| case 3: |
| iFragStart = Integer.parseInt(stValue); |
| break; |
| case 4: |
| iFragEnd = Integer.parseInt(stValue); |
| break; |
| case 5: |
| iSelStart = Integer.parseInt(stValue); |
| break; |
| case 6: |
| iSelEnd = Integer.parseInt(stValue); |
| break; |
| case 7: |
| stBaseURL = stValue; |
| break; |
| }; |
| } catch ( NumberFormatException e ) { |
| throw new IOException(FAILURE_MSG + astEntries[iEntry]+ " value " + e + INVALID_MSG); |
| } |
| } |
| break; |
| } |
| } |
| //some entries could absent in HTML header, |
| //so we have find they by another way. |
| if( -1 == iHTMLStart ) |
| iHTMLStart = iHeadSize; |
| if( -1 == iFragStart ) |
| iFragStart = iHTMLStart; |
| if( -1 == iFragEnd ) |
| iFragEnd = iHTMLEnd; |
| if( -1 == iSelStart ) |
| iSelStart = iFragStart; |
| if( -1 == iSelEnd ) |
| iSelEnd = iFragEnd; |
| |
| //one of possible modes |
| switch( readMode ){ |
| case HTML_READ_ALL: |
| iStartOffset = iHTMLStart; |
| iEndOffset = iHTMLEnd; |
| break; |
| case HTML_READ_FRAGMENT: |
| iStartOffset = iFragStart; |
| iEndOffset = iFragEnd; |
| break; |
| case HTML_READ_SELECTION: |
| default: |
| iStartOffset = iSelStart; |
| iEndOffset = iSelEnd; |
| break; |
| } |
| |
| bufferedStream.reset(); |
| if( -1 == iStartOffset ){ |
| throw new IOException(FAILURE_MSG + "invalid HTML format."); |
| } |
| |
| int curOffset = 0; |
| while (curOffset < iStartOffset){ |
| curOffset += bufferedStream.skip(iStartOffset - curOffset); |
| } |
| |
| iReadCount = curOffset; |
| |
| if( iStartOffset != iReadCount ){ |
| throw new IOException(FAILURE_MSG + "Byte stream ends in description."); |
| } |
| descriptionParsed = true; |
| } |
| |
| @Override |
| public synchronized int read() throws IOException { |
| if( closed ){ |
| throw new IOException("Stream closed"); |
| } |
| |
| if( !descriptionParsed ){ |
| parseDescription(); |
| } |
| if( -1 != iEndOffset && iReadCount >= iEndOffset ) { |
| return -1; |
| } |
| |
| int retval = bufferedStream.read(); |
| if( retval == -1 ) { |
| return -1; |
| } |
| ++iReadCount; |
| return retval; |
| } |
| |
| @Override |
| public synchronized void close() throws IOException { |
| if( !closed ){ |
| closed = true; |
| bufferedStream.close(); |
| } |
| } |
| } |