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 
  41 import java.awt.datatransfer.*;
  42 import sun.awt.datatransfer.*;
  43 
  44 public 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), "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 }