jdk/src/share/classes/sun/awt/datatransfer/DataTransferer.java
author pchelko
Mon, 28 Jul 2014 19:11:52 +0400
changeset 26011 0fa4f1bd2843
parent 26010 9a3cf8ee0776
permissions -rw-r--r--
8051588: DataTransferer.getInstance throws ClassCastException in headless mode Reviewed-by: serb, alexsch

/*
 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.awt.datatransfer;

import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorMap;
import java.awt.datatransfer.FlavorTable;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.io.SequenceInputStream;
import java.io.StringReader;

import java.net.URI;
import java.net.URISyntaxException;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;

import java.util.*;

import sun.datatransfer.DataFlavorUtil;

import sun.awt.AppContext;
import sun.awt.ComponentFactory;
import sun.awt.SunToolkit;

import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.ColorModel;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageWriter;
import javax.imageio.ImageTypeSpecifier;

import javax.imageio.spi.ImageWriterSpi;

import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;

import sun.awt.image.ImageRepresentation;
import sun.awt.image.ToolkitImage;

import java.io.FilePermission;
import java.util.stream.Stream;


/**
 * Provides a set of functions to be shared among the DataFlavor class and
 * platform-specific data transfer implementations.
 *
 * The concept of "flavors" and "natives" is extended to include "formats",
 * which are the numeric values Win32 and X11 use to express particular data
 * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and
 * getFlavorsForNatives(String[]) functions, DataTransferer provides a set
 * of getFormatsFor(Transferable|Flavor|Flavors) and
 * getFlavorsFor(Format|Formats) functions.
 *
 * Also provided are functions for translating a Transferable into a byte
 * array, given a source DataFlavor and a target format, and for translating
 * a byte array or InputStream into an Object, given a source format and
 * a target DataFlavor.
 *
 * @author David Mendenhall
 * @author Danila Sinopalnikov
 *
 * @since 1.3.1
 */
public abstract class DataTransferer {
    /**
     * The <code>DataFlavor</code> representing a Java text encoding String
     * encoded in UTF-8, where
     * <pre>
     *     representationClass = [B
     *     mimeType            = "application/x-java-text-encoding"
     * </pre>
     */
    public static final DataFlavor javaTextEncodingFlavor;

    /**
     * A collection of all natives listed in flavormap.properties with
     * a primary MIME type of "text".
     */
    private static final Set<Long> textNatives =
            Collections.synchronizedSet(new HashSet<>());

    /**
     * The native encodings/charsets for the Set of textNatives.
     */
    private static final Map<Long, String> nativeCharsets =
            Collections.synchronizedMap(new HashMap<>());

    /**
     * The end-of-line markers for the Set of textNatives.
     */
    private static final Map<Long, String> nativeEOLNs =
            Collections.synchronizedMap(new HashMap<>());

    /**
     * The number of terminating NUL bytes for the Set of textNatives.
     */
    private static final Map<Long, Integer> nativeTerminators =
            Collections.synchronizedMap(new HashMap<>());

    /**
     * The key used to store pending data conversion requests for an AppContext.
     */
    private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY";

    static {
        DataFlavor tJavaTextEncodingFlavor = null;
        try {
            tJavaTextEncodingFlavor = new DataFlavor("application/x-java-text-encoding;class=\"[B\"");
        } catch (ClassNotFoundException cannotHappen) {
        }
        javaTextEncodingFlavor = tJavaTextEncodingFlavor;
    }

    /**
     * The accessor method for the singleton DataTransferer instance. Note
     * that in a headless environment, there may be no DataTransferer instance;
     * instead, null will be returned.
     */
    public static synchronized DataTransferer getInstance() {
        return ((ComponentFactory) Toolkit.getDefaultToolkit()).getDataTransferer();
    }

