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