1 /*
2  * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.awt.datatransfer;
27 
28 import java.awt.EventQueue;
29 import java.awt.Graphics;
30 import java.awt.Image;
31 import java.awt.Toolkit;
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.StandardCharsets;
61 import java.nio.charset.UnsupportedCharsetException;
62 
63 import java.lang.reflect.Constructor;
64 import java.lang.reflect.Modifier;
65 
66 import java.security.AccessController;
67 import java.security.PrivilegedAction;
68 import java.security.PrivilegedActionException;
69 import java.security.PrivilegedExceptionAction;
70 import java.security.ProtectionDomain;
71 
72 import java.util.*;
73 
74 import sun.datatransfer.DataFlavorUtil;
75 
76 import sun.awt.AppContext;
77 import sun.awt.ComponentFactory;
78 import sun.awt.SunToolkit;
79 
80 import java.awt.image.BufferedImage;
81 import java.awt.image.ImageObserver;
82 import java.awt.image.RenderedImage;
83 import java.awt.image.WritableRaster;
84 import java.awt.image.ColorModel;
85 
86 import javax.imageio.ImageIO;
87 import javax.imageio.ImageReader;
88 import javax.imageio.ImageReadParam;
89 import javax.imageio.ImageWriter;
90 import javax.imageio.ImageTypeSpecifier;
91 
92 import javax.imageio.spi.ImageWriterSpi;
93 
94 import javax.imageio.stream.ImageInputStream;
95 import javax.imageio.stream.ImageOutputStream;
96 
97 import sun.awt.image.ImageRepresentation;
98 import sun.awt.image.ToolkitImage;
99 
100 import java.io.FilePermission;
101 import java.util.stream.Stream;
102 
103 
104 /**
105  * Provides a set of functions to be shared among the DataFlavor class and
106  * platform-specific data transfer implementations.
107  *
108  * The concept of "flavors" and "natives" is extended to include "formats",
109  * which are the numeric values Win32 and X11 use to express particular data
110  * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and
111  * getFlavorsForNatives(String[]) functions, DataTransferer provides a set
112  * of getFormatsFor(Transferable|Flavor|Flavors) and
113  * getFlavorsFor(Format|Formats) functions.
114  *
115  * Also provided are functions for translating a Transferable into a byte
116  * array, given a source DataFlavor and a target format, and for translating
117  * a byte array or InputStream into an Object, given a source format and
118  * a target DataFlavor.
119  *
120  * @author David Mendenhall
121  * @author Danila Sinopalnikov
122  *
123  * @since 1.3.1
124  */
125 public abstract class DataTransferer {
126     /**
127      * The {@code DataFlavor} representing a Java text encoding String
128      * encoded in UTF-8, where
129      * <pre>
130      *     representationClass = [B
131      *     mimeType            = "application/x-java-text-encoding"
132      * </pre>
133      */
134     public static final DataFlavor javaTextEncodingFlavor;
135 
136     /**
137      * A collection of all natives listed in flavormap.properties with
138      * a primary MIME type of "text".
139      */
140     private static final Set<Long> textNatives =
141             Collections.synchronizedSet(new HashSet<>());
142 
143     /**
144      * The native encodings/charsets for the Set of textNatives.
145      */
146     private static final Map<Long, String> nativeCharsets =
147             Collections.synchronizedMap(new HashMap<>());
148 
149     /**
150      * The end-of-line markers for the Set of textNatives.
151      */
152     private static final Map<Long, String> nativeEOLNs =
153             Collections.synchronizedMap(new HashMap<>());
154 
155     /**
156      * The number of terminating NUL bytes for the Set of textNatives.
157      */
158     private static final Map<Long, Integer> nativeTerminators =
159             Collections.synchronizedMap(new HashMap<>());
160 
161     /**
162      * The key used to store pending data conversion requests for an AppContext.
163      */
164     private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY";
165 
166     static {
167         DataFlavor tJavaTextEncodingFlavor = null;
168         try {
169             tJavaTextEncodingFlavor = new DataFlavor("application/x-java-text-encoding;class=\"[B\"");
170         } catch (ClassNotFoundException cannotHappen) {
171         }
172         javaTextEncodingFlavor = tJavaTextEncodingFlavor;
173     }
174 
175     /**
176      * The accessor method for the singleton DataTransferer instance. Note
177      * that in a headless environment, there may be no DataTransferer instance;
178      * instead, null will be returned.
179      */
getInstance()180     public static synchronized DataTransferer getInstance() {
181         return ((ComponentFactory) Toolkit.getDefaultToolkit()).getDataTransferer();
182     }
183 
184     /**
185      * Converts a FlavorMap to a FlavorTable.
186      */
adaptFlavorMap(final FlavorMap map)187     public static FlavorTable adaptFlavorMap(final FlavorMap map) {
188         if (map instanceof FlavorTable) {
189             return (FlavorTable)map;
190         }
191 
192         return new FlavorTable() {
193             @Override
194             public Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] flavors) {
195                 return map.getNativesForFlavors(flavors);
196             }
197             @Override
198             public Map<String, DataFlavor> getFlavorsForNatives(String[] natives) {
199                 return map.getFlavorsForNatives(natives);
200             }
201             @Override
202             public List<String> getNativesForFlavor(DataFlavor flav) {
203                 Map<DataFlavor, String> natives = getNativesForFlavors(new DataFlavor[]{flav});
204                 String nat = natives.get(flav);
205                 if (nat != null) {
206                     return Collections.singletonList(nat);
207                 } else {
208                     return Collections.emptyList();
209                 }
210             }
211             @Override
212             public List<DataFlavor> getFlavorsForNative(String nat) {
213                 Map<String, DataFlavor> flavors = getFlavorsForNatives(new String[]{nat});
214                 DataFlavor flavor = flavors.get(nat);
215                 if (flavor != null) {
216                     return Collections.singletonList(flavor);
217                 } else {
218                     return Collections.emptyList();
219                 }
220             }
221         };
222     }
223 
224     /**
225      * Returns the default Unicode encoding for the platform. The encoding
226      * need not be canonical. This method is only used by the archaic function
227      * DataFlavor.getTextPlainUnicodeFlavor().
228      */
229     public abstract String getDefaultUnicodeEncoding();
230 
231     /**
232      * This method is called for text flavor mappings established while parsing
233      * the flavormap.properties file. It stores the "eoln" and "terminators"
234      * parameters which are not officially part of the MIME type. They are
235      * MIME parameters specific to the flavormap.properties file format.
236      */
237     public void registerTextFlavorProperties(String nat, String charset,
238                                              String eoln, String terminators) {
239         Long format = getFormatForNativeAsLong(nat);
240 
241         textNatives.add(format);
242         nativeCharsets.put(format, (charset != null && charset.length() != 0)
243                 ? charset : Charset.defaultCharset().name());
244         if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) {
245             nativeEOLNs.put(format, eoln);
246         }
247         if (terminators != null && terminators.length() != 0) {
248             Integer iTerminators = Integer.valueOf(terminators);
249             if (iTerminators > 0) {
250                 nativeTerminators.put(format, iTerminators);
251             }
252         }
253     }
254 
255     /**
256      * Determines whether the native corresponding to the specified long format
257      * was listed in the flavormap.properties file.
258      */
259     protected boolean isTextFormat(long format) {
260         return textNatives.contains(Long.valueOf(format));
261     }
262 
263     protected String getCharsetForTextFormat(Long lFormat) {
264         return nativeCharsets.get(lFormat);
265     }
266 
267     /**
268      * Specifies whether text imported from the native system in the specified
269      * format is locale-dependent. If so, when decoding such text,
270      * 'nativeCharsets' should be ignored, and instead, the Transferable should
271      * be queried for its javaTextEncodingFlavor data for the correct encoding.
272      */
273     public abstract boolean isLocaleDependentTextFormat(long format);
274 
275     /**
276      * Determines whether the DataFlavor corresponding to the specified long
277      * format is DataFlavor.javaFileListFlavor.
278      */
279     public abstract boolean isFileFormat(long format);
280 
281     /**
282      * Determines whether the DataFlavor corresponding to the specified long
283      * format is DataFlavor.imageFlavor.
284      */
285     public abstract boolean isImageFormat(long format);
286 
287     /**
288      * Determines whether the format is a URI list we can convert to
289      * a DataFlavor.javaFileListFlavor.
290      */
291     protected boolean isURIListFormat(long format) {
292         return false;
293     }
294 
295     /**
296      * Returns a Map whose keys are all of the possible formats into which the
297      * Transferable's transfer data flavors can be translated. The value of
298      * each key is the DataFlavor in which the Transferable's data should be
299      * requested when converting to the format.
300      * <p>
301      * The map keys are sorted according to the native formats preference
302      * order.
303      */
304     public SortedMap<Long,DataFlavor> getFormatsForTransferable(Transferable contents,
305                                                                 FlavorTable map)
306     {
307         DataFlavor[] flavors = contents.getTransferDataFlavors();
308         if (flavors == null) {
309             return Collections.emptySortedMap();
310         }
311         return getFormatsForFlavors(flavors, map);
312     }
313 
314     /**
315      * Returns a Map whose keys are all of the possible formats into which data
316      * in the specified DataFlavors can be translated. The value of each key
317      * is the DataFlavor in which the Transferable's data should be requested
318      * when converting to the format.
319      * <p>
320      * The map keys are sorted according to the native formats preference
321      * order.
322      *
323      * @param flavors the data flavors
324      * @param map the FlavorTable which contains mappings between
325      *            DataFlavors and data formats
326      * @throws NullPointerException if flavors or map is {@code null}
327      */
328     public SortedMap<Long, DataFlavor> getFormatsForFlavors(DataFlavor[] flavors,
329                                                             FlavorTable map)
330     {
331         Map<Long,DataFlavor> formatMap = new HashMap<>(flavors.length);
332         Map<Long,DataFlavor> textPlainMap = new HashMap<>(flavors.length);
333         // Maps formats to indices that will be used to sort the formats
334         // according to the preference order.
335         // Larger index value corresponds to the more preferable format.
336         Map<Long, Integer> indexMap = new HashMap<>(flavors.length);
337         Map<Long, Integer> textPlainIndexMap = new HashMap<>(flavors.length);
338 
339         int currentIndex = 0;
340 
341         // Iterate backwards so that preferred DataFlavors are used over
342         // other DataFlavors. (See javadoc for
343         // Transferable.getTransferDataFlavors.)
344         for (int i = flavors.length - 1; i >= 0; i--) {
345             DataFlavor flavor = flavors[i];
346             if (flavor == null) continue;
347 
348             // Don't explicitly test for String, since it is just a special
349             // case of Serializable
350             if (flavor.isFlavorTextType() ||
351                 flavor.isFlavorJavaFileListType() ||
352                 DataFlavor.imageFlavor.equals(flavor) ||
353                 flavor.isRepresentationClassSerializable() ||
354                 flavor.isRepresentationClassInputStream() ||
355                 flavor.isRepresentationClassRemote())
356             {
357                 List<String> natives = map.getNativesForFlavor(flavor);
358 
359                 currentIndex += natives.size();
360 
361                 for (String aNative : natives) {
362                     Long lFormat = getFormatForNativeAsLong(aNative);
363                     Integer index = currentIndex--;
364 
365                     formatMap.put(lFormat, flavor);
366                     indexMap.put(lFormat, index);
367 
368                     // SystemFlavorMap.getNativesForFlavor will return
369                     // text/plain natives for all text/*. While this is good
370                     // for a single text/* flavor, we would prefer that
371                     // text/plain native data come from a text/plain flavor.
372                     if (("text".equals(flavor.getPrimaryType()) &&
373                             "plain".equals(flavor.getSubType())) ||
374                             flavor.equals(DataFlavor.stringFlavor)) {
375                         textPlainMap.put(lFormat, flavor);
376                         textPlainIndexMap.put(lFormat, index);
377                     }
378                 }
379 
380                 currentIndex += natives.size();
381             }
382         }
383 
384         formatMap.putAll(textPlainMap);
385         indexMap.putAll(textPlainIndexMap);
386 
387         // Sort the map keys according to the formats preference order.
388         Comparator<Long> comparator = DataFlavorUtil.getIndexOrderComparator(indexMap).reversed();
389         SortedMap<Long, DataFlavor> sortedMap = new TreeMap<>(comparator);
390         sortedMap.putAll(formatMap);
391 
392         return sortedMap;
393     }
394 
395     /**
396      * Reduces the Map output for the root function to an array of the
397      * Map's keys.
398      */
399     public long[] getFormatsForTransferableAsArray(Transferable contents,
400                                                    FlavorTable map) {
401         return keysToLongArray(getFormatsForTransferable(contents, map));
402     }
403 
404     /**
405      * Returns a Map whose keys are all of the possible DataFlavors into which
406      * data in the specified formats can be translated. The value of each key
407      * is the format in which the Clipboard or dropped data should be requested
408      * when converting to the DataFlavor.
409      */
410     public Map<DataFlavor, Long> getFlavorsForFormats(long[] formats, FlavorTable map) {
411         Map<DataFlavor, Long> flavorMap = new HashMap<>(formats.length);
412         Set<AbstractMap.SimpleEntry<Long, DataFlavor>> mappingSet = new HashSet<>(formats.length);
413         Set<DataFlavor> flavorSet = new HashSet<>(formats.length);
414 
415         // First step: build flavorSet, mappingSet and initial flavorMap
416         // flavorSet  - the set of all the DataFlavors into which
417         //              data in the specified formats can be translated;
418         // mappingSet - the set of all the mappings from the specified formats
419         //              into any DataFlavor;
420         // flavorMap  - after this step, this map maps each of the DataFlavors
421         //              from flavorSet to any of the specified formats.
422         for (long format : formats) {
423             String nat = getNativeForFormat(format);
424             List<DataFlavor> flavors = map.getFlavorsForNative(nat);
425             for (DataFlavor flavor : flavors) {
426                 // Don't explicitly test for String, since it is just a special
427                 // case of Serializable
428                 if (flavor.isFlavorTextType() ||
429                         flavor.isFlavorJavaFileListType() ||
430                         DataFlavor.imageFlavor.equals(flavor) ||
431                         flavor.isRepresentationClassSerializable() ||
432                         flavor.isRepresentationClassInputStream() ||
433                         flavor.isRepresentationClassRemote()) {
434 
435                     AbstractMap.SimpleEntry<Long, DataFlavor> mapping =
436                             new AbstractMap.SimpleEntry<>(format, flavor);
437                     flavorMap.put(flavor, format);
438                     mappingSet.add(mapping);
439                     flavorSet.add(flavor);
440                 }
441             }
442         }
443 
444         // Second step: for each DataFlavor try to figure out which of the
445         // specified formats is the best to translate to this flavor.
446         // Then map each flavor to the best format.
447         // For the given flavor, FlavorTable indicates which native will
448         // best reflect data in the specified flavor to the underlying native
449         // platform. We assume that this native is the best to translate
450         // to this flavor.
451         // Note: FlavorTable allows one-way mappings, so we can occasionally
452         // map a flavor to the format for which the corresponding
453         // format-to-flavor mapping doesn't exist. For this reason we have built
454         // a mappingSet of all format-to-flavor mappings for the specified formats
455         // and check if the format-to-flavor mapping exists for the
456         // (flavor,format) pair being added.
457         for (DataFlavor flavor : flavorSet) {
458             List<String> natives = map.getNativesForFlavor(flavor);
459             for (String aNative : natives) {
460                 Long lFormat = getFormatForNativeAsLong(aNative);
461                 if (mappingSet.contains(new AbstractMap.SimpleEntry<>(lFormat, flavor))) {
462                     flavorMap.put(flavor, lFormat);
463                     break;
464                 }
465             }
466         }
467 
468         return flavorMap;
469     }
470 
471     /**
472      * Returns a Set of all DataFlavors for which
473      * 1) a mapping from at least one of the specified formats exists in the
474      * specified map and
475      * 2) the data translation for this mapping can be performed by the data
476      * transfer subsystem.
477      *
478      * @param formats the data formats
479      * @param map the FlavorTable which contains mappings between
480      *            DataFlavors and data formats
481      * @throws NullPointerException if formats or map is {@code null}
482      */
483     public Set<DataFlavor> getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) {
484         Set<DataFlavor> flavorSet = new HashSet<>(formats.length);
485 
486         for (long format : formats) {
487             List<DataFlavor> flavors = map.getFlavorsForNative(getNativeForFormat(format));
488             for (DataFlavor flavor : flavors) {
489                 // Don't explicitly test for String, since it is just a special
490                 // case of Serializable
491                 if (flavor.isFlavorTextType() ||
492                         flavor.isFlavorJavaFileListType() ||
493                         DataFlavor.imageFlavor.equals(flavor) ||
494                         flavor.isRepresentationClassSerializable() ||
495                         flavor.isRepresentationClassInputStream() ||
496                         flavor.isRepresentationClassRemote()) {
497                     flavorSet.add(flavor);
498                 }
499             }
500         }
501 
502         return flavorSet;
503     }
504 
505     /**
506      * Returns an array of all DataFlavors for which
507      * 1) a mapping from at least one of the specified formats exists in the
508      * specified map and
509      * 2) the data translation for this mapping can be performed by the data
510      * transfer subsystem.
511      * The array will be sorted according to a
512      * {@code DataFlavorComparator} created with the specified
513      * map as an argument.
514      *
515      * @param formats the data formats
516      * @param map the FlavorTable which contains mappings between
517      *            DataFlavors and data formats
518      * @throws NullPointerException if formats or map is {@code null}
519      */
520     public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats,
521                                                     FlavorTable map) {
522         // getFlavorsForFormatsAsSet() is less expensive than
523         // getFlavorsForFormats().
524         return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map));
525     }
526 
527     /**
528      * Looks-up or registers the String native with the native data transfer
529      * system and returns a long format corresponding to that native.
530      */
531     protected abstract Long getFormatForNativeAsLong(String str);
532 
533     /**
534      * Looks-up the String native corresponding to the specified long format in
535      * the native data transfer system.
536      */
537     protected abstract String getNativeForFormat(long format);
538 
539     /* Contains common code for finding the best charset for
540      * clipboard string encoding/decoding, basing on clipboard
541      * format and localeTransferable(on decoding, if available)
542      */
543     protected String getBestCharsetForTextFormat(Long lFormat,
544         Transferable localeTransferable) throws IOException
545     {
546         String charset = null;
547         if (localeTransferable != null &&
548             isLocaleDependentTextFormat(lFormat) &&
549             localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) {
550             try {
551                 byte[] charsetNameBytes = (byte[])localeTransferable
552                         .getTransferData(javaTextEncodingFlavor);
553                 charset = new String(charsetNameBytes, StandardCharsets.UTF_8);
554             } catch (UnsupportedFlavorException cannotHappen) {
555             }
556         } else {
557             charset = getCharsetForTextFormat(lFormat);
558         }
559         if (charset == null) {
560             // Only happens when we have a custom text type.
561             charset = Charset.defaultCharset().name();
562         }
563         return charset;
564     }
565 
566     /**
567      *  Translation function for converting string into
568      *  a byte array. Search-and-replace EOLN. Encode into the
569      *  target format. Append terminating NUL bytes.
570      *
571      *  Java to Native string conversion
572      */
573     private byte[] translateTransferableString(String str,
574                                                long format) throws IOException
575     {
576         Long lFormat = format;
577         String charset = getBestCharsetForTextFormat(lFormat, null);
578         // Search and replace EOLN. Note that if EOLN is "\n", then we
579         // never added an entry to nativeEOLNs anyway, so we'll skip this
580         // code altogether.
581         // windows: "abc\nde"->"abc\r\nde"
582         String eoln = nativeEOLNs.get(lFormat);
583         if (eoln != null) {
584             int length = str.length();
585             StringBuilder buffer = new StringBuilder(length * 2); // 2 is a heuristic
586             for (int i = 0; i < length; i++) {
587                 // Fix for 4914613 - skip native EOLN
588                 if (str.startsWith(eoln, i)) {
589                     buffer.append(eoln);
590                     i += eoln.length() - 1;
591                     continue;
592                 }
593                 char c = str.charAt(i);
594                 if (c == '\n') {
595                     buffer.append(eoln);
596                 } else {
597                     buffer.append(c);
598                 }
599             }
600             str = buffer.toString();
601         }
602 
603         // Encode text in target format.
604         byte[] bytes = str.getBytes(charset);
605 
606         // Append terminating NUL bytes. Note that if terminators is 0,
607         // the we never added an entry to nativeTerminators anyway, so
608         // we'll skip code altogether.
609         // "abcde" -> "abcde\0"
610         Integer terminators = nativeTerminators.get(lFormat);
611         if (terminators != null) {
612             int numTerminators = terminators;
613             byte[] terminatedBytes =
614                 new byte[bytes.length + numTerminators];
615             System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length);
616             for (int i = bytes.length; i < terminatedBytes.length; i++) {
617                 terminatedBytes[i] = 0x0;
618             }
619             bytes = terminatedBytes;
620         }
621         return bytes;
622     }
623 
624     /**
625      * Translating either a byte array or an InputStream into an String.
626      * Strip terminators and search-and-replace EOLN.
627      *
628      * Native to Java string conversion
629      */
630     private String translateBytesToString(byte[] bytes, long format,
631                                           Transferable localeTransferable)
632             throws IOException
633     {
634 
635         Long lFormat = format;
636         String charset = getBestCharsetForTextFormat(lFormat, localeTransferable);
637 
638         // Locate terminating NUL bytes. Note that if terminators is 0,
639         // the we never added an entry to nativeTerminators anyway, so
640         // we'll skip code altogether.
641 
642         // In other words: we are doing char alignment here basing on suggestion
643         // that count of zero-'terminators' is a number of bytes in one symbol
644         // for selected charset (clipboard format). It is not complitly true for
645         // multibyte coding like UTF-8, but helps understand the procedure.
646         // "abcde\0" -> "abcde"
647 
648         String eoln = nativeEOLNs.get(lFormat);
649         Integer terminators = nativeTerminators.get(lFormat);
650         int count;
651         if (terminators != null) {
652             int numTerminators = terminators;
653 search:
654             for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) {
655                 for (int i = count; i < count + numTerminators; i++) {
656                     if (bytes[i] != 0x0) {
657                         continue search;
658                     }
659                 }
660                 // found terminators
661                 break search;
662             }
663         } else {
664             count = bytes.length;
665         }
666 
667         // Decode text to chars. Don't include any terminators.
668         String converted = new String(bytes, 0, count, charset);
669 
670         // Search and replace EOLN. Note that if EOLN is "\n", then we
671         // never added an entry to nativeEOLNs anyway, so we'll skip this
672         // code altogether.
673         // Count of NUL-terminators and EOLN coding are platform-specific and
674         // loaded from flavormap.properties file
675         // windows: "abc\r\nde" -> "abc\nde"
676 
677         if (eoln != null) {
678 
679             /* Fix for 4463560: replace EOLNs symbol-by-symbol instead
680              * of using buf.replace()
681              */
682 
683             char[] buf = converted.toCharArray();
684             char[] eoln_arr = eoln.toCharArray();
685             int j = 0;
686             boolean match;
687 
688             for (int i = 0; i < buf.length; ) {
689                 // Catch last few bytes
690                 if (i + eoln_arr.length > buf.length) {
691                     buf[j++] = buf[i++];
692                     continue;
693                 }
694 
695                 match = true;
696                 for (int k = 0, l = i; k < eoln_arr.length; k++, l++) {
697                     if (eoln_arr[k] != buf[l]) {
698                         match = false;
699                         break;
700                     }
701                 }
702                 if (match) {
703                     buf[j++] = '\n';
704                     i += eoln_arr.length;
705                 } else {
706                     buf[j++] = buf[i++];
707                 }
708             }
709             converted = new String(buf, 0, j);
710         }
711 
712         return converted;
713     }
714 
715 
716     /**
717      * Primary translation function for translating a Transferable into
718      * a byte array, given a source DataFlavor and target format.
719      */
720     @SuppressWarnings("deprecation")
721     public byte[] translateTransferable(Transferable contents,
722                                         DataFlavor flavor,
723                                         long format) throws IOException
724     {
725         // Obtain the transfer data in the source DataFlavor.
726         //
727         // Note that we special case DataFlavor.plainTextFlavor because
728         // StringSelection supports this flavor incorrectly -- instead of
729         // returning an InputStream as the DataFlavor representation class
730         // states, it returns a Reader. Instead of using this broken
731         // functionality, we request the data in stringFlavor (the other
732         // DataFlavor which StringSelection supports) and use the String
733         // translator.
734         Object obj;
735         boolean stringSelectionHack;
736         try {
737             obj = contents.getTransferData(flavor);
738             if (obj == null) {
739                 return null;
740             }
741             if (flavor.equals(DataFlavor.plainTextFlavor) &&
742                 !(obj instanceof InputStream))
743             {
744                 obj = contents.getTransferData(DataFlavor.stringFlavor);
745                 if (obj == null) {
746                     return null;
747                 }
748                 stringSelectionHack = true;
749             } else {
750                 stringSelectionHack = false;
751             }
752         } catch (UnsupportedFlavorException e) {
753             throw new IOException(e.getMessage());
754         }
755 
756         // Source data is a String. Search-and-replace EOLN. Encode into the
757         // target format. Append terminating NUL bytes.
758         if (stringSelectionHack ||
759             (String.class.equals(flavor.getRepresentationClass()) &&
760              DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
761 
762             String str = removeSuspectedData(flavor, contents, (String)obj);
763 
764             return translateTransferableString(
765                 str,
766                 format);
767 
768         // Source data is a Reader. Convert to a String and recur. In the
769         // future, we may want to rewrite this so that we encode on demand.
770         } else if (flavor.isRepresentationClassReader()) {
771             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
772                 throw new IOException
773                     ("cannot transfer non-text data as Reader");
774             }
775 
776             StringBuilder buf = new StringBuilder();
777             try (Reader r = (Reader)obj) {
778                 int c;
779                 while ((c = r.read()) != -1) {
780                     buf.append((char)c);
781                 }
782             }
783 
784             return translateTransferableString(
785                 buf.toString(),
786                 format);
787 
788         // Source data is a CharBuffer. Convert to a String and recur.
789         } else if (flavor.isRepresentationClassCharBuffer()) {
790             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
791                 throw new IOException
792                     ("cannot transfer non-text data as CharBuffer");
793             }
794 
795             CharBuffer buffer = (CharBuffer)obj;
796             int size = buffer.remaining();
797             char[] chars = new char[size];
798             buffer.get(chars, 0, size);
799 
800             return translateTransferableString(
801                 new String(chars),
802                 format);
803 
804         // Source data is a char array. Convert to a String and recur.
805         } else if (char[].class.equals(flavor.getRepresentationClass())) {
806             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
807                 throw new IOException
808                     ("cannot transfer non-text data as char array");
809             }
810 
811             return translateTransferableString(
812                 new String((char[])obj),
813                 format);
814 
815         // Source data is a ByteBuffer. For arbitrary flavors, simply return
816         // the array. For text flavors, decode back to a String and recur to
817         // reencode according to the requested format.
818         } else if (flavor.isRepresentationClassByteBuffer()) {
819             ByteBuffer buffer = (ByteBuffer)obj;
820             int size = buffer.remaining();
821             byte[] bytes = new byte[size];
822             buffer.get(bytes, 0, size);
823 
824             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
825                 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
826                 return translateTransferableString(
827                     new String(bytes, sourceEncoding),
828                     format);
829             } else {
830                 return bytes;
831             }
832 
833         // Source data is a byte array. For arbitrary flavors, simply return
834         // the array. For text flavors, decode back to a String and recur to
835         // reencode according to the requested format.
836         } else if (byte[].class.equals(flavor.getRepresentationClass())) {
837             byte[] bytes = (byte[])obj;
838 
839             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
840                 String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
841                 return translateTransferableString(
842                     new String(bytes, sourceEncoding),
843                     format);
844             } else {
845                 return bytes;
846             }
847         // Source data is Image
848         } else if (DataFlavor.imageFlavor.equals(flavor)) {
849             if (!isImageFormat(format)) {
850                 throw new IOException("Data translation failed: " +
851                                       "not an image format");
852             }
853 
854             Image image = (Image)obj;
855             byte[] bytes = imageToPlatformBytes(image, format);
856 
857             if (bytes == null) {
858                 throw new IOException("Data translation failed: " +
859                     "cannot convert java image to native format");
860             }
861             return bytes;
862         }
863 
864         byte[] theByteArray = null;
865 
866         // Target data is a file list. Source data must be a
867         // java.util.List which contains java.io.File or String instances.
868         if (isFileFormat(format)) {
869             if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
870                 throw new IOException("data translation failed");
871             }
872 
873             final List<?> list = (List<?>)obj;
874 
875             final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
876 
877             final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);
878 
879             try (ByteArrayOutputStream bos = convertFileListToBytes(fileList)) {
880                 theByteArray = bos.toByteArray();
881             }
882 
883         // Target data is a URI list. Source data must be a
884         // java.util.List which contains java.io.File or String instances.
885         } else if (isURIListFormat(format)) {
886             if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
887                 throw new IOException("data translation failed");
888             }
889             String nat = getNativeForFormat(format);
890             String targetCharset = null;
891             if (nat != null) {
892                 try {
893                     targetCharset = new DataFlavor(nat).getParameter("charset");
894                 } catch (ClassNotFoundException cnfe) {
895                     throw new IOException(cnfe);
896                 }
897             }
898             if (targetCharset == null) {
899                 targetCharset = "UTF-8";
900             }
901             final List<?> list = (List<?>)obj;
902             final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
903             final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);
904             final ArrayList<String> uriList = new ArrayList<>(fileList.size());
905             for (String fileObject : fileList) {
906                 final URI uri = new File(fileObject).toURI();
907                 // Some implementations are fussy about the number of slashes (file:///path/to/file is best)
908                 try {
909                     uriList.add(new URI(uri.getScheme(), "", uri.getPath(), uri.getFragment()).toString());
910                 } catch (URISyntaxException uriSyntaxException) {
911                     throw new IOException(uriSyntaxException);
912                   }
913               }
914 
915             byte[] eoln = "\r\n".getBytes(targetCharset);
916 
917             try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
918                 for (String uri : uriList) {
919                     byte[] bytes = uri.getBytes(targetCharset);
920                     bos.write(bytes, 0, bytes.length);
921                     bos.write(eoln, 0, eoln.length);
922                 }
923                 theByteArray = bos.toByteArray();
924             }
925 
926         // Source data is an InputStream. For arbitrary flavors, just grab the
927         // bytes and dump them into a byte array. For text flavors, decode back
928         // to a String and recur to reencode according to the requested format.
929         } else if (flavor.isRepresentationClassInputStream()) {
930 
931             // Workaround to JDK-8024061: Exception thrown when drag and drop
932             //      between two components is executed quickly.
933             // and JDK-8065098:  JColorChooser no longer supports drag and drop
934             //      between two JVM instances
935             if (!(obj instanceof InputStream)) {
936                 return new byte[0];
937             }
938 
939             try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
940                 try (InputStream is = (InputStream)obj) {
941                     is.mark(Integer.MAX_VALUE);
942                     is.transferTo(bos);
943                     is.reset();
944                 }
945 
946                 if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
947                     byte[] bytes = bos.toByteArray();
948                     String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
949                     return translateTransferableString(
950                                new String(bytes, sourceEncoding),
951                                format);
952                 }
953                 theByteArray = bos.toByteArray();
954             }
955 
956 
957         // Source data is an RMI object
958         } else if (flavor.isRepresentationClassRemote()) {
959             theByteArray = convertObjectToBytes(DataFlavorUtil.RMI.newMarshalledObject(obj));
960 
961         // Source data is Serializable
962         } else if (flavor.isRepresentationClassSerializable()) {
963 
964             theByteArray = convertObjectToBytes(obj);
965 
966         } else {
967             throw new IOException("data translation failed");
968         }
969 
970 
971 
972         return theByteArray;
973     }
974 
975     private static byte[] convertObjectToBytes(Object object) throws IOException {
976         try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
977              ObjectOutputStream oos = new ObjectOutputStream(bos))
978         {
979             oos.writeObject(object);
980             return bos.toByteArray();
981         }
982     }
983 
984     protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException;
985 
986     private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str)
987             throws IOException
988     {
989         if (null == System.getSecurityManager()
990             || !flavor.isMimeTypeEqual("text/uri-list"))
991         {
992             return str;
993         }
994 
995         final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
996 
997         try {
998             return AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> {
999 
1000                 StringBuilder allowedFiles = new StringBuilder(str.length());
1001                 String [] uriArray = str.split("(\\s)+");
1002 
1003                 for (String fileName : uriArray)
1004                 {
1005                     File file = new File(fileName);
1006                     if (file.exists() &&
1007                         !(isFileInWebstartedCache(file) ||
1008                         isForbiddenToRead(file, userProtectionDomain)))
1009                     {
1010                         if (0 != allowedFiles.length())
1011                         {
1012                             allowedFiles.append("\\r\\n");
1013                         }
1014 
1015                         allowedFiles.append(fileName);
1016                     }
1017                 }
1018 
1019                 return allowedFiles.toString();
1020             });
1021         } catch (PrivilegedActionException pae) {
1022             throw new IOException(pae.getMessage(), pae);
1023         }
1024     }
1025 
1026     private static ProtectionDomain getUserProtectionDomain(Transferable contents) {
1027         return contents.getClass().getProtectionDomain();
1028     }
1029 
1030     private boolean isForbiddenToRead (File file, ProtectionDomain protectionDomain)
1031     {
1032         if (null == protectionDomain) {
1033             return false;
1034         }
1035         try {
1036             FilePermission filePermission =
1037                     new FilePermission(file.getCanonicalPath(), "read, delete");
1038             if (protectionDomain.implies(filePermission)) {
1039                 return false;
1040             }
1041         } catch (IOException e) {}
1042 
1043         return true;
1044     }
1045 
1046     private ArrayList<String> castToFiles(final List<?> files,
1047                                           final ProtectionDomain userProtectionDomain) throws IOException {
1048         try {
1049             return AccessController.doPrivileged((PrivilegedExceptionAction<ArrayList<String>>) () -> {
1050                 ArrayList<String> fileList = new ArrayList<>();
1051                 for (Object fileObject : files)
1052                 {
1053                     File file = castToFile(fileObject);
1054                     if (file != null &&
1055                         (null == System.getSecurityManager() ||
1056                         !(isFileInWebstartedCache(file) ||
1057                         isForbiddenToRead(file, userProtectionDomain))))
1058                     {
1059                         fileList.add(file.getCanonicalPath());
1060                     }
1061                 }
1062                 return fileList;
1063             });
1064         } catch (PrivilegedActionException pae) {
1065             throw new IOException(pae.getMessage());
1066         }
1067     }
1068 
1069     // It is important do not use user's successors
1070     // of File class.
1071     private File castToFile(Object fileObject) throws IOException {
1072         String filePath = null;
1073         if (fileObject instanceof File) {
1074             filePath = ((File)fileObject).getCanonicalPath();
1075         } else if (fileObject instanceof String) {
1076            filePath = (String) fileObject;
1077         } else {
1078            return null;
1079         }
1080         return new File(filePath);
1081     }
1082 
1083     private static final String[] DEPLOYMENT_CACHE_PROPERTIES = {
1084         "deployment.system.cachedir",
1085         "deployment.user.cachedir",
1086         "deployment.javaws.cachedir",
1087         "deployment.javapi.cachedir"
1088     };
1089 
1090     private static final ArrayList <File> deploymentCacheDirectoryList = new ArrayList<>();
1091 
1092     private static boolean isFileInWebstartedCache(File f) {
1093 
1094         if (deploymentCacheDirectoryList.isEmpty()) {
1095             for (String cacheDirectoryProperty : DEPLOYMENT_CACHE_PROPERTIES) {
1096                 String cacheDirectoryPath = System.getProperty(cacheDirectoryProperty);
1097                 if (cacheDirectoryPath != null) {
1098                     try {
1099                         File cacheDirectory = (new File(cacheDirectoryPath)).getCanonicalFile();
1100                         if (cacheDirectory != null) {
1101                             deploymentCacheDirectoryList.add(cacheDirectory);
1102                         }
1103                     } catch (IOException ioe) {}
1104                 }
1105             }
1106         }
1107 
1108         for (File deploymentCacheDirectory : deploymentCacheDirectoryList) {
1109             for (File dir = f; dir != null; dir = dir.getParentFile()) {
1110                 if (dir.equals(deploymentCacheDirectory)) {
1111                     return true;
1112                 }
1113             }
1114         }
1115 
1116         return false;
1117     }
1118 
1119 
1120     public Object translateBytes(byte[] bytes, DataFlavor flavor,
1121                                  long format, Transferable localeTransferable)
1122         throws IOException
1123     {
1124 
1125         Object theObject = null;
1126 
1127         // Source data is a file list. Use the dragQueryFile native function to
1128         // do most of the decoding. Then wrap File objects around the String
1129         // filenames and return a List.
1130         if (isFileFormat(format)) {
1131             if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
1132                 throw new IOException("data translation failed");
1133             }
1134             String[] filenames = dragQueryFile(bytes);
1135             if (filenames == null) {
1136                 return null;
1137             }
1138 
1139             // Convert the strings to File objects
1140             File[] files = new File[filenames.length];
1141             for (int i = 0; i < filenames.length; i++) {
1142                 files[i] = new File(filenames[i]);
1143             }
1144 
1145             // Turn the list of Files into a List and return
1146             theObject = Arrays.asList(files);
1147 
1148             // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor
1149             // where possible.
1150         } else if (isURIListFormat(format)
1151                     && DataFlavor.javaFileListFlavor.equals(flavor)) {
1152 
1153             try (ByteArrayInputStream str = new ByteArrayInputStream(bytes))  {
1154 
1155                 URI[] uris = dragQueryURIs(str, format, localeTransferable);
1156                 if (uris == null) {
1157                     return null;
1158                 }
1159                 List<File> files = new ArrayList<>();
1160                 for (URI uri : uris) {
1161                     try {
1162                         files.add(new File(uri));
1163                     } catch (IllegalArgumentException illegalArg) {
1164                         // When converting from URIs to less generic files,
1165                         // common practice (Wine, SWT) seems to be to
1166                         // silently drop the URIs that aren't local files.
1167                     }
1168                 }
1169                 theObject = files;
1170             }
1171 
1172             // Target data is a String. Strip terminating NUL bytes. Decode bytes
1173             // into characters. Search-and-replace EOLN.
1174         } else if (String.class.equals(flavor.getRepresentationClass()) &&
1175                    DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1176 
1177             theObject = translateBytesToString(bytes, format, localeTransferable);
1178 
1179             // Target data is a Reader. Obtain data in InputStream format, encoded
1180             // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
1181             // back to chars on demand.
1182         } else if (flavor.isRepresentationClassReader()) {
1183             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1184                 theObject = translateStream(bais,
1185                         flavor, format, localeTransferable);
1186             }
1187             // Target data is a CharBuffer. Recur to obtain String and wrap.
1188         } else if (flavor.isRepresentationClassCharBuffer()) {
1189             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1190                 throw new IOException("cannot transfer non-text data as CharBuffer");
1191             }
1192 
1193             CharBuffer buffer = CharBuffer.wrap(
1194                 translateBytesToString(bytes,format, localeTransferable));
1195 
1196             theObject = constructFlavoredObject(buffer, flavor, CharBuffer.class);
1197 
1198             // Target data is a char array. Recur to obtain String and convert to
1199             // char array.
1200         } else if (char[].class.equals(flavor.getRepresentationClass())) {
1201             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1202                 throw new IOException
1203                           ("cannot transfer non-text data as char array");
1204             }
1205 
1206             theObject = translateBytesToString(
1207                 bytes, format, localeTransferable).toCharArray();
1208 
1209             // Target data is a ByteBuffer. For arbitrary flavors, just return
1210             // the raw bytes. For text flavors, convert to a String to strip
1211             // terminators and search-and-replace EOLN, then reencode according to
1212             // the requested flavor.
1213         } else if (flavor.isRepresentationClassByteBuffer()) {
1214             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1215                 bytes = translateBytesToString(
1216                     bytes, format, localeTransferable).getBytes(
1217                         DataFlavorUtil.getTextCharset(flavor)
1218                     );
1219             }
1220 
1221             ByteBuffer buffer = ByteBuffer.wrap(bytes);
1222             theObject = constructFlavoredObject(buffer, flavor, ByteBuffer.class);
1223 
1224             // Target data is a byte array. For arbitrary flavors, just return
1225             // the raw bytes. For text flavors, convert to a String to strip
1226             // terminators and search-and-replace EOLN, then reencode according to
1227             // the requested flavor.
1228         } else if (byte[].class.equals(flavor.getRepresentationClass())) {
1229             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1230                 theObject = translateBytesToString(
1231                     bytes, format, localeTransferable
1232                 ).getBytes(DataFlavorUtil.getTextCharset(flavor));
1233             } else {
1234                 theObject = bytes;
1235             }
1236 
1237             // Target data is an InputStream. For arbitrary flavors, just return
1238             // the raw bytes. For text flavors, decode to strip terminators and
1239             // search-and-replace EOLN, then reencode according to the requested
1240             // flavor.
1241         } else if (flavor.isRepresentationClassInputStream()) {
1242 
1243             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1244                 theObject = translateStream(bais, flavor, format, localeTransferable);
1245             }
1246 
1247         } else if (flavor.isRepresentationClassRemote()) {
1248             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
1249                  ObjectInputStream ois = new ObjectInputStream(bais)) {
1250 
1251                 theObject = DataFlavorUtil.RMI.getMarshalledObject(ois.readObject());
1252             } catch (Exception e) {
1253                 throw new IOException(e.getMessage());
1254             }
1255 
1256             // Target data is Serializable
1257         } else if (flavor.isRepresentationClassSerializable()) {
1258 
1259             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1260                 theObject = translateStream(bais, flavor, format, localeTransferable);
1261             }
1262 
1263             // Target data is Image
1264         } else if (DataFlavor.imageFlavor.equals(flavor)) {
1265             if (!isImageFormat(format)) {
1266                 throw new IOException("data translation failed");
1267             }
1268 
1269             theObject = platformImageBytesToImage(bytes, format);
1270         }
1271 
1272         if (theObject == null) {
1273             throw new IOException("data translation failed");
1274         }
1275 
1276         return theObject;
1277 
1278     }
1279 
1280     /**
1281      * Primary translation function for translating
1282      * an InputStream into an Object, given a source format and a target
1283      * DataFlavor.
1284      */
1285     @SuppressWarnings("deprecation")
1286     public Object translateStream(InputStream str, DataFlavor flavor,
1287                                   long format, Transferable localeTransferable)
1288         throws IOException
1289     {
1290 
1291         Object theObject = null;
1292         // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor
1293         // where possible.
1294         if (isURIListFormat(format)
1295                 && DataFlavor.javaFileListFlavor.equals(flavor))
1296         {
1297 
1298             URI[] uris = dragQueryURIs(str, format, localeTransferable);
1299             if (uris == null) {
1300                 return null;
1301             }
1302             List<File> files = new ArrayList<>();
1303             for (URI uri : uris) {
1304                 try {
1305                     files.add(new File(uri));
1306                 } catch (IllegalArgumentException illegalArg) {
1307                     // When converting from URIs to less generic files,
1308                     // common practice (Wine, SWT) seems to be to
1309                     // silently drop the URIs that aren't local files.
1310                 }
1311             }
1312             theObject = files;
1313 
1314         // Target data is a String. Strip terminating NUL bytes. Decode bytes
1315         // into characters. Search-and-replace EOLN.
1316         } else if (String.class.equals(flavor.getRepresentationClass()) &&
1317                    DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1318 
1319             return translateBytesToString(inputStreamToByteArray(str),
1320                 format, localeTransferable);
1321 
1322             // Special hack to maintain backwards-compatibility with the brokenness
1323             // of StringSelection. Return a StringReader instead of an InputStream.
1324             // Recur to obtain String and encapsulate.
1325         } else if (DataFlavor.plainTextFlavor.equals(flavor)) {
1326             theObject = new StringReader(translateBytesToString(
1327                 inputStreamToByteArray(str),
1328                 format, localeTransferable));
1329 
1330             // Target data is an InputStream. For arbitrary flavors, just return
1331             // the raw bytes. For text flavors, decode to strip terminators and
1332             // search-and-replace EOLN, then reencode according to the requested
1333             // flavor.
1334         } else if (flavor.isRepresentationClassInputStream()) {
1335             theObject = translateStreamToInputStream(str, flavor, format,
1336                                                                localeTransferable);
1337 
1338             // Target data is a Reader. Obtain data in InputStream format, encoded
1339             // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
1340             // back to chars on demand.
1341         } else if (flavor.isRepresentationClassReader()) {
1342             if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1343                 throw new IOException
1344                           ("cannot transfer non-text data as Reader");
1345             }
1346 
1347             InputStream is = (InputStream)translateStreamToInputStream(
1348                     str, DataFlavor.plainTextFlavor,
1349                     format, localeTransferable);
1350 
1351             String unicode = DataFlavorUtil.getTextCharset(DataFlavor.plainTextFlavor);
1352 
1353             Reader reader = new InputStreamReader(is, unicode);
1354 
1355             theObject = constructFlavoredObject(reader, flavor, Reader.class);
1356             // Target data is a byte array
1357         } else if (byte[].class.equals(flavor.getRepresentationClass())) {
1358             if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1359                 theObject = translateBytesToString(inputStreamToByteArray(str), format, localeTransferable)
1360                         .getBytes(DataFlavorUtil.getTextCharset(flavor));
1361             } else {
1362                 theObject = inputStreamToByteArray(str);
1363             }
1364             // Target data is an RMI object
1365         } else if (flavor.isRepresentationClassRemote()) {
1366             try (ObjectInputStream ois = new ObjectInputStream(str)) {
1367                 theObject = DataFlavorUtil.RMI.getMarshalledObject(ois.readObject());
1368             } catch (Exception e) {
1369                 throw new IOException(e.getMessage());
1370             }
1371 
1372             // Target data is Serializable
1373         } else if (flavor.isRepresentationClassSerializable()) {
1374             try (ObjectInputStream ois =
1375                      new ObjectInputStream(str))
1376             {
1377                 theObject = ois.readObject();
1378             } catch (Exception e) {
1379                 throw new IOException(e.getMessage());
1380             }
1381             // Target data is Image
1382         } else if (DataFlavor.imageFlavor.equals(flavor)) {
1383             if (!isImageFormat(format)) {
1384                 throw new IOException("data translation failed");
1385             }
1386             theObject = platformImageBytesToImage(inputStreamToByteArray(str), format);
1387         }
1388 
1389         if (theObject == null) {
1390             throw new IOException("data translation failed");
1391         }
1392 
1393         return theObject;
1394 
1395     }
1396 
1397     /**
1398      * For arbitrary flavors, just use the raw InputStream. For text flavors,
1399      * ReencodingInputStream will decode and reencode the InputStream on demand
1400      * so that we can strip terminators and search-and-replace EOLN.
1401      */
1402     private Object translateStreamToInputStream
1403         (InputStream str, DataFlavor flavor, long format,
1404          Transferable localeTransferable) throws IOException
1405     {
1406         if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1407             str = new ReencodingInputStream
1408                 (str, format, DataFlavorUtil.getTextCharset(flavor),
1409                  localeTransferable);
1410         }
1411 
1412         return constructFlavoredObject(str, flavor, InputStream.class);
1413     }
1414 
1415     /**
1416      * We support representations which are exactly of the specified Class,
1417      * and also arbitrary Objects which have a constructor which takes an
1418      * instance of the Class as its sole parameter.
1419      */
1420     private Object constructFlavoredObject(Object arg, DataFlavor flavor,
1421                                            Class<?> clazz)
1422         throws IOException
1423     {
1424         final Class<?> dfrc = flavor.getRepresentationClass();
1425 
1426         if (clazz.equals(dfrc)) {
1427             return arg; // simple case
1428         } else {
1429             Constructor<?>[] constructors;
1430 
1431             try {
1432                 constructors = AccessController.doPrivileged(
1433                         (PrivilegedAction<Constructor<?>[]>) dfrc::getConstructors);
1434             } catch (SecurityException se) {
1435                 throw new IOException(se.getMessage());
1436             }
1437 
1438             Constructor<?> constructor = Stream.of(constructors)
1439                     .filter(c -> Modifier.isPublic(c.getModifiers()))
1440                     .filter(c -> {
1441                         Class<?>[] ptypes = c.getParameterTypes();
1442                         return ptypes != null
1443                                 && ptypes.length == 1
1444                                 && clazz.equals(ptypes[0]);
1445                     })
1446                     .findFirst()
1447                     .orElseThrow(() ->
1448                             new IOException("can't find <init>(L"+ clazz + ";)V for class: " + dfrc.getName()));
1449 
1450             try {
1451                 return constructor.newInstance(arg);
1452             } catch (Exception e) {
1453                 throw new IOException(e.getMessage());
1454             }
1455         }
1456     }
1457 
1458     /**
1459      * Used for decoding and reencoding an InputStream on demand so that we
1460      * can strip NUL terminators and perform EOLN search-and-replace.
1461      */
1462     public class ReencodingInputStream extends InputStream {
1463         BufferedReader wrapped;
1464         final char[] in = new char[2];
1465         byte[] out;
1466 
1467         CharsetEncoder encoder;
1468         CharBuffer inBuf;
1469         ByteBuffer outBuf;
1470 
1471         char[] eoln;
1472         int numTerminators;
1473 
1474         boolean eos;
1475         int index, limit;
1476 
1477         public ReencodingInputStream(InputStream bytestream, long format,
1478                                      String targetEncoding,
1479                                      Transferable localeTransferable)
1480             throws IOException
1481         {
1482             Long lFormat = format;
1483 
1484             String sourceEncoding = getBestCharsetForTextFormat(format, localeTransferable);
1485             wrapped = new BufferedReader(new InputStreamReader(bytestream, sourceEncoding));
1486 
1487             if (targetEncoding == null) {
1488                 // Throw NullPointerException for compatibility with the former
1489                 // call to sun.io.CharToByteConverter.getConverter(null)
1490                 // (Charset.forName(null) throws unspecified IllegalArgumentException
1491                 // now; see 6228568)
1492                 throw new NullPointerException("null target encoding");
1493             }
1494 
1495             try {
1496                 encoder = Charset.forName(targetEncoding).newEncoder();
1497                 out = new byte[(int)(encoder.maxBytesPerChar() * 2 + 0.5)];
1498                 inBuf = CharBuffer.wrap(in);
1499                 outBuf = ByteBuffer.wrap(out);
1500             } catch (IllegalCharsetNameException
1501                     | UnsupportedCharsetException
1502                     | UnsupportedOperationException e) {
1503                 throw new IOException(e.toString());
1504             }
1505 
1506             String sEoln = nativeEOLNs.get(lFormat);
1507             if (sEoln != null) {
1508                 eoln = sEoln.toCharArray();
1509             }
1510 
1511             // A hope and a prayer that this works generically. This will
1512             // definitely work on Win32.
1513             Integer terminators = nativeTerminators.get(lFormat);
1514             if (terminators != null) {
1515                 numTerminators = terminators;
1516             }
1517         }
1518 
1519         private int readChar() throws IOException {
1520             int c = wrapped.read();
1521 
1522             if (c == -1) { // -1 is EOS
1523                 eos = true;
1524                 return -1;
1525             }
1526 
1527             // "c == 0" is not quite correct, but good enough on Windows.
1528             if (numTerminators > 0 && c == 0) {
1529                 eos = true;
1530                 return -1;
1531             } else if (eoln != null && matchCharArray(eoln, c)) {
1532                 c = '\n' & 0xFFFF;
1533             }
1534 
1535             return c;
1536         }
1537 
1538         public int read() throws IOException {
1539             if (eos) {
1540                 return -1;
1541             }
1542 
1543             if (index >= limit) {
1544                 // deal with supplementary characters
1545                 int c = readChar();
1546                 if (c == -1) {
1547                     return -1;
1548                 }
1549 
1550                 in[0] = (char) c;
1551                 in[1] = 0;
1552                 inBuf.limit(1);
1553                 if (Character.isHighSurrogate((char) c)) {
1554                     c = readChar();
1555                     if (c != -1) {
1556                         in[1] = (char) c;
1557                         inBuf.limit(2);
1558                     }
1559                 }
1560 
1561                 inBuf.rewind();
1562                 outBuf.limit(out.length).rewind();
1563                 encoder.encode(inBuf, outBuf, false);
1564                 outBuf.flip();
1565                 limit = outBuf.limit();
1566 
1567                 index = 0;
1568 
1569                 return read();
1570             } else {
1571                 return out[index++] & 0xFF;
1572             }
1573         }
1574 
1575         public int available() throws IOException {
1576             return ((eos) ? 0 : (limit - index));
1577         }
1578 
1579         public void close() throws IOException {
1580             wrapped.close();
1581         }
1582 
1583         /**
1584          * Checks to see if the next array.length characters in wrapped
1585          * match array. The first character is provided as c. Subsequent
1586          * characters are read from wrapped itself. When this method returns,
1587          * the wrapped index may be different from what it was when this
1588          * method was called.
1589          */
1590         private boolean matchCharArray(char[] array, int c)
1591             throws IOException
1592         {
1593             wrapped.mark(array.length);  // BufferedReader supports mark
1594 
1595             int count = 0;
1596             if ((char)c == array[0]) {
1597                 for (count = 1; count < array.length; count++) {
1598                     c = wrapped.read();
1599                     if (c == -1 || ((char)c) != array[count]) {
1600                         break;
1601                     }
1602                 }
1603             }
1604 
1605             if (count == array.length) {
1606                 return true;
1607             } else {
1608                 wrapped.reset();
1609                 return false;
1610             }
1611         }
1612     }
1613 
1614     /**
1615      * Decodes a byte array into a set of String filenames.
1616      */
1617     protected abstract String[] dragQueryFile(byte[] bytes);
1618 
1619     /**
1620      * Decodes URIs from either a byte array or a stream.
1621      */
1622     protected URI[] dragQueryURIs(InputStream stream,
1623                                   long format,
1624                                   Transferable localeTransferable)
1625       throws IOException
1626     {
1627         throw new IOException(
1628             new UnsupportedOperationException("not implemented on this platform"));
1629     }
1630 
1631     /**
1632      * Translates either a byte array or an input stream which contain
1633      * platform-specific image data in the given format into an Image.
1634      */
1635 
1636 
1637     protected abstract Image platformImageBytesToImage(
1638         byte[] bytes,long format) throws IOException;
1639 
1640     /**
1641      * Translates either a byte array or an input stream which contain
1642      * an image data in the given standard format into an Image.
1643      *
1644      * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif
1645      */
1646     protected Image standardImageBytesToImage(
1647         byte[] bytes, String mimeType) throws IOException
1648     {
1649 
1650         Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByMIMEType(mimeType);
1651 
1652         if (!readerIterator.hasNext()) {
1653             throw new IOException("No registered service provider can decode " +
1654                                   " an image from " + mimeType);
1655         }
1656 
1657         IOException ioe = null;
1658 
1659         while (readerIterator.hasNext()) {
1660             ImageReader imageReader = readerIterator.next();
1661             try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
1662                 try (ImageInputStream imageInputStream = ImageIO.createImageInputStream(bais)) {
1663                     ImageReadParam param = imageReader.getDefaultReadParam();
1664                     imageReader.setInput(imageInputStream, true, true);
1665                     BufferedImage bufferedImage = imageReader.read(imageReader.getMinIndex(), param);
1666                     if (bufferedImage != null) {
1667                         return bufferedImage;
1668                     }
1669                 } finally {
1670                     imageReader.dispose();
1671                 }
1672             } catch (IOException e) {
1673                 ioe = e;
1674                 continue;
1675             }
1676         }
1677 
1678         if (ioe == null) {
1679             ioe = new IOException("Registered service providers failed to decode"
1680                                   + " an image from " + mimeType);
1681         }
1682 
1683         throw ioe;
1684     }
1685 
1686     /**
1687      * Translates a Java Image into a byte array which contains platform-
1688      * specific image data in the given format.
1689      */
1690     protected abstract byte[] imageToPlatformBytes(Image image, long format)
1691       throws IOException;
1692 
1693     /**
1694      * Translates a Java Image into a byte array which contains
1695      * an image data in the given standard format.
1696      *
1697      * @param mimeType image MIME type, such as: image/png, image/jpeg
1698      */
1699     protected byte[] imageToStandardBytes(Image image, String mimeType)
1700       throws IOException {
1701         IOException originalIOE = null;
1702 
1703         Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
1704 
1705         if (!writerIterator.hasNext()) {
1706             throw new IOException("No registered service provider can encode " +
1707                                   " an image to " + mimeType);
1708         }
1709 
1710         if (image instanceof RenderedImage) {
1711             // Try to encode the original image.
1712             try {
1713                 return imageToStandardBytesImpl((RenderedImage)image, mimeType);
1714             } catch (IOException ioe) {
1715                 originalIOE = ioe;
1716             }
1717         }
1718 
1719         // Retry with a BufferedImage.
1720         int width = 0;
1721         int height = 0;
1722         if (image instanceof ToolkitImage) {
1723             ImageRepresentation ir = ((ToolkitImage)image).getImageRep();
1724             ir.reconstruct(ImageObserver.ALLBITS);
1725             width = ir.getWidth();
1726             height = ir.getHeight();
1727         } else {
1728             width = image.getWidth(null);
1729             height = image.getHeight(null);
1730         }
1731 
1732         ColorModel model = ColorModel.getRGBdefault();
1733         WritableRaster raster =
1734             model.createCompatibleWritableRaster(width, height);
1735 
1736         BufferedImage bufferedImage =
1737             new BufferedImage(model, raster, model.isAlphaPremultiplied(),
1738                               null);
1739 
1740         Graphics g = bufferedImage.getGraphics();
1741         try {
1742             g.drawImage(image, 0, 0, width, height, null);
1743         } finally {
1744             g.dispose();
1745         }
1746 
1747         try {
1748             return imageToStandardBytesImpl(bufferedImage, mimeType);
1749         } catch (IOException ioe) {
1750             if (originalIOE != null) {
1751                 throw originalIOE;
1752             } else {
1753                 throw ioe;
1754             }
1755         }
1756     }
1757 
1758     byte[] imageToStandardBytesImpl(RenderedImage renderedImage,
1759                                               String mimeType)
1760         throws IOException {
1761 
1762         Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
1763 
1764         ImageTypeSpecifier typeSpecifier =
1765             new ImageTypeSpecifier(renderedImage);
1766 
1767         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1768         IOException ioe = null;
1769 
1770         while (writerIterator.hasNext()) {
1771             ImageWriter imageWriter = writerIterator.next();
1772             ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();
1773 
1774             if (!writerSpi.canEncodeImage(typeSpecifier)) {
1775                 continue;
1776             }
1777 
1778             try {
1779                 try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(baos)) {
1780                     imageWriter.setOutput(imageOutputStream);
1781                     imageWriter.write(renderedImage);
1782                     imageOutputStream.flush();
1783                 }
1784             } catch (IOException e) {
1785                 imageWriter.dispose();
1786                 baos.reset();
1787                 ioe = e;
1788                 continue;
1789             }
1790 
1791             imageWriter.dispose();
1792             baos.close();
1793             return baos.toByteArray();
1794         }
1795 
1796         baos.close();
1797 
1798         if (ioe == null) {
1799             ioe = new IOException("Registered service providers failed to encode "
1800                                   + renderedImage + " to " + mimeType);
1801         }
1802 
1803         throw ioe;
1804     }
1805 
1806     /**
1807      * Concatenates the data represented by two objects. Objects can be either
1808      * byte arrays or instances of {@code InputStream}. If both arguments
1809      * are byte arrays byte array will be returned. Otherwise an
1810      * {@code InputStream} will be returned.
1811      * <p>
1812      * Currently is only called from native code to prepend palette data to
1813      * platform-specific image data during image transfer on Win32.
1814      *
1815      * @param obj1 the first object to be concatenated.
1816      * @param obj2 the second object to be concatenated.
1817      * @return a byte array or an {@code InputStream} which represents
1818      *         a logical concatenation of the two arguments.
1819      * @throws NullPointerException is either of the arguments is
1820      *         {@code null}
1821      * @throws ClassCastException is either of the arguments is
1822      *         neither byte array nor an instance of {@code InputStream}.
1823      */
1824     private Object concatData(Object obj1, Object obj2) {
1825         InputStream str1 = null;
1826         InputStream str2 = null;
1827 
1828         if (obj1 instanceof byte[]) {
1829             byte[] arr1 = (byte[])obj1;
1830             if (obj2 instanceof byte[]) {
1831                 byte[] arr2 = (byte[])obj2;
1832                 byte[] ret = new byte[arr1.length + arr2.length];
1833                 System.arraycopy(arr1, 0, ret, 0, arr1.length);
1834                 System.arraycopy(arr2, 0, ret, arr1.length, arr2.length);
1835                 return ret;
1836             } else {
1837                 str1 = new ByteArrayInputStream(arr1);
1838                 str2 = (InputStream)obj2;
1839             }
1840         } else {
1841             str1 = (InputStream)obj1;
1842             if (obj2 instanceof byte[]) {
1843                 str2 = new ByteArrayInputStream((byte[])obj2);
1844             } else {
1845                 str2 = (InputStream)obj2;
1846             }
1847         }
1848 
1849         return new SequenceInputStream(str1, str2);
1850     }
1851 
1852     public byte[] convertData(final Object source,
1853                               final Transferable contents,
1854                               final long format,
1855                               final Map<Long, DataFlavor> formatMap,
1856                               final boolean isToolkitThread)
1857         throws IOException
1858     {
1859         byte[] ret = null;
1860 
1861         /*
1862          * If the current thread is the Toolkit thread we should post a
1863          * Runnable to the event dispatch thread associated with source Object,
1864          * since translateTransferable() calls Transferable.getTransferData()
1865          * that may contain client code.
1866          */
1867         if (isToolkitThread) try {
1868             final Stack<byte[]> stack = new Stack<>();
1869             final Runnable dataConverter = new Runnable() {
1870                 // Guard against multiple executions.
1871                 private boolean done = false;
1872                 public void run() {
1873                     if (done) {
1874                         return;
1875                     }
1876                     byte[] data = null;
1877                     try {
1878                         DataFlavor flavor = formatMap.get(format);
1879                         if (flavor != null) {
1880                             data = translateTransferable(contents, flavor, format);
1881                         }
1882                     } catch (Exception e) {
1883                         e.printStackTrace();
1884                         data = null;
1885                     }
1886                     try {
1887                         getToolkitThreadBlockedHandler().lock();
1888                         stack.push(data);
1889                         getToolkitThreadBlockedHandler().exit();
1890                     } finally {
1891                         getToolkitThreadBlockedHandler().unlock();
1892                         done = true;
1893                     }
1894                 }
1895             };
1896 
1897             final AppContext appContext = SunToolkit.targetToAppContext(source);
1898 
1899             getToolkitThreadBlockedHandler().lock();
1900 
1901             if (appContext != null) {
1902                 appContext.put(DATA_CONVERTER_KEY, dataConverter);
1903             }
1904 
1905             SunToolkit.executeOnEventHandlerThread(source, dataConverter);
1906 
1907             while (stack.empty()) {
1908                 getToolkitThreadBlockedHandler().enter();
1909             }
1910 
1911             if (appContext != null) {
1912                 appContext.remove(DATA_CONVERTER_KEY);
1913             }
1914 
1915             ret = stack.pop();
1916         } finally {
1917             getToolkitThreadBlockedHandler().unlock();
1918         } else {
1919             DataFlavor flavor = formatMap.get(format);
1920             if (flavor != null) {
1921                 ret = translateTransferable(contents, flavor, format);
1922             }
1923         }
1924 
1925         return ret;
1926     }
1927 
1928     public void processDataConversionRequests() {
1929         if (EventQueue.isDispatchThread()) {
1930             AppContext appContext = AppContext.getAppContext();
1931             getToolkitThreadBlockedHandler().lock();
1932             try {
1933                 Runnable dataConverter =
1934                     (Runnable)appContext.get(DATA_CONVERTER_KEY);
1935                 if (dataConverter != null) {
1936                     dataConverter.run();
1937                     appContext.remove(DATA_CONVERTER_KEY);
1938                 }
1939             } finally {
1940                 getToolkitThreadBlockedHandler().unlock();
1941             }
1942         }
1943     }
1944 
1945     public abstract ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler();
1946 
1947     /**
1948      * Helper function to reduce a Map with Long keys to a long array.
1949      * <p>
1950      * The map keys are sorted according to the native formats preference
1951      * order.
1952      */
1953     public static long[] keysToLongArray(SortedMap<Long, ?> map) {
1954         Set<Long> keySet = map.keySet();
1955         long[] retval = new long[keySet.size()];
1956         int i = 0;
1957         for (Iterator<Long> iter = keySet.iterator(); iter.hasNext(); i++) {
1958             retval[i] = iter.next();
1959         }
1960         return retval;
1961     }
1962 
1963     /**
1964      * Helper function to convert a Set of DataFlavors to a sorted array.
1965      * The array will be sorted according to {@code DataFlavorComparator}.
1966      */
1967     public static DataFlavor[] setToSortedDataFlavorArray(Set<DataFlavor> flavorsSet) {
1968         DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
1969         flavorsSet.toArray(flavors);
1970         final Comparator<DataFlavor> comparator = DataFlavorUtil.getDataFlavorComparator().reversed();
1971         Arrays.sort(flavors, comparator);
1972         return flavors;
1973     }
1974 
1975     /**
1976      * Helper function to convert an InputStream to a byte[] array.
1977      */
1978     protected static byte[] inputStreamToByteArray(InputStream str)
1979         throws IOException
1980     {
1981         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1982             int len = 0;
1983             byte[] buf = new byte[8192];
1984 
1985             while ((len = str.read(buf)) != -1) {
1986                 baos.write(buf, 0, len);
1987             }
1988 
1989             return baos.toByteArray();
1990         }
1991     }
1992 
1993     /**
1994      * Returns platform-specific mappings for the specified native.
1995      * If there are no platform-specific mappings for this native, the method
1996      * returns an empty {@code List}.
1997      */
1998     public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
1999         return new LinkedHashSet<>();
2000     }
2001 
2002     /**
2003      * Returns platform-specific mappings for the specified flavor.
2004      * If there are no platform-specific mappings for this flavor, the method
2005      * returns an empty {@code List}.
2006      */
2007     public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
2008         return new LinkedHashSet<>();
2009     }
2010 }
2011