blob: 5dd12441081fb423244e530fb064e3ea880e6e8a [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.awt.datatransfer;
27
28import java.awt.AWTError;
29import java.awt.EventQueue;
30import java.awt.Image;
31import java.awt.Graphics;
32import java.awt.Toolkit;
33
34import java.awt.datatransfer.DataFlavor;
35import java.awt.datatransfer.FlavorMap;
36import java.awt.datatransfer.FlavorTable;
37import java.awt.datatransfer.StringSelection;
38import java.awt.datatransfer.Transferable;
39import java.awt.datatransfer.UnsupportedFlavorException;
40
41import java.io.BufferedReader;
42import java.io.ByteArrayInputStream;
43import java.io.ByteArrayOutputStream;
44import java.io.File;
45import java.io.InputStream;
46import java.io.InputStreamReader;
47import java.io.IOException;
48import java.io.ObjectInputStream;
49import java.io.ObjectOutputStream;
50import java.io.Reader;
51import java.io.SequenceInputStream;
52import java.io.StringReader;
53
54import java.nio.ByteBuffer;
55import java.nio.CharBuffer;
56import java.nio.charset.Charset;
57import java.nio.charset.CharsetEncoder;
58import java.nio.charset.IllegalCharsetNameException;
59import java.nio.charset.UnsupportedCharsetException;
60
61import java.lang.reflect.Constructor;
62import java.lang.reflect.InvocationTargetException;
63import java.lang.reflect.Method;
64import java.lang.reflect.Modifier;
65
66import java.rmi.MarshalledObject;
67
68import java.security.AccessController;
69import java.security.PrivilegedAction;
70import java.security.PrivilegedActionException;
71import java.security.PrivilegedExceptionAction;
72
73import java.util.ArrayList;
74import java.util.Arrays;
75import java.util.Collections;
76import java.util.Comparator;
77import java.util.HashMap;
78import java.util.HashSet;
79import java.util.Iterator;
80import java.util.List;
81import java.util.Map;
82import java.util.SortedMap;
83import java.util.SortedSet;
84import java.util.Set;
85import java.util.Stack;
86import java.util.TreeMap;
87import java.util.TreeSet;
88
89import java.util.logging.*;
90
91import sun.awt.AppContext;
92import sun.awt.SunToolkit;
93
94import java.awt.image.BufferedImage;
95import java.awt.image.ImageObserver;
96import java.awt.image.RenderedImage;
97import java.awt.image.WritableRaster;
98import java.awt.image.ColorModel;
99
100import javax.imageio.ImageIO;
101import javax.imageio.ImageReader;
102import javax.imageio.ImageReadParam;
103import javax.imageio.ImageWriter;
104import javax.imageio.ImageTypeSpecifier;
105
106import javax.imageio.spi.ImageWriterSpi;
107
108import javax.imageio.stream.ImageInputStream;
109import javax.imageio.stream.ImageOutputStream;
110
111import sun.awt.image.ImageRepresentation;
112import sun.awt.image.ToolkitImage;
113
114
115/**
116 * Provides a set of functions to be shared among the DataFlavor class and
117 * platform-specific data transfer implementations.
118 *
119 * The concept of "flavors" and "natives" is extended to include "formats",
120 * which are the numeric values Win32 and X11 use to express particular data
121 * types. Like FlavorMap, which provides getNativesForFlavors(DataFlavor[]) and
122 * getFlavorsForNatives(String[]) functions, DataTransferer provides a set
123 * of getFormatsFor(Transferable|Flavor|Flavors) and
124 * getFlavorsFor(Format|Formats) functions.
125 *
126 * Also provided are functions for translating a Transferable into a byte
127 * array, given a source DataFlavor and a target format, and for translating
128 * a byte array or InputStream into an Object, given a source format and
129 * a target DataFlavor.
130 *
131 * @author David Mendenhall
132 * @author Danila Sinopalnikov
133 *
134 * @since 1.3.1
135 */
136public abstract class DataTransferer {
137
138 /**
139 * Cached value of Class.forName("[C");
140 */
141 public static final Class charArrayClass;
142
143 /**
144 * Cached value of Class.forName("[B");
145 */
146 public static final Class byteArrayClass;
147
148 /**
149 * The <code>DataFlavor</code> representing plain text with Unicode
150 * encoding, where:
151 * <pre>
152 * representationClass = java.lang.String
153 * mimeType = "text/plain; charset=Unicode"
154 * </pre>
155 */
156 public static final DataFlavor plainTextStringFlavor;
157
158 /**
159 * The <code>DataFlavor</code> representing a Java text encoding String
160 * encoded in UTF-8, where
161 * <pre>
162 * representationClass = [B
163 * mimeType = "application/x-java-text-encoding"
164 * </pre>
165 */
166 public static final DataFlavor javaTextEncodingFlavor;
167
168 private static SortedSet standardEncodings;
169
170 /**
171 * Tracks whether a particular text/* MIME type supports the charset
172 * parameter. The Map is initialized with all of the standard MIME types
173 * listed in the DataFlavor.selectBestTextFlavor method comment. Additional
174 * entries may be added during the life of the JRE for text/<other> types.
175 */
176 private static final Map textMIMESubtypeCharsetSupport;
177
178 /**
179 * Cache of the platform default encoding as specified in the
180 * "file.encoding" system property.
181 */
182 private static String defaultEncoding;
183
184 /**
185 * A collection of all natives listed in flavormap.properties with
186 * a primary MIME type of "text".
187 */
188 private static final Set textNatives =
189 Collections.synchronizedSet(new HashSet());
190
191 /**
192 * The native encodings/charsets for the Set of textNatives.
193 */
194 private static final Map nativeCharsets =
195 Collections.synchronizedMap(new HashMap());
196
197 /**
198 * The end-of-line markers for the Set of textNatives.
199 */
200 private static final Map nativeEOLNs =
201 Collections.synchronizedMap(new HashMap());
202
203 /**
204 * The number of terminating NUL bytes for the Set of textNatives.
205 */
206 private static final Map nativeTerminators =
207 Collections.synchronizedMap(new HashMap());
208
209 /**
210 * The key used to store pending data conversion requests for an AppContext.
211 */
212 private static final String DATA_CONVERTER_KEY = "DATA_CONVERTER_KEY";
213
214 /**
215 * The singleton DataTransferer instance. It is created during MToolkit
216 * or WToolkit initialization.
217 */
218 private static DataTransferer transferer;
219
220 private static final Logger dtLog = Logger.getLogger("sun.awt.datatransfer.DataTransfer");
221
222 static {
223 Class tCharArrayClass = null, tByteArrayClass = null;
224 try {
225 tCharArrayClass = Class.forName("[C");
226 tByteArrayClass = Class.forName("[B");
227 } catch (ClassNotFoundException cannotHappen) {
228 }
229 charArrayClass = tCharArrayClass;
230 byteArrayClass = tByteArrayClass;
231
232 DataFlavor tPlainTextStringFlavor = null;
233 try {
234 tPlainTextStringFlavor = new DataFlavor
235 ("text/plain;charset=Unicode;class=java.lang.String");
236 } catch (ClassNotFoundException cannotHappen) {
237 }
238 plainTextStringFlavor = tPlainTextStringFlavor;
239
240 DataFlavor tJavaTextEncodingFlavor = null;
241 try {
242 tJavaTextEncodingFlavor = new DataFlavor
243 ("application/x-java-text-encoding;class=\"[B\"");
244 } catch (ClassNotFoundException cannotHappen) {
245 }
246 javaTextEncodingFlavor = tJavaTextEncodingFlavor;
247
248 Map tempMap = new HashMap(17);
249 tempMap.put("sgml", Boolean.TRUE);
250 tempMap.put("xml", Boolean.TRUE);
251 tempMap.put("html", Boolean.TRUE);
252 tempMap.put("enriched", Boolean.TRUE);
253 tempMap.put("richtext", Boolean.TRUE);
254 tempMap.put("uri-list", Boolean.TRUE);
255 tempMap.put("directory", Boolean.TRUE);
256 tempMap.put("css", Boolean.TRUE);
257 tempMap.put("calendar", Boolean.TRUE);
258 tempMap.put("plain", Boolean.TRUE);
259 tempMap.put("rtf", Boolean.FALSE);
260 tempMap.put("tab-separated-values", Boolean.FALSE);
261 tempMap.put("t140", Boolean.FALSE);
262 tempMap.put("rfc822-headers", Boolean.FALSE);
263 tempMap.put("parityfec", Boolean.FALSE);
264 textMIMESubtypeCharsetSupport = Collections.synchronizedMap(tempMap);
265 }
266
267 /**
268 * The accessor method for the singleton DataTransferer instance. Note
269 * that in a headless environment, there may be no DataTransferer instance;
270 * instead, null will be returned.
271 */
272 public static DataTransferer getInstance() {
273 if (transferer == null) {
274 synchronized (DataTransferer.class) {
275 if (transferer == null) {
276 final String name = SunToolkit.
277 getDataTransfererClassName();
278 if (name != null) {
279 PrivilegedAction action = new PrivilegedAction() {
280 public Object run() {
281 Class cls = null;
282 Method method = null;
283 Object ret = null;
284
285 try {
286 cls = Class.forName(name);
287 } catch (ClassNotFoundException e) {
288 ClassLoader cl = ClassLoader.
289 getSystemClassLoader();
290 if (cl != null) {
291 try {
292 cls = cl.loadClass(name);
293 } catch (ClassNotFoundException ee) {
294 ee.printStackTrace();
295 throw new AWTError("DataTransferer not found: " + name);
296 }
297 }
298 }
299 if (cls != null) {
300 try {
301 method = cls.getDeclaredMethod
302 ("getInstanceImpl");
303 method.setAccessible(true);
304 } catch (NoSuchMethodException e) {
305 e.printStackTrace();
306 throw new AWTError("Cannot instantiate DataTransferer: " + name);
307 } catch (SecurityException e) {
308 e.printStackTrace();
309 throw new AWTError("Access is denied for DataTransferer: " + name);
310 }
311 }
312 if (method != null) {
313 try {
314 ret = method.invoke(null);
315 } catch (InvocationTargetException e) {
316 e.printStackTrace();
317 throw new AWTError("Cannot instantiate DataTransferer: " + name);
318 } catch (IllegalAccessException e) {
319 e.printStackTrace();
320 throw new AWTError("Cannot access DataTransferer: " + name);
321 }
322 }
323 return ret;
324 }
325 };
326 transferer = (DataTransferer)
327 AccessController.doPrivileged(action);
328 }
329 }
330 }
331 }
332 return transferer;
333 }
334
335 /**
336 * Converts an arbitrary text encoding to its canonical name.
337 */
338 public static String canonicalName(String encoding) {
339 if (encoding == null) {
340 return null;
341 }
342 try {
343 return Charset.forName(encoding).name();
344 } catch (IllegalCharsetNameException icne) {
345 return encoding;
346 } catch (UnsupportedCharsetException uce) {
347 return encoding;
348 }
349 }
350
351 /**
352 * If the specified flavor is a text flavor which supports the "charset"
353 * parameter, then this method returns that parameter, or the default
354 * charset if no such parameter was specified at construction. For non-
355 * text DataFlavors, and for non-charset text flavors, this method returns
356 * null.
357 */
358 public static String getTextCharset(DataFlavor flavor) {
359 if (!isFlavorCharsetTextType(flavor)) {
360 return null;
361 }
362
363 String encoding = flavor.getParameter("charset");
364
365 return (encoding != null) ? encoding : getDefaultTextCharset();
366 }
367
368 /**
369 * Returns the platform's default character encoding.
370 */
371 public static String getDefaultTextCharset() {
372 if (defaultEncoding != null) {
373 return defaultEncoding;
374 }
375 return defaultEncoding = Charset.defaultCharset().name();
376 }
377
378 /**
379 * Tests only whether the flavor's MIME type supports the charset
380 * parameter. Must only be called for flavors with a primary type of
381 * "text".
382 */
383 public static boolean doesSubtypeSupportCharset(DataFlavor flavor) {
384 if (dtLog.isLoggable(Level.FINE)) {
385 if (!"text".equals(flavor.getPrimaryType())) {
386 dtLog.log(Level.FINE, "Assertion (\"text\".equals(flavor.getPrimaryType())) failed");
387 }
388 }
389
390 String subType = flavor.getSubType();
391 if (subType == null) {
392 return false;
393 }
394
395 Object support = textMIMESubtypeCharsetSupport.get(subType);
396
397 if (support != null) {
398 return (support == Boolean.TRUE);
399 }
400
401 boolean ret_val = (flavor.getParameter("charset") != null);
402 textMIMESubtypeCharsetSupport.put
403 (subType, (ret_val) ? Boolean.TRUE : Boolean.FALSE);
404 return ret_val;
405 }
406 public static boolean doesSubtypeSupportCharset(String subType,
407 String charset)
408 {
409 Object support = textMIMESubtypeCharsetSupport.get(subType);
410
411 if (support != null) {
412 return (support == Boolean.TRUE);
413 }
414
415 boolean ret_val = (charset != null);
416 textMIMESubtypeCharsetSupport.put
417 (subType, (ret_val) ? Boolean.TRUE : Boolean.FALSE);
418 return ret_val;
419 }
420
421 /**
422 * Returns whether this flavor is a text type which supports the
423 * 'charset' parameter.
424 */
425 public static boolean isFlavorCharsetTextType(DataFlavor flavor) {
426 // Although stringFlavor doesn't actually support the charset
427 // parameter (because its primary MIME type is not "text"), it should
428 // be treated as though it does. stringFlavor is semantically
429 // equivalent to "text/plain" data.
430 if (DataFlavor.stringFlavor.equals(flavor)) {
431 return true;
432 }
433
434 if (!"text".equals(flavor.getPrimaryType()) ||
435 !doesSubtypeSupportCharset(flavor))
436 {
437 return false;
438 }
439
440 Class rep_class = flavor.getRepresentationClass();
441
442 if (flavor.isRepresentationClassReader() ||
443 String.class.equals(rep_class) ||
444 flavor.isRepresentationClassCharBuffer() ||
445 DataTransferer.charArrayClass.equals(rep_class))
446 {
447 return true;
448 }
449
450 if (!(flavor.isRepresentationClassInputStream() ||
451 flavor.isRepresentationClassByteBuffer() ||
452 DataTransferer.byteArrayClass.equals(rep_class))) {
453 return false;
454 }
455
456 String charset = flavor.getParameter("charset");
457
458 return (charset != null)
459 ? DataTransferer.isEncodingSupported(charset)
460 : true; // null equals default encoding which is always supported
461 }
462
463 /**
464 * Returns whether this flavor is a text type which does not support the
465 * 'charset' parameter.
466 */
467 public static boolean isFlavorNoncharsetTextType(DataFlavor flavor) {
468 if (!"text".equals(flavor.getPrimaryType()) ||
469 doesSubtypeSupportCharset(flavor))
470 {
471 return false;
472 }
473
474 return (flavor.isRepresentationClassInputStream() ||
475 flavor.isRepresentationClassByteBuffer() ||
476 DataTransferer.byteArrayClass.
477 equals(flavor.getRepresentationClass()));
478 }
479
480 /**
481 * Determines whether this JRE can both encode and decode text in the
482 * specified encoding.
483 */
484 public static boolean isEncodingSupported(String encoding) {
485 if (encoding == null) {
486 return false;
487 }
488 try {
489 return Charset.isSupported(encoding);
490 } catch (IllegalCharsetNameException icne) {
491 return false;
492 }
493 }
494
495 /**
496 * Returns an Iterator which traverses a SortedSet of Strings which are
497 * a total order of the standard character sets supported by the JRE. The
498 * ordering follows the same principles as DataFlavor.selectBestTextFlavor.
499 * So as to avoid loading all available character converters, optional,
500 * non-standard, character sets are not included.
501 */
502 public static Iterator standardEncodings() {
503 if (standardEncodings == null) {
504 TreeSet tempSet = new TreeSet(defaultCharsetComparator);
505 tempSet.add("US-ASCII");
506 tempSet.add("ISO-8859-1");
507 tempSet.add("UTF-8");
508 tempSet.add("UTF-16BE");
509 tempSet.add("UTF-16LE");
510 tempSet.add("UTF-16");
511 tempSet.add(getDefaultTextCharset());
512 standardEncodings = Collections.unmodifiableSortedSet(tempSet);
513 }
514 return standardEncodings.iterator();
515 }
516
517 /**
518 * Converts a FlavorMap to a FlavorTable.
519 */
520 public static FlavorTable adaptFlavorMap(final FlavorMap map) {
521 if (map instanceof FlavorTable) {
522 return (FlavorTable)map;
523 }
524
525 return new FlavorTable() {
526 public Map getNativesForFlavors(DataFlavor[] flavors) {
527 return map.getNativesForFlavors(flavors);
528 }
529 public Map getFlavorsForNatives(String[] natives) {
530 return map.getFlavorsForNatives(natives);
531 }
532 public List getNativesForFlavor(DataFlavor flav) {
533 Map natives =
534 getNativesForFlavors(new DataFlavor[] { flav } );
535 String nat = (String)natives.get(flav);
536 if (nat != null) {
537 List list = new ArrayList(1);
538 list.add(nat);
539 return list;
540 } else {
541 return Collections.EMPTY_LIST;
542 }
543 }
544 public List getFlavorsForNative(String nat) {
545 Map flavors =
546 getFlavorsForNatives(new String[] { nat } );
547 DataFlavor flavor = (DataFlavor)flavors.get(nat);
548 if (flavor != null) {
549 List list = new ArrayList(1);
550 list.add(flavor);
551 return list;
552 } else {
553 return Collections.EMPTY_LIST;
554 }
555 }
556 };
557 }
558
559 /**
560 * Returns the default Unicode encoding for the platform. The encoding
561 * need not be canonical. This method is only used by the archaic function
562 * DataFlavor.getTextPlainUnicodeFlavor().
563 */
564 public abstract String getDefaultUnicodeEncoding();
565
566 /**
567 * This method is called for text flavor mappings established while parsing
568 * the flavormap.properties file. It stores the "eoln" and "terminators"
569 * parameters which are not officially part of the MIME type. They are
570 * MIME parameters specific to the flavormap.properties file format.
571 */
572 public void registerTextFlavorProperties(String nat, String charset,
573 String eoln, String terminators) {
574 Long format = getFormatForNativeAsLong(nat);
575
576 textNatives.add(format);
577 nativeCharsets.put(format, (charset != null && charset.length() != 0)
578 ? charset : getDefaultTextCharset());
579 if (eoln != null && eoln.length() != 0 && !eoln.equals("\n")) {
580 nativeEOLNs.put(format, eoln);
581 }
582 if (terminators != null && terminators.length() != 0) {
583 Integer iTerminators = Integer.valueOf(terminators);
584 if (iTerminators.intValue() > 0) {
585 nativeTerminators.put(format, iTerminators);
586 }
587 }
588 }
589
590 /**
591 * Determines whether the native corresponding to the specified long format
592 * was listed in the flavormap.properties file.
593 */
594 protected boolean isTextFormat(long format) {
595 return textNatives.contains(Long.valueOf(format));
596 }
597
598 protected String getCharsetForTextFormat(Long lFormat) {
599 return (String)nativeCharsets.get(lFormat);
600 }
601
602 /**
603 * Specifies whether text imported from the native system in the specified
604 * format is locale-dependent. If so, when decoding such text,
605 * 'nativeCharsets' should be ignored, and instead, the Transferable should
606 * be queried for its javaTextEncodingFlavor data for the correct encoding.
607 */
608 public abstract boolean isLocaleDependentTextFormat(long format);
609
610 /**
611 * Determines whether the DataFlavor corresponding to the specified long
612 * format is DataFlavor.javaFileListFlavor.
613 */
614 public abstract boolean isFileFormat(long format);
615
616 /**
617 * Determines whether the DataFlavor corresponding to the specified long
618 * format is DataFlavor.imageFlavor.
619 */
620 public abstract boolean isImageFormat(long format);
621
622 /**
623 * Returns a Map whose keys are all of the possible formats into which the
624 * Transferable's transfer data flavors can be translated. The value of
625 * each key is the DataFlavor in which the Transferable's data should be
626 * requested when converting to the format.
627 * <p>
628 * The map keys are sorted according to the native formats preference
629 * order.
630 */
631 public SortedMap getFormatsForTransferable(Transferable contents,
632 FlavorTable map) {
633 DataFlavor[] flavors = contents.getTransferDataFlavors();
634 if (flavors == null) {
635 return new TreeMap();
636 }
637 return getFormatsForFlavors(flavors, map);
638 }
639
640 /**
641 * Returns a Map whose keys are all of the possible formats into which data
642 * in the specified DataFlavor can be translated. The value of each key
643 * is the DataFlavor in which a Transferable's data should be requested
644 * when converting to the format.
645 * <p>
646 * The map keys are sorted according to the native formats preference
647 * order.
648 */
649 public SortedMap getFormatsForFlavor(DataFlavor flavor, FlavorTable map) {
650 return getFormatsForFlavors(new DataFlavor[] { flavor },
651 map);
652 }
653
654 /**
655 * Returns a Map whose keys are all of the possible formats into which data
656 * in the specified DataFlavors can be translated. The value of each key
657 * is the DataFlavor in which the Transferable's data should be requested
658 * when converting to the format.
659 * <p>
660 * The map keys are sorted according to the native formats preference
661 * order.
662 *
663 * @param flavors the data flavors
664 * @param map the FlavorTable which contains mappings between
665 * DataFlavors and data formats
666 * @throws NullPointerException if flavors or map is <code>null</code>
667 */
668 public SortedMap getFormatsForFlavors(DataFlavor[] flavors, FlavorTable map) {
669 Map formatMap = new HashMap(flavors.length);
670 Map textPlainMap = new HashMap(flavors.length);
671 // Maps formats to indices that will be used to sort the formats
672 // according to the preference order.
673 // Larger index value corresponds to the more preferable format.
674 Map indexMap = new HashMap(flavors.length);
675 Map textPlainIndexMap = new HashMap(flavors.length);
676
677 int currentIndex = 0;
678
679 // Iterate backwards so that preferred DataFlavors are used over
680 // other DataFlavors. (See javadoc for
681 // Transferable.getTransferDataFlavors.)
682 for (int i = flavors.length - 1; i >= 0; i--) {
683 DataFlavor flavor = flavors[i];
684 if (flavor == null) continue;
685
686 // Don't explicitly test for String, since it is just a special
687 // case of Serializable
688 if (flavor.isFlavorTextType() ||
689 flavor.isFlavorJavaFileListType() ||
690 DataFlavor.imageFlavor.equals(flavor) ||
691 flavor.isRepresentationClassSerializable() ||
692 flavor.isRepresentationClassInputStream() ||
693 flavor.isRepresentationClassRemote())
694 {
695 List natives = map.getNativesForFlavor(flavor);
696
697 currentIndex += natives.size();
698
699 for (Iterator iter = natives.iterator(); iter.hasNext(); ) {
700 Long lFormat =
701 getFormatForNativeAsLong((String)iter.next());
702 Integer index = Integer.valueOf(currentIndex--);
703
704 formatMap.put(lFormat, flavor);
705 indexMap.put(lFormat, index);
706
707 // SystemFlavorMap.getNativesForFlavor will return
708 // text/plain natives for all text/*. While this is good
709 // for a single text/* flavor, we would prefer that
710 // text/plain native data come from a text/plain flavor.
711 if (("text".equals(flavor.getPrimaryType()) &&
712 "plain".equals(flavor.getSubType())) ||
713 flavor.equals(DataFlavor.stringFlavor))
714 {
715 textPlainMap.put(lFormat, flavor);
716 textPlainIndexMap.put(lFormat, index);
717 }
718 }
719
720 currentIndex += natives.size();
721 }
722 }
723
724 formatMap.putAll(textPlainMap);
725 indexMap.putAll(textPlainIndexMap);
726
727 // Sort the map keys according to the formats preference order.
728 Comparator comparator =
729 new IndexOrderComparator(indexMap, IndexedComparator.SELECT_WORST);
730 SortedMap sortedMap = new TreeMap(comparator);
731 sortedMap.putAll(formatMap);
732
733 return sortedMap;
734 }
735
736 /**
737 * Reduces the Map output for the root function to an array of the
738 * Map's keys.
739 */
740 public long[] getFormatsForTransferableAsArray(Transferable contents,
741 FlavorTable map) {
742 return keysToLongArray(getFormatsForTransferable(contents, map));
743 }
744 public long[] getFormatsForFlavorAsArray(DataFlavor flavor,
745 FlavorTable map) {
746 return keysToLongArray(getFormatsForFlavor(flavor, map));
747 }
748 public long[] getFormatsForFlavorsAsArray(DataFlavor[] flavors,
749 FlavorTable map) {
750 return keysToLongArray(getFormatsForFlavors(flavors, map));
751 }
752
753 /**
754 * Returns a Map whose keys are all of the possible DataFlavors into which
755 * data in the specified format can be translated. The value of each key
756 * is the format in which the Clipboard or dropped data should be requested
757 * when converting to the DataFlavor.
758 */
759 public Map getFlavorsForFormat(long format, FlavorTable map) {
760 return getFlavorsForFormats(new long[] { format }, map);
761 }
762
763 /**
764 * Returns a Map whose keys are all of the possible DataFlavors into which
765 * data in the specified formats can be translated. The value of each key
766 * is the format in which the Clipboard or dropped data should be requested
767 * when converting to the DataFlavor.
768 */
769 public Map getFlavorsForFormats(long[] formats, FlavorTable map) {
770 Map flavorMap = new HashMap(formats.length);
771 Set mappingSet = new HashSet(formats.length);
772 Set flavorSet = new HashSet(formats.length);
773
774 // First step: build flavorSet, mappingSet and initial flavorMap
775 // flavorSet - the set of all the DataFlavors into which
776 // data in the specified formats can be translated;
777 // mappingSet - the set of all the mappings from the specified formats
778 // into any DataFlavor;
779 // flavorMap - after this step, this map maps each of the DataFlavors
780 // from flavorSet to any of the specified formats.
781 for (int i = 0; i < formats.length; i++) {
782 long format = formats[i];
783 String nat = getNativeForFormat(format);
784 List flavors = map.getFlavorsForNative(nat);
785
786 for (Iterator iter = flavors.iterator(); iter.hasNext(); ) {
787 DataFlavor flavor = (DataFlavor)iter.next();
788
789 // Don't explicitly test for String, since it is just a special
790 // case of Serializable
791 if (flavor.isFlavorTextType() ||
792 flavor.isFlavorJavaFileListType() ||
793 DataFlavor.imageFlavor.equals(flavor) ||
794 flavor.isRepresentationClassSerializable() ||
795 flavor.isRepresentationClassInputStream() ||
796 flavor.isRepresentationClassRemote())
797 {
798 Long lFormat = Long.valueOf(format);
799 Object mapping =
800 DataTransferer.createMapping(lFormat, flavor);
801 flavorMap.put(flavor, lFormat);
802 mappingSet.add(mapping);
803 flavorSet.add(flavor);
804 }
805 }
806 }
807
808 // Second step: for each DataFlavor try to figure out which of the
809 // specified formats is the best to translate to this flavor.
810 // Then map each flavor to the best format.
811 // For the given flavor, FlavorTable indicates which native will
812 // best reflect data in the specified flavor to the underlying native
813 // platform. We assume that this native is the best to translate
814 // to this flavor.
815 // Note: FlavorTable allows one-way mappings, so we can occasionally
816 // map a flavor to the format for which the corresponding
817 // format-to-flavor mapping doesn't exist. For this reason we have built
818 // a mappingSet of all format-to-flavor mappings for the specified formats
819 // and check if the format-to-flavor mapping exists for the
820 // (flavor,format) pair being added.
821 for (Iterator flavorIter = flavorSet.iterator();
822 flavorIter.hasNext(); ) {
823 DataFlavor flavor = (DataFlavor)flavorIter.next();
824
825 List natives = map.getNativesForFlavor(flavor);
826
827 for (Iterator nativeIter = natives.iterator();
828 nativeIter.hasNext(); ) {
829 Long lFormat =
830 getFormatForNativeAsLong((String)nativeIter.next());
831 Object mapping = DataTransferer.createMapping(lFormat, flavor);
832
833 if (mappingSet.contains(mapping)) {
834 flavorMap.put(flavor, lFormat);
835 break;
836 }
837 }
838 }
839
840 return flavorMap;
841 }
842
843 /**
844 * Returns a Set of all DataFlavors for which
845 * 1) a mapping from at least one of the specified formats exists in the
846 * specified map and
847 * 2) the data translation for this mapping can be performed by the data
848 * transfer subsystem.
849 *
850 * @param formats the data formats
851 * @param map the FlavorTable which contains mappings between
852 * DataFlavors and data formats
853 * @throws NullPointerException if formats or map is <code>null</code>
854 */
855 public Set getFlavorsForFormatsAsSet(long[] formats, FlavorTable map) {
856 Set flavorSet = new HashSet(formats.length);
857
858 for (int i = 0; i < formats.length; i++) {
859 String nat = getNativeForFormat(formats[i]);
860 List flavors = map.getFlavorsForNative(nat);
861
862 for (Iterator iter = flavors.iterator(); iter.hasNext(); ) {
863 DataFlavor flavor = (DataFlavor)iter.next();
864
865 // Don't explicitly test for String, since it is just a special
866 // case of Serializable
867 if (flavor.isFlavorTextType() ||
868 flavor.isFlavorJavaFileListType() ||
869 DataFlavor.imageFlavor.equals(flavor) ||
870 flavor.isRepresentationClassSerializable() ||
871 flavor.isRepresentationClassInputStream() ||
872 flavor.isRepresentationClassRemote())
873 {
874 flavorSet.add(flavor);
875 }
876 }
877 }
878
879 return flavorSet;
880 }
881
882 /**
883 * Returns an array of all DataFlavors for which
884 * 1) a mapping from the specified format exists in the specified map and
885 * 2) the data translation for this mapping can be performed by the data
886 * transfer subsystem.
887 * The array will be sorted according to a
888 * <code>DataFlavorComparator</code> created with the specified
889 * map as an argument.
890 *
891 * @param format the data format
892 * @param map the FlavorTable which contains mappings between
893 * DataFlavors and data formats
894 * @throws NullPointerException if map is <code>null</code>
895 */
896 public DataFlavor[] getFlavorsForFormatAsArray(long format,
897 FlavorTable map) {
898 return getFlavorsForFormatsAsArray(new long[] { format }, map);
899 }
900
901 /**
902 * Returns an array of all DataFlavors for which
903 * 1) a mapping from at least one of the specified formats exists in the
904 * specified map and
905 * 2) the data translation for this mapping can be performed by the data
906 * transfer subsystem.
907 * The array will be sorted according to a
908 * <code>DataFlavorComparator</code> created with the specified
909 * map as an argument.
910 *
911 * @param formats the data formats
912 * @param map the FlavorTable which contains mappings between
913 * DataFlavors and data formats
914 * @throws NullPointerException if formats or map is <code>null</code>
915 */
916 public DataFlavor[] getFlavorsForFormatsAsArray(long[] formats,
917 FlavorTable map) {
918 // getFlavorsForFormatsAsSet() is less expensive than
919 // getFlavorsForFormats().
920 return setToSortedDataFlavorArray(getFlavorsForFormatsAsSet(formats, map));
921 }
922
923 /**
924 * Returns an object that represents a mapping between the specified
925 * key and value. <tt>null</tt> values and the <tt>null</tt> keys are
926 * permitted. The internal representation of the mapping object is
927 * irrelevant. The only requrement is that the two mapping objects are equal
928 * if and only if their keys are equal and their values are equal.
929 * More formally, the two mapping objects are equal if and only if
930 * <tt>(value1 == null ? value2 == null : value1.equals(value2))
931 * && (key1 == null ? key2 == null : key1.equals(key2))</tt>.
932 */
933 private static Object createMapping(Object key, Object value) {
934 // NOTE: Should be updated to use AbstractMap.SimpleEntry as
935 // soon as it is made public.
936 return Arrays.asList(new Object[] { key, value });
937 }
938
939 /**
940 * Looks-up or registers the String native with the native data transfer
941 * system and returns a long format corresponding to that native.
942 */
943 protected abstract Long getFormatForNativeAsLong(String str);
944
945 /**
946 * Looks-up the String native corresponding to the specified long format in
947 * the native data transfer system.
948 */
949 protected abstract String getNativeForFormat(long format);
950
951 /* Contains common code for finding the best charset for
952 * clipboard string encoding/decoding, basing on clipboard
953 * format and localeTransferable(on decoding, if available)
954 */
955 private String getBestCharsetForTextFormat(Long lFormat,
956 Transferable localeTransferable) throws IOException
957 {
958 String charset = null;
959 if (localeTransferable != null &&
960 isLocaleDependentTextFormat(lFormat) &&
961 localeTransferable.isDataFlavorSupported(javaTextEncodingFlavor))
962 {
963 try {
964 charset = new String(
965 (byte[])localeTransferable.getTransferData(javaTextEncodingFlavor),
966 "UTF-8"
967 );
968 } catch (UnsupportedFlavorException cannotHappen) {
969 }
970 } else {
971 charset = getCharsetForTextFormat(lFormat);
972 }
973 if (charset == null) {
974 // Only happens when we have a custom text type.
975 charset = getDefaultTextCharset();
976 }
977 return charset;
978 }
979
980 /**
981 * Translation function for converting string into
982 * a byte array. Search-and-replace EOLN. Encode into the
983 * target format. Append terminating NUL bytes.
984 *
985 * Java to Native string conversion
986 */
987 private byte[] translateTransferableString(String str,
988 long format) throws IOException
989 {
990 Long lFormat = Long.valueOf(format);
991 String charset = getBestCharsetForTextFormat(lFormat, null);
992 // Search and replace EOLN. Note that if EOLN is "\n", then we
993 // never added an entry to nativeEOLNs anyway, so we'll skip this
994 // code altogether.
995 // windows: "abc\nde"->"abc\r\nde"
996 String eoln = (String)nativeEOLNs.get(lFormat);
997 if (eoln != null) {
998 int length = str.length();
999 StringBuffer buffer =
1000 new StringBuffer(length * 2); // 2 is a heuristic
1001 for (int i = 0; i < length; i++) {
1002 // Fix for 4914613 - skip native EOLN
1003 if (str.startsWith(eoln, i)) {
1004 buffer.append(eoln);
1005 i += eoln.length() - 1;
1006 continue;
1007 }
1008 char c = str.charAt(i);
1009 if (c == '\n') {
1010 buffer.append(eoln);
1011 } else {
1012 buffer.append(c);
1013 }
1014 }
1015 str = buffer.toString();
1016 }
1017
1018 // Encode text in target format.
1019 byte[] bytes = str.getBytes(charset);
1020
1021 // Append terminating NUL bytes. Note that if terminators is 0,
1022 // the we never added an entry to nativeTerminators anyway, so
1023 // we'll skip code altogether.
1024 // "abcde" -> "abcde\0"
1025 Integer terminators = (Integer)nativeTerminators.get(lFormat);
1026 if (terminators != null) {
1027 int numTerminators = terminators.intValue();
1028 byte[] terminatedBytes =
1029 new byte[bytes.length + numTerminators];
1030 System.arraycopy(bytes, 0, terminatedBytes, 0, bytes.length);
1031 for (int i = bytes.length; i < terminatedBytes.length; i++) {
1032 terminatedBytes[i] = 0x0;
1033 }
1034 bytes = terminatedBytes;
1035 }
1036 return bytes;
1037 }
1038
1039 /**
1040 * Translating either a byte array or an InputStream into an String.
1041 * Strip terminators and search-and-replace EOLN.
1042 *
1043 * Native to Java string conversion
1044 */
1045 private String translateBytesOrStreamToString(InputStream str, byte[] bytes,
1046 long format,
1047 Transferable localeTransferable)
1048 throws IOException
1049 {
1050 // A String holds all of its data in memory at one time, so
1051 // we can't avoid reading the entire InputStream at this point.
1052 if (bytes == null) {
1053 bytes = inputStreamToByteArray(str);
1054 }
1055 str.close();
1056
1057 Long lFormat = Long.valueOf(format);
1058 String charset = getBestCharsetForTextFormat(lFormat, localeTransferable);
1059
1060 // Locate terminating NUL bytes. Note that if terminators is 0,
1061 // the we never added an entry to nativeTerminators anyway, so
1062 // we'll skip code altogether.
1063
1064 // In other words: we are doing char alignment here basing on suggestion
1065 // that count of zero-'terminators' is a number of bytes in one symbol
1066 // for selected charset (clipboard format). It is not complitly true for
1067 // multibyte coding like UTF-8, but helps understand the procedure.
1068 // "abcde\0" -> "abcde"
1069
1070 String eoln = (String)nativeEOLNs.get(lFormat);
1071 Integer terminators = (Integer)nativeTerminators.get(lFormat);
1072 int count;
1073 if (terminators != null) {
1074 int numTerminators = terminators.intValue();
1075search:
1076 for (count = 0; count < (bytes.length - numTerminators + 1); count += numTerminators) {
1077 for (int i = count; i < count + numTerminators; i++) {
1078 if (bytes[i] != 0x0) {
1079 continue search;
1080 }
1081 }
1082 // found terminators
1083 break search;
1084 }
1085 } else {
1086 count = bytes.length;
1087 }
1088
1089 // Decode text to chars. Don't include any terminators.
1090 String converted = new String(bytes, 0, count, charset);
1091
1092 // Search and replace EOLN. Note that if EOLN is "\n", then we
1093 // never added an entry to nativeEOLNs anyway, so we'll skip this
1094 // code altogether.
1095 // Count of NUL-terminators and EOLN coding are platform-specific and
1096 // loaded from flavormap.properties file
1097 // windows: "abc\r\nde" -> "abc\nde"
1098
1099 if (eoln != null) {
1100
1101 /* Fix for 4463560: replace EOLNs symbol-by-symbol instead
1102 * of using buf.replace()
1103 */
1104
1105 char[] buf = converted.toCharArray();
1106 char[] eoln_arr = eoln.toCharArray();
1107 converted = null;
1108 int j = 0;
1109 boolean match;
1110
1111 for (int i = 0; i < buf.length; ) {
1112 // Catch last few bytes
1113 if (i + eoln_arr.length > buf.length) {
1114 buf[j++] = buf[i++];
1115 continue;
1116 }
1117
1118 match = true;
1119 for (int k = 0, l = i; k < eoln_arr.length; k++, l++) {
1120 if (eoln_arr[k] != buf[l]) {
1121 match = false;
1122 break;
1123 }
1124 }
1125 if (match) {
1126 buf[j++] = '\n';
1127 i += eoln_arr.length;
1128 } else {
1129 buf[j++] = buf[i++];
1130 }
1131 }
1132 converted = new String(buf, 0, j);
1133 }
1134
1135 return converted;
1136 }
1137
1138
1139 /**
1140 * Primary translation function for translating a Transferable into
1141 * a byte array, given a source DataFlavor and target format.
1142 */
1143 public byte[] translateTransferable(Transferable contents,
1144 DataFlavor flavor,
1145 long format) throws IOException
1146 {
1147 // Obtain the transfer data in the source DataFlavor.
1148 //
1149 // Note that we special case DataFlavor.plainTextFlavor because
1150 // StringSelection supports this flavor incorrectly -- instead of
1151 // returning an InputStream as the DataFlavor representation class
1152 // states, it returns a Reader. Instead of using this broken
1153 // functionality, we request the data in stringFlavor (the other
1154 // DataFlavor which StringSelection supports) and use the String
1155 // translator.
1156 Object obj;
1157 boolean stringSelectionHack;
1158 try {
1159 obj = contents.getTransferData(flavor);
1160 if (obj == null) {
1161 return null;
1162 }
1163 if (flavor.equals(DataFlavor.plainTextFlavor) &&
1164 !(obj instanceof InputStream))
1165 {
1166 obj = contents.getTransferData(DataFlavor.stringFlavor);
1167 if (obj == null) {
1168 return null;
1169 }
1170 stringSelectionHack = true;
1171 } else {
1172 stringSelectionHack = false;
1173 }
1174 } catch (UnsupportedFlavorException e) {
1175 throw new IOException(e.getMessage());
1176 }
1177
1178 // Source data is a String. Search-and-replace EOLN. Encode into the
1179 // target format. Append terminating NUL bytes.
1180 if (stringSelectionHack ||
1181 (String.class.equals(flavor.getRepresentationClass()) &&
1182 isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1183
1184 return translateTransferableString(
1185 (String)obj,
1186 format);
1187
1188 // Source data is a Reader. Convert to a String and recur. In the
1189 // future, we may want to rewrite this so that we encode on demand.
1190 } else if (flavor.isRepresentationClassReader()) {
1191 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1192 throw new IOException
1193 ("cannot transfer non-text data as Reader");
1194 }
1195
1196 Reader r = (Reader)obj;
1197 StringBuffer buf = new StringBuffer();
1198 int c;
1199 while ((c = r.read()) != -1) {
1200 buf.append((char)c);
1201 }
1202 r.close();
1203
1204 return translateTransferableString(
1205 buf.toString(),
1206 format);
1207
1208 // Source data is a CharBuffer. Convert to a String and recur.
1209 } else if (flavor.isRepresentationClassCharBuffer()) {
1210 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1211 throw new IOException
1212 ("cannot transfer non-text data as CharBuffer");
1213 }
1214
1215 CharBuffer buffer = (CharBuffer)obj;
1216 int size = buffer.remaining();
1217 char[] chars = new char[size];
1218 buffer.get(chars, 0, size);
1219
1220 return translateTransferableString(
1221 new String(chars),
1222 format);
1223
1224 // Source data is a char array. Convert to a String and recur.
1225 } else if (charArrayClass.equals(flavor.getRepresentationClass())) {
1226 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1227 throw new IOException
1228 ("cannot transfer non-text data as char array");
1229 }
1230
1231 return translateTransferableString(
1232 new String((char[])obj),
1233 format);
1234
1235 // Source data is a ByteBuffer. For arbitrary flavors, simply return
1236 // the array. For text flavors, decode back to a String and recur to
1237 // reencode according to the requested format.
1238 } else if (flavor.isRepresentationClassByteBuffer()) {
1239 ByteBuffer buffer = (ByteBuffer)obj;
1240 int size = buffer.remaining();
1241 byte[] bytes = new byte[size];
1242 buffer.get(bytes, 0, size);
1243
1244 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1245 String sourceEncoding = DataTransferer.getTextCharset(flavor);
1246 return translateTransferableString(
1247 new String(bytes, sourceEncoding),
1248 format);
1249 } else {
1250 return bytes;
1251 }
1252
1253 // Source data is a byte array. For arbitrary flavors, simply return
1254 // the array. For text flavors, decode back to a String and recur to
1255 // reencode according to the requested format.
1256 } else if (byteArrayClass.equals(flavor.getRepresentationClass())) {
1257 byte[] bytes = (byte[])obj;
1258
1259 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1260 String sourceEncoding = DataTransferer.getTextCharset(flavor);
1261 return translateTransferableString(
1262 new String(bytes, sourceEncoding),
1263 format);
1264 } else {
1265 return bytes;
1266 }
1267 // Source data is Image
1268 } else if (DataFlavor.imageFlavor.equals(flavor)) {
1269 if (!isImageFormat(format)) {
1270 throw new IOException("Data translation failed: " +
1271 "not an image format");
1272 }
1273
1274 Image image = (Image)obj;
1275 byte[] bytes = imageToPlatformBytes(image, format);
1276
1277 if (bytes == null) {
1278 throw new IOException("Data translation failed: " +
1279 "cannot convert java image to native format");
1280 }
1281 return bytes;
1282 }
1283
1284 ByteArrayOutputStream bos = new ByteArrayOutputStream();
1285
1286 // Target data is a file list. Source data must be a
1287 // java.util.List which contains java.io.File or String instances.
1288 if (isFileFormat(format)) {
1289 if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
1290 throw new IOException("data translation failed");
1291 }
1292 final List list = (List)obj;
1293 int nFiles = 0;
1294 for (int i = 0; i < list.size(); i++) {
1295 Object o = list.get(i);
1296 if (o instanceof File || o instanceof String) {
1297 nFiles++;
1298 }
1299 }
1300 final String[] files = new String[nFiles];
1301
1302 try {
1303 AccessController.doPrivileged(new PrivilegedExceptionAction() {
1304 public Object run() throws IOException {
1305 for (int i = 0, j = 0; i < list.size(); i++) {
1306 Object o = list.get(i);
1307 if (o instanceof File) {
1308 files[j++] = ((File)o).getCanonicalPath();
1309 } else if (o instanceof String) {
1310 files[j++] = (String)o;
1311 }
1312 }
1313 return null;
1314 }
1315 });
1316 } catch (PrivilegedActionException pae) {
1317 throw new IOException(pae.getMessage());
1318 }
1319
1320 for (int i = 0; i < files.length; i++) {
1321 byte[] bytes = files[i].getBytes();
1322 if (i != 0) bos.write(0);
1323 bos.write(bytes, 0, bytes.length);
1324 }
1325
1326 // Source data is an InputStream. For arbitrary flavors, just grab the
1327 // bytes and dump them into a byte array. For text flavors, decode back
1328 // to a String and recur to reencode according to the requested format.
1329 } else if (flavor.isRepresentationClassInputStream()) {
1330 InputStream is = (InputStream)obj;
1331 boolean eof = false;
1332 int avail = is.available();
1333 byte[] tmp = new byte[avail > 8192 ? avail : 8192];
1334 do {
1335 int ret;
1336 if (!(eof = (ret = is.read(tmp, 0, tmp.length)) == -1)) {
1337 bos.write(tmp, 0, ret);
1338 }
1339 } while (!eof);
1340 is.close();
1341
1342 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1343 byte[] bytes = bos.toByteArray();
1344 bos.close();
1345 String sourceEncoding = DataTransferer.getTextCharset(flavor);
1346 return translateTransferableString(
1347 new String(bytes, sourceEncoding),
1348 format);
1349 }
1350
1351 // Source data is an RMI object
1352 } else if (flavor.isRepresentationClassRemote()) {
1353 MarshalledObject mo = new MarshalledObject(obj);
1354 ObjectOutputStream oos = new ObjectOutputStream(bos);
1355 oos.writeObject(mo);
1356 oos.close();
1357
1358 // Source data is Serializable
1359 } else if (flavor.isRepresentationClassSerializable()) {
1360 ObjectOutputStream oos = new ObjectOutputStream(bos);
1361 oos.writeObject(obj);
1362 oos.close();
1363
1364 } else {
1365 throw new IOException("data translation failed");
1366 }
1367
1368 byte[] ret = bos.toByteArray();
1369 bos.close();
1370 return ret;
1371 }
1372
1373 public Object translateBytes(byte[] bytes, DataFlavor flavor,
1374 long format, Transferable localeTransferable)
1375 throws IOException
1376 {
1377 return translateBytesOrStream(null, bytes, flavor, format,
1378 localeTransferable);
1379 }
1380
1381 public Object translateStream(InputStream str, DataFlavor flavor,
1382 long format, Transferable localeTransferable)
1383 throws IOException
1384 {
1385 return translateBytesOrStream(str, null, flavor, format,
1386 localeTransferable);
1387 }
1388
1389
1390 /**
1391 * Primary translation function for translating either a byte array or
1392 * an InputStream into an Object, given a source format and a target
1393 * DataFlavor.
1394 *
1395 * One of str/bytes is non-null; the other is null.
1396 * The conversion from byte[] to InputStream is cheap, so do that
1397 * immediately if necessary. The opposite conversion is expensive,
1398 * so avoid it if possible.
1399 */
1400 protected Object translateBytesOrStream(InputStream str, byte[] bytes,
1401 DataFlavor flavor, long format,
1402 Transferable localeTransferable)
1403 throws IOException
1404 {
1405
1406 if (str == null) {
1407 str = new ByteArrayInputStream(bytes);
1408 }
1409
1410 // Source data is a file list. Use the dragQueryFile native function to
1411 // do most of the decoding. Then wrap File objects around the String
1412 // filenames and return a List.
1413 if (isFileFormat(format)) {
1414 if (!DataFlavor.javaFileListFlavor.equals(flavor)) {
1415 throw new IOException("data translation failed");
1416 }
1417 if (bytes == null) {
1418 bytes = inputStreamToByteArray(str);
1419 }
1420 String[] filenames = dragQueryFile(bytes);
1421 if (filenames == null) {
1422 str.close();
1423 return null;
1424 }
1425
1426 // Convert the strings to File objects
1427 File[] files = new File[filenames.length];
1428 for (int i = 0; i < filenames.length; i++) {
1429 files[i] = new File(filenames[i]);
1430 }
1431 str.close();
1432
1433 // Turn the list of Files into a List and return
1434 return Arrays.asList(files);
1435
1436 // Target data is a String. Strip terminating NUL bytes. Decode bytes
1437 // into characters. Search-and-replace EOLN.
1438 } else if (String.class.equals(flavor.getRepresentationClass()) &&
1439 isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1440
1441 return translateBytesOrStreamToString(
1442 str, bytes,
1443 format, localeTransferable);
1444
1445 // Special hack to maintain backwards-compatibility with the brokenness
1446 // of StringSelection. Return a StringReader instead of an InputStream.
1447 // Recur to obtain String and encapsulate.
1448 } else if (DataFlavor.plainTextFlavor.equals(flavor)) {
1449 return new StringReader(translateBytesOrStreamToString(
1450 str, bytes,
1451 format, localeTransferable));
1452
1453 // Target data is an InputStream. For arbitrary flavors, just return
1454 // the raw bytes. For text flavors, decode to strip terminators and
1455 // search-and-replace EOLN, then reencode according to the requested
1456 // flavor.
1457 } else if (flavor.isRepresentationClassInputStream()) {
1458 return translateBytesOrStreamToInputStream(str, flavor, format,
1459 localeTransferable);
1460
1461 // Target data is a Reader. Obtain data in InputStream format, encoded
1462 // as "Unicode" (utf-16be). Then use an InputStreamReader to decode
1463 // back to chars on demand.
1464 } else if (flavor.isRepresentationClassReader()) {
1465 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1466 throw new IOException
1467 ("cannot transfer non-text data as Reader");
1468 }
1469
1470 InputStream is = (InputStream)
1471 translateBytesOrStreamToInputStream
1472 (str, DataFlavor.plainTextFlavor, format,
1473 localeTransferable);
1474 String unicode =
1475 DataTransferer.getTextCharset(DataFlavor.plainTextFlavor);
1476 Reader reader = new InputStreamReader(is, unicode);
1477
1478 return constructFlavoredObject(reader, flavor, Reader.class);
1479
1480 // Target data is a CharBuffer. Recur to obtain String and wrap.
1481 } else if (flavor.isRepresentationClassCharBuffer()) {
1482 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1483 throw new IOException
1484 ("cannot transfer non-text data as CharBuffer");
1485 }
1486
1487 CharBuffer buffer = CharBuffer.wrap(translateBytesOrStreamToString(
1488 str, bytes,
1489 format, localeTransferable));
1490
1491 return constructFlavoredObject(buffer, flavor, CharBuffer.class);
1492
1493 // Target data is a char array. Recur to obtain String and convert to
1494 // char array.
1495 } else if (charArrayClass.equals(flavor.getRepresentationClass())) {
1496 if (!(isFlavorCharsetTextType(flavor) && isTextFormat(format))) {
1497 throw new IOException
1498 ("cannot transfer non-text data as char array");
1499 }
1500
1501 return translateBytesOrStreamToString(
1502 str, bytes,
1503 format, localeTransferable).toCharArray();
1504
1505 // Target data is a ByteBuffer. For arbitrary flavors, just return
1506 // the raw bytes. For text flavors, convert to a String to strip
1507 // terminators and search-and-replace EOLN, then reencode according to
1508 // the requested flavor.
1509 } else if (flavor.isRepresentationClassByteBuffer()) {
1510 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1511 bytes = translateBytesOrStreamToString(
1512 str, bytes,
1513 format, localeTransferable
1514 ).getBytes(
1515 DataTransferer.getTextCharset(flavor)
1516 );
1517 } else {
1518 if (bytes == null) {
1519 bytes = inputStreamToByteArray(str);
1520 }
1521 }
1522
1523 ByteBuffer buffer = ByteBuffer.wrap(bytes);
1524 return constructFlavoredObject(buffer, flavor, ByteBuffer.class);
1525
1526 // Target data is a byte array. For arbitrary flavors, just return
1527 // the raw bytes. For text flavors, convert to a String to strip
1528 // terminators and search-and-replace EOLN, then reencode according to
1529 // the requested flavor.
1530 } else if (byteArrayClass.equals(flavor.getRepresentationClass())) {
1531 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1532 return translateBytesOrStreamToString(
1533 str, bytes,
1534 format, localeTransferable
1535 ).getBytes(
1536 DataTransferer.getTextCharset(flavor)
1537 );
1538 } else {
1539 return (bytes != null) ? bytes : inputStreamToByteArray(str);
1540 }
1541
1542 // Target data is an RMI object
1543 } else if (flavor.isRepresentationClassRemote()) {
1544 try {
1545 byte[] ba = inputStreamToByteArray(str);
1546 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(ba));
1547 Object ret = ((MarshalledObject)(ois.readObject())).get();
1548 ois.close();
1549 str.close();
1550 return ret;
1551 } catch (Exception e) {
1552 throw new IOException(e.getMessage());
1553 }
1554
1555 // Target data is Serializable
1556 } else if (flavor.isRepresentationClassSerializable()) {
1557 try {
1558 byte[] ba = inputStreamToByteArray(str);
1559 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(ba));
1560 Object ret = ois.readObject();
1561 ois.close();
1562 str.close();
1563 return ret;
1564 } catch (Exception e) {
1565 throw new IOException(e.getMessage());
1566 }
1567
1568 // Target data is Image
1569 } else if (DataFlavor.imageFlavor.equals(flavor)) {
1570 if (!isImageFormat(format)) {
1571 throw new IOException("data translation failed");
1572 }
1573
1574 Image image = platformImageBytesOrStreamToImage(str, bytes, format);
1575 str.close();
1576 return image;
1577 }
1578
1579 throw new IOException("data translation failed");
1580 }
1581
1582 /**
1583 * For arbitrary flavors, just use the raw InputStream. For text flavors,
1584 * ReencodingInputStream will decode and reencode the InputStream on demand
1585 * so that we can strip terminators and search-and-replace EOLN.
1586 */
1587 private Object translateBytesOrStreamToInputStream
1588 (InputStream str, DataFlavor flavor, long format,
1589 Transferable localeTransferable) throws IOException
1590 {
1591 if (isFlavorCharsetTextType(flavor) && isTextFormat(format)) {
1592 str = new ReencodingInputStream
1593 (str, format, DataTransferer.getTextCharset(flavor),
1594 localeTransferable);
1595 }
1596
1597 return constructFlavoredObject(str, flavor, InputStream.class);
1598 }
1599
1600 /**
1601 * We support representations which are exactly of the specified Class,
1602 * and also arbitrary Objects which have a constructor which takes an
1603 * instance of the Class as its sole parameter.
1604 */
1605 private Object constructFlavoredObject(Object arg, DataFlavor flavor,
1606 Class clazz)
1607 throws IOException
1608 {
1609 final Class dfrc = flavor.getRepresentationClass();
1610
1611 if (clazz.equals(dfrc)) {
1612 return arg; // simple case
1613 } else {
1614 Constructor[] constructors = null;
1615
1616 try {
1617 constructors = (Constructor[])
1618 AccessController.doPrivileged(new PrivilegedAction() {
1619 public Object run() {
1620 return dfrc.getConstructors();
1621 }
1622 });
1623 } catch (SecurityException se) {
1624 throw new IOException(se.getMessage());
1625 }
1626
1627 Constructor constructor = null;
1628
1629 for (int j = 0; j < constructors.length; j++) {
1630 if (!Modifier.isPublic(constructors[j].getModifiers())) {
1631 continue;
1632 }
1633
1634 Class[] ptypes = constructors[j].getParameterTypes();
1635
1636 if (ptypes != null && ptypes.length == 1 &&
1637 clazz.equals(ptypes[0])) {
1638 constructor = constructors[j];
1639 break;
1640 }
1641 }
1642
1643 if (constructor == null) {
1644 throw new IOException("can't find <init>(L"+ clazz +
1645 ";)V for class: " + dfrc.getName());
1646 }
1647
1648 try {
1649 return constructor.newInstance(new Object[] { arg } );
1650 } catch (Exception e) {
1651 throw new IOException(e.getMessage());
1652 }
1653 }
1654 }
1655
1656 /**
1657 * Used for decoding and reencoding an InputStream on demand so that we
1658 * can strip NUL terminators and perform EOLN search-and-replace.
1659 */
1660 public class ReencodingInputStream extends InputStream {
1661 protected BufferedReader wrapped;
1662 protected final char[] in = new char[1];
1663 protected byte[] out;
1664
1665 protected CharsetEncoder encoder;
1666 protected CharBuffer inBuf;
1667 protected ByteBuffer outBuf;
1668
1669 protected char[] eoln;
1670 protected int numTerminators;
1671
1672 protected boolean eos;
1673 protected int index, limit;
1674
1675 public ReencodingInputStream(InputStream bytestream, long format,
1676 String targetEncoding,
1677 Transferable localeTransferable)
1678 throws IOException
1679 {
1680 Long lFormat = Long.valueOf(format);
1681
1682 String sourceEncoding = null;
1683 if (isLocaleDependentTextFormat(format) &&
1684 localeTransferable != null &&
1685 localeTransferable.
1686 isDataFlavorSupported(javaTextEncodingFlavor))
1687 {
1688 try {
1689 sourceEncoding = new String((byte[])localeTransferable.
1690 getTransferData(javaTextEncodingFlavor),
1691 "UTF-8");
1692 } catch (UnsupportedFlavorException cannotHappen) {
1693 }
1694 } else {
1695 sourceEncoding = getCharsetForTextFormat(lFormat);
1696 }
1697
1698 if (sourceEncoding == null) {
1699 // Only happens when we have a custom text type.
1700 sourceEncoding = getDefaultTextCharset();
1701 }
1702 wrapped = new BufferedReader
1703 (new InputStreamReader(bytestream, sourceEncoding));
1704
1705 if (targetEncoding == null) {
1706 // Throw NullPointerException for compatibility with the former
1707 // call to sun.io.CharToByteConverter.getConverter(null)
1708 // (Charset.forName(null) throws unspecified IllegalArgumentException
1709 // now; see 6228568)
1710 throw new NullPointerException("null target encoding");
1711 }
1712
1713 try {
1714 encoder = Charset.forName(targetEncoding).newEncoder();
1715 out = new byte[(int)(encoder.maxBytesPerChar() + 0.5)];
1716 inBuf = CharBuffer.wrap(in);
1717 outBuf = ByteBuffer.wrap(out);
1718 } catch (IllegalCharsetNameException e) {
1719 throw new IOException(e.toString());
1720 } catch (UnsupportedCharsetException e) {
1721 throw new IOException(e.toString());
1722 } catch (UnsupportedOperationException e) {
1723 throw new IOException(e.toString());
1724 }
1725
1726 String sEoln = (String)nativeEOLNs.get(lFormat);
1727 if (sEoln != null) {
1728 eoln = sEoln.toCharArray();
1729 }
1730
1731 // A hope and a prayer that this works generically. This will
1732 // definitely work on Win32.
1733 Integer terminators = (Integer)nativeTerminators.get(lFormat);
1734 if (terminators != null) {
1735 numTerminators = terminators.intValue();
1736 }
1737 }
1738
1739 public int read() throws IOException {
1740 if (eos) {
1741 return -1;
1742 }
1743
1744 if (index >= limit) {
1745 int c = wrapped.read();
1746
1747 if (c == -1) { // -1 is EOS
1748 eos = true;
1749 return -1;
1750 }
1751
1752 // "c == 0" is not quite correct, but good enough on Windows.
1753 if (numTerminators > 0 && c == 0) {
1754 eos = true;
1755 return -1;
1756 } else if (eoln != null && matchCharArray(eoln, c)) {
1757 c = '\n' & 0xFFFF;
1758 }
1759
1760 in[0] = (char)c;
1761
1762 inBuf.rewind();
1763 outBuf.rewind();
1764 encoder.encode(inBuf, outBuf, false);
1765 outBuf.flip();
1766 limit = outBuf.limit();
1767
1768 index = 0;
1769
1770 return read();
1771 } else {
1772 return out[index++] & 0xFF;
1773 }
1774 }
1775
1776 public int available() throws IOException {
1777 return ((eos) ? 0 : (limit - index));
1778 }
1779
1780 public void close() throws IOException {
1781 wrapped.close();
1782 }
1783
1784 /**
1785 * Checks to see if the next array.length characters in wrapped
1786 * match array. The first character is provided as c. Subsequent
1787 * characters are read from wrapped itself. When this method returns,
1788 * the wrapped index may be different from what it was when this
1789 * method was called.
1790 */
1791 private boolean matchCharArray(char[] array, int c)
1792 throws IOException
1793 {
1794 wrapped.mark(array.length); // BufferedReader supports mark
1795
1796 int count = 0;
1797 if ((char)c == array[0]) {
1798 for (count = 1; count < array.length; count++) {
1799 c = wrapped.read();
1800 if (c == -1 || ((char)c) != array[count]) {
1801 break;
1802 }
1803 }
1804 }
1805
1806 if (count == array.length) {
1807 return true;
1808 } else {
1809 wrapped.reset();
1810 return false;
1811 }
1812 }
1813 }
1814
1815 /**
1816 * Decodes a byte array into a set of String filenames.
1817 */
1818 protected abstract String[] dragQueryFile(byte[] bytes);
1819
1820 /**
1821 * Translates either a byte array or an input stream which contain
1822 * platform-specific image data in the given format into an Image.
1823 */
1824 protected abstract Image platformImageBytesOrStreamToImage(InputStream str,
1825 byte[] bytes,
1826 long format)
1827 throws IOException;
1828
1829 /**
1830 * Translates either a byte array or an input stream which contain
1831 * an image data in the given standard format into an Image.
1832 *
1833 * @param mimeType image MIME type, such as: image/png, image/jpeg, image/gif
1834 */
1835 protected Image standardImageBytesOrStreamToImage(InputStream inputStream,
1836 byte[] bytes,
1837 String mimeType)
1838 throws IOException {
1839 if (inputStream == null) {
1840 inputStream = new ByteArrayInputStream(bytes);
1841 }
1842
1843 Iterator readerIterator = ImageIO.getImageReadersByMIMEType(mimeType);
1844
1845 if (!readerIterator.hasNext()) {
1846 throw new IOException("No registered service provider can decode " +
1847 " an image from " + mimeType);
1848 }
1849
1850 IOException ioe = null;
1851
1852 while (readerIterator.hasNext()) {
1853 ImageReader imageReader = (ImageReader)readerIterator.next();
1854 try {
1855 ImageInputStream imageInputStream =
1856 ImageIO.createImageInputStream(inputStream);
1857
1858 try {
1859 ImageReadParam param = imageReader.getDefaultReadParam();
1860 imageReader.setInput(imageInputStream, true, true);
1861 BufferedImage bufferedImage =
1862 imageReader.read(imageReader.getMinIndex(), param);
1863 if (bufferedImage != null) {
1864 return bufferedImage;
1865 }
1866 } finally {
1867 imageInputStream.close();
1868 imageReader.dispose();
1869 }
1870 } catch (IOException e) {
1871 ioe = e;
1872 continue;
1873 }
1874 }
1875
1876 if (ioe == null) {
1877 ioe = new IOException("Registered service providers failed to decode"
1878 + " an image from " + mimeType);
1879 }
1880
1881 throw ioe;
1882 }
1883
1884 /**
1885 * Translates a Java Image into a byte array which contains platform-
1886 * specific image data in the given format.
1887 */
1888 protected abstract byte[] imageToPlatformBytes(Image image, long format)
1889 throws IOException;
1890
1891 /**
1892 * Translates a Java Image into a byte array which contains
1893 * an image data in the given standard format.
1894 *
1895 * @param mimeType image MIME type, such as: image/png, image/jpeg
1896 */
1897 protected byte[] imageToStandardBytes(Image image, String mimeType)
1898 throws IOException {
1899 IOException originalIOE = null;
1900
1901 Iterator writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
1902
1903 if (!writerIterator.hasNext()) {
1904 throw new IOException("No registered service provider can encode " +
1905 " an image to " + mimeType);
1906 }
1907
1908 if (image instanceof RenderedImage) {
1909 // Try to encode the original image.
1910 try {
1911 return imageToStandardBytesImpl((RenderedImage)image, mimeType);
1912 } catch (IOException ioe) {
1913 originalIOE = ioe;
1914 }
1915 }
1916
1917 // Retry with a BufferedImage.
1918 int width = 0;
1919 int height = 0;
1920 if (image instanceof ToolkitImage) {
1921 ImageRepresentation ir = ((ToolkitImage)image).getImageRep();
1922 ir.reconstruct(ImageObserver.ALLBITS);
1923 width = ir.getWidth();
1924 height = ir.getHeight();
1925 } else {
1926 width = image.getWidth(null);
1927 height = image.getHeight(null);
1928 }
1929
1930 ColorModel model = ColorModel.getRGBdefault();
1931 WritableRaster raster =
1932 model.createCompatibleWritableRaster(width, height);
1933
1934 BufferedImage bufferedImage =
1935 new BufferedImage(model, raster, model.isAlphaPremultiplied(),
1936 null);
1937
1938 Graphics g = bufferedImage.getGraphics();
1939 try {
1940 g.drawImage(image, 0, 0, width, height, null);
1941 } finally {
1942 g.dispose();
1943 }
1944
1945 try {
1946 return imageToStandardBytesImpl(bufferedImage, mimeType);
1947 } catch (IOException ioe) {
1948 if (originalIOE != null) {
1949 throw originalIOE;
1950 } else {
1951 throw ioe;
1952 }
1953 }
1954 }
1955
1956 protected byte[] imageToStandardBytesImpl(RenderedImage renderedImage,
1957 String mimeType)
1958 throws IOException {
1959
1960 Iterator writerIterator = ImageIO.getImageWritersByMIMEType(mimeType);
1961
1962 ImageTypeSpecifier typeSpecifier =
1963 new ImageTypeSpecifier(renderedImage);
1964
1965 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1966 IOException ioe = null;
1967
1968 while (writerIterator.hasNext()) {
1969 ImageWriter imageWriter = (ImageWriter)writerIterator.next();
1970 ImageWriterSpi writerSpi = imageWriter.getOriginatingProvider();
1971
1972 if (!writerSpi.canEncodeImage(typeSpecifier)) {
1973 continue;
1974 }
1975
1976 try {
1977 ImageOutputStream imageOutputStream =
1978 ImageIO.createImageOutputStream(baos);
1979 try {
1980 imageWriter.setOutput(imageOutputStream);
1981 imageWriter.write(renderedImage);
1982 imageOutputStream.flush();
1983 } finally {
1984 imageOutputStream.close();
1985 }
1986 } catch (IOException e) {
1987 imageWriter.dispose();
1988 baos.reset();
1989 ioe = e;
1990 continue;
1991 }
1992
1993 imageWriter.dispose();
1994 baos.close();
1995 return baos.toByteArray();
1996 }
1997
1998 baos.close();
1999
2000 if (ioe == null) {
2001 ioe = new IOException("Registered service providers failed to encode "
2002 + renderedImage + " to " + mimeType);
2003 }
2004
2005 throw ioe;
2006 }
2007
2008 /**
2009 * Concatenates the data represented by two objects. Objects can be either
2010 * byte arrays or instances of <code>InputStream</code>. If both arguments
2011 * are byte arrays byte array will be returned. Otherwise an
2012 * <code>InputStream</code> will be returned.
2013 * <p>
2014 * Currently is only called from native code to prepend palette data to
2015 * platform-specific image data during image transfer on Win32.
2016 *
2017 * @param obj1 the first object to be concatenated.
2018 * @param obj2 the second object to be concatenated.
2019 * @return a byte array or an <code>InputStream</code> which represents
2020 * a logical concatenation of the two arguments.
2021 * @throws NullPointerException is either of the arguments is
2022 * <code>null</code>
2023 * @throws ClassCastException is either of the arguments is
2024 * neither byte array nor an instance of <code>InputStream</code>.
2025 */
2026 private Object concatData(Object obj1, Object obj2) {
2027 InputStream str1 = null;
2028 InputStream str2 = null;
2029
2030 if (obj1 instanceof byte[]) {
2031 byte[] arr1 = (byte[])obj1;
2032 if (obj2 instanceof byte[]) {
2033 byte[] arr2 = (byte[])obj2;
2034 byte[] ret = new byte[arr1.length + arr2.length];
2035 System.arraycopy(arr1, 0, ret, 0, arr1.length);
2036 System.arraycopy(arr2, 0, ret, arr1.length, arr2.length);
2037 return ret;
2038 } else {
2039 str1 = new ByteArrayInputStream(arr1);
2040 str2 = (InputStream)obj2;
2041 }
2042 } else {
2043 str1 = (InputStream)obj1;
2044 if (obj2 instanceof byte[]) {
2045 str2 = new ByteArrayInputStream((byte[])obj2);
2046 } else {
2047 str2 = (InputStream)obj2;
2048 }
2049 }
2050
2051 return new SequenceInputStream(str1, str2);
2052 }
2053
2054 public byte[] convertData(final Object source,
2055 final Transferable contents,
2056 final long format,
2057 final Map formatMap,
2058 final boolean isToolkitThread)
2059 throws IOException
2060 {
2061 byte[] ret = null;
2062
2063 /*
2064 * If the current thread is the Toolkit thread we should post a
2065 * Runnable to the event dispatch thread associated with source Object,
2066 * since translateTransferable() calls Transferable.getTransferData()
2067 * that may contain client code.
2068 */
2069 if (isToolkitThread) try {
2070 final Stack stack = new Stack();
2071 final Runnable dataConverter = new Runnable() {
2072 // Guard against multiple executions.
2073 private boolean done = false;
2074 public void run() {
2075 if (done) {
2076 return;
2077 }
2078 byte[] data = null;
2079 try {
2080 DataFlavor flavor = (DataFlavor)formatMap.get(Long.valueOf(format));
2081 if (flavor != null) {
2082 data = translateTransferable(contents, flavor, format);
2083 }
2084 } catch (Exception e) {
2085 e.printStackTrace();
2086 data = null;
2087 }
2088 try {
2089 getToolkitThreadBlockedHandler().lock();
2090 stack.push(data);
2091 getToolkitThreadBlockedHandler().exit();
2092 } finally {
2093 getToolkitThreadBlockedHandler().unlock();
2094 done = true;
2095 }
2096 }
2097 };
2098
2099 final AppContext appContext = SunToolkit.targetToAppContext(source);
2100
2101 getToolkitThreadBlockedHandler().lock();
2102
2103 if (appContext != null) {
2104 appContext.put(DATA_CONVERTER_KEY, dataConverter);
2105 }
2106
2107 SunToolkit.executeOnEventHandlerThread(source, dataConverter);
2108
2109 while (stack.empty()) {
2110 getToolkitThreadBlockedHandler().enter();
2111 }
2112
2113 if (appContext != null) {
2114 appContext.remove(DATA_CONVERTER_KEY);
2115 }
2116
2117 ret = (byte[])stack.pop();
2118 } finally {
2119 getToolkitThreadBlockedHandler().unlock();
2120 } else {
2121 DataFlavor flavor = (DataFlavor)
2122 formatMap.get(Long.valueOf(format));
2123 if (flavor != null) {
2124 ret = translateTransferable(contents, flavor, format);
2125 }
2126 }
2127
2128 return ret;
2129 }
2130
2131 public void processDataConversionRequests() {
2132 if (EventQueue.isDispatchThread()) {
2133 AppContext appContext = AppContext.getAppContext();
2134 getToolkitThreadBlockedHandler().lock();
2135 try {
2136 Runnable dataConverter =
2137 (Runnable)appContext.get(DATA_CONVERTER_KEY);
2138 if (dataConverter != null) {
2139 dataConverter.run();
2140 appContext.remove(DATA_CONVERTER_KEY);
2141 }
2142 } finally {
2143 getToolkitThreadBlockedHandler().unlock();
2144 }
2145 }
2146 }
2147
2148 public abstract ToolkitThreadBlockedHandler
2149 getToolkitThreadBlockedHandler();
2150
2151 /**
2152 * Helper function to reduce a Map with Long keys to a long array.
2153 * <p>
2154 * The map keys are sorted according to the native formats preference
2155 * order.
2156 */
2157 public static long[] keysToLongArray(SortedMap map) {
2158 Set keySet = map.keySet();
2159 long[] retval = new long[keySet.size()];
2160 int i = 0;
2161 for (Iterator iter = keySet.iterator(); iter.hasNext(); i++) {
2162 retval[i] = ((Long)iter.next()).longValue();
2163 }
2164 return retval;
2165 }
2166
2167 /**
2168 * Helper function to reduce a Map with DataFlavor keys to a DataFlavor
2169 * array. The array will be sorted according to
2170 * <code>DataFlavorComparator</code>.
2171 */
2172 public static DataFlavor[] keysToDataFlavorArray(Map map) {
2173 return setToSortedDataFlavorArray(map.keySet(), map);
2174 }
2175
2176 /**
2177 * Helper function to convert a Set of DataFlavors to a sorted array.
2178 * The array will be sorted according to <code>DataFlavorComparator</code>.
2179 */
2180 public static DataFlavor[] setToSortedDataFlavorArray(Set flavorsSet) {
2181 DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
2182 flavorsSet.toArray(flavors);
2183 Arrays.sort(flavors, defaultFlavorComparator);
2184 return flavors;
2185 }
2186
2187 /**
2188 * Helper function to convert a Set of DataFlavors to a sorted array.
2189 * The array will be sorted according to a
2190 * <code>DataFlavorComparator</code> created with the specified
2191 * flavor-to-native map as an argument.
2192 */
2193 public static DataFlavor[] setToSortedDataFlavorArray
2194 (Set flavorsSet, Map flavorToNativeMap)
2195 {
2196 DataFlavor[] flavors = new DataFlavor[flavorsSet.size()];
2197 flavorsSet.toArray(flavors);
2198 Comparator comparator =
2199 new DataFlavorComparator(flavorToNativeMap,
2200 IndexedComparator.SELECT_WORST);
2201 Arrays.sort(flavors, comparator);
2202 return flavors;
2203 }
2204
2205 /**
2206 * Helper function to convert an InputStream to a byte[] array.
2207 */
2208 protected static byte[] inputStreamToByteArray(InputStream str)
2209 throws IOException
2210 {
2211 ByteArrayOutputStream baos = new ByteArrayOutputStream();
2212 int len = 0;
2213 byte[] buf = new byte[8192];
2214
2215 while ((len = str.read(buf)) != -1) {
2216 baos.write(buf, 0, len);
2217 }
2218
2219 return baos.toByteArray();
2220 }
2221
2222 /**
2223 * Returns platform-specific mappings for the specified native.
2224 * If there are no platform-specific mappings for this native, the method
2225 * returns an empty <code>List</code>.
2226 */
2227 public List getPlatformMappingsForNative(String nat) {
2228 return new ArrayList();
2229 }
2230
2231 /**
2232 * Returns platform-specific mappings for the specified flavor.
2233 * If there are no platform-specific mappings for this flavor, the method
2234 * returns an empty <code>List</code>.
2235 */
2236 public List getPlatformMappingsForFlavor(DataFlavor df) {
2237 return new ArrayList();
2238 }
2239
2240 private static CharsetComparator defaultCharsetComparator =
2241 new CharsetComparator(IndexedComparator.SELECT_WORST);
2242 private static DataFlavorComparator defaultFlavorComparator =
2243 new DataFlavorComparator(IndexedComparator.SELECT_WORST);
2244
2245 /**
2246 * A Comparator which includes a helper function for comparing two Objects
2247 * which are likely to be keys in the specified Map.
2248 */
2249 public abstract static class IndexedComparator implements Comparator {
2250
2251 /**
2252 * The best Object (e.g., DataFlavor) will be the last in sequence.
2253 */
2254 public static final boolean SELECT_BEST = true;
2255
2256 /**
2257 * The best Object (e.g., DataFlavor) will be the first in sequence.
2258 */
2259 public static final boolean SELECT_WORST = false;
2260
2261 protected final boolean order;
2262
2263 public IndexedComparator() {
2264 this(SELECT_BEST);
2265 }
2266
2267 public IndexedComparator(boolean order) {
2268 this.order = order;
2269 }
2270
2271 /**
2272 * Helper method to compare two objects by their Integer indices in the
2273 * given map. If the map doesn't contain an entry for either of the
2274 * objects, the fallback index will be used for the object instead.
2275 *
2276 * @param indexMap the map which maps objects into Integer indexes.
2277 * @param obj1 the first object to be compared.
2278 * @param obj2 the second object to be compared.
2279 * @param fallbackIndex the Integer to be used as a fallback index.
2280 * @return a negative integer, zero, or a positive integer as the
2281 * first object is mapped to a less, equal to, or greater
2282 * index than the second.
2283 */
2284 protected static int compareIndices(Map indexMap,
2285 Object obj1, Object obj2,
2286 Integer fallbackIndex) {
2287 Integer index1 = (Integer)indexMap.get(obj1);
2288 Integer index2 = (Integer)indexMap.get(obj2);
2289
2290 if (index1 == null) {
2291 index1 = fallbackIndex;
2292 }
2293 if (index2 == null) {
2294 index2 = fallbackIndex;
2295 }
2296
2297 return index1.compareTo(index2);
2298 }
2299
2300 /**
2301 * Helper method to compare two objects by their Long indices in the
2302 * given map. If the map doesn't contain an entry for either of the
2303 * objects, the fallback index will be used for the object instead.
2304 *
2305 * @param indexMap the map which maps objects into Long indexes.
2306 * @param obj1 the first object to be compared.
2307 * @param obj2 the second object to be compared.
2308 * @param fallbackIndex the Long to be used as a fallback index.
2309 * @return a negative integer, zero, or a positive integer as the
2310 * first object is mapped to a less, equal to, or greater
2311 * index than the second.
2312 */
2313 protected static int compareLongs(Map indexMap,
2314 Object obj1, Object obj2,
2315 Long fallbackIndex) {
2316 Long index1 = (Long)indexMap.get(obj1);
2317 Long index2 = (Long)indexMap.get(obj2);
2318
2319 if (index1 == null) {
2320 index1 = fallbackIndex;
2321 }
2322 if (index2 == null) {
2323 index2 = fallbackIndex;
2324 }
2325
2326 return index1.compareTo(index2);
2327 }
2328 }
2329
2330 /**
2331 * An IndexedComparator which compares two String charsets. The comparison
2332 * follows the rules outlined in DataFlavor.selectBestTextFlavor. In order
2333 * to ensure that non-Unicode, non-ASCII, non-default charsets are sorted
2334 * in alphabetical order, charsets are not automatically converted to their
2335 * canonical forms.
2336 */
2337 public static class CharsetComparator extends IndexedComparator {
2338 private static final Map charsets;
2339 private static String defaultEncoding;
2340
2341 private static final Integer DEFAULT_CHARSET_INDEX = Integer.valueOf(2);
2342 private static final Integer OTHER_CHARSET_INDEX = Integer.valueOf(1);
2343 private static final Integer WORST_CHARSET_INDEX = Integer.valueOf(0);
2344 private static final Integer UNSUPPORTED_CHARSET_INDEX =
2345 Integer.valueOf(Integer.MIN_VALUE);
2346
2347 private static final String UNSUPPORTED_CHARSET = "UNSUPPORTED";
2348
2349 static {
2350 HashMap charsetsMap = new HashMap(8, 1.0f);
2351
2352 // we prefer Unicode charsets
2353 charsetsMap.put(canonicalName("UTF-16LE"), Integer.valueOf(4));
2354 charsetsMap.put(canonicalName("UTF-16BE"), Integer.valueOf(5));
2355 charsetsMap.put(canonicalName("UTF-8"), Integer.valueOf(6));
2356 charsetsMap.put(canonicalName("UTF-16"), Integer.valueOf(7));
2357
2358 // US-ASCII is the worst charset supported
2359 charsetsMap.put(canonicalName("US-ASCII"), WORST_CHARSET_INDEX);
2360
2361 String defEncoding = DataTransferer.canonicalName
2362 (DataTransferer.getDefaultTextCharset());
2363
2364 if (charsetsMap.get(defaultEncoding) == null) {
2365 charsetsMap.put(defaultEncoding, DEFAULT_CHARSET_INDEX);
2366 }
2367 charsetsMap.put(UNSUPPORTED_CHARSET, UNSUPPORTED_CHARSET_INDEX);
2368
2369 charsets = Collections.unmodifiableMap(charsetsMap);
2370 }
2371
2372 public CharsetComparator() {
2373 this(SELECT_BEST);
2374 }
2375
2376 public CharsetComparator(boolean order) {
2377 super(order);
2378 }
2379
2380 /**
2381 * Compares two String objects. Returns a negative integer, zero,
2382 * or a positive integer as the first charset is worse than, equal to,
2383 * or better than the second.
2384 *
2385 * @param obj1 the first charset to be compared
2386 * @param obj2 the second charset to be compared
2387 * @return a negative integer, zero, or a positive integer as the
2388 * first argument is worse, equal to, or better than the
2389 * second.
2390 * @throws ClassCastException if either of the arguments is not
2391 * instance of String
2392 * @throws NullPointerException if either of the arguments is
2393 * <code>null</code>.
2394 */
2395 public int compare(Object obj1, Object obj2) {
2396 String charset1 = null;
2397 String charset2 = null;
2398 if (order == SELECT_BEST) {
2399 charset1 = (String)obj1;
2400 charset2 = (String)obj2;
2401 } else {
2402 charset1 = (String)obj2;
2403 charset2 = (String)obj1;
2404 }
2405
2406 return compareCharsets(charset1, charset2);
2407 }
2408
2409 /**
2410 * Compares charsets. Returns a negative integer, zero, or a positive
2411 * integer as the first charset is worse than, equal to, or better than
2412 * the second.
2413 * <p>
2414 * Charsets are ordered according to the following rules:
2415 * <ul>
2416 * <li>All unsupported charsets are equal.
2417 * <li>Any unsupported charset is worse than any supported charset.
2418 * <li>Unicode charsets, such as "UTF-16", "UTF-8", "UTF-16BE" and
2419 * "UTF-16LE", are considered best.
2420 * <li>After them, platform default charset is selected.
2421 * <li>"US-ASCII" is the worst of supported charsets.
2422 * <li>For all other supported charsets, the lexicographically less
2423 * one is considered the better.
2424 * </ul>
2425 *
2426 * @param charset1 the first charset to be compared
2427 * @param charset2 the second charset to be compared.
2428 * @return a negative integer, zero, or a positive integer as the
2429 * first argument is worse, equal to, or better than the
2430 * second.
2431 */
2432 protected int compareCharsets(String charset1, String charset2) {
2433 charset1 = getEncoding(charset1);
2434 charset2 = getEncoding(charset2);
2435
2436 int comp = compareIndices(charsets, charset1, charset2,
2437 OTHER_CHARSET_INDEX);
2438
2439 if (comp == 0) {
2440 return charset2.compareTo(charset1);
2441 }
2442
2443 return comp;
2444 }
2445
2446 /**
2447 * Returns encoding for the specified charset according to the
2448 * following rules:
2449 * <ul>
2450 * <li>If the charset is <code>null</code>, then <code>null</code> will
2451 * be returned.
2452 * <li>Iff the charset specifies an encoding unsupported by this JRE,
2453 * <code>UNSUPPORTED_CHARSET</code> will be returned.
2454 * <li>If the charset specifies an alias name, the corresponding
2455 * canonical name will be returned iff the charset is a known
2456 * Unicode, ASCII, or default charset.
2457 * </ul>
2458 *
2459 * @param charset the charset.
2460 * @return an encoding for this charset.
2461 */
2462 protected static String getEncoding(String charset) {
2463 if (charset == null) {
2464 return null;
2465 } else if (!DataTransferer.isEncodingSupported(charset)) {
2466 return UNSUPPORTED_CHARSET;
2467 } else {
2468 // Only convert to canonical form if the charset is one
2469 // of the charsets explicitly listed in the known charsets
2470 // map. This will happen only for Unicode, ASCII, or default
2471 // charsets.
2472 String canonicalName = DataTransferer.canonicalName(charset);
2473 return (charsets.containsKey(canonicalName))
2474 ? canonicalName
2475 : charset;
2476 }
2477 }
2478 }
2479
2480 /**
2481 * An IndexedComparator which compares two DataFlavors. For text flavors,
2482 * the comparison follows the rules outlined in
2483 * DataFlavor.selectBestTextFlavor. For non-text flavors, unknown
2484 * application MIME types are preferred, followed by known
2485 * application/x-java-* MIME types. Unknown application types are preferred
2486 * because if the user provides his own data flavor, it will likely be the
2487 * most descriptive one. For flavors which are otherwise equal, the
2488 * flavors' native formats are compared, with greater long values
2489 * taking precedence.
2490 */
2491 public static class DataFlavorComparator extends IndexedComparator {
2492 protected final Map flavorToFormatMap;
2493
2494 private final CharsetComparator charsetComparator;
2495
2496 private static final Map exactTypes;
2497 private static final Map primaryTypes;
2498 private static final Map nonTextRepresentations;
2499 private static final Map textTypes;
2500 private static final Map decodedTextRepresentations;
2501 private static final Map encodedTextRepresentations;
2502
2503 private static final Integer UNKNOWN_OBJECT_LOSES =
2504 Integer.valueOf(Integer.MIN_VALUE);
2505 private static final Integer UNKNOWN_OBJECT_WINS =
2506 Integer.valueOf(Integer.MAX_VALUE);
2507
2508 private static final Long UNKNOWN_OBJECT_LOSES_L =
2509 Long.valueOf(Long.MIN_VALUE);
2510 private static final Long UNKNOWN_OBJECT_WINS_L =
2511 Long.valueOf(Long.MAX_VALUE);
2512
2513 static {
2514 {
2515 HashMap exactTypesMap = new HashMap(4, 1.0f);
2516
2517 // application/x-java-* MIME types
2518 exactTypesMap.put("application/x-java-file-list",
2519 Integer.valueOf(0));
2520 exactTypesMap.put("application/x-java-serialized-object",
2521 Integer.valueOf(1));
2522 exactTypesMap.put("application/x-java-jvm-local-objectref",
2523 Integer.valueOf(2));
2524 exactTypesMap.put("application/x-java-remote-object",
2525 Integer.valueOf(3));
2526
2527 exactTypes = Collections.unmodifiableMap(exactTypesMap);
2528 }
2529
2530 {
2531 HashMap primaryTypesMap = new HashMap(1, 1.0f);
2532
2533 primaryTypesMap.put("application", Integer.valueOf(0));
2534
2535 primaryTypes = Collections.unmodifiableMap(primaryTypesMap);
2536 }
2537
2538 {
2539 HashMap nonTextRepresentationsMap = new HashMap(3, 1.0f);
2540
2541 nonTextRepresentationsMap.put(java.io.InputStream.class,
2542 Integer.valueOf(0));
2543 nonTextRepresentationsMap.put(java.io.Serializable.class,
2544 Integer.valueOf(1));
2545 nonTextRepresentationsMap.put(java.rmi.Remote.class,
2546 Integer.valueOf(2));
2547
2548 nonTextRepresentations =
2549 Collections.unmodifiableMap(nonTextRepresentationsMap);
2550 }
2551
2552 {
2553 HashMap textTypesMap = new HashMap(16, 1.0f);
2554
2555 // plain text
2556 textTypesMap.put("text/plain", Integer.valueOf(0));
2557
2558 // stringFlavor
2559 textTypesMap.put("application/x-java-serialized-object",
2560 Integer.valueOf(1));
2561
2562 // misc
2563 textTypesMap.put("text/calendar", Integer.valueOf(2));
2564 textTypesMap.put("text/css", Integer.valueOf(3));
2565 textTypesMap.put("text/directory", Integer.valueOf(4));
2566 textTypesMap.put("text/parityfec", Integer.valueOf(5));
2567 textTypesMap.put("text/rfc822-headers", Integer.valueOf(6));
2568 textTypesMap.put("text/t140", Integer.valueOf(7));
2569 textTypesMap.put("text/tab-separated-values", Integer.valueOf(8));
2570 textTypesMap.put("text/uri-list", Integer.valueOf(9));
2571
2572 // enriched
2573 textTypesMap.put("text/richtext", Integer.valueOf(10));
2574 textTypesMap.put("text/enriched", Integer.valueOf(11));
2575 textTypesMap.put("text/rtf", Integer.valueOf(12));
2576
2577 // markup
2578 textTypesMap.put("text/html", Integer.valueOf(13));
2579 textTypesMap.put("text/xml", Integer.valueOf(14));
2580 textTypesMap.put("text/sgml", Integer.valueOf(15));
2581
2582 textTypes = Collections.unmodifiableMap(textTypesMap);
2583 }
2584
2585 {
2586 HashMap decodedTextRepresentationsMap = new HashMap(4, 1.0f);
2587
2588 decodedTextRepresentationsMap.put
2589 (DataTransferer.charArrayClass, Integer.valueOf(0));
2590 decodedTextRepresentationsMap.put
2591 (java.nio.CharBuffer.class, Integer.valueOf(1));
2592 decodedTextRepresentationsMap.put
2593 (java.lang.String.class, Integer.valueOf(2));
2594 decodedTextRepresentationsMap.put
2595 (java.io.Reader.class, Integer.valueOf(3));
2596
2597 decodedTextRepresentations =
2598 Collections.unmodifiableMap(decodedTextRepresentationsMap);
2599 }
2600
2601 {
2602 HashMap encodedTextRepresentationsMap = new HashMap(3, 1.0f);
2603
2604 encodedTextRepresentationsMap.put
2605 (DataTransferer.byteArrayClass, Integer.valueOf(0));
2606 encodedTextRepresentationsMap.put
2607 (java.nio.ByteBuffer.class, Integer.valueOf(1));
2608 encodedTextRepresentationsMap.put
2609 (java.io.InputStream.class, Integer.valueOf(2));
2610
2611 encodedTextRepresentations =
2612 Collections.unmodifiableMap(encodedTextRepresentationsMap);
2613 }
2614 }
2615
2616 public DataFlavorComparator() {
2617 this(SELECT_BEST);
2618 }
2619
2620 public DataFlavorComparator(boolean order) {
2621 super(order);
2622
2623 charsetComparator = new CharsetComparator(order);
2624 flavorToFormatMap = Collections.EMPTY_MAP;
2625 }
2626
2627 public DataFlavorComparator(Map map) {
2628 this(map, SELECT_BEST);
2629 }
2630
2631 public DataFlavorComparator(Map map, boolean order) {
2632 super(order);
2633
2634 charsetComparator = new CharsetComparator(order);
2635 HashMap hashMap = new HashMap(map.size());
2636 hashMap.putAll(map);
2637 flavorToFormatMap = Collections.unmodifiableMap(hashMap);
2638 }
2639
2640 public int compare(Object obj1, Object obj2) {
2641 DataFlavor flavor1 = null;
2642 DataFlavor flavor2 = null;
2643 if (order == SELECT_BEST) {
2644 flavor1 = (DataFlavor)obj1;
2645 flavor2 = (DataFlavor)obj2;
2646 } else {
2647 flavor1 = (DataFlavor)obj2;
2648 flavor2 = (DataFlavor)obj1;
2649 }
2650
2651 if (flavor1.equals(flavor2)) {
2652 return 0;
2653 }
2654
2655 int comp = 0;
2656
2657 String primaryType1 = flavor1.getPrimaryType();
2658 String subType1 = flavor1.getSubType();
2659 String mimeType1 = primaryType1 + "/" + subType1;
2660 Class class1 = flavor1.getRepresentationClass();
2661
2662 String primaryType2 = flavor2.getPrimaryType();
2663 String subType2 = flavor2.getSubType();
2664 String mimeType2 = primaryType2 + "/" + subType2;
2665 Class class2 = flavor2.getRepresentationClass();
2666
2667 if (flavor1.isFlavorTextType() && flavor2.isFlavorTextType()) {
2668 // First, compare MIME types
2669 comp = compareIndices(textTypes, mimeType1, mimeType2,
2670 UNKNOWN_OBJECT_LOSES);
2671 if (comp != 0) {
2672 return comp;
2673 }
2674
2675 // Only need to test one flavor because they both have the
2676 // same MIME type. Also don't need to worry about accidentally
2677 // passing stringFlavor because either
2678 // 1. Both flavors are stringFlavor, in which case the
2679 // equality test at the top of the function succeeded.
2680 // 2. Only one flavor is stringFlavor, in which case the MIME
2681 // type comparison returned a non-zero value.
2682 if (doesSubtypeSupportCharset(flavor1)) {
2683 // Next, prefer the decoded text representations of Reader,
2684 // String, CharBuffer, and [C, in that order.
2685 comp = compareIndices(decodedTextRepresentations, class1,
2686 class2, UNKNOWN_OBJECT_LOSES);
2687 if (comp != 0) {
2688 return comp;
2689 }
2690
2691 // Next, compare charsets
2692 comp = charsetComparator.compareCharsets
2693 (DataTransferer.getTextCharset(flavor1),
2694 DataTransferer.getTextCharset(flavor2));
2695 if (comp != 0) {
2696 return comp;
2697 }
2698 }
2699
2700 // Finally, prefer the encoded text representations of
2701 // InputStream, ByteBuffer, and [B, in that order.
2702 comp = compareIndices(encodedTextRepresentations, class1,
2703 class2, UNKNOWN_OBJECT_LOSES);
2704 if (comp != 0) {
2705 return comp;
2706 }
2707 } else {
2708 // First, prefer application types.
2709 comp = compareIndices(primaryTypes, primaryType1, primaryType2,
2710 UNKNOWN_OBJECT_LOSES);
2711 if (comp != 0) {
2712 return comp;
2713 }
2714
2715 // Next, look for application/x-java-* types. Prefer unknown
2716 // MIME types because if the user provides his own data flavor,
2717 // it will likely be the most descriptive one.
2718 comp = compareIndices(exactTypes, mimeType1, mimeType2,
2719 UNKNOWN_OBJECT_WINS);
2720 if (comp != 0) {
2721 return comp;
2722 }
2723
2724 // Finally, prefer the representation classes of Remote,
2725 // Serializable, and InputStream, in that order.
2726 comp = compareIndices(nonTextRepresentations, class1, class2,
2727 UNKNOWN_OBJECT_LOSES);
2728 if (comp != 0) {
2729 return comp;
2730 }
2731 }
2732
2733 // As a last resort, take the DataFlavor with the greater integer
2734 // format.
2735 return compareLongs(flavorToFormatMap, flavor1, flavor2,
2736 UNKNOWN_OBJECT_LOSES_L);
2737 }
2738 }
2739
2740 /*
2741 * Given the Map that maps objects to Integer indices and a boolean value,
2742 * this Comparator imposes a direct or reverse order on set of objects.
2743 * <p>
2744 * If the specified boolean value is SELECT_BEST, the Comparator imposes the
2745 * direct index-based order: an object A is greater than an object B if and
2746 * only if the index of A is greater than the index of B. An object that
2747 * doesn't have an associated index is less or equal than any other object.
2748 * <p>
2749 * If the specified boolean value is SELECT_WORST, the Comparator imposes the
2750 * reverse index-based order: an object A is greater than an object B if and
2751 * only if A is less than B with the direct index-based order.
2752 */
2753 public static class IndexOrderComparator extends IndexedComparator {
2754 private final Map indexMap;
2755 private static final Integer FALLBACK_INDEX =
2756 Integer.valueOf(Integer.MIN_VALUE);
2757
2758 public IndexOrderComparator(Map indexMap) {
2759 super(SELECT_BEST);
2760 this.indexMap = indexMap;
2761 }
2762
2763 public IndexOrderComparator(Map indexMap, boolean order) {
2764 super(order);
2765 this.indexMap = indexMap;
2766 }
2767
2768 public int compare(Object obj1, Object obj2) {
2769 if (order == SELECT_WORST) {
2770 return -compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX);
2771 } else {
2772 return compareIndices(indexMap, obj1, obj2, FALLBACK_INDEX);
2773 }
2774 }
2775 }
2776}