    /**
     * Converts a FlavorMap to a FlavorTable.
     */
    public static FlavorTable adaptFlavorMap(final FlavorMap map) {
        if (map instanceof FlavorTable) {
            return (FlavorTable)map;
        }

        return new FlavorTable() {
            @Override
            public Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] flavors) {
                return map.getNativesForFlavors(flavors);
            }
            @Override
            public Map<String, DataFlavor> getFlavorsForNatives(String[] natives) {
                return map.getFlavorsForNatives(natives);
            }
            @Override
            public List<String> getNativesForFlavor(DataFlavor flav) {
                Map<DataFlavor, String> natives = getNativesForFlavors(new DataFlavor[]{flav});
                String nat = natives.get(flav);
                if (nat != null) {
                    return Collections.singletonList(nat);
                } else {
                    return Collections.emptyList();
                }
            }
            @Override
            public List<DataFlavor> getFlavorsForNative(String nat) {
                Map<String, DataFlavor> flavors = getFlavorsForNatives(new String[]{nat});
                DataFlavor flavor = flavors.get(nat);
                if (flavor != null) {
                    return Collections.singletonList(flavor);
                } else {
                    return Collections.emptyList();
                }
            }
        };
    }

    /**
     * Returns the default Unicode encoding for the platform. The encoding
     * need not be canonical. This method is only used by the archaic function
     * DataFlavor.getTextPlainUnicodeFlavor().
     */
    public abstract String getDefaultUnicodeEncoding();

    /**
     * This method is called for text flavor mappings established while parsing
     * the flavormap.properties file. It stores the "eoln" and "terminators"
     * parameters which are not officially part of the MIME type. They are
     * MIME parameters specific to the flavormap.properties file format.
     */
    public void registerTextFlavorProperties(String nat, String charset,
                                             String eoln, String terminators) {
        Long format = getFormatForNativeAsLong(nat);

        textNatives.add(format);
        nativeCharsets.put(format, (charset != null && charset.length() != 0)
                ? charset : Charset.defaultCharset().name());
        if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) {
            nativeEOLNs.put(format, eoln);
        }
        if (terminators != null && terminators.length() != 0) {
            Integer iTerminators = Integer.valueOf(terminators);
            if (iTerminators > 0) {
                nativeTerminators.put(format, iTerminators);
            }
        }
    }

    /**
     * Determines whether the native corresponding to the specified long format
     * was listed in the flavormap.properties file.
     */
    protected boolean isTextFormat(long format) {
        return textNatives.contains(Long.valueOf(format));
    }

    protected String getCharsetForTextFormat(Long lFormat) {
        return nativeCharsets.get(lFormat);
    }

    /**
     * Specifies whether text imported from the native system in the specified
     * format is locale-dependent. If so, when decoding such text,
     * 'nativeCharsets' should be ignored, and instead, the Transferable should
     * be queried for its javaTextEncodingFlavor data for the correct encoding.
     */
    public abstract boolean isLocaleDependentTextFormat(long format);

    /**
     * Determines whether the DataFlavor corresponding to the specified long
     * format is DataFlavor.javaFileListFlavor.
     */
    public abstract boolean isFileFormat(long format);

    /**
     * Determines whether the DataFlavor corresponding to the specified long
     * format is DataFlavor.imageFlavor.
     */
    public abstract boolean isImageFormat(long format);

    /**
     * Determines whether the format is a URI list we can convert to
     * a DataFlavor.javaFileListFlavor.
     */
    protected boolean isURIListFormat(long format) {
        return false;
    }

    /**
     * Returns a Map whose keys are all of the possible formats into which the
     * Transferable's transfer data flavors can be translated. The value of
     * each key is the DataFlavor in which the Transferable's data should be
     * requested when converting to the format.
     * <p>
     * The map keys are sorted according to the native formats preference
     * order.
     */
    public SortedMap<Long,DataFlavor> getFormatsForTransferable(Transferable contents,
                                                                FlavorTable map)
    {
        DataFlavor[] flavors = contents.getTransferDataFlavors();
        if (flavors == null) {
            return Collections.emptySortedMap();
        }
        return getFormatsForFlavors(flavors, map);
    }

    /**
     * Returns a Map whose keys are all of the possible formats into which data
     * in the specified DataFlavors can be translated. The value of each key
     * is the DataFlavor in which the Transferable's data should be requested
     * when converting to the format.
     * <p>
     * The map keys are sorted according to the native formats preference
     * order.
     *
     * @param flavors the data flavors
     * @param map the FlavorTable which contains mappings between
     *            DataFlavors and data formats
     * @throws NullPointerException if flavors or map is <code>null</code>
     */
    public SortedMap<Long, DataFlavor> getFormatsForFlavors(DataFlavor[] flavors,
                                                            FlavorTable map)
    {
        Map<Long,DataFlavor> formatMap = new HashMap<>(flavors.length);
        Map<Long,DataFlavor> textPlainMap = new HashMap<>(flavors.length);
        // Maps formats to indices that will be used to sort the formats
        // according to the preference order.
        // Larger index value corresponds to the more preferable format.
        Map<Long, Integer> indexMap = new HashMap<>(flavors.length);
        Map<Long, Integer> textPlainIndexMap = new HashMap<>(flavors.length);

        int currentIndex = 0;

        // Iterate backwards so that preferred DataFlavors are used over
        // other DataFlavors. (See javadoc for
        // Transferable.getTransferDataFlavors.)
        for (int i = flavors.length - 1; i >= 0; i--) {
            DataFlavor flavor = flavors[i];
            if (flavor == null) continue;

            // Don't explicitly test for String, since it is just a special
            // case of Serializable
            if (flavor.isFlavorTextType() ||
                flavor.isFlavorJavaFileListType() ||
                DataFlavor.imageFlavor.equals(flavor) ||
                flavor.isRepresentationClassSerializable() ||
                flavor.isRepresentationClassInputStream() ||
                flavor.isRepresentationClassRemote())
            {
                List<String> natives = map.getNativesForFlavor(flavor);

                currentIndex += natives.size();

                for (String aNative : natives) {
                    Long lFormat = getFormatForNativeAsLong(aNative);
                    Integer index = currentIndex--;

                    formatMap.put(lFormat, flavor);
                    indexMap.put(lFormat, index);

                    // SystemFlavorMap.getNativesForFlavor will return
                    // text/plain natives for all text/*. While this is good
                    // for a single text/* flavor, we would prefer that
                    // text/plain native data come from a text/plain flavor.
                    if (("text".equals(flavor.getPrimaryType()) &&
                            "plain".equals(flavor.getSubType())) ||
                            flavor.equals(DataFlavor.stringFlavor)) {
                        textPlainMap.put(lFormat, flavor);
                        textPlainIndexMap.put(lFormat, index);
                    }
                }

                currentIndex += natives.size();
            }
        }

        formatMap.putAll(textPlainMap);
        indexMap.putAll(textPlainIndexMap);

        // Sort the map keys according to the formats preference order.
        Comparator<Long> comparator = DataFlavorUtil.getIndexOrderComparator(indexMap).reversed();
        SortedMap<Long, DataFlavor> sortedMap = new TreeMap<>(comparator);
        sortedMap.putAll(formatMap);

        return sortedMap;
    }

    /**
     * Reduces the Map output for the root function to an array of the
     * Map's keys.
     */
    public long[] getFormatsForTransferableAsArray(Transferable contents,
                                                   FlavorTable map) {
        return keysToLongArray(getFormatsForTransferable(contents, map));
    }

    /**
     * Returns a Map whose keys are all of the possible DataFlavors into which
     * data in the specified formats can be translated. The value of each key
     * is the format in which the Clipboard or dropped data should be requested
     * when converting to the DataFlavor.
     */
    public Map<DataFlavor, Long> getFlavorsForFormats(long[] formats, FlavorTable map) {
        Map<DataFlavor, Long> flavorMap = new HashMap<>(formats.length);
        Set<AbstractMap.SimpleEntry<Long, DataFlavor>> mappingSet = new HashSet<>(formats.length);
        Set<DataFlavor> flavorSet = new HashSet<>(formats.length);

        // First step: build flavorSet, mappingSet and initial flavorMap
        // flavorSet  - the set of all the DataFlavors into which
        //              data in the specified formats can be translated;
        // mappingSet - the set of all the mappings from the specified formats
        //              into any DataFlavor;
        // flavorMap  - after this step, this map maps each of the DataFlavors
        //              from flavorSet to any of the specified formats.
        for (long format : formats) {
            String nat = getNativeForFormat(format);
            List<DataFlavor> flavors = map.getFlavorsForNative(nat);
            for (DataFlavor flavor : flavors) {
                // Don't explicitly test for String, since it is just a special
                // case of Serializable
                if (flavor.isFlavorTextType() ||
                        flavor.isFlavorJavaFileListType() ||
                        DataFlavor.imageFlavor.equals(flavor) ||
                        flavor.isRepresentationClassSerializable() ||
                        flavor.isRepresentationClassInputStream() ||
                        flavor.isRepresentationClassRemote()) {

                    AbstractMap.SimpleEntry<Long, DataFlavor> mapping =
                            new AbstractMap.SimpleEntry<>(format, flavor);
                    flavorMap.put(flavor, format);
                    mappingSet.add(mapping);
                    flavorSet.add(flavor);
                }
            }
        }

        // Second step: for each DataFlavor try to figure out which of the
        // specified formats is the best to translate to this flavor.
        // Then map each flavor to the best format.
        // For the given flavor, FlavorTable indicates which native will
        // best reflect data in the specified flavor to the underlying native
        // platform. We assume that this native is the best to translate
        // to this flavor.
        // Note: FlavorTable allows one-way mappings, so we can occasionally
        // map a flavor to the format for which the corresponding
        // format-to-flavor mapping doesn't exist. For this reason we have built
        // a mappingSet of all format-to-flavor mappings for the specified formats
        // and check if the format-to-flavor mapping exists for the
        // (flavor,format) pair being added.
        for (DataFlavor flavor : flavorSet) {
            List<String> natives = map.getNativesForFlavor(flavor);
            for (String aNative : natives) {
                Long lFormat = getFormatForNativeAsLong(aNative);
                if (mappingSet.contains(new AbstractMap.SimpleEntry<>(lFormat, flavor))) {
                    flavorMap.put(flavor, lFormat);
                    break;
                }
            }
        }

        return flavorMap;
    }

    /**
     * Returns a Set of all DataFlavors for which
     * 1) a mapping from at least one of the specified formats exists in the
     * specified map and
     * 2) the data translation for this mapping can be performed by the data
     * transfer subsystem.
     *
     * @param formats the data formats
     * @param map the FlavorTable which contains mappings between
     *            DataFlavors and data formats
     * @throws NullPointerException if formats or map is <code>null</code>
     */
    public Set<DataFlavor> getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) {
        Set<DataFlavor> flavorSet = new HashSet<>(formats.length);

        for (long format : formats) {
            List<DataFlavor> flavors = map.getFlavorsForNative(getNativeForFormat(format));
            for (DataFlavor flavor : flavors) {
                // Don't explicitly test for String, since it is just a special
                // case of Serializable
                if (flavor.isFlavorTextType() ||
                        flavor.isFlavorJavaFileListType() ||
                        DataFlavor.imageFlavor.equals(flavor) ||
                        flavor.isRepresentationClassSerializable() ||
                        flavor.isRepresentationClassInputStream() ||
                        flavor.isRepresentationClassRemote()) {
                    flavorSet.add(flavor);
                }
            }
        }

        return flavorSet;
    }

    /**
     * Returns an array of all DataFlavors for which
     * 1) a mapping from at least one of the specified formats exists in the
     * specified map and
     * 2) the data translation for this mapping can be performed by the data
     * transfer subsystem.
     * The array will be sorted according to a
     * <code>DataFlavorComparator</code> created with the specified
     * map as an argument.
     *
     * @param formats the data formats
     * @param map the FlavorTable which contains mappings between
     *            DataFlavors and data formats
     * @throws NullPointerException if formats or map is <code>null</code>
     */
    public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats,
                                                    FlavorTable map) {
        // getFlavorsForFormatsAsSet() is less expensive than
        // getFlavorsForFormats().
        return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map));
    }

    /**
     * Looks-up or registers the String native with the native data transfer
     * system and returns a long format corresponding to that native.
     */
    protected abstract Long getFormatForNativeAsLong(String str);

    /**
     * Looks-up the String native corresponding to the specified long format in
     * the native data transfer system.
     */
    protected abstract String getNativeForFormat(long format);

    /* Contains common code for finding the best charset for
     * clipboard string encoding/decoding, basing on clipboard
     * format and localeTransferable(on decoding, if available)
     */
    protected String getBestCharsetForTextFormat(Long lFormat,
        Transferable localeTransferable) throws IOException
    {
        String charset = null;
        if (localeTransferable != null &&
            isLocaleDependentTextFormat(lFormat) &&
            localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor)) {
            try {
                byte[] charsetNameBytes = (byte[])localeTransferable
                        .getTransferData(javaTextEncodingFlavor);
                charset = new String(charsetNameBytes, StandardCharsets.UTF_8);
            } catch (UnsupportedFlavorException cannotHappen) {
            }
        } else {
            charset = getCharsetForTextFormat(lFormat);
        }
        if (charset == null) {
            // Only happens when we have a custom text type.
            charset = Charset.defaultCharset().name();
        }
        return charset;
    }

    /**
     *  Translation function for converting string into
     *  a byte array. Search-and-replace EOLN. Encode into the
     *  target format. Append terminating NUL bytes.
     *
     *  Java to Native string conversion
     */
    private byte[] translateTransferableString(String str,
                                               long format) throws IOException
    {
        Long lFormat = format;
        String charset = getBestCharsetForTextFormat(lFormat, null);
        // Search and replace EOLN. Note that if EOLN is "\n", then we
        // never added an entry to nativeEOLNs anyway, so we'll skip this
        // code altogether.
        // windows: "abc\nde"->"abc\r\nde"
        String eoln = nativeEOLNs.get(lFormat);
        if (eoln != null) {
            int length = str.length();
            StringBuilder buffer = new StringBuilder(length * 2); // 2 is a heuristic
            for (int i = 0; i < length; i++) {
                // Fix for 4914613 - skip native EOLN
                if (str.startsWith(eoln, i)) {
                    buffer.append(eoln);
                    i += eoln.length() - 1;
                    continue;
                }
                char c = str.charAt(i);
                if (c == '\n') {
                    buffer.append(eoln);
                } else {
                    buffer.append(c);
                }
            }
            str = buffer.toString();
        }

        // Encode text in target format.
        byte[] bytes = str.getBytes(charset);

        // Append terminating NUL bytes. Note that if terminators is 0,
        // the we never added an entry to nativeTerminators anyway, so
        // we'll skip code altogether.
        // "abcde" -> "abcde\0"
        Integer terminators = nativeTerminators.get(lFormat);
        if (terminators != null) {
            int numTerminators = terminators;
            byte[] terminatedBytes =
                new byte[bytes.length + numTerminators];
            System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length);
            for (int i = bytes.length; i < terminatedBytes.length; i++) {
                terminatedBytes[i] = 0x0;
            }
            bytes = terminatedBytes;
        }
        return bytes;
    }

    /**
     * Translating either a byte array or an InputStream into an String.
     * Strip terminators and search-and-replace EOLN.
     *
     * Native to Java string conversion
     */
    private String translateBytesToString(byte[] bytes, long format,
                                          Transferable localeTransferable)
            throws IOException
    {

        Long lFormat = format;
        String charset = getBestCharsetForTextFormat(lFormat, localeTransferable);

        // Locate terminating NUL bytes. Note that if terminators is 0,
        // the we never added an entry to nativeTerminators anyway, so
        // we'll skip code altogether.

        // In other words: we are doing char alignment here basing on suggestion
        // that count of zero-'terminators' is a number of bytes in one symbol
        // for selected charset (clipboard format). It is not complitly true for
        // multibyte coding like UTF-8, but helps understand the procedure.
        // "abcde\0" -> "abcde"

        String eoln = nativeEOLNs.get(lFormat);
        Integer terminators = nativeTerminators.get(lFormat);
        int count;
        if (terminators != null) {
            int numTerminators = terminators;
search:
            for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) {
                for (int i = count; i < count + numTerminators; i++) {
                    if (bytes[i] != 0x0) {
                        continue search;
                    }
                }
                // found terminators
                break search;
            }
        } else {
            count = bytes.length;
        }

        // Decode text to chars. Don't include any terminators.
        String converted = new String(bytes, 0, count, charset);

        // Search and replace EOLN. Note that if EOLN is "\n", then we
        // never added an entry to nativeEOLNs anyway, so we'll skip this
        // code altogether.
        // Count of NUL-terminators and EOLN coding are platform-specific and
        // loaded from flavormap.properties file
        // windows: "abc\r\nde" -> "abc\nde"

        if (eoln != null) {

            /* Fix for 4463560: replace EOLNs symbol-by-symbol instead
             * of using buf.replace()
             */

            char[] buf = converted.toCharArray();
            char[] eoln_arr = eoln.toCharArray();
            int j = 0;
            boolean match;

            for (int i = 0; i < buf.length; ) {
                // Catch last few bytes
                if (i + eoln_arr.length > buf.length) {
                    buf[j++] = buf[i++];
                    continue;
                }

                match = true;
                for (int k = 0, l = i; k < eoln_arr.length; k++, l++) {
                    if (eoln_arr[k] != buf[l]) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    buf[j++] = '\n';
                    i += eoln_arr.length;
                } else {
                    buf[j++] = buf[i++];
                }
            }
            converted = new String(buf, 0, j);
        }

        return converted;
    }


    /**
     * Primary translation function for translating a Transferable into
     * a byte array, given a source DataFlavor and target format.
     */
    public byte[] translateTransferable(Transferable contents,
                                        DataFlavor flavor,
                                        long format) throws IOException
    {
        // Obtain the transfer data in the source DataFlavor.
        //
        // Note that we special case DataFlavor.plainTextFlavor because
        // StringSelection supports this flavor incorrectly -- instead of
        // returning an InputStream as the DataFlavor representation class
        // states, it returns a Reader. Instead of using this broken
        // functionality, we request the data in stringFlavor (the other
        // DataFlavor which StringSelection supports) and use the String
        // translator.
        Object obj;
        boolean stringSelectionHack;
        try {
            obj = contents.getTransferData(flavor);
            if (obj == null) {
                return null;
            }
            if (flavor.equals(DataFlavor.plainTextFlavor) &&
                !(obj instanceof InputStream))
            {
                obj = contents.getTransferData(DataFlavor.stringFlavor);
                if (obj == null) {
                    return null;
                }
                stringSelectionHack = true;
            } else {
                stringSelectionHack = false;
            }
        } catch (UnsupportedFlavorException e) {
            throw new IOException(e.getMessage());
        }

        // Source data is a String. Search-and-replace EOLN. Encode into the
        // target format. Append terminating NUL bytes.
        if (stringSelectionHack ||
            (String.class.equals(flavor.getRepresentationClass()) &&
             DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {

            String str = removeSuspectedData(flavor, contents, (String)obj);

            return translateTransferableString(
                str,
                format);

        // Source data is a Reader. Convert to a String and recur. In the
        // future, we may want to rewrite this so that we encode on demand.
        } else if (flavor.isRepresentationClassReader()) {
            if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
                throw new IOException
                    ("cannot transfer non-text data as Reader");
            }

            StringBuilder buf = new StringBuilder();
            try (Reader r = (Reader)obj) {
                int c;
                while ((c = r.read()) != -1) {
                    buf.append((char)c);
                }
            }

            return translateTransferableString(
                buf.toString(),
                format);

        // Source data is a CharBuffer. Convert to a String and recur.
        } else if (flavor.isRepresentationClassCharBuffer()) {
            if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
                throw new IOException
                    ("cannot transfer non-text data as CharBuffer");
            }

            CharBuffer buffer = (CharBuffer)obj;
            int size = buffer.remaining();
            char[] chars = new char[size];
            buffer.get(chars, 0, size);

            return translateTransferableString(
                new String(chars),
                format);

        // Source data is a char array. Convert to a String and recur.
        } else if (char[].class.equals(flavor.getRepresentationClass())) {
            if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
                throw new IOException
                    ("cannot transfer non-text data as char array");
            }

            return translateTransferableString(
                new String((char[])obj),
                format);

        // Source data is a ByteBuffer. For arbitrary flavors, simply return
        // the array. For text flavors, decode back to a String and recur to
        // reencode according to the requested format.
        } else if (flavor.isRepresentationClassByteBuffer()) {
            ByteBuffer buffer = (ByteBuffer)obj;
            int size = buffer.remaining();
            byte[] bytes = new byte[size];
            buffer.get(bytes, 0, size);

            if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
                String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
                return translateTransferableString(
                    new String(bytes, sourceEncoding),
                    format);
            } else {
                return bytes;
            }

        // Source data is a byte array. For arbitrary flavors, simply return
        // the array. For text flavors, decode back to a String and recur to
        // reencode according to the requested format.
        } else if (byte[].class.equals(flavor.getRepresentationClass())) {
            byte[] bytes = (byte[])obj;

            if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
                String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
                return translateTransferableString(
                    new String(bytes, sourceEncoding),
                    format);
            } else {
                return bytes;
            }
        // Source data is Image
        } else if (DataFlavor.imageFlavor.equals(flavor)) {
            if (!isImageFormat(format)) {
                throw new IOException("Data translation failed: " +
                                      "not an image format");
            }

            Image image = (Image)obj;
            byte[] bytes = imageToPlatformBytes(image, format);

            if (bytes == null) {
                throw new IOException("Data translation failed: " +
                    "cannot convert java image to native format");
            }
            return bytes;
        }

        byte[] theByteArray = null;

        // Target data is a file list. Source data must be a
        // java.util.List which contains java.io.File or String instances.
        if (isFileFormat(format)) {
            if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
                throw new IOException("data translation failed");
            }

            final List<?> list = (List<?>)obj;

            final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);

            final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);

            try (ByteArrayOutputStream bos = convertFileListToBytes(fileList)) {
                theByteArray = bos.toByteArray();
            }

        // Target data is a URI list. Source data must be a
        // java.util.List which contains java.io.File or String instances.
        } else if (isURIListFormat(format)) {
            if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
                throw new IOException("data translation failed");
            }
            String nat = getNativeForFormat(format);
            String targetCharset = null;
            if (nat != null) {
                try {
                    targetCharset = new DataFlavor(nat).getParameter("charset");
                } catch (ClassNotFoundException cnfe) {
                    throw new IOException(cnfe);
                }
            }
            if (targetCharset == null) {
                targetCharset = "UTF-8";
            }
            final List<?> list = (List<?>)obj;
            final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);
            final ArrayList<String> fileList = castToFiles(list, userProtectionDomain);
            final ArrayList<String> uriList = new ArrayList<>(fileList.size());
            for (String fileObject : fileList) {
                final URI uri = new File(fileObject).toURI();
                // Some implementations are fussy about the number of slashes (file:///path/to/file is best)
                try {
                    uriList.add(new URI(uri.getScheme(), "", uri.getPath(), uri.getFragment()).toString());
                } catch (URISyntaxException uriSyntaxException) {
                    throw new IOException(uriSyntaxException);
                  }
              }

            byte[] eoln = "\r\n".getBytes(targetCharset);

            try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                for (String uri : uriList) {
                    byte[] bytes = uri.getBytes(targetCharset);
                    bos.write(bytes, 0, bytes.length);
                    bos.write(eoln, 0, eoln.length);
                }
                theByteArray = bos.toByteArray();
            }

        // Source data is an InputStream. For arbitrary flavors, just grab the
        // bytes and dump them into a byte array. For text flavors, decode back
        // to a String and recur to reencode according to the requested format.
        } else if (flavor.isRepresentationClassInputStream()) {
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                try (InputStream is = (InputStream)obj) {
                    boolean eof = false;
                    int avail = is.available();
                    byte[] tmp = new byte[avail > 8192 ? avail : 8192];
                    do {
                        int aValue;
                        if (!(eof = (aValue = is.read(tmp, 0, tmp.length)) == -1)) {
                            bos.write(tmp, 0, aValue);
                        }
                    } while (!eof);
                }

                if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
                    byte[] bytes = bos.toByteArray();
                    String sourceEncoding = DataFlavorUtil.getTextCharset(flavor);
                    return translateTransferableString(
                               new String(bytes, sourceEncoding),
                               format);
                }
                theByteArray = bos.toByteArray();
            }


        // Source data is an RMI object
        } else if (flavor.isRepresentationClassRemote()) {
            theByteArray = convertObjectToBytes(DataFlavorUtil.RMI.newMarshalledObject(obj));

        // Source data is Serializable
        } else if (flavor.isRepresentationClassSerializable()) {

            theByteArray = convertObjectToBytes(obj);

        } else {
            throw new IOException("data translation failed");
        }



        return theByteArray;
    }

    private static byte[] convertObjectToBytes(Object object) throws IOException {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos))
        {
            oos.writeObject(object);
            return bos.toByteArray();
        }
    }

    protected abstract ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException;

    private String removeSuspectedData(DataFlavor flavor, final Transferable contents, final String str)
            throws IOException
    {
        if (null == System.getSecurityManager()
            || !flavor.isMimeTypeEqual("text/uri-list"))
        {
            return str;
        }

        final ProtectionDomain userProtectionDomain = getUserProtectionDomain(contents);

        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<String>) () -> {

                StringBuilder allowedFiles = new StringBuilder(str.length());
                String [] uriArray = str.split("(\\s)+");

                for (String fileName : uriArray)
                {
                    File file = new File(fileName);
                    if (file.exists() &&
                        !(isFileInWebstartedCache(file) ||
                        isForbiddenToRead(file, userProtectionDomain)))
                    {
                        if (0 != allowedFiles.length())
                        {
                            allowedFiles.append("\\r\\n");
                        }

                        allowedFiles.append(fileName);
                    }
                }

                return allowedFiles.toString();
            });
        } catch (PrivilegedActionException pae) {
            throw new IOException(pae.getMessage(), pae);
        }
    }

    private static ProtectionDomain getUserProtectionDomain(Transferable contents) {
        return contents.getClass().getProtectionDomain();
    }

    private boolean isForbiddenToRead (File file, ProtectionDomain protectionDomain)
    {
        if (null == protectionDomain) {
            return false;
        }
        try {
            FilePermission filePermission =
                    new FilePermission(file.getCanonicalPath(), "read, delete");
            if (protectionDomain.implies(filePermission)) {
                return false;
            }
        } catch (IOException e) {}

        return true;
    }

    private ArrayList<String> castToFiles(final List<?> files,
                                          final ProtectionDomain userProtectionDomain) throws IOException {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<ArrayList<String>>) () -> {
                ArrayList<String> fileList = new ArrayList<>();
                for (Object fileObject : files)
                {
                    File file = castToFile(fileObject);
                    if (file != null &&
                        (null == System.getSecurityManager() ||
                        !(isFileInWebstartedCache(file) ||
                        isForbiddenToRead(file, userProtectionDomain))))
                    {
                        fileList.add(file.getCanonicalPath());
                    }
                }
                return fileList;
            });
        } catch (PrivilegedActionException pae) {
            throw new IOException(pae.getMessage());
        }
    }

    // It is important do not use user's successors
    // of File class.
    private File castToFile(Object fileObject) throws IOException {
        String filePath = null;
        if (fileObject instanceof File) {
            filePath = ((File)fileObject).getCanonicalPath();
        } else if (fileObject instanceof String) {
           filePath = (String) fileObject;
        } else {
           return null;
        }
        return new File(filePath);
    }

    private final static String[] DEPLOYMENT_CACHE_PROPERTIES = {
        "deployment.system.cachedir",
        "deployment.user.cachedir",
        "deployment.javaws.cachedir",
        "deployment.javapi.cachedir"
    };

    private final static ArrayList <File> deploymentCacheDirectoryList = new ArrayList<>();

    private static boolean isFileInWebstartedCache(File f) {

        if (deploymentCacheDirectoryList.isEmpty()) {
            for (String cacheDirectoryProperty : DEPLOYMENT_CACHE_PROPERTIES) {
                String cacheDirectoryPath = System.getProperty(cacheDirectoryProperty);
                if (cacheDirectoryPath != null) {
                    try {
                        File cacheDirectory = (new File(cacheDirectoryPath)).getCanonicalFile();
                        if (cacheDirectory != null) {
                            deploymentCacheDirectoryList.add(cacheDirectory);
                        }
                    } catch (IOException ioe) {}
                }
            }
        }

        for (File deploymentCacheDirectory : deploymentCacheDirectoryList) {
            for (File dir = f; dir != null; dir = dir.getParentFile()) {
                if (dir.equals(deploymentCacheDirectory)) {
                    return true;
                }
            }
        }

        return false;
    }


    public Object translateBytes(byte[] bytes, DataFlavor flavor,
                                 long format, Transferable localeTransferable)
        throws IOException
    {

        Object theObject = null;

        // Source data is a file list. Use the dragQueryFile native function to
        // do most of the decoding. Then wrap File objects around the String
        // filenames and return a List.
        if (isFileFormat(format)) {
            if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
                throw new IOException("data translation failed");
            }
            String[] filenames = dragQueryFile(bytes);
            if (filenames == null) {
                return null;
            }

            // Convert the strings to File objects
            File[] files = new File[filenames.length];
            for (int i = 0; i < filenames.length; i++) {
                files[i] = new File(filenames[i]);
            }

            // Turn the list of Files into a List and return
            theObject = Arrays.asList(files);

            // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor
            // where possible.
        } else if (isURIListFormat(format)
                    && DataFlavor.javaFileListFlavor.equals(flavor)) {

            try (ByteArrayInputStream str = new ByteArrayInputStream(bytes))  {

                URI uris[] = dragQueryURIs(str, format, localeTransferable);
                if (uris == null) {
                    return null;
                }
                List<File> files = new ArrayList<>();
                for (URI uri : uris) {
                    try {
                        files.add(new File(uri));
                    } catch (IllegalArgumentException illegalArg) {
                        // When converting from URIs to less generic files,
                        // common practice (Wine, SWT) seems to be to
                        // silently drop the URIs that aren't local files.
                    }
                }
                theObject = files;
            }

            // Target data is a String. Strip terminating NUL bytes. Decode bytes
            // into characters. Search-and-replace EOLN.
        } else if (String.class.equals(flavor.getRepresentationClass()) &&
                   DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {

            theObject = translateBytesToString(bytes, format, localeTransferable);

            // Target data is a Reader. Obtain data in InputStream format, encoded
            // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
            // back to chars on demand.
        } else if (flavor.isRepresentationClassReader()) {
            try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
                theObject = translateStream(bais,
                        flavor, format, localeTransferable);
            }
            // Target data is a CharBuffer. Recur to obtain String and wrap.
        } else if (flavor.isRepresentationClassCharBuffer()) {
            if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
                throw new IOException("cannot transfer non-text data as CharBuffer");
            }

            CharBuffer buffer = CharBuffer.wrap(
                translateBytesToString(bytes,format, localeTransferable));

            theObject = constructFlavoredObject(buffer, flavor, CharBuffer.class);

            // Target data is a char array. Recur to obtain String and convert to
            // char array.
        } else if (char[].class.equals(flavor.getRepresentationClass())) {
            if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
                throw new IOException
                          ("cannot transfer non-text data as char array");
            }

            theObject = translateBytesToString(
                bytes, format, localeTransferable).toCharArray();

            // Target data is a ByteBuffer. For arbitrary flavors, just return
            // the raw bytes. For text flavors, convert to a String to strip
            // terminators and search-and-replace EOLN, then reencode according to
            // the requested flavor.
        } else if (flavor.isRepresentationClassByteBuffer()) {
            if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
                bytes = translateBytesToString(
                    bytes, format, localeTransferable).getBytes(
                        DataFlavorUtil.getTextCharset(flavor)
                    );
            }

            ByteBuffer buffer = ByteBuffer.wrap(bytes);
            theObject = constructFlavoredObject(buffer, flavor, ByteBuffer.class);

            // Target data is a byte array. For arbitrary flavors, just return
            // the raw bytes. For text flavors, convert to a String to strip
            // terminators and search-and-replace EOLN, then reencode according to
            // the requested flavor.
        } else if (byte[].class.equals(flavor.getRepresentationClass())) {
            if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
                theObject = translateBytesToString(
                    bytes, format, localeTransferable
                ).getBytes(DataFlavorUtil.getTextCharset(flavor));
            } else {
                theObject = bytes;
            }

            // Target data is an InputStream. For arbitrary flavors, just return
            // the raw bytes. For text flavors, decode to strip terminators and
            // search-and-replace EOLN, then reencode according to the requested
            // flavor.
        } else if (flavor.isRepresentationClassInputStream()) {

            try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
                theObject = translateStream(bais, flavor, format, localeTransferable);
            }

        } else if (flavor.isRepresentationClassRemote()) {
            try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
                 ObjectInputStream ois = new ObjectInputStream(bais)) {

                theObject = DataFlavorUtil.RMI.getMarshalledObject(ois.readObject());
            } catch (Exception e) {
                throw new IOException(e.getMessage());
            }

            // Target data is Serializable
        } else if (flavor.isRepresentationClassSerializable()) {

            try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
                theObject = translateStream(bais, flavor, format, localeTransferable);
            }

            // Target data is Image
        } else if (DataFlavor.imageFlavor.equals(flavor)) {
            if (!isImageFormat(format)) {
                throw new IOException("data translation failed");
            }

            theObject = platformImageBytesToImage(bytes, format);
        }

        if (theObject == null) {
            throw new IOException("data translation failed");
        }

        return theObject;

    }

    /**
     * Primary translation function for translating
     * an InputStream into an Object, given a source format and a target
     * DataFlavor.
     */
    public Object translateStream(InputStream str, DataFlavor flavor,
                                  long format, Transferable localeTransferable)
        throws IOException
    {

        Object theObject = null;
        // Source data is a URI list. Convert to DataFlavor.javaFileListFlavor
        // where possible.
        if (isURIListFormat(format)
                && DataFlavor.javaFileListFlavor.equals(flavor))
        {

            URI uris[] = dragQueryURIs(str, format, localeTransferable);
            if (uris == null) {
                return null;
            }
            List<File> files = new ArrayList<>();
            for (URI uri : uris) {
                try {
                    files.add(new File(uri));
                } catch (IllegalArgumentException illegalArg) {
                    // When converting from URIs to less generic files,
                    // common practice (Wine, SWT) seems to be to
                    // silently drop the URIs that aren't local files.
                }
            }
            theObject = files;

        // Target data is a String. Strip terminating NUL bytes. Decode bytes
        // into characters. Search-and-replace EOLN.
        } else if (String.class.equals(flavor.getRepresentationClass()) &&
                   DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {

            return translateBytesToString(inputStreamToByteArray(str),
                format, localeTransferable);

            // Special hack to maintain backwards-compatibility with the brokenness
            // of StringSelection. Return a StringReader instead of an InputStream.
            // Recur to obtain String and encapsulate.
        } else if (DataFlavor.plainTextFlavor.equals(flavor)) {
            theObject = new StringReader(translateBytesToString(
                inputStreamToByteArray(str),
                format, localeTransferable));

            // Target data is an InputStream. For arbitrary flavors, just return
            // the raw bytes. For text flavors, decode to strip terminators and
            // search-and-replace EOLN, then reencode according to the requested
            // flavor.
        } else if (flavor.isRepresentationClassInputStream()) {
            theObject = translateStreamToInputStream(str, flavor, format,
                                                               localeTransferable);

            // Target data is a Reader. Obtain data in InputStream format, encoded
            // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
            // back to chars on demand.
        } else if (flavor.isRepresentationClassReader()) {
            if (!(DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
                throw new IOException
                          ("cannot transfer non-text data as Reader");
            }

            InputStream is = (InputStream)translateStreamToInputStream(
                    str, DataFlavor.plainTextFlavor,
                    format, localeTransferable);

            String unicode = DataFlavorUtil.getTextCharset(DataFlavor.plainTextFlavor);

            Reader reader = new InputStreamReader(is, unicode);

            theObject = constructFlavoredObject(reader, flavor, Reader.class);
            // Target data is a byte array
        } else if (byte[].class.equals(flavor.getRepresentationClass())) {
            if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
                theObject = translateBytesToString(inputStreamToByteArray(str), format, localeTransferable)
                        .getBytes(DataFlavorUtil.getTextCharset(flavor));
            } else {
                theObject = inputStreamToByteArray(str);
            }
            // Target data is an RMI object
        } else if (flavor.isRepresentationClassRemote()) {
            try (ObjectInputStream ois = new ObjectInputStream(str)) {
                theObject = DataFlavorUtil.RMI.getMarshalledObject(ois.readObject());
            } catch (Exception e) {
                throw new IOException(e.getMessage());
            }

            // Target data is Serializable
        } else if (flavor.isRepresentationClassSerializable()) {
            try (ObjectInputStream ois =
                     new ObjectInputStream(str))
            {
                theObject = ois.readObject();
            } catch (Exception e) {
                throw new IOException(e.getMessage());
            }
            // Target data is Image
        } else if (DataFlavor.imageFlavor.equals(flavor)) {
            if (!isImageFormat(format)) {
                throw new IOException("data translation failed");
            }
            theObject = platformImageBytesToImage(inputStreamToByteArray(str), format);
        }

        if (theObject == null) {
            throw new IOException("data translation failed");
        }

        return theObject;

    }

    /**
     * For arbitrary flavors, just use the raw InputStream. For text flavors,
     * ReencodingInputStream will decode and reencode the InputStream on demand
     * so that we can strip terminators and search-and-replace EOLN.
     */
    private Object translateStreamToInputStream
        (InputStream str, DataFlavor flavor, long format,
         Transferable localeTransferable) throws IOException
    {
        if (DataFlavorUtil.isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
            str = new ReencodingInputStream
                (str, format, DataFlavorUtil.getTextCharset(flavor),
                 localeTransferable);
        }

        return constructFlavoredObject(str, flavor, InputStream.class);
    }

    /**
     * We support representations which are exactly of the specified Class,
     * and also arbitrary Objects which have a constructor which takes an
     * instance of the Class as its sole parameter.
     */
    private Object constructFlavoredObject(Object arg, DataFlavor flavor,
                                           Class<?> clazz)
        throws IOException
    {
        final Class<?> dfrc = flavor.getRepresentationClass();

        if (clazz.equals(dfrc)) {
            return arg; // simple case
        } else {
            Constructor<?>[] constructors;

            try {
                constructors = AccessController.doPrivileged(
                        (PrivilegedAction<Constructor<?>[]>) dfrc::getConstructors);
            } catch (SecurityException se) {
                throw new IOException(se.getMessage());
            }

            Constructor<?> constructor = Stream.of(constructors)
                    .filter(c -> Modifier.isPublic(c.getModifiers()))
                    .filter(c -> {
                        Class<?>[] ptypes = c.getParameterTypes();
                        return ptypes != null
                                && ptypes.length == 1
                                && clazz.equals(ptypes[0]);
                    })
                    .findFirst()
                    .orElseThrow(() ->
                            new IOException("can't find <init>(L"+ clazz + ";)V for class: " + dfrc.getName()));

            try {
                return constructor.newInstance(arg);
            } catch (Exception e) {
                throw new IOException(e.getMessage());
            }
        }
    }

    /**
     * Used for decoding and reencoding an InputStream on demand so that we
     * can strip NUL terminators and perform EOLN search-and-replace.
     */
    public class ReencodingInputStream extends InputStream {
        BufferedReader wrapped;
        final char[] in = new char[2];
        byte[] out;

        CharsetEncoder encoder;
        CharBuffer inBuf;
        ByteBuffer outBuf;

        char[] eoln;
        int numTerminators;

        boolean eos;
        int index, limit;

        public ReencodingInputStream(InputStream bytestream, long format,
                                     String targetEncoding,
                                     Transferable localeTransferable)
            throws IOException
        {
            Long lFormat = format;

            String sourceEncoding = getBestCharsetForTextFormat(format, localeTransferable);
            wrapped = new BufferedReader(new InputStreamReader(bytestream, sourceEncoding));

            if (targetEncoding == null) {
                // Throw NullPointerException for compatibility with the former
                // call to sun.io.CharToByteConverter.getConverter(null)
                // (Charset.forName(null) throws unspecified IllegalArgumentException
                // now; see 6228568)
                throw new NullPointerException("null target encoding");
            }

            try {
                encoder = Charset.forName(targetEncoding).newEncoder();
                out = new byte[(int)(encoder.maxBytesPerChar() * 2 + 0.5)];
                inBuf = CharBuffer.wrap(in);
                outBuf = ByteBuffer.wrap(out);
            } catch (IllegalCharsetNameException
                    | UnsupportedCharsetException
                    | UnsupportedOperationException e) {
                throw new IOException(e.toString());
            }

            String sEoln = nativeEOLNs.get(lFormat);
            if (sEoln != null) {
                eoln = sEoln.toCharArray();
            }

            // A hope and a prayer that this works generically. This will
            // definitely work on Win32.
            Integer terminators = nativeTerminators.get(lFormat);
            if (terminators != null) {
                numTerminators = terminators;
            }
        }

        private int readChar() throws IOException {
            int c = wrapped.read();

            if (c == -1) { // -1 is EOS
                eos = true;
                return -1;
            }

            // "c == 0" is not quite correct, but good enough on Windows.
            if (numTerminators > 0 && c == 0) {
                eos = true;
                return -1;
            } else if (eoln != null && matchCharArray(eoln, c)) {
                c = '\n' & 0xFFFF;
            }

            return c;
        }

        public int read() throws IOException {
            if (eos) {
                return -1;
            }

            if (index >= limit) {
                // deal with supplementary characters
                int c = readChar();
                if (c == -1) {
                    return -1;
                }

                in[0] = (char) c;
                in[1] = 0;
                inBuf.limit(1);
                if (Character.isHighSurrogate((char) c)) {
                    c = readChar();
                    if (c != -1) {
                        in[1] = (char) c;
                        inBuf.limit(2);
                    }
                }

                inBuf.rewind();
                outBuf.limit(out.length).rewind();
                encoder.encode(inBuf, outBuf, false);
                outBuf.flip();
                limit = outBuf.limit();

                index = 0;

                return read();
            } else {
                return out[index++] & 0xFF;
            }
        }

        public int available() throws IOException {
            return ((eos) ? 0 : (limit - index));
        }

        public void close() throws IOException {
            wrapped.close();
        }

        /**
         * Checks to see if the next array.length characters in wrapped
         * match array. The first character is provided as c. Subsequent
         * characters are read from wrapped itself. When this method returns,
         * the wrapped index may be different from what it was when this
         * method was called.
         */
        private boolean matchCharArray(char[] array, int c)
            throws IOException
        {
            wrapped.mark(array.length);  // BufferedReader supports mark

            int count = 0;
            if ((char)c == array[0]) {
                for (count = 1; count < array.length; count++) {
                    c = wrapped.read();
                    if (c == -1 || ((char)c) != array[count]) {
                        break;
                    }
                }
            }

            if (count == array.length) {
                return true;
            } else {
                wrapped.reset();
                return false;
            }
        }
    }

    /**
     * Decodes a byte array into a set of String filenames.
     */
    protected abstract String[] dragQueryFile(byte[] bytes);

    /**
     * Decodes URIs from either a byte array or a stream.
     */
    protected URI[] dragQueryURIs(InputStream stream,
                                  long format,
                                  Transferable localeTransferable)
      throws IOException
    {
        throw new IOException(
            new UnsupportedOperationException("not implemented on this platform"));
    }

    /**
     * Translates either a byte array or an input stream which contain
     * platform-specific image data in the given format into an Image.
     */


    protected abstract Image platformImageBytesToImage(
        byte[] bytes,long format) throws IOException;

    /**
     * Translates either a byte array or an input stream which contain
     * an image data in the given standard format into an Image.
     *
     * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif
     */
    protected Image standardImageBytesToImage(
        byte[] bytes, String mimeType) throws IOException
    {

        Iterator<ImageReader> readerIterator = ImageIO.getImageReadersByMIMEType(mimeType);

        if (!readerIterator.hasNext()) {
            throw new IOException("No registered service provider can decode " +
                                  " an image from " + mimeType);
        }

        IOException ioe = null;

        while (readerIterator.hasNext()) {
            ImageReader imageReader = readerIterator.next();
            try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
                try (ImageInputStream imageInputStream = ImageIO.createImageInputStream(bais)) {
                    ImageReadParam param = imageReader.getDefaultReadParam();
                    imageReader.setInput(imageInputStream, true, true);
                    BufferedImage bufferedImage = imageReader.read(imageReader.getMinIndex(), param);
                    if (bufferedImage != null) {
                        return bufferedImage;
                    }
                } finally {
                    imageReader.dispose();
                }
            } catch (IOException e) {
                ioe = e;
                continue;
            }
        }

        if (ioe == null) {
            ioe = new IOException("Registered service providers failed to decode"
                                  + " an image from " + mimeType);
        }

        throw ioe;
    }

    /**
     * Translates a Java Image into a byte array which contains platform-
     * specific image data in the given format.
     */
    protected abstract byte[] imageToPlatformBytes(Image image, long format)
      throws IOException;

    /**
     * Translates a Java Image into a byte array which contains
     * an image data in the given standard format.
     *
     * @param mimeType image MIME type, such as: image/png, image/jpeg
     */
    protected byte[] imageToStandardBytes(Image image, String mimeType)
      throws IOException {
        IOException originalIOE = null;

        Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);

        if (!writerIterator.hasNext()) {
            throw new IOException("No registered service provider can encode " +
                                  " an image to " + mimeType);
        }

        if (image instanceof RenderedImage) {
            // Try to encode the original image.
            try {
                return imageToStandardBytesImpl((RenderedImage)image, mimeType);
            } catch (IOException ioe) {
                originalIOE = ioe;
            }
        }

        // Retry with a BufferedImage.
        int width = 0;
        int height = 0;
        if (image instanceof ToolkitImage) {
            ImageRepresentation ir = ((ToolkitImage)image).getImageRep();
            ir.reconstruct(ImageObserver.ALLBITS);
            width = ir.getWidth();
            height = ir.getHeight();
        } else {
            width = image.getWidth(null);
            height = image.getHeight(null);
        }

        ColorModel model = ColorModel.getRGBdefault();
        WritableRaster raster =
            model.createCompatibleWritableRaster(width, height);

        BufferedImage bufferedImage =
            new BufferedImage(model, raster, model.isAlphaPremultiplied(),
                              null);

        Graphics g = bufferedImage.getGraphics();
        try {
            g.drawImage(image, 0, 0, width, height, null);
        } finally {
            g.dispose();
        }

        try {
            return imageToStandardBytesImpl(bufferedImage, mimeType);
        } catch (IOException ioe) {
            if (originalIOE != null) {
                throw originalIOE;
            } else {
                throw ioe;
            }
        }
    }

    byte[] imageToStandardBytesImpl(RenderedImage renderedImage,
                                              String mimeType)
        throws IOException {

        Iterator<ImageWriter> writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);

        ImageTypeSpecifier typeSpecifier =
            new ImageTypeSpecifier(renderedImage);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        IOException ioe = null;

        while (writerIterator.hasNext()) {
            ImageWriter imageWriter = writerIterator.next();
            ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();

            if (!writerSpi.canEncodeImage(typeSpecifier)) {
                continue;
            }

            try {
                try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(baos)) {
                    imageWriter.setOutput(imageOutputStream);
                    imageWriter.write(renderedImage);
                    imageOutputStream.flush();
                }
            } catch (IOException e) {
                imageWriter.dispose();
                baos.reset();
                ioe = e;
                continue;
            }

            imageWriter.dispose();
            baos.close();
            return baos.toByteArray();
        }

        baos.close();

        if (ioe == null) {
            ioe = new IOException("Registered service providers failed to encode "
                                  + renderedImage + " to " + mimeType);
        }

        throw ioe;
    }

    /**
     * Concatenates the data represented by two objects. Objects can be either
     * byte arrays or instances of <code>InputStream</code>. If both arguments
     * are byte arrays byte array will be returned. Otherwise an
     * <code>InputStream</code> will be returned.
     * <p>
     * Currently is only called from native code to prepend palette data to
     * platform-specific image data during image transfer on Win32.
     *
     * @param obj1 the first object to be concatenated.
     * @param obj2 the second object to be concatenated.
     * @return a byte array or an <code>InputStream</code> which represents
     *         a logical concatenation of the two arguments.
     * @throws NullPointerException is either of the arguments is
     *         <code>null</code>
     * @throws ClassCastException is either of the arguments is
     *         neither byte array nor an instance of <code>InputStream</code>.
     */
    private Object concatData(Object obj1, Object obj2) {
        InputStream str1 = null;
        InputStream str2 = null;

        if (obj1 instanceof byte[]) {
            byte[] arr1 = (byte[])obj1;
            if (obj2 instanceof byte[]) {
                byte[] arr2 = (byte[])obj2;
                byte[] ret = new byte[arr1.length + arr2.length];
                System.arraycopy(arr1, 0, ret, 0, arr1.length);
                System.arraycopy(arr2, 0, ret, arr1.length, arr2.length);
                return ret;
            } else {
                str1 = new ByteArrayInputStream(arr1);
                str2 = (InputStream)obj2;
            }
        } else {
            str1 = (InputStream)obj1;
            if (obj2 instanceof byte[]) {
                str2 = new ByteArrayInputStream((byte[])obj2);
            } else {
                str2 = (InputStream)obj2;
            }
        }

        return new SequenceInputStream(str1, str2);
    }

    public byte[] convertData(final Object source,
                              final Transferable contents,
                              final long format,
                              final Map<Long, DataFlavor> formatMap,
                              final boolean isToolkitThread)
        throws IOException
    {
        byte[] ret = null;

        /*
         * If the current thread is the Toolkit thread we should post a
         * Runnable to the event dispatch thread associated with source Object,
         * since translateTransferable() calls Transferable.getTransferData()
         * that may contain client code.
         */
        if (isToolkitThread) try {
            final Stack<byte[]> stack = new Stack<>();
            final Runnable dataConverter = new Runnable() {
                // Guard against multiple executions.
                private boolean done = false;
                public void run() {
                    if (done) {
                        return;
                    }
                    byte[] data = null;
                    try {
                        DataFlavor flavor = formatMap.get(format);
                        if (flavor != null) {
                            data = translateTransferable(contents, flavor, format);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        data = null;
                    }
                    try {
                        getToolkitThreadBlockedHandler().lock();
                        stack.push(data);
                        getToolkitThreadBlockedHandler().exit();
                    } finally {
                        getToolkitThreadBlockedHandler().unlock();
                        done = true;
                    }
                }
            };

            final AppContext appContext = SunToolkit.targetToAppContext(source);

            getToolkitThreadBlockedHandler().lock();

            if (appContext != null) {
                appContext.put(DATA_CONVERTER_KEY, dataConverter);
            }

            SunToolkit.executeOnEventHandlerThread(source, dataConverter);

            while (stack.empty()) {
                getToolkitThreadBlockedHandler().enter();
            }

            if (appContext != null) {
                appContext.remove(DATA_CONVERTER_KEY);
            }

            ret = stack.pop();
        } finally {
            getToolkitThreadBlockedHandler().unlock();
        } else {
            DataFlavor flavor = formatMap.get(format);
            if (flavor != null) {
                ret = translateTransferable(contents, flavor, format);
            }
        }

        return ret;
    }

    public void processDataConversionRequests() {
        if (EventQueue.isDispatchThread()) {
            AppContext appContext = AppContext.getAppContext();
            getToolkitThreadBlockedHandler().lock();
            try {
                Runnable dataConverter =
                    (Runnable)appContext.get(DATA_CONVERTER_KEY);
                if (dataConverter != null) {
                    dataConverter.run();
                    appContext.remove(DATA_CONVERTER_KEY);
                }
            } finally {
                getToolkitThreadBlockedHandler().unlock();
            }
        }
    }

    public abstract ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler();

    /**
     * Helper function to reduce a Map with Long keys to a long array.
     * <p>
     * The map keys are sorted according to the native formats preference
     * order.
     */
    public static long[] keysToLongArray(SortedMap<Long, ?> map) {
        Set<Long> keySet = map.keySet();
        long[] retval = new long[keySet.size()];
        int i = 0;
        for (Iterator<Long> iter = keySet.iterator(); iter.hasNext(); i++) {
            retval[i] = iter.next();
        }
        return retval;
    }

    /**
     * Helper function to convert a Set of DataFlavors to a sorted array.
     * The array will be sorted according to <code>DataFlavorComparator</code>.
     */
    public static DataFlavor[] setToSortedDataFlavorArray(Set<DataFlavor> flavorsSet) {
        DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
        flavorsSet.toArray(flavors);
        final Comparator<DataFlavor> comparator = DataFlavorUtil.getDataFlavorComparator().reversed();
        Arrays.sort(flavors, comparator);
        return flavors;
    }

    /**
     * Helper function to convert an InputStream to a byte[] array.
     */
    protected static byte[] inputStreamToByteArray(InputStream str)
        throws IOException
    {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            int len = 0;
            byte[] buf = new byte[8192];

            while ((len = str.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }

            return baos.toByteArray();
        }
    }

    /**
     * Returns platform-specific mappings for the specified native.
     * If there are no platform-specific mappings for this native, the method
     * returns an empty <code>List</code>.
     */
    public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
        return new LinkedHashSet<>();
    }

    /**
     * Returns platform-specific mappings for the specified flavor.
     * If there are no platform-specific mappings for this flavor, the method
     * returns an empty <code>List</code>.
     */
    public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
        return new LinkedHashSet<>();
    }
}