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 sun.awt.datatransfer.desktop.DataTransferer;
  29 import sun.awt.datatransfer.desktop.ToolkitThreadBlockedHandler;
  30 
  31 import java.awt.*;
  32 
  33 import java.io.*;
  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 
  40 import java.awt.datatransfer.*;
  41 
  42 public class CDataTransferer extends DataTransferer {
  43     private static final Map<String, Long> predefinedClipboardNameMap;
  44     private static final Map<Long, String> predefinedClipboardFormatMap;
  45 
  46     // See SystemFlavorMap, or the flavormap.properties file:
  47     // We should define a few more types in flavormap.properties, it's rather slim now.
  48     private static final String[] predefinedClipboardNames = {
  49         "",
  50         "STRING",
  51         "FILE_NAME",
  52         "TIFF",
  53         "RICH_TEXT",
  54         "HTML",
  55         "PDF",
  56         "URL",
  57         "PNG",
  58         "JFIF"
  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 
  83     private CDataTransferer() {}
  84 
  85     private static CDataTransferer fTransferer;
  86 
  87     static synchronized CDataTransferer getInstanceImpl() {
  88         if (fTransferer == null) {
  89             fTransferer = new CDataTransferer();
  90         }
  91 
  92         return fTransferer;
  93     }
  94 
  95     @Override
  96     public String getDefaultUnicodeEncoding() {
  97         return "utf-16le";
  98     }
  99 
 100     @Override
 101     public boolean isLocaleDependentTextFormat(long format) {
 102         return format == CF_STRING;
 103     }
 104 
 105     @Override
 106     public boolean isFileFormat(long format) {
 107         return format == CF_FILE;
 108     }
 109 
 110     @Override
 111     public boolean isImageFormat(long format) {
 112         int ifmt = (int)format;
 113         switch(ifmt) {
 114             case CF_TIFF:
 115             case CF_PDF:
 116             case CF_PNG:
 117             case CF_JPEG:
 118                 return true;
 119             default:
 120                 return false;
 121         }
 122     }
 123 
 124     @Override
 125     public Object translateBytes(byte[] bytes, DataFlavor flavor,
 126                                     long format, Transferable transferable) throws IOException {
 127 
 128             if (format == CF_URL && URL.class.equals(flavor.getRepresentationClass()))
 129             {
 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 
 138                 return new URL(new String(bytes, charset));
 139             }
 140 
 141             if (format == CF_STRING) {
 142                 bytes = Normalizer.normalize(new String(bytes, "UTF8"), Form.NFC).getBytes("UTF8");
 143             }
 144 
 145             return super.translateBytes(bytes, flavor, format, transferable);
 146     }
 147 
 148     @Override
 149     synchronized protected Long getFormatForNativeAsLong(String str) {
 150         Long format = predefinedClipboardNameMap.get(str);
 151 
 152         if (format == null) {
 153             if (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) {
 154                 // Do not try to access native system for the unknown format
 155                 return -1L;
 156             }
 157             format = registerFormatWithPasteboard(str);
 158             predefinedClipboardNameMap.put(str, format);
 159             predefinedClipboardFormatMap.put(format, str);
 160         }
 161 
 162         return format;
 163     }
 164 
 165     /*
 166      * Adds type to native mapping NSDictionary.
 167      */
 168     private native long registerFormatWithPasteboard(String type);
 169 
 170     // Get registered native format string for an index, return null if unknown:
 171     private native String formatForIndex(long index);
 172 
 173     @Override
 174     protected String getNativeForFormat(long format) {
 175         String returnValue = null;
 176 
 177         // The most common case - just index the array of predefined names:
 178         if (format >= 0 && format < predefinedClipboardNames.length) {
 179             returnValue = predefinedClipboardNames[(int) format];
 180         } else {
 181             Long formatObj = format;
 182             returnValue = predefinedClipboardFormatMap.get(formatObj);
 183 
 184             // predefinedClipboardFormatMap may not know this format:
 185             if (returnValue == null) {
 186                 returnValue = formatForIndex(format);
 187 
 188                 // Native clipboard may not know this format either:
 189                 if (returnValue != null) {
 190                     predefinedClipboardNameMap.put(returnValue, formatObj);
 191                     predefinedClipboardFormatMap.put(formatObj, returnValue);
 192                 }
 193             }
 194         }
 195 
 196         if (returnValue == null) {
 197             returnValue = predefinedClipboardNames[CF_UNSUPPORTED];
 198         }
 199 
 200         return returnValue;
 201     }
 202 
 203     private final ToolkitThreadBlockedHandler handler = new CToolkitThreadBlockedHandler();
 204 
 205     @Override
 206     public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() {
 207         return handler;
 208     }
 209 
 210     @Override
 211     protected byte[] imageToPlatformBytes(Image image, long format) {
 212         return CImage.getCreator().getPlatformImageBytes(image);
 213     }
 214 
 215     private static native String[] nativeDragQueryFile(final byte[] bytes);
 216     @Override
 217     protected String[] dragQueryFile(final byte[] bytes) {
 218         if (bytes == null) return null;
 219         if (new String(bytes).startsWith("Unsupported type")) return null;
 220         return nativeDragQueryFile(bytes);
 221     }
 222 
 223     @Override
 224     protected Image platformImageBytesToImage(byte[] bytes, long format) throws IOException {
 225         return CImage.getCreator().createImageFromPlatformImageBytes(bytes);
 226     }
 227 
 228     @Override
 229     protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException {
 230         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 231         for (String file : fileList) {
 232             byte[] bytes = file.getBytes();
 233             bos.write(bytes, 0, bytes.length);
 234             bos.write(0);
 235         }
 236         return bos;
 237     }
 238 
 239     @Override
 240     protected boolean isURIListFormat(long format) {
 241         String nat = getNativeForFormat(format);
 242         if (nat == null) {
 243             return false;
 244         }
 245         try {
 246             DataFlavor df = new DataFlavor(nat);
 247             if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) {
 248                 return true;
 249             }
 250         } catch (Exception e) {
 251             // Not a MIME format.
 252         }
 253         return false;
 254     }
 255 }
 256 
 257