1 /*
2 * Copyright (c) 2000, 2008, 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.datatransfer;
27
28 import java.awt.AWTError;
29 import java.awt.EventQueue;
30 import java.awt.Image;
31 import java.awt.Graphics;
32
33 import java.awt.datatransfer.DataFlavor;
34 import java.awt.datatransfer.FlavorMap;
35 import java.awt.datatransfer.FlavorTable;
36 import java.awt.datatransfer.Transferable;
37 import java.awt.datatransfer.UnsupportedFlavorException;
38
39 import java.io.BufferedReader;
40 import java.io.ByteArrayInputStream;
41 import java.io.ByteArrayOutputStream;
42 import java.io.File;
43 import java.io.InputStream;
44 import java.io.InputStreamReader;
45 import java.io.IOException;
46 import java.io.ObjectInputStream;
47 import java.io.ObjectOutputStream;
48 import java.io.Reader;
49 import java.io.SequenceInputStream;
50 import java.io.StringReader;
51
52 import java.net.URI;
53 import java.net.URISyntaxException;
54
55 import java.nio.ByteBuffer;
56 import java.nio.CharBuffer;
57 import java.nio.charset.Charset;
58 import java.nio.charset.CharsetEncoder;
59 import java.nio.charset.IllegalCharsetNameException;
60 import java.nio.charset.UnsupportedCharsetException;
61
62 import java.lang.reflect.Constructor;
63 import java.lang.reflect.InvocationTargetException;
64 import java.lang.reflect.Method;
65 import java.lang.reflect.Modifier;
66
67 import java.security.AccessController;
68 import java.security.PrivilegedAction;
69 import java.security.PrivilegedActionException;
70 import java.security.PrivilegedExceptionAction;
71 import java.security.ProtectionDomain;
72
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.Collections;
76 import java.util.Comparator;
77 import java.util.HashMap;
78 import java.util.HashSet;
79 import java.util.Iterator;
80 import java.util.List;
81 import java.util.Map;
82 import java.util.SortedMap;
83 import java.util.SortedSet;
84 import java.util.Set;
85 import java.util.Stack;
86 import java.util.TreeMap;
87 import java.util.TreeSet;
88
89 import sun.util.logging.PlatformLogger;
90
91 import sun.awt.AppContext;
92 import sun.awt.SunToolkit;
93
94 import java.awt.image.BufferedImage;
95 import java.awt.image.ImageObserver;
96 import java.awt.image.RenderedImage;
97 import java.awt.image.WritableRaster;
98 import java.awt.image.ColorModel;
99
100 import javax.imageio.ImageIO;
101 import javax.imageio.ImageReader;
102 import javax.imageio.ImageReadParam;
103 import javax.imageio.ImageWriter;
104 import javax.imageio.ImageTypeSpecifier;
105
106 import javax.imageio.spi.ImageWriterSpi;
107
108 import javax.imageio.stream.ImageInputStream;
109 import javax.imageio.stream.ImageOutputStream;
110
111 import sun.awt.image.ImageRepresentation;
112 import sun.awt.image.ToolkitImage;
113
114 import java.io.FilePermission;
115
116
117 /**
118 * Provides a set of functions to be shared among the DataFlavor class and
119 * platform-specific data transfer implementations.
120 *
121 * The concept of "flavors" and "natives" is extended to include "formats",
122 * which are the numeric values Win32 and X11 use to express particular data
123 * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and
124 * getFlavorsForNatives(String[]) functions, DataTransferer provides a set
125 * of getFormatsFor(Transferable|Flavor|Flavors) and
126 * getFlavorsFor(Format|Formats) functions.
127 *
128 * Also provided are functions for translating a Transferable into a byte
129 * array, given a source DataFlavor and a target format, and for translating
130 * a byte array or InputStream into an Object, given a source format and
131 * a target DataFlavor.
132 *
133 * @author David Mendenhall
134 * @author Danila Sinopalnikov
135 *
136 * @since 1.3.1
137 */
138 public abstract class DataTransferer {
139
140 /**
141 * Cached value of Class.forName("[C");
142 */
143 public static final Class charArrayClass;
144
145 /**
146 * Cached value of Class.forName("[B");
147 */
148 public static final Class byteArrayClass;
149
150 /**
151 * The <code>DataFlavor</code> representing plain text with Unicode
152 * encoding, where:
153 * <pre>
154 * representationClass = java.lang.String
155 * mimeType = "text/plain; charset=Unicode"
156 * </pre>
157 */
158 public static final DataFlavor plainTextStringFlavor;
159
160 /**
161 * The <code>DataFlavor</code> representing a Java text encoding String
162 * encoded in UTF-8, where
163 * <pre>
164 * representationClass = [B
165 * mimeType = "application/x-java-text-encoding"
166 * </pre>
167 */
168 public static final DataFlavor javaTextEncodingFlavor;
169
170 /**
171 * Lazy initialization of Standard Encodings.
172 */
173 private static class StandardEncodingsHolder {
174 private static final SortedSet<String> standardEncodings = load();
175
176 private static SortedSet<String> load() {
177 final Comparator comparator =
178 new CharsetComparator(IndexedComparator.SELECT_WORST);
179 final SortedSet<String> tempSet = new TreeSet<String>(comparator);
180 tempSet.add("US-ASCII");
181 tempSet.add("ISO-8859-1");
182 tempSet.add("UTF-8");
183 tempSet.add("UTF-16BE");
184 tempSet.add("UTF-16LE");
185 tempSet.add("UTF-16");
186 tempSet.add(getDefaultTextCharset());
187 return Collections.unmodifiableSortedSet(tempSet);
188 }
189 }
190
191 /**
192 * Tracks whether a particular text/* MIME type supports the charset
193 * parameter. The Map is initialized with all of the standard MIME types
194 * listed in the DataFlavor.selectBestTextFlavor method comment. Additional
195 * entries may be added during the life of the JRE for text/<other> types.
196 */
197 private static final Map textMIMESubtypeCharsetSupport;
198
199 /**
200 * Cache of the platform default encoding as specified in the
201 * "file.encoding" system property.
202 */
203 private static String defaultEncoding;
204
205 /**
206 * A collection of all natives listed in flavormap.properties with
207 * a primary MIME type of "text".
208 */
209 private static final Set textNatives =
210 Collections.synchronizedSet(new HashSet());
211
212 /**
213 * The native encodings/charsets for the Set of textNatives.
214 */
215 private static final Map nativeCharsets =
216 Collections.synchronizedMap(new HashMap());
217
218 /**
219 * The end-of-line markers for the Set of textNatives.
220 */
221 private static final Map nativeEOLNs =
222 Collections.synchronizedMap(new HashMap());
223
224 /**
225 * The number of terminating NUL bytes for the Set of textNatives.
226 */
227 private static final Map nativeTerminators =
228 Collections.synchronizedMap(new HashMap());
229
230 /**
231 * The key used to store pending data conversion requests for an AppContext.
232 */
233 private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY";
234
235 /**
236 * The singleton DataTransferer instance. It is created during MToolkit
237 * or WToolkit initialization.
238 */
239 private static DataTransferer transferer;
240
241 private static final PlatformLogger dtLog = PlatformLogger.getLogger("sun.awt.datatransfer.DataTransfer");
242
243 static {
244 Class tCharArrayClass = null, tByteArrayClass = null;
245 try {
246 tCharArrayClass = Class.forName("[C");
247 tByteArrayClass = Class.forName("[B");
248 } catch (ClassNotFoundException cannotHappen) {
249 }
250 charArrayClass = tCharArrayClass;
251 byteArrayClass = tByteArrayClass;
252
253 DataFlavor tPlainTextStringFlavor = null;
254 try {
255 tPlainTextStringFlavor = new DataFlavor
256 ("text/plain;charset=Unicode;class=java.lang.String");
257 } catch (ClassNotFoundException cannotHappen) {
258 }
259 plainTextStringFlavor = tPlainTextStringFlavor;
260
261 DataFlavor tJavaTextEncodingFlavor = null;
262 try {
263 tJavaTextEncodingFlavor = new DataFlavor
264 ("application/x-java-text-encoding;class=\"[B\"");
265 } catch (ClassNotFoundException cannotHappen) {
266 }
267 javaTextEncodingFlavor = tJavaTextEncodingFlavor;
268
269 Map tempMap = new HashMap(17);
270 tempMap.put("sgml", Boolean.TRUE);
271 tempMap.put("xml", Boolean.TRUE);
272 tempMap.put("html", Boolean.TRUE);
273 tempMap.put("enriched", Boolean.TRUE);
274 tempMap.put("richtext", Boolean.TRUE);
275 tempMap.put("uri-list", Boolean.TRUE);
276 tempMap.put("directory", Boolean.TRUE);
277 tempMap.put("css", Boolean.TRUE);
278 tempMap.put("calendar", Boolean.TRUE);
279 tempMap.put("plain", Boolean.TRUE);
280 tempMap.put("rtf", Boolean.FALSE);
281 tempMap.put("tab-separated-values", Boolean.FALSE);
282 tempMap.put("t140", Boolean.FALSE);
283 tempMap.put("rfc822-headers", Boolean.FALSE);
284 tempMap.put("parityfec", Boolean.FALSE);
285 textMIMESubtypeCharsetSupport = Collections.synchronizedMap(tempMap);
286 }
287
288 /**
289 * The accessor method for the singleton DataTransferer instance. Note
290 * that in a headless environment, there may be no DataTransferer instance;
291 * instead, null will be returned.
292 */
293 public static DataTransferer getInstance() {
294 synchronized (DataTransferer.class) {
295 if (transferer == null) {
296 final String name = SunToolkit.getDataTransfererClassName();
297 if (name != null) {
298 PrivilegedAction<DataTransferer> action = new PrivilegedAction<DataTransferer>()
299 {
300 public DataTransferer run() {
301 Class cls = null;
302 Method method = null;
303 DataTransferer ret = null;
304
305 try {
306 cls = Class.forName(name);
307 } catch (ClassNotFoundException e) {
308 ClassLoader cl = ClassLoader.
309 getSystemClassLoader();
310 if (cl != null) {
311 try {
312 cls = cl.loadClass(name);
313 } catch (ClassNotFoundException ee) {
314 ee.printStackTrace();
315 throw new AWTError("DataTransferer not found: " + name);
316 }
317 }
318 }
319 if (cls != null) {
320 try {
321 method = cls.getDeclaredMethod("getInstanceImpl");
322 method.setAccessible(true);
323 } catch (NoSuchMethodException e) {
324 e.printStackTrace();
325 throw new AWTError("Cannot instantiate DataTransferer: " + name);
326 } catch (SecurityException e) {
327 e.printStackTrace();
328 throw new AWTError("Access is denied for DataTransferer: " + name);
329 }
330 }
331 if (method != null) {
332 try {
333 ret = (DataTransferer) method.invoke(null);
334 } catch (InvocationTargetException e) {
335 e.printStackTrace();
336 throw new AWTError("Cannot instantiate DataTransferer: " + name);
337 } catch (IllegalAccessException e) {
338 e.printStackTrace();
339 throw new AWTError("Cannot access DataTransferer: " + name);
340 }
341 }
342 return ret;
343 }
344 };
345 transferer = AccessController.doPrivileged(action);
346 }
347 }
348 }
349 return transferer;
350 }
351
352 /**
353 * Converts an arbitrary text encoding to its canonical name.
354 */
355 public static String canonicalName(String encoding) {
356 if (encoding == null) {
357 return null;
358 }
359 try {
360 return Charset.forName(encoding).name();
361 } catch (IllegalCharsetNameException icne) {
362 return encoding;
363 } catch (UnsupportedCharsetException uce) {
364 return encoding;
365 }
366 }
367
368 /**
369 * If the specified flavor is a text flavor which supports the "charset"
370 * parameter, then this method returns that parameter, or the default
371 * charset if no such parameter was specified at construction. For non-
372 * text DataFlavors, and for non-charset text flavors, this method returns
373 * null.
374 */
375 public static String getTextCharset(DataFlavor flavor) {
376 if (!isFlavorCharsetTextType(flavor)) {
377 return null;
378 }
379
380 String encoding = flavor.getParameter("charset");
381
382 return (encoding != null) ? encoding : getDefaultTextCharset();
383 }
384
385 /**
386 * Returns the platform's default character encoding.
387 */
388 public static String getDefaultTextCharset() {
389 if (defaultEncoding != null) {
390 return defaultEncoding;
391 }
392 return defaultEncoding = Charset.defaultCharset().name();
393 }
394
395 /**
396 * Tests only whether the flavor's MIME type supports the charset
397 * parameter. Must only be called for flavors with a primary type of
398 * "text".
399 */
400 public static boolean doesSubtypeSupportCharset(DataFlavor flavor) {
401 if (dtLog.isLoggable(PlatformLogger.Level.FINE)) {
402 if (!"text".equals(flavor.getPrimaryType())) {
403 dtLog.fine("Assertion (\"text\".equals(flavor.getPrimaryType())) failed");
404 }
405 }
406
407 String subType = flavor.getSubType();
408 if (subType == null) {
409 return false;
410 }
411
412 Object support = textMIMESubtypeCharsetSupport.get(subType);
413
414 if (support != null) {
415 return (support == Boolean.TRUE);
416 }
417
418 boolean ret_val = (flavor.getParameter("charset") != null);
419 textMIMESubtypeCharsetSupport.put
420 (subType, (ret_val) ? Boolean.TRUE : Boolean.FALSE);
421 return ret_val;
422 }
423 public static boolean doesSubtypeSupportCharset(String subType,
424 String charset)
425 {
426 Object support = textMIMESubtypeCharsetSupport.get(subType);
427
428 if (support != null) {
429 return (support == Boolean.TRUE);
430 }
431
432 boolean ret_val = (charset != null);
433 textMIMESubtypeCharsetSupport.put
434 (subType, (ret_val) ? Boolean.TRUE : Boolean.FALSE);
435 return ret_val;
436 }
437
438 /**
439 * Returns whether this flavor is a text type which supports the
440 * 'charset' parameter.
441 */
442 public static boolean isFlavorCharsetTextType(DataFlavor flavor) {
443 // Although stringFlavor doesn't actually support the charset
444 // parameter (because its primary MIME type is not "text"), it should
445 // be treated as though it does. stringFlavor is semantically
446 // equivalent to "text/plain" data.
447 if (DataFlavor.stringFlavor.equals(flavor)) {
448 return true;
449 }
450
451 if (!"text".equals(flavor.getPrimaryType()) ||
452 !doesSubtypeSupportCharset(flavor))
453 {
454 return false;
455 }
456
457 Class rep_class = flavor.getRepresentationClass();
458
459 if (flavor.isRepresentationClassReader() ||
460 String.class.equals(rep_class) ||
461 flavor.isRepresentationClassCharBuffer() ||
462 DataTransferer.charArrayClass.equals(rep_class))
463 {
464 return true;
465 }
466
467 if (!(flavor.isRepresentationClassInputStream() ||
468 flavor.isRepresentationClassByteBuffer() ||
469 DataTransferer.byteArrayClass.equals(rep_class))) {
470 return false;
471 }
472
473 String charset = flavor.getParameter("charset");
474
475 return (charset != null)
476 ? DataTransferer.isEncodingSupported(charset)
477 : true; // null equals default encoding which is always supported
478 }
479
480 /**
481 * Returns whether this flavor is a text type which does not support the
482 * 'charset' parameter.
483 */
484 public static boolean isFlavorNoncharsetTextType(DataFlavor flavor) {
485 if (!"text".equals(flavor.getPrimaryType()) ||
486 doesSubtypeSupportCharset(flavor))
487 {
488 return false;
489 }
490
491 return (flavor.isRepresentationClassInputStream() ||
492 flavor.isRepresentationClassByteBuffer() ||
493 DataTransferer.byteArrayClass.
494 equals(flavor.getRepresentationClass()));
495 }
496
497 /**
498 * Determines whether this JRE can both encode and decode text in the
499 * specified encoding.
500 */
501 public static boolean isEncodingSupported(String encoding) {
502 if (encoding == null) {
503 return false;
504 }
505 try {
506 return Charset.isSupported(encoding);
507 } catch (IllegalCharsetNameException icne) {
508 return false;
509 }
510 }
511
512 /**
513 * Returns {@code true} if the given type is a java.rmi.Remote.
514 */
515 public static boolean isRemote(Class<?> type) {
516 return RMI.isRemote(type);
517 }
518
519 /**
520 * Returns an Iterator which traverses a SortedSet of Strings which are
521 * a total order of the standard character sets supported by the JRE. The
522 * ordering follows the same principles as DataFlavor.selectBestTextFlavor.
523 * So as to avoid loading all available character converters, optional,
524 * non-standard, character sets are not included.
525 */
526 public static Set <String> standardEncodings() {
527 return StandardEncodingsHolder.standardEncodings;
528 }
529
530 /**
531 * Converts a FlavorMap to a FlavorTable.
532 */
533 public static FlavorTable adaptFlavorMap(final FlavorMap map) {
534 if (map instanceof FlavorTable) {
535 return (FlavorTable)map;
536 }
537
538 return new FlavorTable() {
539 public Map getNativesForFlavors(DataFlavor[] flavors) {
540 return map.getNativesForFlavors(flavors);
541 }
542 public Map getFlavorsForNatives(String[] natives) {
543 return map.getFlavorsForNatives(natives);
544 }
545 public List getNativesForFlavor(DataFlavor flav) {
546 Map natives =
547 getNativesForFlavors(new DataFlavor[] { flav } );
548 String nat = (String)natives.get(flav);
549 if (nat != null) {
550 List list = new ArrayList(1);
551 list.add(nat);
552 return list;
553 } else {
554 return Collections.EMPTY_LIST;
555 }
556 }
557 public List getFlavorsForNative(String nat) {
558 Map flavors =
559 getFlavorsForNatives(new String[] { nat } );
560 DataFlavor flavor = (DataFlavor)flavors.get(nat);
561 if (flavor != null) {
562 List list = new ArrayList(1);
563 list.add(flavor);
564 return list;
565 } else {
566 return Collections.EMPTY_LIST;
567 }
568 }
569 };
570 }
571
572 /**
573 * Returns the default Unicode encoding for the platform. The encoding
574 * need not be canonical. This method is only used by the archaic function
575 * DataFlavor.getTextPlainUnicodeFlavor().
576 */
577 public abstract String getDefaultUnicodeEncoding();
578
579 /**
580 * This method is called for text flavor mappings established while parsing
581 * the flavormap.properties file. It stores the "eoln" and "terminators"
582 * parameters which are not officially part of the MIME type. They are
583 * MIME parameters specific to the flavormap.properties file format.
584 */
585 public void registerTextFlavorProperties(String nat, String charset,
586 String eoln, String terminators) {
587 Long format = getFormatForNativeAsLong(nat);
588
589 textNatives.add(format);
590 nativeCharsets.put(format, (charset != null && charset.length() != 0)
591 ? charset : getDefaultTextCharset());
592 if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) {
593 nativeEOLNs.put(format, eoln);
594 }
595 if (terminators != null && terminators.length() != 0) {
596 Integer iTerminators = Integer.valueOf(terminators);
597 if (iTerminators.intValue() > 0) {
598 nativeTerminators.put(format, iTerminators);
599 }
600 }
601 }
602
603 /**
604 * Determines whether the native corresponding to the specified long format
605 * was listed in the flavormap.properties file.
606 */
607 protected boolean isTextFormat(long format) {
608 return textNatives.contains(Long.valueOf(format));
609 }
610
611 protected String getCharsetForTextFormat(Long lFormat) {
612 return (String)nativeCharsets.get(lFormat);
613 }
614
615 /**
616 * Specifies whether text imported from the native system in the specified
617 * format is locale-dependent. If so, when decoding such text,
618 * 'nativeCharsets' should be ignored, and instead, the Transferable should
619 * be queried for its javaTextEncodingFlavor data for the correct encoding.
620 */
621 public abstract boolean isLocaleDependentTextFormat(long format);
622
623 /**
624 * Determines whether the DataFlavor corresponding to the specified long
625 * format is DataFlavor.javaFileListFlavor.
626 */
627 public abstract boolean isFileFormat(long format);
628
629 /**
630 * Determines whether the DataFlavor corresponding to the specified long
631 * format is DataFlavor.imageFlavor.
632 */
633 public abstract boolean isImageFormat(long format);
634
635 /**
636 * Determines whether the format is a URI list we can convert to
637 * a DataFlavor.javaFileListFlavor.
638 */
639 protected boolean isURIListFormat(long format) {
640 return false;
641 }
642
643 /**
644 * Returns a Map whose keys are all of the possible formats into which the
645 * Transferable's transfer data flavors can be translated. The value of
646 * each key is the DataFlavor in which the Transferable's data should be
647 * requested when converting to the format.
648 * <p>
649 * The map keys are sorted according to the native formats preference
650 * order.
651 */
652 public SortedMap<Long,DataFlavor> getFormatsForTransferable(
653 Transferable contents, FlavorTable map)
654 {
655 DataFlavor[] flavors = contents.getTransferDataFlavors();
656 if (flavors == null) {
657 return new TreeMap();
658 }
659 return getFormatsForFlavors(flavors, map);
660 }
661
662 /**
663 * Returns a Map whose keys are all of the possible formats into which data
664 * in the specified DataFlavor can be translated. The value of each key
665 * is the DataFlavor in which a Transferable's data should be requested
666 * when converting to the format.
667 * <p>
668 * The map keys are sorted according to the native formats preference
669 * order.
670 */
671 public SortedMap getFormatsForFlavor(DataFlavor flavor, FlavorTable map) {
672 return getFormatsForFlavors(new DataFlavor[] { flavor },
673 map);
674 }
675
676 /**
677 * Returns a Map whose keys are all of the possible formats into which data
678 * in the specified DataFlavors can be translated. The value of each key
679 * is the DataFlavor in which the Transferable's data should be requested
680 * when converting to the format.
681 * <p>
682 * The map keys are sorted according to the native formats preference
683 * order.
684 *
685 * @param flavors the data flavors
686 * @param map the FlavorTable which contains mappings between
687 * DataFlavors and data formats
688 * @throws NullPointerException if flavors or map is <code>null</code>
689 */
690 public SortedMap <Long, DataFlavor> getFormatsForFlavors(
691 DataFlavor[] flavors, FlavorTable map)
692 {
693 Map <Long,DataFlavor> formatMap =
694 new HashMap <> (flavors.length);
695 Map <Long,DataFlavor> textPlainMap =
696 new HashMap <> (flavors.length);
697 // Maps formats to indices that will be used to sort the formats
698 // according to the preference order.
699 // Larger index value corresponds to the more preferable format.
700 Map indexMap = new HashMap(flavors.length);
701 Map textPlainIndexMap = new HashMap(flavors.length);
702
703 int currentIndex = 0;
704
705 // Iterate backwards so that preferred DataFlavors are used over
706 // other DataFlavors. (See javadoc for
707 // Transferable.getTransferDataFlavors.)
708 for (int i = flavors.length - 1; i >= 0; i--) {
709 DataFlavor flavor = flavors[i];
710 if (flavor == null) continue;
711
712 // Don't explicitly test for String, since it is just a special
713 // case of Serializable
714 if (flavor.isFlavorTextType() ||
715 flavor.isFlavorJavaFileListType() ||
716 DataFlavor.imageFlavor.equals(flavor) ||
717 flavor.isRepresentationClassSerializable() ||
718 flavor.isRepresentationClassInputStream() ||
719 flavor.isRepresentationClassRemote())
720 {
721 List natives = map.getNativesForFlavor(flavor);
722
723 currentIndex += natives.size();
724
725 for (Iterator iter = natives.iterator(); iter.hasNext(); ) {
726 Long lFormat =
727 getFormatForNativeAsLong((String)iter.next());
728 Integer index = Integer.valueOf(currentIndex--);
729
730 formatMap.put(lFormat, flavor);
731 indexMap.put(lFormat, index);
732
733 // SystemFlavorMap.getNativesForFlavor will return
734 // text/plain natives for all text/*. While this is good
735 // for a single text/* flavor, we would prefer that
736 // text/plain native data come from a text/plain flavor.
737 if (("text".equals(flavor.getPrimaryType()) &&
738 "plain".equals(flavor.getSubType())) ||
739 flavor.equals(DataFlavor.stringFlavor))
740 {
741 textPlainMap.put(lFormat, flavor);
742 textPlainIndexMap.put(lFormat, index);
743 }
744 }
745
746 currentIndex += natives.size();
747 }
748 }
749
750 formatMap.putAll(textPlainMap);
751 indexMap.putAll(textPlainIndexMap);
752
753 // Sort the map keys according to the formats preference order.
754 Comparator comparator =
755 new IndexOrderComparator(indexMap, IndexedComparator.SELECT_WORST);
756 SortedMap sortedMap = new TreeMap(comparator);
757 sortedMap.putAll(formatMap);
758
759 return sortedMap;
760 }
761
762 /**
763 * Reduces the Map output for the root function to an array of the
764 * Map's keys.
765 */
766 public long[] getFormatsForTransferableAsArray(Transferable contents,
767 FlavorTable map) {
768 return keysToLongArray(getFormatsForTransferable(contents, map));
769 }
770 public long[] getFormatsForFlavorAsArray(DataFlavor flavor,
771 FlavorTable map) {
772 return keysToLongArray(getFormatsForFlavor(flavor, map));
773 }
774 public long[] getFormatsForFlavorsAsArray(DataFlavor[] flavors,
775 FlavorTable map) {
776 return keysToLongArray(getFormatsForFlavors(flavors, map));
777 }
778
779 /**
780 * Returns a Map whose keys are all of the possible DataFlavors into which
781 * data in the specified format can be translated. The value of each key
782 * is the format in which the Clipboard or dropped data should be requested
783 * when converting to the DataFlavor.
784 */
785 public Map getFlavorsForFormat(long format, FlavorTable map) {
786 return getFlavorsForFormats(new long[] { format }, map);
787 }
788
789 /**
790 * Returns a Map whose keys are all of the possible DataFlavors into which
791 * data in the specified formats can be translated. The value of each key
792 * is the format in which the Clipboard or dropped data should be requested
793 * when converting to the DataFlavor.
794 */
795 public Map getFlavorsForFormats(long[] formats, FlavorTable map) {
796 Map flavorMap = new HashMap(formats.length);
797 Set mappingSet = new HashSet(formats.length);
798 Set flavorSet = new HashSet(formats.length);
799
800 // First step: build flavorSet, mappingSet and initial flavorMap
801 // flavorSet - the set of all the DataFlavors into which
802 // data in the specified formats can be translated;
803 // mappingSet - the set of all the mappings from the specified formats
804 // into any DataFlavor;
805 // flavorMap - after this step, this map maps each of the DataFlavors
806 // from flavorSet to any of the specified formats.
807 for (int i = 0; i < formats.length; i++) {
808 long format = formats[i];
809 String nat = getNativeForFormat(format);
810 List flavors = map.getFlavorsForNative(nat);
811
812 for (Iterator iter = flavors.iterator(); iter.hasNext(); ) {
813 DataFlavor flavor = (DataFlavor)iter.next();
814
815 // Don't explicitly test for String, since it is just a special
816 // case of Serializable
817 if (flavor.isFlavorTextType() ||
818 flavor.isFlavorJavaFileListType() ||
819 DataFlavor.imageFlavor.equals(flavor) ||
820 flavor.isRepresentationClassSerializable() ||
821 flavor.isRepresentationClassInputStream() ||
822 flavor.isRepresentationClassRemote())
823 {
824 Long lFormat = Long.valueOf(format);
825 Object mapping =
826 DataTransferer.createMapping(lFormat, flavor);
827 flavorMap.put(flavor, lFormat);
828 mappingSet.add(mapping);
829 flavorSet.add(flavor);
830 }
831 }
832 }
833
834 // Second step: for each DataFlavor try to figure out which of the
835 // specified formats is the best to translate to this flavor.
836 // Then map each flavor to the best format.
837 // For the given flavor, FlavorTable indicates which native will
838 // best reflect data in the specified flavor to the underlying native
839 // platform. We assume that this native is the best to translate
840 // to this flavor.
841 // Note: FlavorTable allows one-way mappings, so we can occasionally
842 // map a flavor to the format for which the corresponding
843 // format-to-flavor mapping doesn't exist. For this reason we have built
844 // a mappingSet of all format-to-flavor mappings for the specified formats
845 // and check if the format-to-flavor mapping exists for the
846 // (flavor,format) pair being added.
847 for (Iterator flavorIter = flavorSet.iterator();
848 flavorIter.hasNext(); ) {
849 DataFlavor flavor = (DataFlavor)flavorIter.next();
850
851 List natives = map.getNativesForFlavor(flavor);
852
853 for (Iterator nativeIter = natives.iterator();
854 nativeIter.hasNext(); ) {
855 Long lFormat =
856 getFormatForNativeAsLong((String)nativeIter.next());
857 Object mapping = DataTransferer.createMapping(lFormat, flavor);
858
859 if (mappingSet.contains(mapping)) {
860 flavorMap.put(flavor, lFormat);
861 break;
862 }
863 }
864 }
865
866 return flavorMap;
867 }
868
869 /**
870 * Returns a Set of all DataFlavors for which
871 * 1) a mapping from at least one of the specified formats exists in the
872 * specified map and
873 * 2) the data translation for this mapping can be performed by the data
874 * transfer subsystem.
875 *
876 * @param formats the data formats
877 * @param map the FlavorTable which contains mappings between
878 * DataFlavors and data formats
879 * @throws NullPointerException if formats or map is <code>null</code>
880 */
881 public Set getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) {
882 Set flavorSet = new HashSet(formats.length);
883
884 for (int i = 0; i < formats.length; i++) {
885 String nat = getNativeForFormat(formats[i]);
886 List flavors = map.getFlavorsForNative(nat);
887
888 for (Iterator iter = flavors.iterator(); iter.hasNext(); ) {
889 DataFlavor flavor = (DataFlavor)iter.next();
890
891 // Don't explicitly test for String, since it is just a special
892 // case of Serializable
893 if (flavor.isFlavorTextType() ||
894 flavor.isFlavorJavaFileListType() ||
895 DataFlavor.imageFlavor.equals(flavor) ||
896 flavor.isRepresentationClassSerializable() ||
897 flavor.isRepresentationClassInputStream() ||
898 flavor.isRepresentationClassRemote())
899 {
900 flavorSet.add(flavor);
901 }
902 }
903 }
904
905 return flavorSet;
906 }
907
908 /**
909 * Returns an array of all DataFlavors for which
910 * 1) a mapping from the specified format exists in the specified map and
911 * 2) the data translation for this mapping can be performed by the data
912 * transfer subsystem.
913 * The array will be sorted according to a
914 * <code>DataFlavorComparator</code> created with the specified
915 * map as an argument.
916 *
917 * @param format the data format
918 * @param map the FlavorTable which contains mappings between
919 * DataFlavors and data formats
920 * @throws NullPointerException if map is <code>null</code>
921 */
922 public DataFlavor[] getFlavorsForFormatAsArray(long format,
923 FlavorTable map) {
924 return getFlavorsForFormatsAsArray(new long[] { format }, map);
925 }
926
927 /**
928 * Returns an array of all DataFlavors for which
929 * 1) a mapping from at least one of the specified formats exists in the
930 * specified map and
931 * 2) the data translation for this mapping can be performed by the data
932 * transfer subsystem.
933 * The array will be sorted according to a
934 * <code>DataFlavorComparator</code> created with the specified
935 * map as an argument.
936 *
937 * @param formats the data formats
938 * @param map the FlavorTable which contains mappings between
939 * DataFlavors and data formats
940 * @throws NullPointerException if formats or map is <code>null</code>
941 */
942 public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats,
943 FlavorTable map) {
944 // getFlavorsForFormatsAsSet() is less expensive than
945 // getFlavorsForFormats().
946 return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map));
947 }
948
949 /**
950 * Returns an object that represents a mapping between the specified
951 * key and value. <tt>null</tt> values and the <tt>null</tt> keys are
952 * permitted. The internal representation of the mapping object is
953 * irrelevant. The only requrement is that the two mapping objects are equal
954 * if and only if their keys are equal and their values are equal.
955 * More formally, the two mapping objects are equal if and only if
956 * <tt>(value1 == null ? value2 == null : value1.equals(value2))
957 * && (key1 == null ? key2 == null : key1.equals(key2))</tt>.
958 */
959 private static Object createMapping(Object key, Object value) {
960 // NOTE: Should be updated to use AbstractMap.SimpleEntry as
961 // soon as it is made public.
962 return Arrays.asList(new Object[] { key, value });
963 }
964
965 /**
966 * Looks-up or registers the String native with the native data transfer
967 * system and returns a long format corresponding to that native.
968 */
969 protected abstract Long getFormatForNativeAsLong(String str);
970
971 /**
972 * Looks-up the String native corresponding to the specified long format in
973 * the native data transfer system.
974 */
975 protected abstract String getNativeForFormat(long format);
976
977 /* Contains common code for finding the best charset for
978 * clipboard string encoding/decoding, basing on clipboard
979 * format and localeTransferable(on decoding, if available)
980 */
981 private String getBestCharsetForTextFormat(Long lFormat,
982 Transferable localeTransferable) throws IOException
983 {
984 String charset = null;
985 if (localeTransferable != null &&
986 isLocaleDependentTextFormat(lFormat) &&
987 localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor))
988 {
989 try {
990 charset = new String(
991 (byte[])localeTransferable.getTransferData(javaTextEncodingFlavor),
992 "UTF-8"
993 );
994 } catch (UnsupportedFlavorException cannotHappen) {
995 }
996 } else {
997 charset = getCharsetForTextFormat(lFormat);
998 }
999 if (charset == null) {
1000 // Only happens when we have a custom text type.
1001 charset = getDefaultTextCharset();
1002 }
1003 return charset;
1004 }
1005
1006 /**
1007 * Translation function for converting string into
1008 * a byte array. Search-and-replace EOLN. Encode into the
1009 * target format. Append terminating NUL bytes.
1010 *
1011 * Java to Native string conversion
1012 */
1013 private byte[] translateTransferableString(String str,
1014 long format) throws IOException
1015 {
1016 Long lFormat = Long.valueOf(format);
1017 String charset = getBestCharsetForTextFormat(lFormat, null);
1018 // Search and replace EOLN. Note that if EOLN is "\n", then we
1019 // never added an entry to nativeEOLNs anyway, so we'll skip this
1020 // code altogether.
1021 // windows: "abc\nde"->"abc\r\nde"
1022 String eoln = (String)nativeEOLNs.get(lFormat);
1023 if (eoln != null) {
1024 int length = str.length();
1025 StringBuffer buffer =
1026 new StringBuffer(length * 2); // 2 is a heuristic
1027 for (int i = 0; i < length; i++) {
1028 // Fix for 4914613 - skip native EOLN
1029 if (str.startsWith(eoln, i)) {
1030 buffer.append(eoln);
1031 i += eoln.length() - 1;
1032 continue;
1033 }
1034 char c = str.charAt(i);
1035 if (c == '\n') {
1036 buffer.append(eoln);
1037 } else {
1038 buffer.append(c);
1039 }
1040 }
1041 str = buffer.toString();
1042 }
1043
1044 // Encode text in target format.
1045 byte[] bytes = str.getBytes(charset);
1046
1047 // Append terminating NUL bytes. Note that if terminators is 0,
1048 // the we never added an entry to nativeTerminators anyway, so
1049 // we'll skip code altogether.
1050 // "abcde" -> "abcde\0"
1051 Integer terminators = (Integer)nativeTerminators.get(lFormat);
1052 if (terminators != null) {
1053 int numTerminators = terminators.intValue();
1054 byte[] terminatedBytes =
1055 new byte[bytes.length + numTerminators];
1056 System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length);
1057 for (int i = bytes.length; i < terminatedBytes.length; i++) {
1058 terminatedBytes[i] = 0x0;
1059 }
1060 bytes = terminatedBytes;
1061 }
1062 return bytes;
1063 }
1064
1065 /**
1066 * Translating either a byte array or an InputStream into an String.
1067 * Strip terminators and search-and-replace EOLN.
1068 *
1069 * Native to Java string conversion
1070 */
1071 private String translateBytesToString(byte[] bytes, long format,
1072 Transferable localeTransferable)
1073 throws IOException
1074 {
1075
1076 Long lFormat = Long.valueOf(format);
1077 String charset = getBestCharsetForTextFormat(lFormat, localeTransferable);
1078
1079 // Locate terminating NUL bytes. Note that if terminators is 0,
1080 // the we never added an entry to nativeTerminators anyway, so
1081 // we'll skip code altogether.
1082
1083 // In other words: we are doing char alignment here basing on suggestion
1084 // that count of zero-'terminators' is a number of bytes in one symbol
1085 // for selected charset (clipboard format). It is not complitly true for
1086 // multibyte coding like UTF-8, but helps understand the procedure.
1087 // "abcde\0" -> "abcde"
1088
1089 String eoln = (String)nativeEOLNs.get(lFormat);
1090 Integer terminators = (Integer)nativeTerminators.get(lFormat);
1091 int count;
1092 if (terminators != null) {
1093 int numTerminators = terminators.intValue();
1094 search:
1095 for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) {
1096 for (int i = count; i < count + numTerminators; i++) {
1097 if (bytes[i] != 0x0) {
1098 continue search;
1099 }
1100 }
1101 // found terminators
1102 break search;
1103 }
1104 } else {
1105 count = bytes.length;
1106 }
1107
1108 // Decode text to chars. Don't include any terminators.
1109 String converted = new String(bytes, 0, count, charset);
1110
1111 // Search and replace EOLN. Note that if EOLN is "\n", then we
1112 // never added an entry to nativeEOLNs anyway, so we'll skip this
1113 // code altogether.
1114 // Count of NUL-terminators and EOLN coding are platform-specific and
1115 // loaded from flavormap.properties file
1116 // windows: "abc\r\nde" -> "abc\nde"
1117
1118 if (eoln != null) {
1119
1120 /* Fix for 4463560: replace EOLNs symbol-by-symbol instead
1121 * of using buf.replace()
1122 */
1123
1124 char[] buf = converted.toCharArray();
1125 char[] eoln_arr = eoln.toCharArray();
1126 converted = null;
1127 int j = 0;
1128 boolean match;
1129
1130 for (int i = 0; i < buf.length; ) {
1131 // Catch last few bytes
1132 if (i + eoln_arr.length > buf.length) {
1133 buf[j++] = buf[i++];
1134 continue;
1135 }
1136
1137 match = true;
1138 for (int k = 0, l = i; k < eoln_arr.length; k++, l++) {
1139 if (eoln_arr[k] != buf[l]) {
1140 match = false;
1141 break;
1142 }
1143 }
1144 if (match) {
1145 buf[j++] = '\n';
1146 i += eoln_arr.length;
1147 } else {
1148 buf[j++] = buf[i++];
1149 }
1150 }
1151 converted = new String(buf, 0, j);
1152 }
1153
1154 return converted;
1155 }
1156
1157
1158 /**
1159 * Primary translation function for translating a Transferable into
1160 * a byte array, given a source DataFlavor and target format.
1161 */
1162 public byte[] translateTransferable(Transferable contents,
1163 DataFlavor flavor,
1164 long format) throws IOException
1165 {
1166 // Obtain the transfer data in the source DataFlavor.
1167 //
1168 // Note that we special case DataFlavor.plainTextFlavor because
1169 // StringSelection supports this flavor incorrectly -- instead of
1170 // returning an InputStream as the DataFlavor representation class
1171 // states, it returns a Reader. Instead of using this broken
1172 // functionality, we request the data in stringFlavor (the other
1173 // DataFlavor which StringSelection supports) and use the String
1174 // translator.
1175 Object obj;
1176 boolean stringSelectionHack;
1177 try {
1178 obj = contents.getTransferData(flavor);
1179 if (obj == null) {
1180 return null;
1181 }
1182 if (flavor.equals(DataFlavor.plainTextFlavor) &&
1183 !(obj instanceof InputStream))
1184 {
1185 obj = contents.getTransferData(DataFlavor.stringFlavor);
1186 if (obj == null) {
1187 return null;
1188 }
1189 stringSelectionHack = true;
1190 } else {
1191 stringSelectionHack = false;
1192 }
1193 } catch (UnsupportedFlavorException e) {
1194 throw new IOException(e.getMessage());
1195 }
1196
1197 // Source data is a String. Search-and-replace EOLN. Encode into the
1198 // target format. Append terminating NUL bytes.
1199 if (stringSelectionHack ||
1200 (String.class.equals(flavor.getRepresentationClass()) &&
1201 isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1202
1203 String str = removeSuspectedData(flavor, contents, (String)obj);
1204
1205 return translateTransferableString(
1206 str,
1207 format);
1208
1209 // Source data is a Reader. Convert to a String and recur. In the
1210 // future, we may want to rewrite this so that we encode on demand.
1211 } else if (flavor.isRepresentationClassReader()) {
1212 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1213 throw new IOException
1214 ("cannot transfer non-text data as Reader");
1215 }
1216
1217 StringBuffer buf = new StringBuffer();
1218 try (Reader r = (Reader)obj) {
1219 int c;
1220 while ((c = r.read()) != -1) {
1221 buf.append((char)c);
1222 }
1223 }
1224
1225 return translateTransferableString(
1226 buf.toString(),
1227 format);
1228
1229 // Source data is a CharBuffer. Convert to a String and recur.
1230 } else if (flavor.isRepresentationClassCharBuffer()) {
1231 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1232 throw new IOException
1233 ("cannot transfer non-text data as CharBuffer");
1234 }
1235
1236 CharBuffer buffer = (CharBuffer)obj;
1237 int size = buffer.remaining();
1238 char[] chars = new char[size];
1239 buffer.get(chars, 0, size);
1240
1241 return translateTransferableString(
1242 new String(chars),
1243 format);
1244
1245 // Source data is a char array. Convert to a String and recur.
1246 } else if (charArrayClass.equals(flavor.getRepresentationClass())) {
1247 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1248 throw new IOException
1249 ("cannot transfer non-text data as char array");
1250 }
1251
1252 return translateTransferableString(
1253 new String((char[])obj),
1254 format);
1255
1256 // Source data is a ByteBuffer. For arbitrary flavors, simply return
1257 // the array. For text flavors, decode back to a String and recur to
1258 // reencode according to the requested format.
1259 } else if (flavor.isRepresentationClassByteBuffer()) {
1260 ByteBuffer buffer = (ByteBuffer)obj;
1261 int size = buffer.remaining();
1262 byte[] bytes = new byte[size];
1263 buffer.get(bytes, 0, size);
1264
1265 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1266 String sourceEncoding = DataTransferer.getTextCharset(flavor);
1267 return translateTransferableString(
1268 new String(bytes, sourceEncoding),
1269 format);
1270 } else {
1271 return bytes;
1272 }
1273
1274 // Source data is a byte array. For arbitrary flavors, simply return
1275 // the array. For text flavors, decode back to a String and recur to
1276 // reencode according to the requested format.
1277 } else if (byteArrayClass.equals(flavor.getRepresentationClass())) {
1278 byte[] bytes = (byte[])obj;
1279
1280 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1281 String sourceEncoding = DataTransferer.getTextCharset(flavor);
1282 return translateTransferableString(
1283 new String(bytes, sourceEncoding),
1284 format);
1285 } else {
1286 return bytes;
1287 }
1288 // Source data is Image
1289 } else if (DataFlavor.imageFlavor.equals(flavor)) {
1290 if (!isImageFormat(format)) {
1291 throw new IOException("Data translation failed: " +
1292 "not an image format");
1293 }
1294
1295 Image image = (Image)obj;
1296 byte[] bytes = imageToPlatformBytes(image, format);
1297
1298 if (bytes == null) {
1299 throw new IOException("Data translation failed: " +
1300 "cannot convert java image to native format");
1301 }
1302 return bytes;
1303 }
1304
1305 byte[] theByteArray = null;
1306
1307 // Target data is a file list. Source data must be a
1308 // java.util.List which contains java.io.File or String instances.
1309 if (isFileFormat(format)) {
1310 if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
1311 throw new IOException("data translation failed");
1312 }
1313
1314 final List list = (List)obj;
1315
1316 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
1317
1318 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);
1319
1320 try (ByteArrayOutputStream bos = convertFileListToBytes(fileList)) {
1321 theByteArray = bos.toByteArray();
1322 }
1323
1324 // Target data is a URI list. Source data must be a
1325 // java.util.List which contains java.io.File or String instances.
1326 } else if (isURIListFormat(format)) {
1327 if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
1328 throw new IOException("data translation failed");
1329 }
1330 String nat = getNativeForFormat(format);
1331 String targetCharset = null;
1332 if (nat != null) {
1333 try {
1334 targetCharset = new DataFlavor(nat).getParameter("charset");
1335 } catch (ClassNotFoundException cnfe) {
1336 throw new IOException(cnfe);
1337 }
1338 }
1339 if (targetCharset == null) {
1340 targetCharset = "UTF-8";
1341 }
1342 final List list = (List)obj;
1343 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
1344 final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);
1345 final ArrayList<String> uriList = new ArrayList<String>(fileList.size());
1346 for (String fileObject : fileList) {
1347 final URI uri = new File(fileObject).toURI();
1348 // Some implementations are fussy about the number of slashes (file:///path/to/file is best)
1349 try {
1350 uriList.add(new URI(uri.getScheme(), "", uri.getPath(), uri.getFragment()).toString());
1351 } catch (URISyntaxException uriSyntaxException) {
1352 throw new IOException(uriSyntaxException);
1353 }
1354 }
1355
1356 byte[] eoln = "\r\n".getBytes(targetCharset);
1357
1358 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
1359 for (int i = 0; i < uriList.size(); i++) {
1360 byte[] bytes = uriList.get(i).getBytes(targetCharset);
1361 bos.write(bytes, 0, bytes.length);
1362 bos.write(eoln, 0, eoln.length);
1363 }
1364 theByteArray = bos.toByteArray();
1365 }
1366
1367 // Source data is an InputStream. For arbitrary flavors, just grab the
1368 // bytes and dump them into a byte array. For text flavors, decode back
1369 // to a String and recur to reencode according to the requested format.
1370 } else if (flavor.isRepresentationClassInputStream()) {
1371 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
1372 try (InputStream is = (InputStream)obj) {
1373 boolean eof = false;
1374 int avail = is.available();
1375 byte[] tmp = new byte[avail > 8192 ? avail : 8192];
1376 do {
1377 int aValue;
1378 if (!(eof = (aValue = is.read(tmp, 0, tmp.length)) == -1)) {
1379 bos.write(tmp, 0, aValue);
1380 }
1381 } while (!eof);
1382 }
1383
1384 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1385 byte[] bytes = bos.toByteArray();
1386 String sourceEncoding = DataTransferer.getTextCharset(flavor);
1387 return translateTransferableString(
1388 new String(bytes, sourceEncoding),
1389 format);
1390 }
1391 theByteArray = bos.toByteArray();
1392 }
1393
1394
1395
1396 // Source data is an RMI object
1397 } else if (flavor.isRepresentationClassRemote()) {
1398
1399 Object mo = RMI.newMarshalledObject(obj);
1400 theByteArray = convertObjectToBytes(mo);
1401
1402 // Source data is Serializable
1403 } else if (flavor.isRepresentationClassSerializable()) {
1404
1405 theByteArray = convertObjectToBytes(obj);
1406
1407 } else {
1408 throw new IOException("data translation failed");
1409 }
1410
1411
1412
1413 return theByteArray;
1414 }
1415
1416 private static byte[] convertObjectToBytes(Object object) throws IOException {
1417 try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
1418 ObjectOutputStream oos = new ObjectOutputStream(bos))
1419 {
1420 oos.writeObject(object);
1421 return bos.toByteArray();
1422 }
1423 }
1424
1425 protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException;
1426
1427 private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str)
1428 throws IOException
1429 {
1430 if (null == System.getSecurityManager()
1431 || !flavor.isMimeTypeEqual("text/uri-list"))
1432 {
1433 return str;
1434 }
1435
1436
1437 String ret_val = "";
1438 final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
1439
1440 try {
1441 ret_val = (String) AccessController.doPrivileged(new PrivilegedExceptionAction() {
1442 public Object run() {
1443
1444 StringBuffer allowedFiles = new StringBuffer(str.length());
1445 String [] uriArray = str.split("(\\s)+");
1446
1447 for (String fileName : uriArray)
1448 {
1449 File file = new File(fileName);
1450 if (file.exists() &&
1451 !(isFileInWebstartedCache(file) ||
1452 isForbiddenToRead(file, userProtectionDomain)))
1453 {
1454
1455 if (0 != allowedFiles.length())
1456 {
1457 allowedFiles.append("\\r\\n");
1458 }
1459
1460 allowedFiles.append(fileName);
1461 }
1462 }
1463
1464 return allowedFiles.toString();
1465 }
1466 });
1467 } catch (PrivilegedActionException pae) {
1468 throw new IOException(pae.getMessage(), pae);
1469 }
1470
1471 return ret_val;
1472 }
1473
1474 private static ProtectionDomain getUserProtectionDomain(Transferable contents) {
1475 return contents.getClass().getProtectionDomain();
1476 }
1477
1478 private boolean isForbiddenToRead (File file, ProtectionDomain protectionDomain)
1479 {
1480 if (null == protectionDomain) {
1481 return false;
1482 }
1483 try {
1484 FilePermission filePermission =
1485 new FilePermission(file.getCanonicalPath(), "read, delete");
1486 if (protectionDomain.implies(filePermission)) {
1487 return false;
1488 }
1489 } catch (IOException e) {}
1490
1491 return true;
1492 }
1493
1494 private ArrayList<String> castToFiles(final List files,
1495 final ProtectionDomain userProtectionDomain) throws IOException
1496 {
1497 final ArrayList<String> fileList = new ArrayList<String>();
1498 try {
1499 AccessController.doPrivileged(new PrivilegedExceptionAction() {
1500 public Object run() throws IOException {
1501 for (Object fileObject : files)
1502 {
1503 File file = castToFile(fileObject);
1504 if (file != null &&
1505 (null == System.getSecurityManager() ||
1506 !(isFileInWebstartedCache(file) ||
1507 isForbiddenToRead(file, userProtectionDomain))))
1508 {
1509 fileList.add(file.getCanonicalPath());
1510 }
1511 }
1512 return null;
1513 }
1514 });
1515 } catch (PrivilegedActionException pae) {
1516 throw new IOException(pae.getMessage());
1517 }
1518 return fileList;
1519 }
1520
1521 // It is important do not use user's successors
1522 // of File class.
1523 private File castToFile(Object fileObject) throws IOException {
1524 String filePath = null;
1525 if (fileObject instanceof File) {
1526 filePath = ((File)fileObject).getCanonicalPath();
1527 } else if (fileObject instanceof String) {
1528 filePath = (String) fileObject;
1529 } else {
1530 return null;
1531 }
1532 return new File(filePath);
1533 }
1534
1535 private final static String[] DEPLOYMENT_CACHE_PROPERTIES = {
1536 "deployment.system.cachedir",
1537 "deployment.user.cachedir",
1538 "deployment.javaws.cachedir",
1539 "deployment.javapi.cachedir"
1540 };
1541
1542 private final static ArrayList <File> deploymentCacheDirectoryList =
1543 new ArrayList<File>();
1544
1545 private static boolean isFileInWebstartedCache(File f) {
1546
1547 if (deploymentCacheDirectoryList.isEmpty()) {
1548 for (String cacheDirectoryProperty : DEPLOYMENT_CACHE_PROPERTIES) {
1549 String cacheDirectoryPath = System.getProperty(cacheDirectoryProperty);
1550 if (cacheDirectoryPath != null) {
1551 try {
1552 File cacheDirectory = (new File(cacheDirectoryPath)).getCanonicalFile();
1553 if (cacheDirectory != null) {
1554 deploymentCacheDirectoryList.add(cacheDirectory);
1555 }
1556 } catch (IOException ioe) {}
1557 }
1558 }
1559 }
1560
1561 for (File deploymentCacheDirectory : deploymentCacheDirectoryList) {
1562 for (File dir = f; dir != null; dir = dir.getParentFile()) {
1563 if (dir.equals(deploymentCacheDirectory)) {
1564 return true;
1565 }
1566 }
1567 }
1568
1569 return false;
1570 }
1571
1572
1573 public Object translateBytes(byte[] bytes, DataFlavor flavor,
1574 long format, Transferable localeTransferable)
1575 throws IOException
1576 {
1577
1578 Object theObject = null;
1579
1580 // Source data is a file list. Use the dragQueryFile native function to
1581 // do most of the decoding. Then wrap File objects around the String
1582 // filenames and return a List.
1583 if (isFileFormat(format)) {
1584 if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
1585 throw new IOException("data translation failed");
1586 }
1587 String[] filenames = dragQueryFile(bytes);
1588 if (filenames == null) {
1589 return null;
1590 }
1591
1592 // Convert the strings to File objects
1593 File[] files = new File[filenames.length];
1594 for (int i = 0; i < filenames.length; i++) {
1595 files[i] = new File(filenames[i]);
1596 }
1597
1598 // Turn the list of Files into a List and return
1599 theObject = Arrays.asList(files);
1600
1601 // Target data is a String. Strip terminating NUL bytes. Decode bytes
1602 // into characters. Search-and-replace EOLN.
1603 } else if (String.class.equals(flavor.getRepresentationClass()) &&
1604 isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1605
1606 theObject = translateBytesToString(bytes, format, localeTransferable);
1607
1608 // Target data is a Reader. Obtain data in InputStream format, encoded
1609 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
1610 // back to chars on demand.
1611 } else if (flavor.isRepresentationClassReader()) {
1612 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1613 theObject = translateStream(bais,
1614 flavor, format, localeTransferable);
1615 }
1616 // Target data is a CharBuffer. Recur to obtain String and wrap.
1617 } else if (flavor.isRepresentationClassCharBuffer()) {
1618 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1619 throw new IOException
1620 ("cannot transfer non-text data as CharBuffer");
1621 }
1622
1623 CharBuffer buffer = CharBuffer.wrap(
1624 translateBytesToString(bytes,format, localeTransferable));
1625
1626 theObject = constructFlavoredObject(buffer, flavor, CharBuffer.class);
1627
1628 // Target data is a char array. Recur to obtain String and convert to
1629 // char array.
1630 } else if (charArrayClass.equals(flavor.getRepresentationClass())) {
1631 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1632 throw new IOException
1633 ("cannot transfer non-text data as char array");
1634 }
1635
1636 theObject = translateBytesToString(
1637 bytes, format, localeTransferable).toCharArray();
1638
1639 // Target data is a ByteBuffer. For arbitrary flavors, just return
1640 // the raw bytes. For text flavors, convert to a String to strip
1641 // terminators and search-and-replace EOLN, then reencode according to
1642 // the requested flavor.
1643 } else if (flavor.isRepresentationClassByteBuffer()) {
1644 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1645 bytes = translateBytesToString(
1646 bytes, format, localeTransferable).getBytes(
1647 DataTransferer.getTextCharset(flavor)
1648 );
1649 }
1650
1651 ByteBuffer buffer = ByteBuffer.wrap(bytes);
1652 theObject = constructFlavoredObject(buffer, flavor, ByteBuffer.class);
1653
1654 // Target data is a byte array. For arbitrary flavors, just return
1655 // the raw bytes. For text flavors, convert to a String to strip
1656 // terminators and search-and-replace EOLN, then reencode according to
1657 // the requested flavor.
1658 } else if (byteArrayClass.equals(flavor.getRepresentationClass())) {
1659 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1660 theObject = translateBytesToString(
1661 bytes, format, localeTransferable
1662 ).getBytes(DataTransferer.getTextCharset(flavor));
1663 } else {
1664 theObject = bytes;
1665 }
1666
1667 // Target data is an InputStream. For arbitrary flavors, just return
1668 // the raw bytes. For text flavors, decode to strip terminators and
1669 // search-and-replace EOLN, then reencode according to the requested
1670 // flavor.
1671 } else if (flavor.isRepresentationClassInputStream()) {
1672
1673 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1674 theObject = translateStream(bais, flavor, format, localeTransferable);
1675 }
1676
1677 // Target data is Serializable
1678 } else if (flavor.isRepresentationClassSerializable()) {
1679
1680 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1681 theObject = translateStream(bais, flavor, format, localeTransferable);
1682 }
1683
1684 // Target data is Image
1685 } else if (DataFlavor.imageFlavor.equals(flavor)) {
1686 if (!isImageFormat(format)) {
1687 throw new IOException("data translation failed");
1688 }
1689
1690 theObject = platformImageBytesToImage(bytes, format);
1691 }
1692
1693 if (theObject == null) {
1694 throw new IOException("data translation failed");
1695 }
1696
1697 return theObject;
1698
1699 }
1700
1701 /**
1702 * Primary translation function for translating
1703 * an InputStream into an Object, given a source format and a target
1704 * DataFlavor.
1705 */
1706 public Object translateStream(InputStream str, DataFlavor flavor,
1707 long format, Transferable localeTransferable)
1708 throws IOException
1709 {
1710
1711 Object theObject = null;
1712 // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor
1713 // where possible.
1714 if (isURIListFormat(format)
1715 && DataFlavor.javaFileListFlavor.equals(flavor))
1716 {
1717
1718 URI uris[] = dragQueryURIs(str, format, localeTransferable);
1719 if (uris == null) {
1720 return null;
1721 }
1722 ArrayList files = new ArrayList();
1723 for (URI uri : uris) {
1724 try {
1725 files.add(new File(uri));
1726 } catch (IllegalArgumentException illegalArg) {
1727 // When converting from URIs to less generic files,
1728 // common practice (Wine, SWT) seems to be to
1729 // silently drop the URIs that aren't local files.
1730 }
1731 }
1732 theObject = files;
1733
1734 // Special hack to maintain backwards-compatibility with the brokenness
1735 // of StringSelection. Return a StringReader instead of an InputStream.
1736 // Recur to obtain String and encapsulate.
1737 } else if (DataFlavor.plainTextFlavor.equals(flavor)) {
1738 theObject = new StringReader(translateBytesToString(
1739 inputStreamToByteArray(str),
1740 format, localeTransferable));
1741
1742 // Target data is an InputStream. For arbitrary flavors, just return
1743 // the raw bytes. For text flavors, decode to strip terminators and
1744 // search-and-replace EOLN, then reencode according to the requested
1745 // flavor.
1746 } else if (flavor.isRepresentationClassInputStream()) {
1747 theObject = translateStreamToInputStream(str, flavor, format,
1748 localeTransferable);
1749
1750 // Target data is a Reader. Obtain data in InputStream format, encoded
1751 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
1752 // back to chars on demand.
1753 } else if (flavor.isRepresentationClassReader()) {
1754 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1755 throw new IOException
1756 ("cannot transfer non-text data as Reader");
1757 }
1758
1759 InputStream is = (InputStream)translateStreamToInputStream(
1760 str, DataFlavor.plainTextFlavor,
1761 format, localeTransferable);
1762
1763 String unicode = DataTransferer.getTextCharset(DataFlavor.plainTextFlavor);
1764
1765 Reader reader = new InputStreamReader(is, unicode);
1766
1767 theObject = constructFlavoredObject(reader, flavor, Reader.class);
1768
1769 // Target data is an RMI object
1770 } else if (flavor.isRepresentationClassRemote()) {
1771
1772 try (ObjectInputStream ois =
1773 new ObjectInputStream(str))
1774 {
1775 theObject = RMI.getMarshalledObject(ois.readObject());
1776 }catch (Exception e) {
1777 throw new IOException(e.getMessage());
1778 }
1779
1780 // Target data is Serializable
1781 } else if (flavor.isRepresentationClassSerializable()) {
1782 try (ObjectInputStream ois =
1783 new ObjectInputStream(str))
1784 {
1785 theObject = ois.readObject();
1786 } catch (Exception e) {
1787 throw new IOException(e.getMessage());
1788 }
1789 }
1790
1791
1792 return theObject;
1793
1794 }
1795
1796 /**
1797 * For arbitrary flavors, just use the raw InputStream. For text flavors,
1798 * ReencodingInputStream will decode and reencode the InputStream on demand
1799 * so that we can strip terminators and search-and-replace EOLN.
1800 */
1801 private Object translateStreamToInputStream
1802 (InputStream str, DataFlavor flavor, long format,
1803 Transferable localeTransferable) throws IOException
1804 {
1805 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1806 str = new ReencodingInputStream
1807 (str, format, DataTransferer.getTextCharset(flavor),
1808 localeTransferable);
1809 }
1810
1811 return constructFlavoredObject(str, flavor, InputStream.class);
1812 }
1813
1814 /**
1815 * We support representations which are exactly of the specified Class,
1816 * and also arbitrary Objects which have a constructor which takes an
1817 * instance of the Class as its sole parameter.
1818 */
1819 private Object constructFlavoredObject(Object arg, DataFlavor flavor,
1820 Class clazz)
1821 throws IOException
1822 {
1823 final Class dfrc = flavor.getRepresentationClass();
1824
1825 if (clazz.equals(dfrc)) {
1826 return arg; // simple case
1827 } else {
1828 Constructor[] constructors = null;
1829
1830 try {
1831 constructors = (Constructor[])
1832 AccessController.doPrivileged(new PrivilegedAction() {
1833 public Object run() {
1834 return dfrc.getConstructors();
1835 }
1836 });
1837 } catch (SecurityException se) {
1838 throw new IOException(se.getMessage());
1839 }
1840
1841 Constructor constructor = null;
1842
1843 for (int j = 0; j < constructors.length; j++) {
1844 if (!Modifier.isPublic(constructors[j].getModifiers())) {
1845 continue;
1846 }
1847
1848 Class[] ptypes = constructors[j].getParameterTypes();
1849
1850 if (ptypes != null && ptypes.length == 1 &&
1851 clazz.equals(ptypes[0])) {
1852 constructor = constructors[j];
1853 break;
1854 }
1855 }
1856
1857 if (constructor == null) {
1858 throw new IOException("can't find <init>(L"+ clazz +
1859 ";)V for class: " + dfrc.getName());
1860 }
1861
1862 try {
1863 return constructor.newInstance(new Object[] { arg } );
1864 } catch (Exception e) {
1865 throw new IOException(e.getMessage());
1866 }
1867 }
1868 }
1869
1870 /**
1871 * Used for decoding and reencoding an InputStream on demand so that we
1872 * can strip NUL terminators and perform EOLN search-and-replace.
1873 */
1874 public class ReencodingInputStream extends InputStream {
1875 protected BufferedReader wrapped;
1876 protected final char[] in = new char[2];
1877 protected byte[] out;
1878
1879 protected CharsetEncoder encoder;
1880 protected CharBuffer inBuf;
1881 protected ByteBuffer outBuf;
1882
1883 protected char[] eoln;
1884 protected int numTerminators;
1885
1886 protected boolean eos;
1887 protected int index, limit;
1888
1889 public ReencodingInputStream(InputStream bytestream, long format,
1890 String targetEncoding,
1891 Transferable localeTransferable)
1892 throws IOException
1893 {
1894 Long lFormat = Long.valueOf(format);
1895
1896 String sourceEncoding = null;
1897 if (isLocaleDependentTextFormat(format) &&
1898 localeTransferable != null &&
1899 localeTransferable.
1900 isDataFlavorSupported(javaTextEncodingFlavor))
1901 {
1902 try {
1903 sourceEncoding = new String((byte[])localeTransferable.
1904 getTransferData(javaTextEncodingFlavor),
1905 "UTF-8");
1906 } catch (UnsupportedFlavorException cannotHappen) {
1907 }
1908 } else {
1909 sourceEncoding = getCharsetForTextFormat(lFormat);
1910 }
1911
1912 if (sourceEncoding == null) {
1913 // Only happens when we have a custom text type.
1914 sourceEncoding = getDefaultTextCharset();
1915 }
1916 wrapped = new BufferedReader
1917 (new InputStreamReader(bytestream, sourceEncoding));
1918
1919 if (targetEncoding == null) {
1920 // Throw NullPointerException for compatibility with the former
1921 // call to sun.io.CharToByteConverter.getConverter(null)
1922 // (Charset.forName(null) throws unspecified IllegalArgumentException
1923 // now; see 6228568)
1924 throw new NullPointerException("null target encoding");
1925 }
1926
1927 try {
1928 encoder = Charset.forName(targetEncoding).newEncoder();
1929 out = new byte[(int)(encoder.maxBytesPerChar() * 2 + 0.5)];
1930 inBuf = CharBuffer.wrap(in);
1931 outBuf = ByteBuffer.wrap(out);
1932 } catch (IllegalCharsetNameException e) {
1933 throw new IOException(e.toString());
1934 } catch (UnsupportedCharsetException e) {
1935 throw new IOException(e.toString());
1936 } catch (UnsupportedOperationException e) {
1937 throw new IOException(e.toString());
1938 }
1939
1940 String sEoln = (String)nativeEOLNs.get(lFormat);
1941 if (sEoln != null) {
1942 eoln = sEoln.toCharArray();
1943 }
1944
1945 // A hope and a prayer that this works generically. This will
1946 // definitely work on Win32.
1947 Integer terminators = (Integer)nativeTerminators.get(lFormat);
1948 if (terminators != null) {
1949 numTerminators = terminators.intValue();
1950 }
1951 }
1952
1953 private int readChar() throws IOException {
1954 int c = wrapped.read();
1955
1956 if (c == -1) { // -1 is EOS
1957 eos = true;
1958 return -1;
1959 }
1960
1961 // "c == 0" is not quite correct, but good enough on Windows.
1962 if (numTerminators > 0 && c == 0) {
1963 eos = true;
1964 return -1;
1965 } else if (eoln != null && matchCharArray(eoln, c)) {
1966 c = '\n' & 0xFFFF;
1967 }
1968
1969 return c;
1970 }
1971
1972 public int read() throws IOException {
1973 if (eos) {
1974 return -1;
1975 }
1976
1977 if (index >= limit) {
1978 // deal with supplementary characters
1979 int c = readChar();
1980 if (c == -1) {
1981 return -1;
1982 }
1983
1984 in[0] = (char) c;
1985 in[1] = 0;
1986 inBuf.limit(1);
1987 if (Character.isHighSurrogate((char) c)) {
1988 c = readChar();
1989 if (c != -1) {
1990 in[1] = (char) c;
1991 inBuf.limit(2);
1992 }
1993 }
1994
1995 inBuf.rewind();
1996 outBuf.limit(out.length).rewind();
1997 encoder.encode(inBuf, outBuf, false);
1998 outBuf.flip();
1999 limit = outBuf.limit();
2000
2001 index = 0;
2002
2003 return read();
2004 } else {
2005 return out[index++] & 0xFF;
2006 }
2007 }
2008
2009 public int available() throws IOException {
2010 return ((eos) ? 0 : (limit - index));
2011 }
2012
2013 public void close() throws IOException {
2014 wrapped.close();
2015 }
2016
2017 /**
2018 * Checks to see if the next array.length characters in wrapped
2019 * match array. The first character is provided as c. Subsequent
2020 * characters are read from wrapped itself. When this method returns,
2021 * the wrapped index may be different from what it was when this
2022 * method was called.
2023 */
2024 private boolean matchCharArray(char[] array, int c)
2025 throws IOException
2026 {
2027 wrapped.mark(array.length); // BufferedReader supports mark
2028
2029 int count = 0;
2030 if ((char)c == array[0]) {
2031 for (count = 1; count < array.length; count++) {
2032 c = wrapped.read();
2033 if (c == -1 || ((char)c) != array[count]) {
2034 break;
2035 }
2036 }
2037 }
2038
2039 if (count == array.length) {
2040 return true;
2041 } else {
2042 wrapped.reset();
2043 return false;
2044 }
2045 }
2046 }
2047
2048 /**
2049 * Decodes a byte array into a set of String filenames.
2050 */
2051 protected abstract String[] dragQueryFile(byte[] bytes);
2052
2053 /**
2054 * Decodes URIs from either a byte array or a stream.
2055 */
2056 protected URI[] dragQueryURIs(InputStream stream,
2057 long format,
2058 Transferable localeTransferable)
2059 throws IOException
2060 {
2061 throw new IOException(
2062 new UnsupportedOperationException("not implemented on this platform"));
2063 }
2064
2065 /**
2066 * Translates either a byte array or an input stream which contain
2067 * platform-specific image data in the given format into an Image.
2068 */
2069
2070
2071 protected abstract Image platformImageBytesToImage(
2072 byte[] bytes,long format) throws IOException;
2073
2074 /**
2075 * Translates either a byte array or an input stream which contain
2076 * an image data in the given standard format into an Image.
2077 *
2078 * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif
2079 */
2080 protected Image standardImageBytesToImage(
2081 byte[] bytes, String mimeType) throws IOException
2082 {
2083
2084 Iterator readerIterator = ImageIO.getImageReadersByMIMEType(mimeType);
2085
2086 if (!readerIterator.hasNext()) {
2087 throw new IOException("No registered service provider can decode " +
2088 " an image from " + mimeType);
2089 }
2090
2091 IOException ioe = null;
2092
2093 while (readerIterator.hasNext()) {
2094 ImageReader imageReader = (ImageReader)readerIterator.next();
2095 try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
2096 ImageInputStream imageInputStream =
2097 ImageIO.createImageInputStream(bais);
2098
2099 try {
2100 ImageReadParam param = imageReader.getDefaultReadParam();
2101 imageReader.setInput(imageInputStream, true, true);
2102 BufferedImage bufferedImage =
2103 imageReader.read(imageReader.getMinIndex(), param);
2104 if (bufferedImage != null) {
2105 return bufferedImage;
2106 }
2107 } finally {
2108 imageInputStream.close();
2109 imageReader.dispose();
2110 }
2111 } catch (IOException e) {
2112 ioe = e;
2113 continue;
2114 }
2115 }
2116
2117 if (ioe == null) {
2118 ioe = new IOException("Registered service providers failed to decode"
2119 + " an image from " + mimeType);
2120 }
2121
2122 throw ioe;
2123 }
2124
2125 /**
2126 * Translates a Java Image into a byte array which contains platform-
2127 * specific image data in the given format.
2128 */
2129 protected abstract byte[] imageToPlatformBytes(Image image, long format)
2130 throws IOException;
2131
2132 /**
2133 * Translates a Java Image into a byte array which contains
2134 * an image data in the given standard format.
2135 *
2136 * @param mimeType image MIME type, such as: image/png, image/jpeg
2137 */
2138 protected byte[] imageToStandardBytes(Image image, String mimeType)
2139 throws IOException {
2140 IOException originalIOE = null;
2141
2142 Iterator writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
2143
2144 if (!writerIterator.hasNext()) {
2145 throw new IOException("No registered service provider can encode " +
2146 " an image to " + mimeType);
2147 }
2148
2149 if (image instanceof RenderedImage) {
2150 // Try to encode the original image.
2151 try {
2152 return imageToStandardBytesImpl((RenderedImage)image, mimeType);
2153 } catch (IOException ioe) {
2154 originalIOE = ioe;
2155 }
2156 }
2157
2158 // Retry with a BufferedImage.
2159 int width = 0;
2160 int height = 0;
2161 if (image instanceof ToolkitImage) {
2162 ImageRepresentation ir = ((ToolkitImage)image).getImageRep();
2163 ir.reconstruct(ImageObserver.ALLBITS);
2164 width = ir.getWidth();
2165 height = ir.getHeight();
2166 } else {
2167 width = image.getWidth(null);
2168 height = image.getHeight(null);
2169 }
2170
2171 ColorModel model = ColorModel.getRGBdefault();
2172 WritableRaster raster =
2173 model.createCompatibleWritableRaster(width, height);
2174
2175 BufferedImage bufferedImage =
2176 new BufferedImage(model, raster, model.isAlphaPremultiplied(),
2177 null);
2178
2179 Graphics g = bufferedImage.getGraphics();
2180 try {
2181 g.drawImage(image, 0, 0, width, height, null);
2182 } finally {
2183 g.dispose();
2184 }
2185
2186 try {
2187 return imageToStandardBytesImpl(bufferedImage, mimeType);
2188 } catch (IOException ioe) {
2189 if (originalIOE != null) {
2190 throw originalIOE;
2191 } else {
2192 throw ioe;
2193 }
2194 }
2195 }
2196
2197 protected byte[] imageToStandardBytesImpl(RenderedImage renderedImage,
2198 String mimeType)
2199 throws IOException {
2200
2201 Iterator writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
2202
2203 ImageTypeSpecifier typeSpecifier =
2204 new ImageTypeSpecifier(renderedImage);
2205
2206 ByteArrayOutputStream baos = new ByteArrayOutputStream();
2207 IOException ioe = null;
2208
2209 while (writerIterator.hasNext()) {
2210 ImageWriter imageWriter = (ImageWriter)writerIterator.next();
2211 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();
2212
2213 if (!writerSpi.canEncodeImage(typeSpecifier)) {
2214 continue;
2215 }
2216
2217 try {
2218 ImageOutputStream imageOutputStream =
2219 ImageIO.createImageOutputStream(baos);
2220 try {
2221 imageWriter.setOutput(imageOutputStream);
2222 imageWriter.write(renderedImage);
2223 imageOutputStream.flush();
2224 } finally {
2225 imageOutputStream.close();
2226 }
2227 } catch (IOException e) {
2228 imageWriter.dispose();
2229 baos.reset();
2230 ioe = e;
2231 continue;
2232 }
2233
2234 imageWriter.dispose();
2235 baos.close();
2236 return baos.toByteArray();
2237 }
2238
2239 baos.close();
2240
2241 if (ioe == null) {
2242 ioe = new IOException("Registered service providers failed to encode "
2243 + renderedImage + " to " + mimeType);
2244 }
2245
2246 throw ioe;
2247 }
2248
2249 /**
2250 * Concatenates the data represented by two objects. Objects can be either
2251 * byte arrays or instances of <code>InputStream</code>. If both arguments
2252 * are byte arrays byte array will be returned. Otherwise an
2253 * <code>InputStream</code> will be returned.
2254 * <p>
2255 * Currently is only called from native code to prepend palette data to
2256 * platform-specific image data during image transfer on Win32.
2257 *
2258 * @param obj1 the first object to be concatenated.
2259 * @param obj2 the second object to be concatenated.
2260 * @return a byte array or an <code>InputStream</code> which represents
2261 * a logical concatenation of the two arguments.
2262 * @throws NullPointerException is either of the arguments is
2263 * <code>null</code>
2264 * @throws ClassCastException is either of the arguments is
2265 * neither byte array nor an instance of <code>InputStream</code>.
2266 */
2267 private Object concatData(Object obj1, Object obj2) {
2268 InputStream str1 = null;
2269 InputStream str2 = null;
2270
2271 if (obj1 instanceof byte[]) {
2272 byte[] arr1 = (byte[])obj1;
2273 if (obj2 instanceof byte[]) {
2274 byte[] arr2 = (byte[])obj2;
2275 byte[] ret = new byte[arr1.length + arr2.length];
2276 System.arraycopy(arr1, 0, ret, 0, arr1.length);
2277 System.arraycopy(arr2, 0, ret, arr1.length, arr2.length);
2278 return ret;
2279 } else {
2280 str1 = new ByteArrayInputStream(arr1);
2281 str2 = (InputStream)obj2;
2282 }
2283 } else {
2284 str1 = (InputStream)obj1;
2285 if (obj2 instanceof byte[]) {
2286 str2 = new ByteArrayInputStream((byte[])obj2);
2287 } else {
2288 str2 = (InputStream)obj2;
2289 }
2290 }
2291
2292 return new SequenceInputStream(str1, str2);
2293 }
2294
2295 public byte[] convertData(final Object source,
2296 final Transferable contents,
2297 final long format,
2298 final Map formatMap,
2299 final boolean isToolkitThread)
2300 throws IOException
2301 {
2302 byte[] ret = null;
2303
2304 /*
2305 * If the current thread is the Toolkit thread we should post a
2306 * Runnable to the event dispatch thread associated with source Object,
2307 * since translateTransferable() calls Transferable.getTransferData()
2308 * that may contain client code.
2309 */
2310 if (isToolkitThread) try {
2311 final Stack stack = new Stack();
2312 final Runnable dataConverter = new Runnable() {
2313 // Guard against multiple executions.
2314 private boolean done = false;
2315 public void run() {
2316 if (done) {
2317 return;
2318 }
2319 byte[] data = null;
2320 try {
2321 DataFlavor flavor = (DataFlavor)formatMap.get(Long.valueOf(format));
2322 if (flavor != null) {
2323 data = translateTransferable(contents, flavor, format);
2324 }
2325 } catch (Exception e) {
2326 e.printStackTrace();
2327 data = null;
2328 }
2329 try {
2330 getToolkitThreadBlockedHandler().lock();
2331 stack.push(data);
2332 getToolkitThreadBlockedHandler().exit();
2333 } finally {
2334 getToolkitThreadBlockedHandler().unlock();
2335 done = true;
2336 }
2337 }
2338 };
2339
2340 final AppContext appContext = SunToolkit.targetToAppContext(source);
2341
2342 getToolkitThreadBlockedHandler().lock();
2343
2344 if (appContext != null) {
2345 appContext.put(DATA_CONVERTER_KEY, dataConverter);
2346 }
2347
2348 SunToolkit.executeOnEventHandlerThread(source, dataConverter);
2349
2350 while (stack.empty()) {
2351 getToolkitThreadBlockedHandler().enter();
2352 }
2353
2354 if (appContext != null) {
2355 appContext.remove(DATA_CONVERTER_KEY);
2356 }
2357
2358 ret = (byte[])stack.pop();
2359 } finally {
2360 getToolkitThreadBlockedHandler().unlock();
2361 } else {
2362 DataFlavor flavor = (DataFlavor)
2363 formatMap.get(Long.valueOf(format));
2364 if (flavor != null) {
2365 ret = translateTransferable(contents, flavor, format);
2366 }
2367 }
2368
2369 return ret;
2370 }
2371
2372 public void processDataConversionRequests() {
2373 if (EventQueue.isDispatchThread()) {
2374 AppContext appContext = AppContext.getAppContext();
2375 getToolkitThreadBlockedHandler().lock();
2376 try {
2377 Runnable dataConverter =
2378 (Runnable)appContext.get(DATA_CONVERTER_KEY);
2379 if (dataConverter != null) {
2380 dataConverter.run();
2381 appContext.remove(DATA_CONVERTER_KEY);
2382 }
2383 } finally {
2384 getToolkitThreadBlockedHandler().unlock();
2385 }
2386 }
2387 }
2388
2389 public abstract ToolkitThreadBlockedHandler
2390 getToolkitThreadBlockedHandler();
2391
2392 /**
2393 * Helper function to reduce a Map with Long keys to a long array.
2394 * <p>
2395 * The map keys are sorted according to the native formats preference
2396 * order.
2397 */
2398 public static long[] keysToLongArray(SortedMap map) {
2399 Set keySet = map.keySet();
2400 long[] retval = new long[keySet.size()];
2401 int i = 0;
2402 for (Iterator iter = keySet.iterator(); iter.hasNext(); i++) {
2403 retval[i] = ((Long)iter.next()).longValue();
2404 }
2405 return retval;
2406 }
2407
2408 /**
2409 * Helper function to reduce a Map with DataFlavor keys to a DataFlavor
2410 * array. The array will be sorted according to
2411 * <code>DataFlavorComparator</code>.
2412 */
2413 public static DataFlavor[] keysToDataFlavorArray(Map map) {
2414 return setToSortedDataFlavorArray(map.keySet(), map);
2415 }
2416
2417 /**
2418 * Helper function to convert a Set of DataFlavors to a sorted array.
2419 * The array will be sorted according to <code>DataFlavorComparator</code>.
2420 */
2421 public static DataFlavor[] setToSortedDataFlavorArray(Set flavorsSet) {
2422 DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
2423 flavorsSet.toArray(flavors);
2424 final Comparator comparator =
2425 new DataFlavorComparator(IndexedComparator.SELECT_WORST);
2426 Arrays.sort(flavors, comparator);
2427 return flavors;
2428 }
2429
2430 /**
2431 * Helper function to convert a Set of DataFlavors to a sorted array.
2432 * The array will be sorted according to a
2433 * <code>DataFlavorComparator</code> created with the specified
2434 * flavor-to-native map as an argument.
2435 */
2436 public static DataFlavor[] setToSortedDataFlavorArray
2437 (Set flavorsSet, Map flavorToNativeMap)
2438 {
2439 DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
2440 flavorsSet.toArray(flavors);
2441 Comparator comparator =
2442 new DataFlavorComparator(flavorToNativeMap,
2443 IndexedComparator.SELECT_WORST);
2444 Arrays.sort(flavors, comparator);
2445 return flavors;
2446 }
2447
2448 /**
2449 * Helper function to convert an InputStream to a byte[] array.
2450 */
2451 protected static byte[] inputStreamToByteArray(InputStream str)
2452 throws IOException
2453 {
2454 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
2455 int len = 0;
2456 byte[] buf = new byte[8192];
2457
2458 while ((len = str.read(buf)) != -1) {
2459 baos.write(buf, 0, len);
2460 }
2461
2462 return baos.toByteArray();
2463 }
2464 }
2465
2466 /**
2467 * Returns platform-specific mappings for the specified native.
2468 * If there are no platform-specific mappings for this native, the method
2469 * returns an empty <code>List</code>.
2470 */
2471 public List getPlatformMappingsForNative(String nat) {
2472 return new ArrayList();
2473 }
2474
2475 /**
2476 * Returns platform-specific mappings for the specified flavor.
2477 * If there are no platform-specific mappings for this flavor, the method
2478 * returns an empty <code>List</code>.
2479 */
2480 public List getPlatformMappingsForFlavor(DataFlavor df) {
2481 return new ArrayList();
2482 }
2483
2484 /**
2485 * A Comparator which includes a helper function for comparing two Objects
2486 * which are likely to be keys in the specified Map.
2487 */
2488 public abstract static class IndexedComparator implements Comparator {
2489
2490 /**
2491 * The best Object (e.g., DataFlavor) will be the last in sequence.
2492 */
2493 public static final boolean SELECT_BEST = true;
2494
2495 /**
2496 * The best Object (e.g., DataFlavor) will be the first in sequence.
2497 */
2498 public static final boolean SELECT_WORST = false;
2499
2500 protected final boolean order;
2501
2502 public IndexedComparator() {
2503 this(SELECT_BEST);
2504 }
2505
2506 public IndexedComparator(boolean order) {
2507 this.order = order;
2508 }
2509
2510 /**
2511 * Helper method to compare two objects by their Integer indices in the
2512 * given map. If the map doesn't contain an entry for either of the
2513 * objects, the fallback index will be used for the object instead.
2514 *
2515 * @param indexMap the map which maps objects into Integer indexes.
2516 * @param obj1 the first object to be compared.
2517 * @param obj2 the second object to be compared.
2518 * @param fallbackIndex the Integer to be used as a fallback index.
2519 * @return a negative integer, zero, or a positive integer as the
2520 * first object is mapped to a less, equal to, or greater
2521 * index than the second.
2522 */
2523 protected static int compareIndices(Map indexMap,
2524 Object obj1, Object obj2,
2525 Integer fallbackIndex) {
2526 Integer index1 = (Integer)indexMap.get(obj1);
2527 Integer index2 = (Integer)indexMap.get(obj2);
2528
2529 if (index1 == null) {
2530 index1 = fallbackIndex;
2531 }
2532 if (index2 == null) {
2533 index2 = fallbackIndex;
2534 }
2535
2536 return index1.compareTo(index2);
2537 }
2538
2539 /**
2540 * Helper method to compare two objects by their Long indices in the
2541 * given map. If the map doesn't contain an entry for either of the
2542 * objects, the fallback index will be used for the object instead.
2543 *
2544 * @param indexMap the map which maps objects into Long indexes.
2545 * @param obj1 the first object to be compared.
2546 * @param obj2 the second object to be compared.
2547 * @param fallbackIndex the Long to be used as a fallback index.
2548 * @return a negative integer, zero, or a positive integer as the
2549 * first object is mapped to a less, equal to, or greater
2550 * index than the second.
2551 */
2552 protected static int compareLongs(Map indexMap,
2553 Object obj1, Object obj2,
2554 Long fallbackIndex) {
2555 Long index1 = (Long)indexMap.get(obj1);
2556 Long index2 = (Long)indexMap.get(obj2);
2557
2558 if (index1 == null) {
2559 index1 = fallbackIndex;
2560 }
2561 if (index2 == null) {
2562 index2 = fallbackIndex;
2563 }
2564
2565 return index1.compareTo(index2);
2566 }
2567 }
2568
2569 /**
2570 * An IndexedComparator which compares two String charsets. The comparison
2571 * follows the rules outlined in DataFlavor.selectBestTextFlavor. In order
2572 * to ensure that non-Unicode, non-ASCII, non-default charsets are sorted
2573 * in alphabetical order, charsets are not automatically converted to their
2574 * canonical forms.
2575 */
2576 public static class CharsetComparator extends IndexedComparator {
2577 private static final Map charsets;
2578 private static String defaultEncoding;
2579
2580 private static final Integer DEFAULT_CHARSET_INDEX = Integer.valueOf(2);
2581 private static final Integer OTHER_CHARSET_INDEX = Integer.valueOf(1);
2582 private static final Integer WORST_CHARSET_INDEX = Integer.valueOf(0);
2583 private static final Integer UNSUPPORTED_CHARSET_INDEX =
2584 Integer.valueOf(Integer.MIN_VALUE);
2585
2586 private static final String UNSUPPORTED_CHARSET = "UNSUPPORTED";
2587
2588 static {
2589 HashMap charsetsMap = new HashMap(8, 1.0f);
2590
2591 // we prefer Unicode charsets
2592 charsetsMap.put(canonicalName("UTF-16LE"), Integer.valueOf(4));
2593 charsetsMap.put(canonicalName("UTF-16BE"), Integer.valueOf(5));
2594 charsetsMap.put(canonicalName("UTF-8"), Integer.valueOf(6));
2595 charsetsMap.put(canonicalName("UTF-16"), Integer.valueOf(7));
2596
2597 // US-ASCII is the worst charset supported
2598 charsetsMap.put(canonicalName("US-ASCII"), WORST_CHARSET_INDEX);
2599
2600 String defEncoding = DataTransferer.canonicalName
2601 (DataTransferer.getDefaultTextCharset());
2602
2603 if (charsetsMap.get(defaultEncoding) == null) {
2604 charsetsMap.put(defaultEncoding, DEFAULT_CHARSET_INDEX);
2605 }
2606 charsetsMap.put(UNSUPPORTED_CHARSET, UNSUPPORTED_CHARSET_INDEX);
2607
2608 charsets = Collections.unmodifiableMap(charsetsMap);
2609 }
2610
2611 public CharsetComparator() {
2612 this(SELECT_BEST);
2613 }
2614
2615 public CharsetComparator(boolean order) {
2616 super(order);
2617 }
2618
2619 /**
2620 * Compares two String objects. Returns a negative integer, zero,
2621 * or a positive integer as the first charset is worse than, equal to,
2622 * or better than the second.
2623 *
2624 * @param obj1 the first charset to be compared
2625 * @param obj2 the second charset to be compared
2626 * @return a negative integer, zero, or a positive integer as the
2627 * first argument is worse, equal to, or better than the
2628 * second.
2629 * @throws ClassCastException if either of the arguments is not
2630 * instance of String
2631 * @throws NullPointerException if either of the arguments is
2632 * <code>null</code>.
2633 */
2634 public int compare(Object obj1, Object obj2) {
2635 String charset1 = null;
2636 String charset2 = null;
2637 if (order == SELECT_BEST) {
2638 charset1 = (String)obj1;
2639 charset2 = (String)obj2;
2640 } else {
2641 charset1 = (String)obj2;
2642 charset2 = (String)obj1;
2643 }
2644
2645 return compareCharsets(charset1, charset2);
2646 }
2647
2648 /**
2649 * Compares charsets. Returns a negative integer, zero, or a positive
2650 * integer as the first charset is worse than, equal to, or better than
2651 * the second.
2652 * <p>
2653 * Charsets are ordered according to the following rules:
2654 * <ul>
2655 * <li>All unsupported charsets are equal.
2656 * <li>Any unsupported charset is worse than any supported charset.
2657 * <li>Unicode charsets, such as "UTF-16", "UTF-8", "UTF-16BE" and
2658 * "UTF-16LE", are considered best.
2659 * <li>After them, platform default charset is selected.
2660 * <li>"US-ASCII" is the worst of supported charsets.
2661 * <li>For all other supported charsets, the lexicographically less
2662 * one is considered the better.
2663 * </ul>
2664 *
2665 * @param charset1 the first charset to be compared
2666 * @param charset2 the second charset to be compared.
2667 * @return a negative integer, zero, or a positive integer as the
2668 * first argument is worse, equal to, or better than the
2669 * second.
2670 */
2671 protected int compareCharsets(String charset1, String charset2) {
2672 charset1 = getEncoding(charset1);
2673 charset2 = getEncoding(charset2);
2674
2675 int comp = compareIndices(charsets, charset1, charset2,
2676 OTHER_CHARSET_INDEX);
2677
2678 if (comp == 0) {
2679 return charset2.compareTo(charset1);
2680 }
2681
2682 return comp;
2683 }
2684
2685 /**
2686 * Returns encoding for the specified charset according to the
2687 * following rules:
2688 * <ul>
2689 * <li>If the charset is <code>null</code>, then <code>null</code> will
2690 * be returned.
2691 * <li>Iff the charset specifies an encoding unsupported by this JRE,
2692 * <code>UNSUPPORTED_CHARSET</code> will be returned.
2693 * <li>If the charset specifies an alias name, the corresponding
2694 * canonical name will be returned iff the charset is a known
2695 * Unicode, ASCII, or default charset.
2696 * </ul>
2697 *
2698 * @param charset the charset.
2699 * @return an encoding for this charset.
2700 */
2701 protected static String getEncoding(String charset) {
2702 if (charset == null) {
2703 return null;
2704 } else if (!DataTransferer.isEncodingSupported(charset)) {
2705 return UNSUPPORTED_CHARSET;
2706 } else {
2707 // Only convert to canonical form if the charset is one
2708 // of the charsets explicitly listed in the known charsets
2709 // map. This will happen only for Unicode, ASCII, or default
2710 // charsets.
2711 String canonicalName = DataTransferer.canonicalName(charset);
2712 return (charsets.containsKey(canonicalName))
2713 ? canonicalName
2714 : charset;
2715 }
2716 }
2717 }
2718
2719 /**
2720 * An IndexedComparator which compares two DataFlavors. For text flavors,
2721 * the comparison follows the rules outlined in
2722 * DataFlavor.selectBestTextFlavor. For non-text flavors, unknown
2723 * application MIME types are preferred, followed by known
2724 * application/x-java-* MIME types. Unknown application types are preferred
2725 * because if the user provides his own data flavor, it will likely be the
2726 * most descriptive one. For flavors which are otherwise equal, the
2727 * flavors' native formats are compared, with greater long values
2728 * taking precedence.
2729 */
2730 public static class DataFlavorComparator extends IndexedComparator {
2731 protected final Map flavorToFormatMap;
2732
2733 private final CharsetComparator charsetComparator;
2734
2735 private static final Map exactTypes;
2736 private static final Map primaryTypes;
2737 private static final Map nonTextRepresentations;
2738 private static final Map textTypes;
2739 private static final Map decodedTextRepresentations;
2740 private static final Map encodedTextRepresentations;
2741
2742 private static final Integer UNKNOWN_OBJECT_LOSES =
2743 Integer.valueOf(Integer.MIN_VALUE);
2744 private static final Integer UNKNOWN_OBJECT_WINS =
2745 Integer.valueOf(Integer.MAX_VALUE);
2746
2747 private static final Long UNKNOWN_OBJECT_LOSES_L =
2748 Long.valueOf(Long.MIN_VALUE);
2749 private static final Long UNKNOWN_OBJECT_WINS_L =
2750 Long.valueOf(Long.MAX_VALUE);
2751
2752 static {
2753 {
2754 HashMap exactTypesMap = new HashMap(4, 1.0f);
2755
2756 // application/x-java-* MIME types
2757 exactTypesMap.put("application/x-java-file-list",
2758 Integer.valueOf(0));
2759 exactTypesMap.put("application/x-java-serialized-object",
2760 Integer.valueOf(1));
2761 exactTypesMap.put("application/x-java-jvm-local-objectref",
2762 Integer.valueOf(2));
2763 exactTypesMap.put("application/x-java-remote-object",
2764 Integer.valueOf(3));
2765
2766 exactTypes = Collections.unmodifiableMap(exactTypesMap);
2767 }
2768
2769 {
2770 HashMap primaryTypesMap = new HashMap(1, 1.0f);
2771
2772 primaryTypesMap.put("application", Integer.valueOf(0));
2773
2774 primaryTypes = Collections.unmodifiableMap(primaryTypesMap);
2775 }
2776
2777 {
2778 HashMap nonTextRepresentationsMap = new HashMap(3, 1.0f);
2779
2780 nonTextRepresentationsMap.put(java.io.InputStream.class,
2781 Integer.valueOf(0));
2782 nonTextRepresentationsMap.put(java.io.Serializable.class,
2783 Integer.valueOf(1));
2784
2785 Class<?> remoteClass = RMI.remoteClass();
2786 if (remoteClass != null) {
2787 nonTextRepresentationsMap.put(remoteClass,
2788 Integer.valueOf(2));
2789 }
2790
2791 nonTextRepresentations =
2792 Collections.unmodifiableMap(nonTextRepresentationsMap);
2793 }
2794
2795 {
2796 HashMap textTypesMap = new HashMap(16, 1.0f);
2797
2798 // plain text
2799 textTypesMap.put("text/plain", Integer.valueOf(0));
2800
2801 // stringFlavor
2802 textTypesMap.put("application/x-java-serialized-object",
2803 Integer.valueOf(1));
2804
2805 // misc
2806 textTypesMap.put("text/calendar", Integer.valueOf(2));
2807 textTypesMap.put("text/css", Integer.valueOf(3));
2808 textTypesMap.put("text/directory", Integer.valueOf(4));
2809 textTypesMap.put("text/parityfec", Integer.valueOf(5));
2810 textTypesMap.put("text/rfc822-headers", Integer.valueOf(6));
2811 textTypesMap.put("text/t140", Integer.valueOf(7));
2812 textTypesMap.put("text/tab-separated-values", Integer.valueOf(8));
2813 textTypesMap.put("text/uri-list", Integer.valueOf(9));
2814
2815 // enriched
2816 textTypesMap.put("text/richtext", Integer.valueOf(10));
2817 textTypesMap.put("text/enriched", Integer.valueOf(11));
2818 textTypesMap.put("text/rtf", Integer.valueOf(12));
2819
2820 // markup
2821 textTypesMap.put("text/html", Integer.valueOf(13));
2822 textTypesMap.put("text/xml", Integer.valueOf(14));
2823 textTypesMap.put("text/sgml", Integer.valueOf(15));
2824
2825 textTypes = Collections.unmodifiableMap(textTypesMap);
2826 }
2827
2828 {
2829 HashMap decodedTextRepresentationsMap = new HashMap(4, 1.0f);
2830
2831 decodedTextRepresentationsMap.put
2832 (DataTransferer.charArrayClass, Integer.valueOf(0));
2833 decodedTextRepresentationsMap.put
2834 (java.nio.CharBuffer.class, Integer.valueOf(1));
2835 decodedTextRepresentationsMap.put
2836 (java.lang.String.class, Integer.valueOf(2));
2837 decodedTextRepresentationsMap.put
2838 (java.io.Reader.class, Integer.valueOf(3));
2839
2840 decodedTextRepresentations =
2841 Collections.unmodifiableMap(decodedTextRepresentationsMap);
2842 }
2843
2844 {
2845 HashMap encodedTextRepresentationsMap = new HashMap(3, 1.0f);
2846
2847 encodedTextRepresentationsMap.put
2848 (DataTransferer.byteArrayClass, Integer.valueOf(0));
2849 encodedTextRepresentationsMap.put
2850 (java.nio.ByteBuffer.class, Integer.valueOf(1));
2851 encodedTextRepresentationsMap.put
2852 (java.io.InputStream.class, Integer.valueOf(2));
2853
2854 encodedTextRepresentations =
2855 Collections.unmodifiableMap(encodedTextRepresentationsMap);
2856 }
2857 }
2858
2859 public DataFlavorComparator() {
2860 this(SELECT_BEST);
2861 }
2862
2863 public DataFlavorComparator(boolean order) {
2864 super(order);
2865
2866 charsetComparator = new CharsetComparator(order);
2867 flavorToFormatMap = Collections.EMPTY_MAP;
2868 }
2869
2870 public DataFlavorComparator(Map map) {
2871 this(map, SELECT_BEST);
2872 }
2873
2874 public DataFlavorComparator(Map map, boolean order) {
2875 super(order);
2876
2877 charsetComparator = new CharsetComparator(order);
2878 HashMap hashMap = new HashMap(map.size());
2879 hashMap.putAll(map);
2880 flavorToFormatMap = Collections.unmodifiableMap(hashMap);
2881 }
2882
2883 public int compare(Object obj1, Object obj2) {
2884 DataFlavor flavor1 = null;
2885 DataFlavor flavor2 = null;
2886 if (order == SELECT_BEST) {
2887 flavor1 = (DataFlavor)obj1;
2888 flavor2 = (DataFlavor)obj2;
2889 } else {
2890 flavor1 = (DataFlavor)obj2;
2891 flavor2 = (DataFlavor)obj1;
2892 }
2893
2894 if (flavor1.equals(flavor2)) {
2895 return 0;
2896 }
2897
2898 int comp = 0;
2899
2900 String primaryType1 = flavor1.getPrimaryType();
2901 String subType1 = flavor1.getSubType();
2902 String mimeType1 = primaryType1 + "/" + subType1;
2903 Class class1 = flavor1.getRepresentationClass();
2904
2905 String primaryType2 = flavor2.getPrimaryType();
2906 String subType2 = flavor2.getSubType();
2907 String mimeType2 = primaryType2 + "/" + subType2;
2908 Class class2 = flavor2.getRepresentationClass();
2909
2910 if (flavor1.isFlavorTextType() && flavor2.isFlavorTextType()) {
2911 // First, compare MIME types
2912 comp = compareIndices(textTypes, mimeType1, mimeType2,
2913 UNKNOWN_OBJECT_LOSES);
2914 if (comp != 0) {
2915 return comp;
2916 }
2917
2918 // Only need to test one flavor because they both have the
2919 // same MIME type. Also don't need to worry about accidentally
2920 // passing stringFlavor because either
2921 // 1. Both flavors are stringFlavor, in which case the
2922 // equality test at the top of the function succeeded.
2923 // 2. Only one flavor is stringFlavor, in which case the MIME
2924 // type comparison returned a non-zero value.
2925 if (doesSubtypeSupportCharset(flavor1)) {
2926 // Next, prefer the decoded text representations of Reader,
2927 // String, CharBuffer, and [C, in that order.
2928 comp = compareIndices(decodedTextRepresentations, class1,
2929 class2, UNKNOWN_OBJECT_LOSES);
2930 if (comp != 0) {
2931 return comp;
2932 }
2933
2934 // Next, compare charsets
2935 comp = charsetComparator.compareCharsets
2936 (DataTransferer.getTextCharset(flavor1),
2937 DataTransferer.getTextCharset(flavor2));
2938 if (comp != 0) {
2939 return comp;
2940 }
2941 }
2942
2943 // Finally, prefer the encoded text representations of
2944 // InputStream, ByteBuffer, and [B, in that order.
2945 comp = compareIndices(encodedTextRepresentations, class1,
2946 class2, UNKNOWN_OBJECT_LOSES);
2947 if (comp != 0) {
2948 return comp;
2949 }
2950 } else {
2951 // First, prefer application types.
2952 comp = compareIndices(primaryTypes, primaryType1, primaryType2,
2953 UNKNOWN_OBJECT_LOSES);
2954 if (comp != 0) {
2955 return comp;
2956 }
2957
2958 // Next, look for application/x-java-* types. Prefer unknown
2959 // MIME types because if the user provides his own data flavor,
2960 // it will likely be the most descriptive one.
2961 comp = compareIndices(exactTypes, mimeType1, mimeType2,
2962 UNKNOWN_OBJECT_WINS);
2963 if (comp != 0) {
2964 return comp;
2965 }
2966
2967 // Finally, prefer the representation classes of Remote,
2968 // Serializable, and InputStream, in that order.
2969 comp = compareIndices(nonTextRepresentations, class1, class2,
2970 UNKNOWN_OBJECT_LOSES);
2971 if (comp != 0) {
2972 return comp;
2973 }
2974 }
2975
2976 // As a last resort, take the DataFlavor with the greater integer
2977 // format.
2978 return compareLongs(flavorToFormatMap, flavor1, flavor2,
2979 UNKNOWN_OBJECT_LOSES_L);
2980 }
2981 }
2982
2983 /*
2984 * Given the Map that maps objects to Integer indices and a boolean value,
2985 * this Comparator imposes a direct or reverse order on set of objects.
2986 * <p>
2987 * If the specified boolean value is SELECT_BEST, the Comparator imposes the
2988 * direct index-based order: an object A is greater than an object B if and
2989 * only if the index of A is greater than the index of B. An object that
2990 * doesn't have an associated index is less or equal than any other object.
2991 * <p>
2992 * If the specified boolean value is SELECT_WORST, the Comparator imposes the
2993 * reverse index-based order: an object A is greater than an object B if and
2994 * only if A is less than B with the direct index-based order.
2995 */
2996 public static class IndexOrderComparator extends IndexedComparator {
2997 private final Map indexMap;
2998 private static final Integer FALLBACK_INDEX =
2999 Integer.valueOf(Integer.MIN_VALUE);
3000
3001 public IndexOrderComparator(Map indexMap) {
3002 super(SELECT_BEST);
3003 this.indexMap = indexMap;
3004 }
3005
3006 public IndexOrderComparator(Map indexMap, boolean order) {
3007 super(order);
3008 this.indexMap = indexMap;
3009 }
3010
3011 public int compare(Object obj1, Object obj2) {
3012 if (order == SELECT_WORST) {
3013 return -compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX);
3014 } else {
3015 return compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX);
3016 }
3017 }
3018 }
3019
3020 /**
3021 * A class that provides access to java.rmi.Remote and java.rmi.MarshalledObject
3022 * without creating a static dependency.
3023 */
3024 private static class RMI {
3025 private static final Class<?> remoteClass = getClass("java.rmi.Remote");
3026 private static final Class<?> marshallObjectClass =
3027 getClass("java.rmi.MarshalledObject");
3028 private static final Constructor<?> marshallCtor =
3029 getConstructor(marshallObjectClass, Object.class);
3030 private static final Method marshallGet =
3031 getMethod(marshallObjectClass, "get");
3032
3033 private static Class<?> getClass(String name) {
3034 try {
3035 return Class.forName(name, true, null);
3036 } catch (ClassNotFoundException e) {
3037 return null;
3038 }
3039 }
3040
3041 private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) {
3042 try {
3043 return (c == null) ? null : c.getDeclaredConstructor(types);
3044 } catch (NoSuchMethodException x) {
3045 throw new AssertionError(x);
3046 }
3047 }
3048
3049 private static Method getMethod(Class<?> c, String name, Class<?>... types) {
3050 try {
3051 return (c == null) ? null : c.getMethod(name, types);
3052 } catch (NoSuchMethodException e) {
3053 throw new AssertionError(e);
3054 }
3055 }
3056
3057 /**
3058 * Returns {@code true} if the given class is java.rmi.Remote.
3059 */
3060 static boolean isRemote(Class<?> c) {
3061 return (remoteClass == null) ? null : remoteClass.isAssignableFrom(c);
3062 }
3063
3064 /**
3065 * Returns java.rmi.Remote.class if RMI is present; otherwise {@code null}.
3066 */
3067 static Class<?> remoteClass() {
3068 return remoteClass;
3069 }
3070
3071 /**
3072 * Returns a new MarshalledObject containing the serialized representation
3073 * of the given object.
3074 */
3075 static Object newMarshalledObject(Object obj) throws IOException {
3076 try {
3077 return marshallCtor.newInstance(obj);
3078 } catch (InstantiationException x) {
3079 throw new AssertionError(x);
3080 } catch (IllegalAccessException x) {
3081 throw new AssertionError(x);
3082 } catch (InvocationTargetException x) {
3083 Throwable cause = x.getCause();
3084 if (cause instanceof IOException)
3085 throw (IOException)cause;
3086 throw new AssertionError(x);
3087 }
3088 }
3089
3090 /**
3091 * Returns a new copy of the contained marshalled object.
3092 */
3093 static Object getMarshalledObject(Object obj)
3094 throws IOException, ClassNotFoundException
3095 {
3096 try {
3097 return marshallGet.invoke(obj);
3098 } catch (IllegalAccessException x) {
3099 throw new AssertionError(x);
3100 } catch (InvocationTargetException x) {
3101 Throwable cause = x.getCause();
3102 if (cause instanceof IOException)
3103 throw (IOException)cause;
3104 if (cause instanceof ClassNotFoundException)
3105 throw (ClassNotFoundException)cause;
3106 throw new AssertionError(x);
3107 }
3108 }
3109 }
3110 }
--- EOF ---