001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Michael Danilov
019: * @version $Revision$
020: */package java.awt.datatransfer;
021:
022: import java.io.*;
023: import java.nio.*;
024: import java.nio.charset.*;
025: import java.util.*;
026:
027: import org.apache.harmony.awt.datatransfer.*;
028: import org.apache.harmony.awt.internal.nls.Messages;
029:
030: public class DataFlavor implements Externalizable, Cloneable {
031:
032: private static final long serialVersionUID = 8367026044764648243L;
033:
034: /**
035: * @deprecated
036: */
037: @Deprecated
038: public static final DataFlavor plainTextFlavor = new DataFlavor(
039: "text/plain; charset=unicode; class=java.io.InputStream", //$NON-NLS-1$
040: "Plain Text"); //$NON-NLS-1$
041:
042: public static final DataFlavor stringFlavor = new DataFlavor(
043: "application/x-java-serialized-object; class=java.lang.String", //$NON-NLS-1$
044: "Unicode String"); //$NON-NLS-1$
045:
046: public static final DataFlavor imageFlavor = new DataFlavor(
047: "image/x-java-image; class=java.awt.Image", //$NON-NLS-1$
048: "Image"); //$NON-NLS-1$
049:
050: public static final DataFlavor javaFileListFlavor = new DataFlavor(
051: "application/x-java-file-list; class=java.util.List", //$NON-NLS-1$
052: "application/x-java-file-list"); //$NON-NLS-1$
053:
054: public static final String javaJVMLocalObjectMimeType = "application/x-java-jvm-local-objectref"; //$NON-NLS-1$
055:
056: public static final String javaRemoteObjectMimeType = "application/x-java-remote-object"; //$NON-NLS-1$
057:
058: public static final String javaSerializedObjectMimeType = "application/x-java-serialized-object"; //$NON-NLS-1$
059:
060: private static final String sortedTextFlavors[] = { "text/sgml", //$NON-NLS-1$
061: "text/xml", //$NON-NLS-1$
062: "text/html", //$NON-NLS-1$
063: "text/rtf", //$NON-NLS-1$
064: "text/enriched", //$NON-NLS-1$
065: "text/richtext", //$NON-NLS-1$
066: "text/uri-list", //$NON-NLS-1$
067: "text/tab-separated-values", //$NON-NLS-1$
068: "text/t140", //$NON-NLS-1$
069: "text/rfc822-headers", //$NON-NLS-1$
070: "text/parityfec", //$NON-NLS-1$
071: "text/directory", //$NON-NLS-1$
072: "text/css", //$NON-NLS-1$
073: "text/calendar", //$NON-NLS-1$
074: "application/x-java-serialized-object", //$NON-NLS-1$
075: "text/plain" //$NON-NLS-1$
076: };
077:
078: private static DataFlavor plainUnicodeFlavor = null;
079:
080: private String humanPresentableName;
081: private Class<?> representationClass;
082: private MimeTypeProcessor.MimeType mimeInfo;
083:
084: public static final DataFlavor getTextPlainUnicodeFlavor() {
085: if (plainUnicodeFlavor == null) {
086: plainUnicodeFlavor = new DataFlavor("text/plain" //$NON-NLS-1$
087: + "; charset=" + DTK.getDTK().getDefaultCharset() //$NON-NLS-1$
088: + "; class=java.io.InputStream", //$NON-NLS-1$
089: "Plain Text"); //$NON-NLS-1$
090: }
091:
092: return plainUnicodeFlavor;
093: }
094:
095: protected static final Class<?> tryToLoadClass(String className,
096: ClassLoader fallback) throws ClassNotFoundException {
097: try {
098: return Class.forName(className);
099: } catch (ClassNotFoundException e0) {
100: try {
101: return ClassLoader.getSystemClassLoader().loadClass(
102: className);
103: } catch (ClassNotFoundException e1) {
104: ClassLoader contextLoader = Thread.currentThread()
105: .getContextClassLoader();
106:
107: if (contextLoader != null) {
108: try {
109: return contextLoader.loadClass(className);
110: } catch (ClassNotFoundException e2) {
111: }
112: }
113:
114: return fallback.loadClass(className);
115: }
116: }
117: }
118:
119: private static boolean isCharsetSupported(String charset) {
120: try {
121: return Charset.isSupported(charset);
122: } catch (IllegalCharsetNameException e) {
123: return false;
124: }
125: }
126:
127: public DataFlavor() {
128: mimeInfo = null;
129: humanPresentableName = null;
130: representationClass = null;
131: }
132:
133: public DataFlavor(Class<?> representationClass,
134: String humanPresentableName) {
135: mimeInfo = new MimeTypeProcessor.MimeType(
136: "application", "x-java-serialized-object"); //$NON-NLS-1$ //$NON-NLS-2$
137:
138: if (humanPresentableName != null) {
139: this .humanPresentableName = humanPresentableName;
140: } else {
141: this .humanPresentableName = "application/x-java-serialized-object"; //$NON-NLS-1$
142: }
143:
144: mimeInfo.addParameter("class", representationClass.getName()); //$NON-NLS-1$
145: this .representationClass = representationClass;
146: }
147:
148: public DataFlavor(String mimeType, String humanPresentableName) {
149: try {
150: init(mimeType, humanPresentableName, null);
151: } catch (ClassNotFoundException e) {
152: // awt.16C=Can't load class: {0}
153: throw new IllegalArgumentException(Messages.getString(
154: "awt.16C", mimeInfo.getParameter("class"))); //$NON-NLS-1$//$NON-NLS-2$
155: }
156: }
157:
158: public DataFlavor(String mimeType) throws ClassNotFoundException {
159: init(mimeType, null, null);
160: }
161:
162: public DataFlavor(String mimeType, String humanPresentableName,
163: ClassLoader classLoader) throws ClassNotFoundException {
164: init(mimeType, humanPresentableName, classLoader);
165: }
166:
167: private void init(String mimeType, String humanPresentableName,
168: ClassLoader classLoader) throws ClassNotFoundException {
169: String className;
170:
171: try {
172: mimeInfo = MimeTypeProcessor.parse(mimeType);
173: } catch (IllegalArgumentException e) {
174: // awt.16D=Can't parse MIME type: {0}
175: throw new IllegalArgumentException(Messages.getString(
176: "awt.16D", mimeType)); //$NON-NLS-1$
177: }
178:
179: if (humanPresentableName != null) {
180: this .humanPresentableName = humanPresentableName;
181: } else {
182: this .humanPresentableName = mimeInfo.getPrimaryType() + '/'
183: + mimeInfo.getSubType();
184: }
185:
186: className = mimeInfo.getParameter("class"); //$NON-NLS-1$
187: if (className == null) {
188: className = "java.io.InputStream"; //$NON-NLS-1$
189: mimeInfo.addParameter("class", className); //$NON-NLS-1$
190: }
191: representationClass = (classLoader == null) ? Class
192: .forName(className) : classLoader.loadClass(className);
193: }
194:
195: private String getCharset() {
196: if ((mimeInfo == null) || isCharsetRedundant()) {
197: return ""; //$NON-NLS-1$
198: }
199: String charset = mimeInfo.getParameter("charset"); //$NON-NLS-1$
200:
201: if (isCharsetRequired()
202: && ((charset == null) || (charset.length() == 0))) {
203: return DTK.getDTK().getDefaultCharset();
204: }
205: if (charset == null) {
206: return ""; //$NON-NLS-1$
207: }
208:
209: return charset;
210: }
211:
212: private boolean isCharsetRequired() {
213: String type = mimeInfo.getFullType();
214:
215: return (type.equals("text/sgml") || //$NON-NLS-1$
216: type.equals("text/xml") || //$NON-NLS-1$
217: type.equals("text/html") || //$NON-NLS-1$
218: type.equals("text/enriched") || //$NON-NLS-1$
219: type.equals("text/richtext") || //$NON-NLS-1$
220: type.equals("text/uri-list") || //$NON-NLS-1$
221: type.equals("text/directory") || //$NON-NLS-1$
222: type.equals("text/css") || //$NON-NLS-1$
223: type.equals("text/calendar") || //$NON-NLS-1$
224: type.equals("application/x-java-serialized-object") || //$NON-NLS-1$
225: type.equals("text/plain")); //$NON-NLS-1$
226: }
227:
228: private boolean isCharsetRedundant() {
229: String type = mimeInfo.getFullType();
230:
231: return (type.equals("text/rtf") || //$NON-NLS-1$
232: type.equals("text/tab-separated-values") || //$NON-NLS-1$
233: type.equals("text/t140") || //$NON-NLS-1$
234: type.equals("text/rfc822-headers") || //$NON-NLS-1$
235: type.equals("text/parityfec")); //$NON-NLS-1$
236: }
237:
238: MimeTypeProcessor.MimeType getMimeInfo() {
239: return mimeInfo;
240: }
241:
242: public String getPrimaryType() {
243: return (mimeInfo != null) ? mimeInfo.getPrimaryType() : null;
244: }
245:
246: public String getSubType() {
247: return (mimeInfo != null) ? mimeInfo.getSubType() : null;
248: }
249:
250: public String getMimeType() {
251: return (mimeInfo != null) ? MimeTypeProcessor
252: .assemble(mimeInfo) : null;
253: }
254:
255: public String getParameter(String paramName) {
256: String lowerName = paramName.toLowerCase();
257:
258: if (lowerName.equals("humanpresentablename")) { //$NON-NLS-1$
259: return humanPresentableName;
260: }
261: return mimeInfo != null ? mimeInfo.getParameter(lowerName)
262: : null;
263: }
264:
265: public String getHumanPresentableName() {
266: return humanPresentableName;
267: }
268:
269: public void setHumanPresentableName(String humanPresentableName) {
270: this .humanPresentableName = humanPresentableName;
271: }
272:
273: public Class<?> getRepresentationClass() {
274: return representationClass;
275: }
276:
277: public final Class<?> getDefaultRepresentationClass() {
278: return InputStream.class;
279: }
280:
281: public final String getDefaultRepresentationClassAsString() {
282: return getDefaultRepresentationClass().getName();
283: }
284:
285: public boolean isRepresentationClassSerializable() {
286: return Serializable.class.isAssignableFrom(representationClass);
287: }
288:
289: public boolean isRepresentationClassRemote() {
290: // Code should be enabled when RMI is supported
291: // return java.rmi.Remote.class.isAssignableFrom(representationClass);
292: return false;
293: }
294:
295: public boolean isRepresentationClassReader() {
296: return Reader.class.isAssignableFrom(representationClass);
297: }
298:
299: public boolean isRepresentationClassInputStream() {
300: return InputStream.class.isAssignableFrom(representationClass);
301: }
302:
303: public boolean isRepresentationClassCharBuffer() {
304: return CharBuffer.class.isAssignableFrom(representationClass);
305: }
306:
307: public boolean isRepresentationClassByteBuffer() {
308: return ByteBuffer.class.isAssignableFrom(representationClass);
309: }
310:
311: /**
312: * @deprecated
313: */
314: @Deprecated
315: protected String normalizeMimeTypeParameter(String parameterName,
316: String parameterValue) {
317: return parameterValue;
318: }
319:
320: /**
321: * @deprecated
322: */
323: @Deprecated
324: protected String normalizeMimeType(String mimeType) {
325: return mimeType;
326: }
327:
328: public final boolean isMimeTypeEqual(DataFlavor dataFlavor) {
329: return mimeInfo != null ? mimeInfo.equals(dataFlavor.mimeInfo)
330: : (dataFlavor.mimeInfo == null);
331: }
332:
333: public boolean isMimeTypeEqual(String mimeType) {
334: try {
335: return mimeInfo.equals(MimeTypeProcessor.parse(mimeType));
336: } catch (IllegalArgumentException e) {
337: return false;
338: }
339: }
340:
341: public synchronized void writeExternal(ObjectOutput os)
342: throws IOException {
343: os.writeObject(humanPresentableName);
344: os.writeObject(mimeInfo);
345: }
346:
347: public synchronized void readExternal(ObjectInput is)
348: throws IOException, ClassNotFoundException {
349: humanPresentableName = (String) is.readObject();
350: mimeInfo = (MimeTypeProcessor.MimeType) is.readObject();
351:
352: representationClass = (mimeInfo != null) ? Class
353: .forName(mimeInfo.getParameter("class")) : null; //$NON-NLS-1$
354: }
355:
356: @Override
357: public Object clone() throws CloneNotSupportedException {
358: DataFlavor clone = new DataFlavor();
359:
360: clone.humanPresentableName = humanPresentableName;
361: clone.representationClass = representationClass;
362: clone.mimeInfo = (mimeInfo != null) ? (MimeTypeProcessor.MimeType) mimeInfo
363: .clone()
364: : null;
365:
366: return clone;
367: }
368:
369: @Override
370: public String toString() {
371: /* The format is based on 1.5 release behavior
372: * which can be revealed by the following code:
373: *
374: * System.out.println(DataFlavor.imageFlavor.toString());
375: */
376:
377: return (getClass().getName() + "[MimeType=(" + getMimeType() //$NON-NLS-1$
378: + ");humanPresentableName=" + humanPresentableName + "]"); //$NON-NLS-1$ //$NON-NLS-2$
379: }
380:
381: public boolean isMimeTypeSerializedObject() {
382: return isMimeTypeEqual(javaSerializedObjectMimeType);
383: }
384:
385: @Override
386: public boolean equals(Object o) {
387: if ((o == null) || !(o instanceof DataFlavor)) {
388: return false;
389: }
390: return equals((DataFlavor) o);
391: }
392:
393: public boolean equals(DataFlavor that) {
394: if (that == this ) {
395: return true;
396: }
397: if (that == null) {
398: return false;
399: }
400: if (mimeInfo == null) {
401: return (that.mimeInfo == null);
402: }
403: if (!(mimeInfo.equals(that.mimeInfo) && representationClass
404: .equals(that.representationClass))) {
405: return false;
406: }
407: if (!mimeInfo.getPrimaryType().equals("text") || isUnicodeFlavor()) { //$NON-NLS-1$
408: return true;
409: }
410:
411: String charset1 = getCharset();
412: String charset2 = that.getCharset();
413:
414: if (!isCharsetSupported(charset1)
415: || !isCharsetSupported(charset2)) {
416: return charset1.equalsIgnoreCase(charset2);
417: }
418: return (Charset.forName(charset1).equals(Charset
419: .forName(charset2)));
420:
421: }
422:
423: @Deprecated
424: public boolean equals(String s) {
425: if (s == null) {
426: return false;
427: }
428:
429: return isMimeTypeEqual(s);
430: }
431:
432: public boolean match(DataFlavor that) {
433: return equals(that);
434: }
435:
436: @Override
437: public int hashCode() {
438: return getKeyInfo().hashCode();
439: }
440:
441: private String getKeyInfo() {
442: String key = mimeInfo.getFullType()
443: + ";class=" + representationClass.getName(); //$NON-NLS-1$
444:
445: if (!mimeInfo.getPrimaryType().equals("text") || isUnicodeFlavor()) { //$NON-NLS-1$
446: return key;
447: }
448:
449: return key + ";charset=" + getCharset().toLowerCase(); //$NON-NLS-1$
450: }
451:
452: public boolean isFlavorSerializedObjectType() {
453: return (isMimeTypeSerializedObject() && isRepresentationClassSerializable());
454: }
455:
456: public boolean isFlavorRemoteObjectType() {
457: return (isMimeTypeEqual(javaRemoteObjectMimeType) && isRepresentationClassRemote());
458: }
459:
460: public boolean isFlavorJavaFileListType() {
461: return (java.util.List.class
462: .isAssignableFrom(representationClass) && isMimeTypeEqual(javaFileListFlavor));
463: }
464:
465: public boolean isFlavorTextType() {
466: if (equals(stringFlavor) || equals(plainTextFlavor)) {
467: return true;
468: }
469: if ((mimeInfo != null)
470: && !mimeInfo.getPrimaryType().equals("text")) { //$NON-NLS-1$
471: return false;
472: }
473:
474: String charset = getCharset();
475:
476: if (isByteCodeFlavor()) {
477: if (charset.length() != 0) {
478: return isCharsetSupported(charset);
479: }
480:
481: return true;
482: }
483:
484: return isUnicodeFlavor();
485: }
486:
487: public Reader getReaderForText(Transferable transferable)
488: throws UnsupportedFlavorException, IOException {
489: Object data = transferable.getTransferData(this );
490:
491: if (data == null) {
492: // awt.16E=Transferable has null data
493: throw new IllegalArgumentException(Messages
494: .getString("awt.16E")); //$NON-NLS-1$
495: }
496:
497: if (data instanceof Reader) {
498: Reader reader = (Reader) data;
499: reader.reset();
500: return reader;
501: } else if (data instanceof String) {
502: return new StringReader((String) data);
503: } else if (data instanceof CharBuffer) {
504: return new CharArrayReader(((CharBuffer) data).array());
505: } else if (data instanceof char[]) {
506: return new CharArrayReader((char[]) data);
507: } else {
508: String charset = getCharset();
509: InputStream stream;
510:
511: if (data instanceof InputStream) {
512: stream = (InputStream) data;
513: stream.reset();
514: } else if (data instanceof ByteBuffer) {
515: stream = new ByteArrayInputStream((((ByteBuffer) data)
516: .array()));
517: } else if (data instanceof byte[]) {
518: stream = new ByteArrayInputStream((byte[]) data);
519: } else {
520: // awt.16F=Can't create reader for this representation class
521: throw new IllegalArgumentException(Messages
522: .getString("awt.16F")); //$NON-NLS-1$
523: }
524:
525: if (charset.length() == 0) {
526: return new InputStreamReader(stream);
527: }
528: return new InputStreamReader(stream, charset);
529: }
530: }
531:
532: public static final DataFlavor selectBestTextFlavor(
533: DataFlavor[] availableFlavors) {
534: if (availableFlavors == null) {
535: return null;
536: }
537:
538: List<List<DataFlavor>> sorted = sortTextFlavorsByType(new LinkedList<DataFlavor>(
539: Arrays.asList(availableFlavors)));
540:
541: if (sorted.isEmpty()) {
542: return null;
543: }
544:
545: List<DataFlavor> bestSorted = sorted.get(0);
546:
547: if (bestSorted.size() == 1) {
548: return bestSorted.get(0);
549: }
550:
551: if (bestSorted.get(0).getCharset().length() == 0) {
552: return selectBestFlavorWOCharset(bestSorted);
553: }
554: return selectBestFlavorWCharset(bestSorted);
555: }
556:
557: private static DataFlavor selectBestFlavorWCharset(
558: List<DataFlavor> list) {
559: List<DataFlavor> best;
560:
561: best = getFlavors(list, Reader.class);
562: if (best != null) {
563: return best.get(0);
564: }
565: best = getFlavors(list, String.class);
566: if (best != null) {
567: return best.get(0);
568: }
569: best = getFlavors(list, CharBuffer.class);
570: if (best != null) {
571: return best.get(0);
572: }
573: best = getFlavors(list, char[].class);
574: if (best != null) {
575: return best.get(0);
576: }
577:
578: return selectBestByCharset(list);
579: }
580:
581: private static DataFlavor selectBestByCharset(List<DataFlavor> list) {
582: List<DataFlavor> best;
583:
584: best = getFlavors(list, new String[] {
585: "UTF-16", "UTF-8", "UTF-16BE", "UTF-16LE" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
586: if (best == null) {
587: best = getFlavors(list, new String[] { DTK.getDTK()
588: .getDefaultCharset() });
589: if (best == null) {
590: best = getFlavors(list, new String[] { "US-ASCII" }); //$NON-NLS-1$
591: if (best == null) {
592: best = selectBestByAlphabet(list);
593: }
594: }
595: }
596:
597: if (best != null) {
598: if (best.size() == 1) {
599: return best.get(0);
600: }
601: return selectBestFlavorWOCharset(best);
602: }
603:
604: return null;
605: }
606:
607: private static List<DataFlavor> selectBestByAlphabet(
608: List<DataFlavor> list) {
609: String charsets[] = new String[list.size()];
610: LinkedList<DataFlavor> best = new LinkedList<DataFlavor>();
611:
612: for (int i = 0; i < charsets.length; i++) {
613: charsets[i] = list.get(i).getCharset();
614: }
615: Arrays.sort(charsets, String.CASE_INSENSITIVE_ORDER);
616:
617: for (DataFlavor flavor : list) {
618: if (charsets[0].equalsIgnoreCase(flavor.getCharset())) {
619: best.add(flavor);
620: }
621: }
622:
623: return best.isEmpty() ? null : best;
624: }
625:
626: private static List<DataFlavor> getFlavors(List<DataFlavor> list,
627: String[] charset) {
628: LinkedList<DataFlavor> sublist = new LinkedList<DataFlavor>();
629:
630: for (Iterator<DataFlavor> i = list.iterator(); i.hasNext();) {
631: DataFlavor flavor = i.next();
632:
633: if (isCharsetSupported(flavor.getCharset())) {
634: for (String element : charset) {
635: if (Charset.forName(element).equals(
636: Charset.forName(flavor.getCharset()))) {
637: sublist.add(flavor);
638: }
639: }
640: } else {
641: i.remove();
642: }
643: }
644:
645: return sublist.isEmpty() ? null : list;
646: }
647:
648: private static DataFlavor selectBestFlavorWOCharset(
649: List<DataFlavor> list) {
650: List<DataFlavor> best;
651:
652: best = getFlavors(list, InputStream.class);
653: if (best != null) {
654: return best.get(0);
655: }
656: best = getFlavors(list, ByteBuffer.class);
657: if (best != null) {
658: return best.get(0);
659: }
660: best = getFlavors(list, byte[].class);
661: if (best != null) {
662: return best.get(0);
663: }
664:
665: return list.get(0);
666: }
667:
668: private static List<DataFlavor> getFlavors(List<DataFlavor> list,
669: Class<?> klass) {
670: LinkedList<DataFlavor> sublist = new LinkedList<DataFlavor>();
671:
672: for (DataFlavor flavor : list) {
673: if (flavor.representationClass.equals(klass)) {
674: sublist.add(flavor);
675: }
676: }
677:
678: return sublist.isEmpty() ? null : list;
679: }
680:
681: private static List<List<DataFlavor>> sortTextFlavorsByType(
682: List<DataFlavor> availableFlavors) {
683: LinkedList<List<DataFlavor>> list = new LinkedList<List<DataFlavor>>();
684:
685: for (String element : sortedTextFlavors) {
686: List<DataFlavor> subList = fetchTextFlavors(
687: availableFlavors, element);
688:
689: if (subList != null) {
690: list.addLast(subList);
691: }
692: }
693: if (!availableFlavors.isEmpty()) {
694: list.addLast(availableFlavors);
695: }
696:
697: return list;
698: }
699:
700: private static List<DataFlavor> fetchTextFlavors(
701: List<DataFlavor> availableFlavors, String mimeType) {
702: LinkedList<DataFlavor> list = new LinkedList<DataFlavor>();
703:
704: for (Iterator<DataFlavor> i = availableFlavors.iterator(); i
705: .hasNext();) {
706: DataFlavor flavor = i.next();
707:
708: if (flavor.isFlavorTextType()) {
709: if (flavor.mimeInfo.getFullType().equals(mimeType)) {
710: if (!list.contains(flavor)) {
711: list.add(flavor);
712: }
713: i.remove();
714: }
715: } else {
716: i.remove();
717: }
718: }
719:
720: return list.isEmpty() ? null : list;
721: }
722:
723: private boolean isUnicodeFlavor() {
724: return (representationClass != null)
725: && (representationClass.equals(Reader.class)
726: || representationClass.equals(String.class)
727: || representationClass.equals(CharBuffer.class) || representationClass
728: .equals(char[].class));
729: }
730:
731: private boolean isByteCodeFlavor() {
732: return (representationClass != null)
733: && (representationClass.equals(InputStream.class)
734: || representationClass.equals(ByteBuffer.class) || representationClass
735: .equals(byte[].class));
736: }
737:
738: }
|