/* * Copyright (c) 2012, 2017, 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 com.sun.javafx.embed.swing; import javafx.scene.input.DataFormat; import java.io.ByteArrayOutputStream; import java.util.Set; import java.util.Map; import java.util.List; import java.util.HashMap; import java.util.HashSet; import java.util.Collections; import java.util.ArrayList; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; final class DataFlavorUtils { static String getFxMimeType(final DataFlavor flavor) { return flavor.getPrimaryType() + "/" + flavor.getSubType(); } static DataFlavor[] getDataFlavors(String[] mimeTypes) { final ArrayList flavors = new ArrayList(mimeTypes.length); for (String mime : mimeTypes) { DataFlavor flavor = null; try { flavor = new DataFlavor(mime); } catch (ClassNotFoundException | IllegalArgumentException e) { continue; } flavors.add(flavor); } return flavors.toArray(new DataFlavor[0]); } static DataFlavor getDataFlavor(final DataFormat format) { DataFlavor[] flavors = getDataFlavors(format.getIdentifiers().toArray(new String[1])); // Well, that's our best guess... return flavors.length == 0 ? null : flavors[0]; } static String getMimeType(final DataFormat format) { // Well, that's our best guess... for (String id : format.getIdentifiers()) return id; return null; } static DataFormat getDataFormat(final DataFlavor flavor) { String mimeType = getFxMimeType(flavor); DataFormat dataFormat = DataFormat.lookupMimeType(mimeType); if (dataFormat == null) { dataFormat = new DataFormat(mimeType); // are we ready for this yet? } return dataFormat; } /** * InputStream implementation backed by a ByteBuffer. * It can handle byte buffers that are backed by arrays * as well as operating system memory. */ private static class ByteBufferInputStream extends InputStream { private final ByteBuffer bb; private ByteBufferInputStream(ByteBuffer bb) { this.bb = bb; } @Override public int available() { return bb.remaining(); } @Override public int read() throws IOException { if (!bb.hasRemaining()) return -1; return bb.get() & 0xFF; // Make sure the value is in [0..255] } @Override public int read(byte[] bytes, int off, int len) throws IOException { if (!bb.hasRemaining()) return -1; len = Math.min(len, bb.remaining()); bb.get(bytes, off, len); return len; } } static Object adjustFxData(final DataFlavor flavor, final Object fxData) throws UnsupportedEncodingException { // TBD: Handle more data types!!! if (fxData instanceof String) { if (flavor.isRepresentationClassInputStream()) { final String encoding = flavor.getParameter("charset"); return new ByteArrayInputStream(encoding != null ? ((String) fxData).getBytes(encoding) : ((String) fxData).getBytes()); } if (flavor.isRepresentationClassByteBuffer()) { // ... } } if (fxData instanceof ByteBuffer) { if (flavor.isRepresentationClassInputStream()) { return new ByteBufferInputStream((ByteBuffer)fxData); } } return fxData; } static Object adjustSwingData(final DataFlavor flavor, final String mimeType, final Object swingData) { if (swingData == null) { return swingData; } if (flavor.isFlavorJavaFileListType()) { // RT-12663 final List fileList = (List)swingData; final String[] paths = new String[fileList.size()]; int i = 0; for (File f : fileList) { paths[i++] = f.getPath(); } return paths; } DataFormat dataFormat = DataFormat.lookupMimeType(mimeType); if (DataFormat.PLAIN_TEXT.equals(dataFormat)) { if (flavor.isFlavorTextType()) { if (swingData instanceof InputStream) { InputStream in = (InputStream)swingData; // TBD: charset ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] bb = new byte[64]; try { int len = in.read(bb); while (len != -1) { out.write(bb, 0, len); len = in.read(bb); } out.close(); return new String(out.toByteArray()); } catch (Exception z) { // ignore } } } else if (swingData != null) { return swingData.toString(); } } return swingData; } static Map adjustSwingDataFlavors(final DataFlavor[] flavors) { // Group data flavors by FX mime type. final Map> mimeType2Flavors = new HashMap<>(flavors.length); for (DataFlavor flavor : flavors) { final String mimeType = getFxMimeType(flavor); if (mimeType2Flavors.containsKey(mimeType)) { final Set mimeTypeFlavors = mimeType2Flavors.get( mimeType); try { mimeTypeFlavors.add(flavor); } catch (UnsupportedOperationException e) { // List of data flavors corresponding to FX mime // type has been finalized already. } } else { Set mimeTypeFlavors = new HashSet(); // If this is text data flavor use DataFlavor representing // a Java Unicode String class. This is what FX expects from // clipboard. if (flavor.isFlavorTextType()) { mimeTypeFlavors.add(DataFlavor.stringFlavor); mimeTypeFlavors = Collections.unmodifiableSet( mimeTypeFlavors); } else { mimeTypeFlavors.add(flavor); } mimeType2Flavors.put(mimeType, mimeTypeFlavors); } } // Choose the best data flavor corresponding to the given FX mime type final Map mimeType2Flavor = new HashMap<>(); for (String mimeType : mimeType2Flavors.keySet()) { final DataFlavor[] mimeTypeFlavors = mimeType2Flavors.get(mimeType). toArray(new DataFlavor[0]); if (mimeTypeFlavors.length == 1) { mimeType2Flavor.put(mimeType, mimeTypeFlavors[0]); } else { // TBD: something better!!! mimeType2Flavor.put(mimeType, mimeTypeFlavors[0]); } } return mimeType2Flavor; } private static Object readData(final Transferable t, final DataFlavor flavor) { Object obj = null; try { obj = t.getTransferData(flavor); } catch (UnsupportedFlavorException ex) { // FIXME: report error ex.printStackTrace(System.err); } catch (IOException ex) { // FIXME: report error ex.printStackTrace(System.err); } return obj; } /** * Returns a Map populated with keys corresponding to all the MIME types * available in the provided Transferable object. If fetchData is true, * then the data is fetched as well, otherwise all the values are set to * null. */ static Map readAllData(final Transferable t, final Map fxMimeType2DataFlavor, final boolean fetchData) { final Map fxMimeType2Data = new HashMap<>(); for (DataFlavor flavor : t.getTransferDataFlavors()) { Object obj = fetchData ? readData(t, flavor) : null; if (obj != null || !fetchData) { String mimeType = getFxMimeType(flavor); obj = adjustSwingData(flavor, mimeType, obj); fxMimeType2Data.put(mimeType, obj); } } for (Map.Entry e: fxMimeType2DataFlavor.entrySet()) { String mimeType = e.getKey(); DataFlavor flavor = e.getValue(); Object obj = fetchData ? readData(t, flavor) : null; if (obj != null || !fetchData) { obj = adjustSwingData(flavor, mimeType, obj); fxMimeType2Data.put(e.getKey(), obj); } } return fxMimeType2Data; } }