1 2 /* 3 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package sun.lwawt.macosx; 28 29 import java.awt.*; 30 31 import java.io.*; 32 import java.net.URI; 33 import java.net.URISyntaxException; 34 import java.net.URL; 35 import java.nio.charset.Charset; 36 import java.text.Normalizer; 37 import java.text.Normalizer.Form; 38 import java.util.*; 39 import java.util.regex.*; 40 import java.awt.datatransfer.*; 41 import sun.awt.datatransfer.*; 42 43 public class CDataTransferer extends DataTransferer { 44 private static final Map<String, Long> predefinedClipboardNameMap; 45 private static final Map<Long, String> predefinedClipboardFormatMap; 46 47 // See SystemFlavorMap, or the flavormap.properties file: 48 // We should define a few more types in flavormap.properties, it's rather slim now. 49 private static final String[] predefinedClipboardNames = { 50 "", 51 "STRING", 52 "FILE_NAME", 53 "TIFF", 54 "RICH_TEXT", 55 "HTML", 56 "PDF", 57 "URL", 58 "PNG", 59 "JFIF", 60 "XPICT" 61 }; 62 63 static { 64 Map<String, Long> nameMap = new HashMap<>(predefinedClipboardNames.length, 1.0f); 65 Map<Long, String> formatMap = new HashMap<>(predefinedClipboardNames.length, 1.0f); 66 for (int i = 1; i < predefinedClipboardNames.length; i++) { 67 nameMap.put(predefinedClipboardNames[i], (long) i); 68 formatMap.put((long) i, predefinedClipboardNames[i]); 69 } 70 predefinedClipboardNameMap = Collections.synchronizedMap(nameMap); 71 predefinedClipboardFormatMap = Collections.synchronizedMap(formatMap); 72 } 73 74 public static final int CF_UNSUPPORTED = 0; 75 public static final int CF_STRING = 1; 76 public static final int CF_FILE = 2; 77 public static final int CF_TIFF = 3; 78 public static final int CF_RICH_TEXT = 4; 79 public static final int CF_HTML = 5; 80 public static final int CF_PDF = 6; 81 public static final int CF_URL = 7; 82 public static final int CF_PNG = 8; 83 public static final int CF_JPEG = 9; 84 public static final int CF_XPICT = 10; 85 86 private CDataTransferer() {} 87 88 private static CDataTransferer fTransferer; 89 90 static synchronized CDataTransferer getInstanceImpl() { 91 if (fTransferer == null) { 92 fTransferer = new CDataTransferer(); 93 } 94 95 return fTransferer; 96 } 97 98 @Override 99 public String getDefaultUnicodeEncoding() { 100 return "utf-16le"; 101 } 102 103 @Override 104 public boolean isLocaleDependentTextFormat(long format) { 105 return format == CF_STRING; 106 } 107 108 @Override 109 public boolean isFileFormat(long format) { 110 return format == CF_FILE; 111 } 112 113 @Override 114 public boolean isImageFormat(long format) { 115 int ifmt = (int)format; 116 switch(ifmt) { 117 case CF_TIFF: 118 case CF_PDF: 119 case CF_PNG: 120 case CF_JPEG: 121 return true; 122 default: 123 return false; 124 } 125 } 126 127 @Override 128 public Object translateBytes(byte[] bytes, DataFlavor flavor, 129 long format, Transferable transferable) throws IOException { 130 131 if (format == CF_URL && URL.class.equals(flavor.getRepresentationClass())) { 132 String charset = Charset.defaultCharset().name(); 133 if (transferable != null && transferable.isDataFlavorSupported(javaTextEncodingFlavor)) { 134 try { 135 charset = new String((byte[]) transferable.getTransferData(javaTextEncodingFlavor), "UTF-8"); 136 } catch (UnsupportedFlavorException cannotHappen) { 137 } 138 } 139 140 String xml = new String(bytes, charset); 141 // macosx pasteboard returns a property list that consists of one URL 142 // let's extract it. 143 return new URL(extractURL(xml)); 144 } 145 146 if(isUriListFlavor(flavor) && format == CF_FILE) { 147 // dragQueryFile works fine with files and url, 148 // it parses and extracts values from property list. 149 // maxosx always returns property list for 150 // CF_URL and CF_FILE 151 String[] strings = dragQueryFile(bytes); 152 if(strings == null) { 153 return null; 154 } 155 bytes = String.join(System.getProperty("line.separator"), 156 strings).getBytes(); 157 // now we extracted uri from xml, now we should treat it as 158 // regular string that allows to translate data to target represantation 159 // class by base method 160 format = CF_STRING; 161 } else if (format == CF_STRING) { 162 bytes = Normalizer.normalize(new String(bytes, "UTF8"), Form.NFC).getBytes("UTF8"); 163 } 164 165 return super.translateBytes(bytes, flavor, format, transferable); 166 } 167 168 private String extractURL(String xml) { 169 Pattern urlExtractorPattern = Pattern.compile("<string>(.*)</string>"); 170 Matcher matcher = urlExtractorPattern.matcher(xml); 171 if (matcher.find()) { 172 return matcher.group(1); 173 } else { 174 return null; 175 } 176 } 177 178 @Override 179 protected synchronized Long getFormatForNativeAsLong(String str) { 180 Long format = predefinedClipboardNameMap.get(str); 181 182 if (format == null) { 183 if (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) { 184 // Do not try to access native system for the unknown format 185 return -1L; 186 } 187 format = registerFormatWithPasteboard(str); 188 predefinedClipboardNameMap.put(str, format); 189 predefinedClipboardFormatMap.put(format, str); 190 } 191 192 return format; 193 } 194 195 /* 196 * Adds type to native mapping NSDictionary. 197 */ 198 private native long registerFormatWithPasteboard(String type); 199 200 // Get registered native format string for an index, return null if unknown: 201 private native String formatForIndex(long index); 202 203 @Override 204 protected String getNativeForFormat(long format) { 205 String returnValue = null; 206 207 // The most common case - just index the array of predefined names: 208 if (format >= 0 && format < predefinedClipboardNames.length) { 209 returnValue = predefinedClipboardNames[(int) format]; 210 } else { 211 Long formatObj = format; 212 returnValue = predefinedClipboardFormatMap.get(formatObj); 213 214 // predefinedClipboardFormatMap may not know this format: 215 if (returnValue == null) { 216 returnValue = formatForIndex(format); 217 218 // Native clipboard may not know this format either: 219 if (returnValue != null) { 220 predefinedClipboardNameMap.put(returnValue, formatObj); 221 predefinedClipboardFormatMap.put(formatObj, returnValue); 222 } 223 } 224 } 225 226 if (returnValue == null) { 227 returnValue = predefinedClipboardNames[CF_UNSUPPORTED]; 228 } 229 230 return returnValue; 231 } 232 233 private final ToolkitThreadBlockedHandler handler = new CToolkitThreadBlockedHandler(); 234 235 @Override 236 public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() { 237 return handler; 238 } 239 240 @Override 241 protected byte[] imageToPlatformBytes(Image image, long format) { 242 return CImage.getCreator().getPlatformImageBytes(image); 243 } 244 245 private static native String[] nativeDragQueryFile(final byte[] bytes); 246 @Override 247 protected String[] dragQueryFile(final byte[] bytes) { 248 if (bytes == null) return null; 249 if (new String(bytes).startsWith("Unsupported type")) return null; 250 return nativeDragQueryFile(bytes); 251 } 252 253 254 @Override 255 protected Image platformImageBytesToImage(byte[] bytes, long format) throws IOException { 256 return CImage.getCreator().createImageFromPlatformImageBytes(bytes); 257 } 258 259 @Override 260 protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException { 261 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 262 for (String file : fileList) { 263 byte[] bytes = file.getBytes(); 264 bos.write(bytes, 0, bytes.length); 265 bos.write(0); 266 } 267 return bos; 268 } 269 270 @Override 271 protected boolean isURIListFormat(long format) { 272 String nat = getNativeForFormat(format); 273 if (nat == null) { 274 return false; 275 } 276 try { 277 DataFlavor df = new DataFlavor(nat); 278 if (isUriListFlavor(df)) { 279 return true; 280 } 281 } catch (Exception e) { 282 // Not a MIME format. 283 } 284 return false; 285 } 286 287 private boolean isUriListFlavor(DataFlavor df) { 288 if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) { 289 return true; 290 } 291 return false; 292 } 293 }