1 /* 2 * Copyright (c) 2011, 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 sun.lwawt.macosx; 27 28 import java.awt.*; 29 30 import java.io.*; 31 import java.net.URL; 32 import java.nio.charset.Charset; 33 import java.text.Normalizer; 34 import java.text.Normalizer.Form; 35 import java.util.*; 36 import java.util.regex.*; 37 38 import java.awt.datatransfer.*; 39 import sun.awt.datatransfer.*; 40 41 public class CDataTransferer extends DataTransferer { 42 private static final Map<String, Long> predefinedClipboardNameMap; 43 private static final Map<Long, String> predefinedClipboardFormatMap; 44 45 // See SystemFlavorMap, or the flavormap.properties file: 46 // We should define a few more types in flavormap.properties, it's rather slim now. 47 private static final String[] predefinedClipboardNames = { 48 "", 49 "STRING", 50 "FILE_NAME", 51 "TIFF", 52 "RICH_TEXT", 53 "HTML", 54 "PDF", 55 "URL", 56 "PNG", 57 "JFIF", 58 "XPICT" 59 }; 60 61 static { 62 Map<String, Long> nameMap = new HashMap<>(predefinedClipboardNames.length, 1.0f); 63 Map<Long, String> formatMap = new HashMap<>(predefinedClipboardNames.length, 1.0f); 64 for (int i = 1; i < predefinedClipboardNames.length; i++) { 65 nameMap.put(predefinedClipboardNames[i], (long) i); 66 formatMap.put((long) i, predefinedClipboardNames[i]); 67 } 68 predefinedClipboardNameMap = Collections.synchronizedMap(nameMap); 69 predefinedClipboardFormatMap = Collections.synchronizedMap(formatMap); 70 } 71 72 public static final int CF_UNSUPPORTED = 0; 73 public static final int CF_STRING = 1; 74 public static final int CF_FILE = 2; 75 public static final int CF_TIFF = 3; 76 public static final int CF_RICH_TEXT = 4; 77 public static final int CF_HTML = 5; 78 public static final int CF_PDF = 6; 79 public static final int CF_URL = 7; 80 public static final int CF_PNG = 8; 81 public static final int CF_JPEG = 9; 82 public static final int CF_XPICT = 10; 83 84 private CDataTransferer() {} 85 86 private static CDataTransferer fTransferer; 87 88 static synchronized CDataTransferer getInstanceImpl() { 89 if (fTransferer == null) { 90 fTransferer = new CDataTransferer(); 91 } 92 93 return fTransferer; 94 } 95 96 @Override 97 public String getDefaultUnicodeEncoding() { 98 return "utf-16le"; 99 } 100 101 @Override 102 public boolean isLocaleDependentTextFormat(long format) { 103 return format == CF_STRING; 104 } 105 106 @Override 107 public boolean isFileFormat(long format) { 108 return format == CF_FILE; 109 } 110 111 @Override 112 public boolean isImageFormat(long format) { 113 int ifmt = (int)format; 114 switch(ifmt) { 115 case CF_TIFF: 116 case CF_PDF: 117 case CF_PNG: 118 case CF_JPEG: 119 return true; 120 default: 121 return false; 122 } 123 } 124 125 @Override 126 public Object translateBytes(byte[] bytes, DataFlavor flavor, 127 long format, Transferable transferable) throws IOException { 128 129 if (format == CF_URL && URL.class.equals(flavor.getRepresentationClass())) { 130 String charset = Charset.defaultCharset().name(); 131 if (transferable != null && transferable.isDataFlavorSupported(javaTextEncodingFlavor)) { 132 try { 133 charset = new String((byte[]) transferable.getTransferData(javaTextEncodingFlavor), "UTF-8"); 134 } catch (UnsupportedFlavorException cannotHappen) { 135 } 136 } 137 String xml = new String(bytes, charset); 138 // macosx pasteboard returns a property list that consists of one URL 139 // let's extract it. 140 return new URL(extractURL(xml)); 141 } 142 143 if (format == CF_STRING) { 144 bytes = Normalizer.normalize(new String(bytes, "UTF8"), Form.NFC).getBytes("UTF8"); 145 } 146 147 return super.translateBytes(bytes, flavor, format, transferable); 148 } 149 150 /** 151 * Macosx pasteboard returns xml document that contains one URL, for exmple: 152 * <pre> 153 * {@code 154 * <?xml version=\"1.0\" encoding=\"UTF-8\"?> 155 * <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"> 156 * <plist version=\"1.0\"> 157 * <array> 158 * <string>file:///path_to_file</string> 159 * <string></string> 160 * </array> 161 * </plist> 162 * } 163 * </pre> 164 */ 165 private String extractURL(String xml) { 166 Pattern urlExtractorPattern = Pattern.compile("<string>(.*)</string>"); 167 Matcher matcher = urlExtractorPattern.matcher(xml); 168 if (matcher.find()) { 169 return matcher.group(1); 170 } else { 171 return null; 172 } 173 } 174 175 @Override 176 protected synchronized Long getFormatForNativeAsLong(String str) { 177 Long format = predefinedClipboardNameMap.get(str); 178 179 if (format == null) { 180 if (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) { 181 // Do not try to access native system for the unknown format 182 return -1L; 183 } 184 format = registerFormatWithPasteboard(str); 185 predefinedClipboardNameMap.put(str, format); 186 predefinedClipboardFormatMap.put(format, str); 187 } 188 189 return format; 190 } 191 192 /* 193 * Adds type to native mapping NSDictionary. 194 */ 195 private native long registerFormatWithPasteboard(String type); 196 197 // Get registered native format string for an index, return null if unknown: 198 private native String formatForIndex(long index); 199 200 @Override 201 protected String getNativeForFormat(long format) { 202 String returnValue = null; 203 204 // The most common case - just index the array of predefined names: 205 if (format >= 0 && format < predefinedClipboardNames.length) { 206 returnValue = predefinedClipboardNames[(int) format]; 207 } else { 208 Long formatObj = format; 209 returnValue = predefinedClipboardFormatMap.get(formatObj); 210 211 // predefinedClipboardFormatMap may not know this format: 212 if (returnValue == null) { 213 returnValue = formatForIndex(format); 214 215 // Native clipboard may not know this format either: 216 if (returnValue != null) { 217 predefinedClipboardNameMap.put(returnValue, formatObj); 218 predefinedClipboardFormatMap.put(formatObj, returnValue); 219 } 220 } 221 } 222 223 if (returnValue == null) { 224 returnValue = predefinedClipboardNames[CF_UNSUPPORTED]; 225 } 226 227 return returnValue; 228 } 229 230 private final ToolkitThreadBlockedHandler handler = new CToolkitThreadBlockedHandler(); 231 232 @Override 233 public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() { 234 return handler; 235 } 236 237 @Override 238 protected byte[] imageToPlatformBytes(Image image, long format) { 239 return CImage.getCreator().getPlatformImageBytes(image); 240 } 241 242 private static native String[] nativeDragQueryFile(final byte[] bytes); 243 @Override 244 protected String[] dragQueryFile(final byte[] bytes) { 245 if (bytes == null) return null; 246 if (new String(bytes).startsWith("Unsupported type")) return null; 247 return nativeDragQueryFile(bytes); 248 } 249 250 @Override 251 protected Image platformImageBytesToImage(byte[] bytes, long format) throws IOException { 252 return CImage.getCreator().createImageFromPlatformImageBytes(bytes); 253 } 254 255 @Override 256 protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException { 257 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 258 for (String file : fileList) { 259 byte[] bytes = file.getBytes(); 260 bos.write(bytes, 0, bytes.length); 261 bos.write(0); 262 } 263 return bos; 264 } 265 266 @Override 267 protected boolean isURIListFormat(long format) { 268 String nat = getNativeForFormat(format); 269 if (nat == null) { 270 return false; 271 } 272 try { 273 DataFlavor df = new DataFlavor(nat); 274 if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) { 275 return true; 276 } 277 } catch (Exception e) { 278 // Not a MIME format. 279 } 280 return false; 281 } 282 }