CDataTransferer.java revision 12677:a4299d47bd00
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
26package sun.lwawt.macosx;
27
28import java.awt.*;
29
30import java.io.*;
31import java.net.URL;
32import java.nio.charset.Charset;
33import java.text.Normalizer;
34import java.text.Normalizer.Form;
35import java.util.*;
36import java.util.regex.*;
37
38import java.awt.datatransfer.*;
39import sun.awt.datatransfer.*;
40
41public 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    };
59
60    static {
61        Map<String, Long> nameMap = new HashMap<>(predefinedClipboardNames.length, 1.0f);
62        Map<Long, String> formatMap = new HashMap<>(predefinedClipboardNames.length, 1.0f);
63        for (int i = 1; i < predefinedClipboardNames.length; i++) {
64            nameMap.put(predefinedClipboardNames[i], (long) i);
65            formatMap.put((long) i, predefinedClipboardNames[i]);
66        }
67        predefinedClipboardNameMap = Collections.synchronizedMap(nameMap);
68        predefinedClipboardFormatMap = Collections.synchronizedMap(formatMap);
69    }
70
71    public static final int CF_UNSUPPORTED = 0;
72    public static final int CF_STRING      = 1;
73    public static final int CF_FILE        = 2;
74    public static final int CF_TIFF        = 3;
75    public static final int CF_RICH_TEXT   = 4;
76    public static final int CF_HTML        = 5;
77    public static final int CF_PDF         = 6;
78    public static final int CF_URL         = 7;
79    public static final int CF_PNG         = 8;
80    public static final int CF_JPEG        = 9;
81
82    private CDataTransferer() {}
83
84    private static CDataTransferer fTransferer;
85
86    static synchronized CDataTransferer getInstanceImpl() {
87        if (fTransferer == null) {
88            fTransferer = new CDataTransferer();
89        }
90
91        return fTransferer;
92    }
93
94    @Override
95    public String getDefaultUnicodeEncoding() {
96        return "utf-16le";
97    }
98
99    @Override
100    public boolean isLocaleDependentTextFormat(long format) {
101        return format == CF_STRING;
102    }
103
104    @Override
105    public boolean isFileFormat(long format) {
106        return format == CF_FILE;
107    }
108
109    @Override
110    public boolean isImageFormat(long format) {
111        int ifmt = (int)format;
112        switch(ifmt) {
113            case CF_TIFF:
114            case CF_PDF:
115            case CF_PNG:
116            case CF_JPEG:
117                return true;
118            default:
119                return false;
120        }
121    }
122
123    @Override
124    public Object translateBytes(byte[] bytes, DataFlavor flavor,
125                                 long format, Transferable transferable) throws IOException {
126
127        if (format == CF_URL && URL.class.equals(flavor.getRepresentationClass())) {
128            String charset = Charset.defaultCharset().name();
129            if (transferable != null && transferable.isDataFlavorSupported(javaTextEncodingFlavor)) {
130                try {
131                    charset = new String((byte[]) transferable.getTransferData(javaTextEncodingFlavor), "UTF-8");
132                } catch (UnsupportedFlavorException cannotHappen) {
133                }
134            }
135            String xml = new String(bytes, charset);
136            // macosx pasteboard returns a property list that consists of one URL
137            // let's extract it.
138            return new URL(extractURL(xml));
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    /**
149     * Macosx pasteboard returns xml document that contains one URL, for exmple:
150     * <pre>
151     *     {@code
152     * <?xml version=\"1.0\" encoding=\"UTF-8\"?>
153     * <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
154     * <plist version=\"1.0\">
155     *      <array>
156     *          <string>file:///path_to_file</string>
157     *          <string></string>
158     *      </array>
159     * </plist>
160     *     }
161     * </pre>
162     */
163    private String extractURL(String xml) {
164        Pattern urlExtractorPattern = Pattern.compile("<string>(.*)</string>");
165        Matcher matcher = urlExtractorPattern.matcher(xml);
166        if (matcher.find()) {
167            return matcher.group(1);
168        } else {
169            return null;
170        }
171    }
172
173    @Override
174    synchronized protected Long getFormatForNativeAsLong(String str) {
175        Long format = predefinedClipboardNameMap.get(str);
176
177        if (format == null) {
178            if (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance()) {
179                // Do not try to access native system for the unknown format
180                return -1L;
181            }
182            format = registerFormatWithPasteboard(str);
183            predefinedClipboardNameMap.put(str, format);
184            predefinedClipboardFormatMap.put(format, str);
185        }
186
187        return format;
188    }
189
190    /*
191     * Adds type to native mapping NSDictionary.
192     */
193    private native long registerFormatWithPasteboard(String type);
194
195    // Get registered native format string for an index, return null if unknown:
196    private native String formatForIndex(long index);
197
198    @Override
199    protected String getNativeForFormat(long format) {
200        String returnValue = null;
201
202        // The most common case - just index the array of predefined names:
203        if (format >= 0 && format < predefinedClipboardNames.length) {
204            returnValue = predefinedClipboardNames[(int) format];
205        } else {
206            Long formatObj = format;
207            returnValue = predefinedClipboardFormatMap.get(formatObj);
208
209            // predefinedClipboardFormatMap may not know this format:
210            if (returnValue == null) {
211                returnValue = formatForIndex(format);
212
213                // Native clipboard may not know this format either:
214                if (returnValue != null) {
215                    predefinedClipboardNameMap.put(returnValue, formatObj);
216                    predefinedClipboardFormatMap.put(formatObj, returnValue);
217                }
218            }
219        }
220
221        if (returnValue == null) {
222            returnValue = predefinedClipboardNames[CF_UNSUPPORTED];
223        }
224
225        return returnValue;
226    }
227
228    private final ToolkitThreadBlockedHandler handler = new CToolkitThreadBlockedHandler();
229
230    @Override
231    public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() {
232        return handler;
233    }
234
235    @Override
236    protected byte[] imageToPlatformBytes(Image image, long format) {
237        return CImage.getCreator().getPlatformImageBytes(image);
238    }
239
240    private static native String[] nativeDragQueryFile(final byte[] bytes);
241    @Override
242    protected String[] dragQueryFile(final byte[] bytes) {
243        if (bytes == null) return null;
244        if (new String(bytes).startsWith("Unsupported type")) return null;
245        return nativeDragQueryFile(bytes);
246    }
247
248    @Override
249    protected Image platformImageBytesToImage(byte[] bytes, long format) throws IOException {
250        return CImage.getCreator().createImageFromPlatformImageBytes(bytes);
251    }
252
253    @Override
254    protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList) throws IOException {
255        ByteArrayOutputStream bos = new ByteArrayOutputStream();
256        for (String file : fileList) {
257            byte[] bytes = file.getBytes();
258            bos.write(bytes, 0, bytes.length);
259            bos.write(0);
260        }
261        return bos;
262    }
263
264    @Override
265    protected boolean isURIListFormat(long format) {
266        String nat = getNativeForFormat(format);
267        if (nat == null) {
268            return false;
269        }
270        try {
271            DataFlavor df = new DataFlavor(nat);
272            if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) {
273                return true;
274            }
275        } catch (Exception e) {
276            // Not a MIME format.
277        }
278        return false;
279    }
280}
281