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 }