1/*
2 * Copyright (c) 2003, 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.awt.X11;
27
28import java.awt.Image;
29
30import java.awt.datatransfer.DataFlavor;
31import java.awt.datatransfer.Transferable;
32
33import java.awt.image.BufferedImage;
34import java.awt.image.ColorModel;
35import java.awt.image.WritableRaster;
36
37import java.io.BufferedReader;
38import java.io.InputStream;
39import java.io.InputStreamReader;
40import java.io.IOException;
41
42import java.net.URI;
43import java.net.URISyntaxException;
44
45import java.util.ArrayList;
46import java.util.Iterator;
47import java.util.LinkedHashSet;
48
49import javax.imageio.ImageIO;
50import javax.imageio.ImageReader;
51import javax.imageio.ImageTypeSpecifier;
52import javax.imageio.ImageWriter;
53import javax.imageio.spi.ImageWriterSpi;
54
55import sun.datatransfer.DataFlavorUtil;
56import sun.awt.datatransfer.DataTransferer;
57import sun.awt.datatransfer.ToolkitThreadBlockedHandler;
58
59import java.io.ByteArrayOutputStream;
60
61/**
62 * Platform-specific support for the data transfer subsystem.
63 */
64public class XDataTransferer extends DataTransferer {
65    static final XAtom FILE_NAME_ATOM = XAtom.get("FILE_NAME");
66    static final XAtom DT_NET_FILE_ATOM = XAtom.get("_DT_NETFILE");
67    static final XAtom PNG_ATOM = XAtom.get("PNG");
68    static final XAtom JFIF_ATOM = XAtom.get("JFIF");
69    static final XAtom TARGETS_ATOM = XAtom.get("TARGETS");
70    static final XAtom INCR_ATOM = XAtom.get("INCR");
71    static final XAtom MULTIPLE_ATOM = XAtom.get("MULTIPLE");
72
73    /**
74     * Singleton constructor
75     */
76    private XDataTransferer() {
77    }
78
79    private static XDataTransferer transferer;
80
81    static synchronized XDataTransferer getInstanceImpl() {
82        if (transferer == null) {
83            transferer = new XDataTransferer();
84        }
85        return transferer;
86    }
87
88    @Override
89    public String getDefaultUnicodeEncoding() {
90        return "iso-10646-ucs-2";
91    }
92
93    @Override
94    public boolean isLocaleDependentTextFormat(long format) {
95        return false;
96    }
97
98    @Override
99    public boolean isTextFormat(long format) {
100        return super.isTextFormat(format)
101            || isMimeFormat(format, "text");
102    }
103
104    @Override
105    protected String getCharsetForTextFormat(Long lFormat) {
106        if (isMimeFormat(lFormat, "text")) {
107            String nat = getNativeForFormat(lFormat);
108            DataFlavor df = new DataFlavor(nat, null);
109            // Ignore the charset parameter of the MIME type if the subtype
110            // doesn't support charset.
111            if (!DataFlavorUtil.doesSubtypeSupportCharset(df)) {
112                return null;
113            }
114            String charset = df.getParameter("charset");
115            if (charset != null) {
116                return charset;
117            }
118        }
119        return super.getCharsetForTextFormat(lFormat);
120    }
121
122    @Override
123    protected boolean isURIListFormat(long format) {
124        String nat = getNativeForFormat(format);
125        if (nat == null) {
126            return false;
127        }
128        try {
129            DataFlavor df = new DataFlavor(nat);
130            if (df.getPrimaryType().equals("text") && df.getSubType().equals("uri-list")) {
131                return true;
132            }
133        } catch (Exception e) {
134            // Not a MIME format.
135        }
136        return false;
137    }
138
139    @Override
140    public boolean isFileFormat(long format) {
141        return format == FILE_NAME_ATOM.getAtom() ||
142            format == DT_NET_FILE_ATOM.getAtom();
143    }
144
145    @Override
146    public boolean isImageFormat(long format) {
147        return format == PNG_ATOM.getAtom() ||
148            format == JFIF_ATOM.getAtom() ||
149            isMimeFormat(format, "image");
150    }
151
152    @Override
153    protected Long getFormatForNativeAsLong(String str) {
154        // Just get the atom. If it has already been retrived
155        // once, we'll get a copy so this should be very fast.
156        return XAtom.get(str).getAtom();
157    }
158
159    @Override
160    protected String getNativeForFormat(long format) {
161        return getTargetNameForAtom(format);
162    }
163
164    public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() {
165        return XToolkitThreadBlockedHandler.getToolkitThreadBlockedHandler();
166    }
167
168    /**
169     * Gets an format name for a given format (atom)
170     */
171    private String getTargetNameForAtom(long atom) {
172        return XAtom.get(atom).getName();
173    }
174
175    @Override
176    protected byte[] imageToPlatformBytes(Image image, long format)
177      throws IOException {
178        String mimeType = null;
179        if (format == PNG_ATOM.getAtom()) {
180            mimeType = "image/png";
181        } else if (format == JFIF_ATOM.getAtom()) {
182            mimeType = "image/jpeg";
183        } else {
184            // Check if an image MIME format.
185            try {
186                String nat = getNativeForFormat(format);
187                DataFlavor df = new DataFlavor(nat);
188                String primaryType = df.getPrimaryType();
189                if ("image".equals(primaryType)) {
190                    mimeType = df.getPrimaryType() + "/" + df.getSubType();
191                }
192            } catch (Exception e) {
193                // Not an image MIME format.
194            }
195        }
196        if (mimeType != null) {
197            return imageToStandardBytes(image, mimeType);
198        } else {
199            String nativeFormat = getNativeForFormat(format);
200            throw new IOException("Translation to " + nativeFormat +
201                                  " is not supported.");
202        }
203    }
204
205    @Override
206    protected ByteArrayOutputStream convertFileListToBytes(ArrayList<String> fileList)
207        throws IOException
208    {
209        ByteArrayOutputStream bos = new ByteArrayOutputStream();
210        for (int i = 0; i < fileList.size(); i++)
211        {
212               byte[] bytes = fileList.get(i).getBytes();
213               if (i != 0) bos.write(0);
214               bos.write(bytes, 0, bytes.length);
215        }
216        return bos;
217    }
218
219    /**
220     * Translates either a byte array or an input stream which contain
221     * platform-specific image data in the given format into an Image.
222     */
223    @Override
224    protected Image platformImageBytesToImage(
225        byte[] bytes, long format) throws IOException
226    {
227        String mimeType = null;
228        if (format == PNG_ATOM.getAtom()) {
229            mimeType = "image/png";
230        } else if (format == JFIF_ATOM.getAtom()) {
231            mimeType = "image/jpeg";
232        } else {
233            // Check if an image MIME format.
234            try {
235                String nat = getNativeForFormat(format);
236                DataFlavor df = new DataFlavor(nat);
237                String primaryType = df.getPrimaryType();
238                if ("image".equals(primaryType)) {
239                    mimeType = df.getPrimaryType() + "/" + df.getSubType();
240                }
241            } catch (Exception e) {
242                // Not an image MIME format.
243            }
244        }
245        if (mimeType != null) {
246            return standardImageBytesToImage(bytes, mimeType);
247        } else {
248            String nativeFormat = getNativeForFormat(format);
249            throw new IOException("Translation from " + nativeFormat +
250                                  " is not supported.");
251        }
252    }
253
254    @Override
255    protected String[] dragQueryFile(byte[] bytes) {
256        XToolkit.awtLock();
257        try {
258            return XlibWrapper.XTextPropertyToStringList(bytes,
259                                                         XAtom.get("STRING").getAtom());
260        } finally {
261            XToolkit.awtUnlock();
262        }
263    }
264
265    @Override
266    protected URI[] dragQueryURIs(InputStream stream,
267                                  long format,
268                                  Transferable localeTransferable)
269      throws IOException {
270
271        String charset = getBestCharsetForTextFormat(format, localeTransferable);
272        try (InputStreamReader isr = new InputStreamReader(stream, charset);
273             BufferedReader reader = new BufferedReader(isr)) {
274            String line;
275            ArrayList<URI> uriList = new ArrayList<>();
276            URI uri;
277            while ((line = reader.readLine()) != null) {
278                try {
279                    uri = new URI(line);
280                } catch (URISyntaxException uriSyntaxException) {
281                    throw new IOException(uriSyntaxException);
282                }
283                uriList.add(uri);
284            }
285            return uriList.toArray(new URI[uriList.size()]);
286        }
287    }
288
289    /**
290     * Returns true if and only if the name of the specified format Atom
291     * constitutes a valid MIME type with the specified primary type.
292     */
293    private boolean isMimeFormat(long format, String primaryType) {
294        String nat = getNativeForFormat(format);
295
296        if (nat == null) {
297            return false;
298        }
299
300        try {
301            DataFlavor df = new DataFlavor(nat);
302            if (primaryType.equals(df.getPrimaryType())) {
303                return true;
304            }
305        } catch (Exception e) {
306            // Not a MIME format.
307        }
308
309        return false;
310    }
311
312    /*
313     * The XDnD protocol prescribes that the Atoms used as targets for data
314     * transfer should have string names that represent the corresponding MIME
315     * types.
316     * To meet this requirement we check if the passed native format constitutes
317     * a valid MIME and return a list of flavors to which the data in this MIME
318     * type can be translated by the Data Transfer subsystem.
319     */
320    @Override
321    public LinkedHashSet<DataFlavor> getPlatformMappingsForNative(String nat) {
322        LinkedHashSet<DataFlavor> flavors = new LinkedHashSet<>();
323
324        if (nat == null) {
325            return flavors;
326        }
327
328        DataFlavor df;
329        try {
330            df = new DataFlavor(nat);
331        } catch (Exception e) {
332            // The string doesn't constitute a valid MIME type.
333            return flavors;
334        }
335
336        DataFlavor value = df;
337        final String primaryType = df.getPrimaryType();
338        final String baseType = primaryType + "/" + df.getSubType();
339
340        // For text formats we map natives to MIME strings instead of data
341        // flavors to enable dynamic text native-to-flavor mapping generation.
342        // See SystemFlavorMap.getFlavorsForNative() for details.
343        if ("image".equals(primaryType)) {
344            Iterator<ImageReader> readers = ImageIO.getImageReadersByMIMEType(baseType);
345            if (readers.hasNext()) {
346                flavors.add(DataFlavor.imageFlavor);
347            }
348        }
349
350        flavors.add(value);
351
352        return flavors;
353    }
354
355    private static ImageTypeSpecifier defaultSpecifier = null;
356
357    private ImageTypeSpecifier getDefaultImageTypeSpecifier() {
358        if (defaultSpecifier == null) {
359            ColorModel model = ColorModel.getRGBdefault();
360            WritableRaster raster =
361                model.createCompatibleWritableRaster(10, 10);
362
363            BufferedImage bufferedImage =
364                new BufferedImage(model, raster, model.isAlphaPremultiplied(),
365                                  null);
366
367            defaultSpecifier = new ImageTypeSpecifier(bufferedImage);
368        }
369
370        return defaultSpecifier;
371    }
372
373    /*
374     * The XDnD protocol prescribes that the Atoms used as targets for data
375     * transfer should have string names that represent the corresponding MIME
376     * types.
377     * To meet this requirement we return a list of formats that represent
378     * MIME types to which the data in this flavor can be translated by the Data
379     * Transfer subsystem.
380     */
381    @Override
382    public LinkedHashSet<String> getPlatformMappingsForFlavor(DataFlavor df) {
383        LinkedHashSet<String> natives = new LinkedHashSet<>(1);
384
385        if (df == null) {
386            return natives;
387        }
388
389        String charset = df.getParameter("charset");
390        String baseType = df.getPrimaryType() + "/" + df.getSubType();
391        String mimeType = baseType;
392
393        if (charset != null && DataFlavorUtil.isFlavorCharsetTextType(df)) {
394            mimeType += ";charset=" + charset;
395        }
396
397        // Add a mapping to the MIME native whenever the representation class
398        // doesn't require translation.
399        if (df.getRepresentationClass() != null &&
400            (df.isRepresentationClassInputStream() ||
401             df.isRepresentationClassByteBuffer() ||
402             byte[].class.equals(df.getRepresentationClass()))) {
403            natives.add(mimeType);
404        }
405
406        if (DataFlavor.imageFlavor.equals(df)) {
407            String[] mimeTypes = ImageIO.getWriterMIMETypes();
408            if (mimeTypes != null) {
409                for (String mime : mimeTypes) {
410                    Iterator<ImageWriter> writers = ImageIO.getImageWritersByMIMEType(mime);
411                    while (writers.hasNext()) {
412                        ImageWriter imageWriter = writers.next();
413                        ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();
414
415                        if (writerSpi != null &&
416                                writerSpi.canEncodeImage(getDefaultImageTypeSpecifier())) {
417                            natives.add(mime);
418                            break;
419                        }
420                    }
421                }
422            }
423        } else if (DataFlavorUtil.isFlavorCharsetTextType(df)) {
424            // stringFlavor is semantically equivalent to the standard
425            // "text/plain" MIME type.
426            if (DataFlavor.stringFlavor.equals(df)) {
427                baseType = "text/plain";
428            }
429
430            for (String encoding : DataFlavorUtil.standardEncodings()) {
431                if (!encoding.equals(charset)) {
432                    natives.add(baseType + ";charset=" + encoding);
433                }
434            }
435
436            // Add a MIME format without specified charset.
437            if (!natives.contains(baseType)) {
438                natives.add(baseType);
439            }
440        }
441
442        return natives;
443    }
444}
445