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 import java.awt.image.*;
  30 import sun.awt.image.ImageRepresentation;
  31 
  32 import java.io.*;
  33 import java.net.URL;
  34 import java.text.Normalizer;
  35 import java.text.Normalizer.Form;
  36 import java.util.*;
  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     };
  57 
  58     static {
  59         Map<String, Long> nameMap = new HashMap<>(predefinedClipboardNames.length, 1.0f);
  60         Map<Long, String> formatMap = new HashMap<>(predefinedClipboardNames.length, 1.0f);
  61         for (int i = 1; i < predefinedClipboardNames.length; i++) {
  62             nameMap.put(predefinedClipboardNames[i], (long) i);
  63             formatMap.put((long) i, predefinedClipboardNames[i]);
  64         }
  65         predefinedClipboardNameMap = Collections.synchronizedMap(nameMap);
  66         predefinedClipboardFormatMap = Collections.synchronizedMap(formatMap);
  67     }
  68 
  69     public static final int CF_UNSUPPORTED = 0;
  70     public static final int CF_STRING      = 1;
  71     public static final int CF_FILE        = 2;
  72     public static final int CF_TIFF        = 3;
  73     public static final int CF_RICH_TEXT   = 4;
  74     public static final int CF_HTML        = 5;
  75     public static final int CF_PDF         = 6;
  76     public static final int CF_URL         = 7;
  77     public static final int CF_PNG         = 10;
  78     public static final int CF_JPEG        = 11;
  79 
  80     private CDataTransferer() {}
  81 
  82     private static CDataTransferer fTransferer;
  83 
  84     static synchronized CDataTransferer getInstanceImpl() {
  85         if (fTransferer == null) {
  86             fTransferer = new CDataTransferer();
  87         }
  88 
  89         return fTransferer;
  90     }
  91 
  92     @Override
  93     public String getDefaultUnicodeEncoding() {
  94         return "utf-16le";
  95     }
  96 
  97     @Override
  98     public boolean isLocaleDependentTextFormat(long format) {
  99         return format == CF_STRING;
 100     }
 101 
 102     @Override
 103     public boolean isFileFormat(long format) {
 104         return format == CF_FILE;
 105     }
 106 
 107     @Override
 108     public boolean isImageFormat(long format) {
 109         int ifmt = (int)format;
 110         switch(ifmt) {
 111             case CF_TIFF:
 112             case CF_PDF:
 113             case CF_PNG:
 114             case CF_JPEG:
 115                 return true;
 116             default:
 117                 return false;
 118         }
 119     }
 120 
 121     @Override
 122     public Object translateBytes(byte[] bytes, DataFlavor flavor,
 123                                     long format, Transferable transferable) throws IOException {
 124 
 125             if (format == CF_URL && URL.class.equals(flavor.getRepresentationClass()))
 126             {
 127                 String charset = getDefaultTextCharset();
 128                 if (transferable != null && transferable.isDataFlavorSupported(javaTextEncodingFlavor)) {
 129                     try {
 130                         charset = new String((byte[])transferable.getTransferData(javaTextEncodingFlavor), "UTF-8");
 131                     } catch (UnsupportedFlavorException cannotHappen) {
 132                     }
 133                 }
 134 
 135                 return new URL(new String(bytes, charset));
 136             }
 137 
 138             if (format == CF_STRING) {
 139                 bytes = Normalizer.normalize(new String(bytes, "UTF8"), Form.NFC).getBytes("UTF8");
 140             }
 141 
 142             return super.translateBytes(bytes, flavor, format, transferable);
 143     }
 144 
 145     @Override
 146     synchronized protected Long getFormatForNativeAsLong(String str) {
 147         Long format = predefinedClipboardNameMap.get(str);
 148 
 149         if (format == null) {
 150             if (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) {
 151                 // Do not try to access native system for the unknown format
 152                 return -1L;
 153             }
 154             format = registerFormatWithPasteboard(str);
 155             predefinedClipboardNameMap.put(str, format);
 156             predefinedClipboardFormatMap.put(format, str);
 157         }
 158 
 159         return format;
 160     }
 161 
 162     /*
 163      * Adds type to native mapping NSDictionary.
 164      */
 165     private native long registerFormatWithPasteboard(String type);
 166 
 167     // Get registered native format string for an index, return null if unknown:
 168     private native String formatForIndex(long index);
 169 
 170     @Override
 171     protected String getNativeForFormat(long format) {
 172         String returnValue = null;
 173 
 174         // The most common case - just index the array of predefined names:
 175         if (format >= 0 && format < predefinedClipboardNames.length) {
 176             returnValue = predefinedClipboardNames[(int) format];
 177         } else {
 178             Long formatObj = format;
 179             returnValue = predefinedClipboardFormatMap.get(formatObj);
 180 
 181             // predefinedClipboardFormatMap may not know this format:
 182             if (returnValue == null) {
 183                 returnValue = formatForIndex(format);
 184 
 185                 // Native clipboard may not know this format either:
 186                 if (returnValue != null) {
 187                     predefinedClipboardNameMap.put(returnValue, formatObj);
 188                     predefinedClipboardFormatMap.put(formatObj, returnValue);
 189                 }
 190             }
 191         }
 192 
 193         if (returnValue == null) {
 194             returnValue = predefinedClipboardNames[CF_UNSUPPORTED];
 195         }
 196 
 197         return returnValue;
 198     }
 199 
 200     private final ToolkitThreadBlockedHandler handler = new CToolkitThreadBlockedHandler();
 201 
 202     @Override
 203     public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() {
 204         return handler;
 205     }
 206 
 207     private native byte[] imageDataToPlatformImageBytes(int[] rData, int nW, int nH);
 208     @Override
 209     protected byte[] imageToPlatformBytes(Image image, long format) {
 210         int w = image.getWidth(null);
 211         int h = image.getHeight(null);
 212         BufferedImage bimage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
 213         Graphics g = bimage.getGraphics();
 214         g.drawImage(image, 0, 0, w, h, null);
 215         g.dispose();
 216         Raster raster = bimage.getRaster();
 217         DataBuffer buffer = raster.getDataBuffer();
 218         return imageDataToPlatformImageBytes(((DataBufferInt)buffer).getData(),
 219                                              raster.getWidth(),
 220                                              raster.getHeight());
 221     }
 222 
 223     private static native String[] nativeDragQueryFile(final byte[] bytes);
 224     @Override
 225     protected String[] dragQueryFile(final byte[] bytes) {
 226         if (bytes == null) return null;
 227         if (new String(bytes).startsWith("Unsupported type")) return null;
 228         return nativeDragQueryFile(bytes);
 229     }
 230 
 231     private native Image getImageForByteStream(byte[] bytes);
 232     /**
 233      * Translates a byte array which contains
 234      * platform-specific image data in the given format into an Image.
 235      */
 236     @Override
 237     protected Image platformImageBytesToImage(byte[] bytes, long format) throws IOException {
 238         return getImageForByteStream(bytes);
 239     }
 240 
 241     @Override
 242     protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException {
 243         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 244         for (String file : fileList) {
 245             byte[] bytes = file.getBytes();
 246             bos.write(bytes, 0, bytes.length);
 247             bos.write(0);
 248         }
 249         return bos;
 250     }
 251 
 252     @Override
 253     protected boolean isURIListFormat(long format) {
 254         String nat = getNativeForFormat(format);
 255         if (nat == null) {
 256             return false;
 257         }
 258         try {
 259             DataFlavor df = new DataFlavor(nat);
 260             if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) {
 261                 return true;
 262             }
 263         } catch (Exception e) {
 264             // Not a MIME format.
 265         }
 266         return false;
 267     }
 268 }
 269 
 270