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