1 /*
   2  * Copyright (c) 2012, 2014, 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 javafx.embed.swing;
  27 
  28 import javafx.scene.input.DataFormat;
  29 
  30 import java.io.ByteArrayOutputStream;
  31 import java.util.Set;
  32 import java.util.Map;
  33 import java.util.List;
  34 import java.util.HashMap;
  35 import java.util.HashSet;
  36 import java.util.Collections;
  37 
  38 import java.awt.datatransfer.DataFlavor;
  39 import java.awt.datatransfer.Transferable;
  40 import java.awt.datatransfer.UnsupportedFlavorException;
  41 
  42 import java.io.ByteArrayInputStream;
  43 import java.io.File;
  44 import java.io.IOException;
  45 import java.io.InputStream;
  46 import java.io.UnsupportedEncodingException;
  47 
  48 import java.nio.ByteBuffer;
  49 
  50 
  51 final class DataFlavorUtils {
  52 
  53     static String getFxMimeType(final DataFlavor flavor) {
  54         return flavor.getPrimaryType() + "/" + flavor.getSubType();
  55     }
  56 
  57     /**
  58      * InputStream implementation backed by a ByteBuffer.
  59      * It can handle byte buffers that are backed by arrays
  60      * as well as operating system memory.
  61      */
  62     private static class ByteBufferInputStream extends InputStream {
  63         private final ByteBuffer bb;
  64 
  65         private ByteBufferInputStream(ByteBuffer bb) { this.bb = bb; }
  66 
  67         @Override public int available() { return bb.remaining(); }
  68 
  69         @Override public int read() throws IOException {
  70             if (!bb.hasRemaining()) return -1;
  71             return bb.get() & 0xFF; // Make sure the value is in [0..255]
  72         }
  73 
  74         @Override public int read(byte[] bytes, int off, int len) throws IOException {
  75             if (!bb.hasRemaining()) return -1;
  76             len = Math.min(len, bb.remaining());
  77             bb.get(bytes, off, len);
  78             return len;
  79         }
  80     }
  81 
  82     static Object adjustFxData(final DataFlavor flavor, final Object fxData)
  83             throws UnsupportedEncodingException
  84     {
  85         // TBD: Handle more data types!!!
  86         if (fxData instanceof String) {
  87             if (flavor.isRepresentationClassInputStream()) {
  88                 final String encoding = flavor.getParameter("charset");
  89                 return new ByteArrayInputStream(encoding != null
  90                         ? ((String) fxData).getBytes(encoding)
  91                         : ((String) fxData).getBytes());
  92             }
  93             if (flavor.isRepresentationClassByteBuffer()) {
  94                 // ...
  95             }
  96         }
  97         if (fxData instanceof ByteBuffer) {
  98             if (flavor.isRepresentationClassInputStream()) {
  99                 return new ByteBufferInputStream((ByteBuffer)fxData);
 100             }
 101         }
 102         return fxData;
 103     }
 104 
 105     static Object adjustSwingData(final DataFlavor flavor,
 106                                   final String mimeType,
 107                                   final Object swingData)
 108     {
 109         if (swingData == null) {
 110             return swingData;
 111         }
 112 
 113         if (flavor.isFlavorJavaFileListType()) {
 114             // RT-12663
 115             final List<File> fileList = (List<File>)swingData;
 116             final String[] paths = new String[fileList.size()];
 117             int i = 0;
 118             for (File f : fileList) {
 119                 paths[i++] = f.getPath();
 120             }
 121             return paths;
 122         }
 123         DataFormat dataFormat = DataFormat.lookupMimeType(mimeType);
 124         if (DataFormat.PLAIN_TEXT.equals(dataFormat)) {
 125             if (flavor.isFlavorTextType()) {
 126                 if (swingData instanceof InputStream) {
 127                     InputStream in = (InputStream)swingData;
 128                     // TBD: charset
 129                     ByteArrayOutputStream out = new ByteArrayOutputStream();
 130                     byte[] bb = new byte[64];
 131                     try {
 132                         int len = in.read(bb);
 133                         while (len != -1) {
 134                             out.write(bb, 0, len);
 135                             len = in.read(bb);
 136                         }
 137                         out.close();
 138                         return new String(out.toByteArray());
 139                     } catch (Exception z) {
 140                         // ignore
 141                     }
 142                 }
 143             } else if (swingData != null) {
 144                 return swingData.toString();
 145             }
 146         }
 147         return swingData;
 148     }
 149 
 150     static Map<String, DataFlavor> adjustSwingDataFlavors(final DataFlavor[] flavors) {
 151         // Group data flavors by FX mime type.
 152         final Map<String, Set<DataFlavor>> mimeType2Flavors =
 153                 new HashMap<>(flavors.length);
 154         for (DataFlavor flavor : flavors) {
 155             final String mimeType = getFxMimeType(flavor);
 156             if (mimeType2Flavors.containsKey(mimeType)) {
 157                 final Set<DataFlavor> mimeTypeFlavors = mimeType2Flavors.get(
 158                         mimeType);
 159                 try {
 160                     mimeTypeFlavors.add(flavor);
 161                 } catch (UnsupportedOperationException e) {
 162                     // List of data flavors corresponding to FX mime
 163                     // type has been finalized already.
 164                 }
 165             } else {
 166                 Set<DataFlavor> mimeTypeFlavors = new HashSet<DataFlavor>();
 167 
 168                 // If this is text data flavor use DataFlavor representing
 169                 // a Java Unicode String class. This is what FX expects from
 170                 // clipboard.
 171                 if (flavor.isFlavorTextType()) {
 172                     mimeTypeFlavors.add(DataFlavor.stringFlavor);
 173                     mimeTypeFlavors = Collections.unmodifiableSet(
 174                             mimeTypeFlavors);
 175                 } else {
 176                     mimeTypeFlavors.add(flavor);
 177                 }
 178 
 179                 mimeType2Flavors.put(mimeType, mimeTypeFlavors);
 180             }
 181         }
 182 
 183         // Choose the best data flavor corresponding to the given FX mime type
 184         final Map<String, DataFlavor> mimeType2Flavor = new HashMap<>();
 185         for (String mimeType : mimeType2Flavors.keySet()) {
 186             final DataFlavor[] mimeTypeFlavors = mimeType2Flavors.get(mimeType).
 187                     toArray(new DataFlavor[0]);
 188             if (mimeTypeFlavors.length == 1) {
 189                 mimeType2Flavor.put(mimeType, mimeTypeFlavors[0]);
 190             } else {
 191                 // TBD: something better!!!
 192                 mimeType2Flavor.put(mimeType, mimeTypeFlavors[0]);
 193             }
 194         }
 195 
 196         return mimeType2Flavor;
 197     }
 198 
 199     private static Object readData(final Transferable t, final DataFlavor flavor) {
 200         Object obj = null;
 201         try {
 202             obj = t.getTransferData(flavor);
 203         } catch (UnsupportedFlavorException ex) {
 204             // FIXME: report error
 205             ex.printStackTrace(System.err);
 206         } catch (IOException ex) {
 207             // FIXME: report error
 208             ex.printStackTrace(System.err);
 209         }
 210         return obj;
 211     }
 212 
 213     /**
 214      * Returns a Map populated with keys corresponding to all the MIME types
 215      * available in the provided Transferable object. If fetchData is true,
 216      * then the data is fetched as well, otherwise all the values are set to
 217      * null.
 218      */
 219     static Map<String, Object> readAllData(final Transferable t,
 220                                            final Map<String, DataFlavor> fxMimeType2DataFlavor,
 221                                            final boolean fetchData)
 222     {
 223         final Map<String, Object> fxMimeType2Data = new HashMap<>();
 224         for (DataFlavor flavor : t.getTransferDataFlavors()) {
 225             Object obj = fetchData ? readData(t, flavor) : null;
 226             if (obj != null || !fetchData) {
 227                 String mimeType = getFxMimeType(flavor);
 228                 obj = adjustSwingData(flavor, mimeType, obj);
 229                 fxMimeType2Data.put(mimeType, obj);
 230             }
 231         }
 232         for (Map.Entry<String, DataFlavor> e: fxMimeType2DataFlavor.entrySet()) {
 233             String mimeType = e.getKey();
 234             DataFlavor flavor = e.getValue();
 235             Object obj = fetchData ? readData(t, flavor) : null;
 236             if (obj != null || !fetchData) {
 237                 obj = adjustSwingData(flavor, mimeType, obj);
 238                 fxMimeType2Data.put(e.getKey(), obj);
 239             }
 240         }
 241         return fxMimeType2Data;
 242     }
 243 }