2
Copyright (C) 2009 Jeroen Frijters
4
This software is provided 'as-is', without any express or implied
5
warranty. In no event will the authors be held liable for any damages
6
arising from the use of this software.
8
Permission is granted to anyone to use this software for any purpose,
9
including commercial applications, and to alter it and redistribute it
10
freely, subject to the following restrictions:
12
1. The origin of this software must not be misrepresented; you must not
13
claim that you wrote the original software. If you use this software
14
in a product, an acknowledgment in the product documentation would be
15
appreciated but is not required.
16
2. Altered source versions must be plainly marked as such, and must not be
17
misrepresented as being the original software.
18
3. This notice may not be removed or altered from any source distribution.
27
import java.awt.Image;
28
import java.awt.datatransfer.DataFlavor;
29
import java.awt.datatransfer.FlavorTable;
30
import java.awt.datatransfer.Transferable;
31
import java.awt.datatransfer.UnsupportedFlavorException;
32
import java.io.BufferedInputStream;
33
import java.io.BufferedReader;
34
import java.io.ByteArrayInputStream;
35
import java.io.IOException;
36
import java.io.InputStream;
37
import java.io.InputStreamReader;
38
import java.io.UnsupportedEncodingException;
40
import java.util.Arrays;
41
import java.util.Collections;
42
import java.util.HashMap;
44
import java.util.SortedMap;
46
import sun.awt.datatransfer.DataTransferer;
47
import cli.System.Runtime.InteropServices.DllImportAttribute;
49
public abstract class IkvmDataTransferer extends DataTransferer {
50
public static final int CF_TEXT = 1;
51
public static final int CF_METAFILEPICT = 3;
52
public static final int CF_DIB = 8;
53
public static final int CF_ENHMETAFILE = 14;
54
public static final int CF_HDROP = 15;
55
public static final int CF_LOCALE = 16;
57
public static final long CF_HTML = RegisterClipboardFormat("HTML Format");
58
public static final long CFSTR_INETURL = RegisterClipboardFormat("UniformResourceLocator");
59
public static final long CF_PNG = RegisterClipboardFormat("PNG");
60
public static final long CF_JFIF = RegisterClipboardFormat("JFIF");
62
private static final String[] predefinedClipboardNames = {
83
private static final Map<String, Long> predefinedClipboardNameMap;
86
Map<String, Long> tempMap = new HashMap<String, Long>(predefinedClipboardNames.length, 1.0f);
87
for (int i = 1; i < predefinedClipboardNames.length; i++) {
88
tempMap.put(predefinedClipboardNames[i], Long.valueOf(i));
90
predefinedClipboardNameMap = Collections.synchronizedMap(tempMap);
93
private static final Long L_CF_LOCALE = (Long) predefinedClipboardNameMap.get(predefinedClipboardNames[CF_LOCALE]);
95
@DllImportAttribute.Annotation(value = "user32.dll", EntryPoint = "RegisterClipboardFormat")
96
private native static int _RegisterClipboardFormat(String format);
98
@cli.System.Security.SecuritySafeCriticalAttribute.Annotation
99
private static int RegisterClipboardFormat(String format)
101
return _RegisterClipboardFormat(format);
104
public SortedMap getFormatsForFlavors(DataFlavor[] flavors, FlavorTable map) {
105
SortedMap retval = super.getFormatsForFlavors(flavors, map);
107
// The Win32 native code does not support exporting LOCALE data, nor
109
retval.remove(L_CF_LOCALE);
114
public String getDefaultUnicodeEncoding() {
118
public byte[] translateTransferable(Transferable contents,
120
long format) throws IOException
122
byte[] bytes = super.translateTransferable(contents, flavor, format);
124
if (format == CF_HTML) {
125
bytes = HTMLCodec.convertToHTMLFormat(bytes);
130
protected Object translateBytesOrStream(InputStream str, byte[] bytes,
131
DataFlavor flavor, long format,
132
Transferable localeTransferable)
135
if (format == CF_HTML && flavor.isFlavorTextType()) {
137
str = new ByteArrayInputStream(bytes);
141
str = new HTMLCodec(str, EHTMLReadMode.HTML_READ_SELECTION);
144
if (format == CFSTR_INETURL &&
145
URL.class.equals(flavor.getRepresentationClass()))
148
bytes = inputStreamToByteArray(str);
151
String charset = getDefaultTextCharset();
152
if (localeTransferable != null && localeTransferable.
153
isDataFlavorSupported(javaTextEncodingFlavor))
156
charset = new String((byte[])localeTransferable.
157
getTransferData(javaTextEncodingFlavor),
159
} catch (UnsupportedFlavorException cannotHappen) {
162
return new URL(new String(bytes, charset));
165
return super.translateBytesOrStream(str, bytes, flavor, format,
169
protected byte[] imageToPlatformBytes(Image image, long format)
171
String mimeType = null;
172
if (format == CF_PNG) {
173
mimeType = "image/png";
174
} else if (format == CF_JFIF) {
175
mimeType = "image/jpeg";
177
if (mimeType != null) {
178
return imageToStandardBytes(image, mimeType);
180
throw new Error("Not implemented");
183
protected abstract byte[] imageToStandardBytes(Image image, String mimeType)
186
protected Image platformImageBytesOrStreamToImage(InputStream str,
187
byte[] bytes, long format) {
188
throw new Error("Not implemented");
191
protected String[] dragQueryFile(byte[] bytes) {
192
throw new Error("Not implemented");
195
protected String getNativeForFormat(long format) {
196
return (format < predefinedClipboardNames.length && format>=0)
197
? predefinedClipboardNames[(int)format]
198
: getClipboardFormatName(format);
201
protected Long getFormatForNativeAsLong(String str) {
202
Long format = (Long)predefinedClipboardNameMap.get(str);
203
if (format == null) {
204
format = Long.valueOf(RegisterClipboardFormat(str));
208
protected abstract String getClipboardFormatName(long format);
210
public boolean isImageFormat(long format) {
211
return format == CF_DIB || format == CF_ENHMETAFILE ||
212
format == CF_METAFILEPICT || format == CF_PNG ||
216
public boolean isLocaleDependentTextFormat(long format) {
217
return format == CF_TEXT || format == CFSTR_INETURL;
220
public boolean isFileFormat(long format) {
221
return format == CF_HDROP;
233
* on decode: This stream takes an InputStream which provides data in CF_HTML format,
234
* strips off the description and context to extract the original HTML data.
236
* on encode: static convertToHTMLFormat is responsible for HTML clipboard header creation
238
class HTMLCodec extends InputStream {
240
public static final String ENCODING = "UTF-8";
242
public static final String VERSION = "Version:";
243
public static final String START_HTML = "StartHTML:";
244
public static final String END_HTML = "EndHTML:";
245
public static final String START_FRAGMENT = "StartFragment:";
246
public static final String END_FRAGMENT = "EndFragment:";
247
public static final String START_SELECTION = "StartSelection:"; //optional
248
public static final String END_SELECTION = "EndSelection:"; //optional
250
public static final String START_FRAGMENT_CMT = "<!--StartFragment-->";
251
public static final String END_FRAGMENT_CMT = "<!--EndFragment-->";
252
public static final String SOURCE_URL = "SourceURL:";
253
public static final String DEF_SOURCE_URL = "about:blank";
255
public static final String EOLN = "\r\n";
257
private static final String VERSION_NUM = "1.0";
258
private static final int PADDED_WIDTH = 10;
260
private static String toPaddedString(int n, int width) {
261
String string = "" + n;
262
int len = string.length();
263
if (n >= 0 && len < width) {
264
char[] array = new char[width - len];
265
Arrays.fill(array, '0');
266
StringBuffer buffer = new StringBuffer(width);
267
buffer.append(array);
268
buffer.append(string);
269
string = buffer.toString();
275
* convertToHTMLFormat adds the MS HTML clipboard header to byte array that
276
* contains the parameters pairs.
278
* The consequence of parameters is fixed, but some or all of them could be
279
* omitted. One parameter per one text line.
280
* It looks like that:
282
* Version:1.0\r\n -- current supported version
283
* StartHTML:000000192\r\n -- shift in array to the first byte after the header
284
* EndHTML:000000757\r\n -- shift in array of last byte for HTML syntax analysis
285
* StartFragment:000000396\r\n -- shift in array jast after <!--StartFragment-->
286
* EndFragment:000000694\r\n -- shift in array before start <!--EndFragment-->
287
* StartSelection:000000398\r\n -- shift in array of the first char in copied selection
288
* EndSelection:000000692\r\n -- shift in array of the last char in copied selection
289
* SourceURL:http://sun.com/\r\n -- base URL for related referenses
290
* <HTML>...<BODY>...<!--StartFragment-->.....................<!--EndFragment-->...</BODY><HTML>
292
* \ StartHTML | \-StartSelection | \EndFragment EndHTML/
293
* \-StartFragment \EndSelection
295
*Combinations with tags sequence
296
*<!--StartFragment--><HTML>...<BODY>...</BODY><HTML><!--EndFragment-->
298
*<HTML>...<!--StartFragment-->...<BODY>...</BODY><!--EndFragment--><HTML>
301
public static byte[] convertToHTMLFormat(byte[] bytes) {
302
// Calculate section offsets
303
String htmlPrefix = "";
304
String htmlSuffix = "";
306
//we have extend the fragment to full HTML document correctly
307
//to avoid HTML and BODY tags doubling
308
String stContext = new String(bytes);
309
String stUpContext = stContext.toUpperCase();
310
if( -1 == stUpContext.indexOf("<HTML") ) {
311
htmlPrefix = "<HTML>";
312
htmlSuffix = "</HTML>";
313
if( -1 == stUpContext.indexOf("<BODY") ) {
314
htmlPrefix = htmlPrefix +"<BODY>";
315
htmlSuffix = "</BODY>" + htmlSuffix;
318
htmlPrefix = htmlPrefix + START_FRAGMENT_CMT;
319
htmlSuffix = END_FRAGMENT_CMT + htmlSuffix;
322
String stBaseUrl = DEF_SOURCE_URL;
324
VERSION.length() + VERSION_NUM.length() + EOLN.length()
325
+ START_HTML.length() + PADDED_WIDTH + EOLN.length()
326
+ END_HTML.length() + PADDED_WIDTH + EOLN.length()
327
+ START_FRAGMENT.length() + PADDED_WIDTH + EOLN.length()
328
+ END_FRAGMENT.length() + PADDED_WIDTH + EOLN.length()
329
+ SOURCE_URL.length() + stBaseUrl.length() + EOLN.length()
331
int nStartFragment = nStartHTML + htmlPrefix.length();
332
int nEndFragment = nStartFragment + bytes.length - 1;
333
int nEndHTML = nEndFragment + htmlSuffix.length();
335
StringBuilder header = new StringBuilder(
337
+ START_FRAGMENT_CMT.length()
340
header.append(VERSION);
341
header.append(VERSION_NUM);
344
header.append(START_HTML);
345
header.append(toPaddedString(nStartHTML, PADDED_WIDTH));
348
header.append(END_HTML);
349
header.append(toPaddedString(nEndHTML, PADDED_WIDTH));
352
header.append(START_FRAGMENT);
353
header.append(toPaddedString(nStartFragment, PADDED_WIDTH));
356
header.append(END_FRAGMENT);
357
header.append(toPaddedString(nEndFragment, PADDED_WIDTH));
360
header.append(SOURCE_URL);
361
header.append(stBaseUrl);
365
header.append(htmlPrefix);
367
byte[] headerBytes = null, trailerBytes = null;
370
headerBytes = new String(header).getBytes(ENCODING);
371
trailerBytes = htmlSuffix.getBytes(ENCODING);
372
} catch (UnsupportedEncodingException cannotHappen) {
375
byte[] retval = new byte[headerBytes.length + bytes.length +
376
trailerBytes.length];
378
System.arraycopy(headerBytes, 0, retval, 0, headerBytes.length);
379
System.arraycopy(bytes, 0, retval, headerBytes.length,
381
System.arraycopy(trailerBytes, 0, retval,
382
headerBytes.length + bytes.length - 1,
383
trailerBytes.length);
384
retval[retval.length-1] = 0;
389
////////////////////////////////////
390
//decoder instance data and methods:
392
private final BufferedInputStream bufferedStream;
393
private boolean descriptionParsed = false;
394
private boolean closed = false;
396
// InputStreamReader uses an 8K buffer. The size is not customizable.
397
public static final int BYTE_BUFFER_LEN = 8192;
399
// CharToByteUTF8.getMaxBytesPerChar returns 3, so we should not buffer
400
// more chars than 3 times the number of bytes we can buffer.
401
public static final int CHAR_BUFFER_LEN = BYTE_BUFFER_LEN / 3;
403
private static final String FAILURE_MSG =
404
"Unable to parse HTML description: ";
405
private static final String INVALID_MSG =
408
//HTML header mapping:
409
private long iHTMLStart,// StartHTML -- shift in array to the first byte after the header
410
iHTMLEnd, // EndHTML -- shift in array of last byte for HTML syntax analysis
411
iFragStart,// StartFragment -- shift in array jast after <!--StartFragment-->
412
iFragEnd, // EndFragment -- shift in array before start <!--EndFragment-->
413
iSelStart, // StartSelection -- shift in array of the first char in copied selection
414
iSelEnd; // EndSelection -- shift in array of the last char in copied selection
415
private String stBaseURL; // SourceURL -- base URL for related referenses
416
private String stVersion; // Version -- current supported version
418
//Stream reader markers:
419
private long iStartOffset,
423
private EHTMLReadMode readMode;
426
InputStream _bytestream,
427
EHTMLReadMode _readMode) throws IOException
429
bufferedStream = new BufferedInputStream(_bytestream, BYTE_BUFFER_LEN);
430
readMode = _readMode;
433
public synchronized String getBaseURL() throws IOException
435
if( !descriptionParsed ) {
440
public synchronized String getVersion() throws IOException
442
if( !descriptionParsed ) {
449
* parseDescription parsing HTML clipboard header as it described in
450
* comment to convertToHTMLFormat
452
private void parseDescription() throws IOException
457
// initialization of array offset pointers
458
// to the same "uninitialized" state.
466
bufferedStream.mark(BYTE_BUFFER_LEN);
467
String astEntries[] = new String[] {
479
BufferedReader bufferedReader = new BufferedReader(
480
new InputStreamReader(
487
long iCRSize = EOLN.length();
488
int iEntCount = astEntries.length;
489
boolean bContinue = true;
491
for( int iEntry = 0; iEntry < iEntCount; ++iEntry ){
492
String stLine = bufferedReader.readLine();
496
//some header entries are optional, but the order is fixed.
497
for( ; iEntry < iEntCount; ++iEntry ){
498
if( !stLine.startsWith(astEntries[iEntry]) ) {
501
iHeadSize += stLine.length() + iCRSize;
502
String stValue = stLine.substring(astEntries[iEntry].length()).trim();
503
if( null!=stValue ) {
510
iHTMLStart = Integer.parseInt(stValue);
513
iHTMLEnd = Integer.parseInt(stValue);
516
iFragStart = Integer.parseInt(stValue);
519
iFragEnd = Integer.parseInt(stValue);
522
iSelStart = Integer.parseInt(stValue);
525
iSelEnd = Integer.parseInt(stValue);
531
} catch ( NumberFormatException e ) {
532
throw new IOException(FAILURE_MSG + astEntries[iEntry]+ " value " + e + INVALID_MSG);
538
//some entries could absent in HTML header,
539
//so we have find they by another way.
540
if( -1 == iHTMLStart )
541
iHTMLStart = iHeadSize;
542
if( -1 == iFragStart )
543
iFragStart = iHTMLStart;
546
if( -1 == iSelStart )
547
iSelStart = iFragStart;
551
//one of possible modes
554
iStartOffset = iHTMLStart;
555
iEndOffset = iHTMLEnd;
557
case HTML_READ_FRAGMENT:
558
iStartOffset = iFragStart;
559
iEndOffset = iFragEnd;
561
case HTML_READ_SELECTION:
563
iStartOffset = iSelStart;
564
iEndOffset = iSelEnd;
568
bufferedStream.reset();
569
if( -1 == iStartOffset ){
570
throw new IOException(FAILURE_MSG + "invalid HTML format.");
572
iReadCount = bufferedStream.skip(iStartOffset);
573
if( iStartOffset != iReadCount ){
574
throw new IOException(FAILURE_MSG + "Byte stream ends in description.");
576
descriptionParsed = true;
579
public synchronized int read() throws IOException {
581
throw new IOException("Stream closed");
584
if( !descriptionParsed ){
587
if( -1 != iEndOffset && iReadCount >= iEndOffset ) {
591
int retval = bufferedStream.read();
599
public synchronized void close() throws IOException {
602
bufferedStream.close();