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