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