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 
26 package sun.awt.X11;
27 
28 import java.awt.Image;
29 
30 import java.awt.datatransfer.DataFlavor;
31 import java.awt.datatransfer.Transferable;
32 
33 import java.awt.image.BufferedImage;
34 import java.awt.image.ColorModel;
35 import java.awt.image.WritableRaster;
36 
37 import java.io.BufferedReader;
38 import java.io.InputStream;
39 import java.io.InputStreamReader;
40 import java.io.IOException;
41 
42 import java.net.URI;
43 import java.net.URISyntaxException;
44 
45 import java.util.ArrayList;
46 import java.util.Iterator;
47 import java.util.LinkedHashSet;
48 
49 import javax.imageio.ImageIO;
50 import javax.imageio.ImageReader;
51 import javax.imageio.ImageTypeSpecifier;
52 import javax.imageio.ImageWriter;
53 import javax.imageio.spi.ImageWriterSpi;
54 
55 import sun.datatransfer.DataFlavorUtil;
56 import sun.awt.datatransfer.DataTransferer;
57 import sun.awt.datatransfer.ToolkitThreadBlockedHandler;
58 
59 import java.io.ByteArrayOutputStream;
60 
61 /**
62  * Platform-specific support for the data transfer subsystem.
63  */
64 public 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      */
XDataTransferer()76     private XDataTransferer() {
77     }
78 
79     private static XDataTransferer transferer;
80 
getInstanceImpl()81     static synchronized XDataTransferer getInstanceImpl() {
82         if (transferer == null) {
83             transferer = new XDataTransferer();
84         }
85         return transferer;
86     }
87 
88     @Override
getDefaultUnicodeEncoding()89     public String getDefaultUnicodeEncoding() {
90         return "iso-10646-ucs-2";
91     }
92 
93     @Override
isLocaleDependentTextFormat(long format)94     public boolean isLocaleDependentTextFormat(long format) {
95         return false;
96     }
97 
98     @Override
isTextFormat(long format)99     public boolean isTextFormat(long format) {
100         return super.isTextFormat(format)
101             || isMimeFormat(format, "text");
102     }
103 
104     @Override
getCharsetForTextFormat(Long lFormat)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
isURIListFormat(long format)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
isFileFormat(long format)140     public boolean isFileFormat(long format) {
141         return format == FILE_NAME_ATOM.getAtom() ||
142             format == DT_NET_FILE_ATOM.getAtom();
143     }
144 
145     @Override
isImageFormat(long format)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
getFormatForNativeAsLong(String str)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
getNativeForFormat(long format)160     protected String getNativeForFormat(long format) {
161         return getTargetNameForAtom(format);
162     }
163 
getToolkitThreadBlockedHandler()164     public ToolkitThreadBlockedHandler getToolkitThreadBlockedHandler() {
165         return XToolkitThreadBlockedHandler.getToolkitThreadBlockedHandler();
166     }
167 
168     /**
169      * Gets an format name for a given format (atom)
170      */
getTargetNameForAtom(long atom)171     private String getTargetNameForAtom(long atom) {
172         return XAtom.get(atom).getName();
173     }
174 
175     @Override
imageToPlatformBytes(Image image, long format)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
convertFileListToBytes(ArrayList<String> fileList)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
platformImageBytesToImage( byte[] bytes, long format)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
dragQueryFile(byte[] bytes)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
dragQueryURIs(InputStream stream, long format, Transferable localeTransferable)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      */
isMimeFormat(long format, String primaryType)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
getPlatformMappingsForNative(String nat)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 
getDefaultImageTypeSpecifier()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
getPlatformMappingsForFlavor(DataFlavor df)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