Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.datatransfer/share/classes/java/awt/datatransfer/SystemFlavorMap.java
41159 views
1
/*
2
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package java.awt.datatransfer;
27
28
import java.io.BufferedReader;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.InputStreamReader;
32
import java.lang.ref.SoftReference;
33
import java.security.AccessController;
34
import java.security.PrivilegedAction;
35
import java.util.ArrayList;
36
import java.util.Collections;
37
import java.util.HashMap;
38
import java.util.HashSet;
39
import java.util.LinkedHashSet;
40
import java.util.List;
41
import java.util.Map;
42
import java.util.Objects;
43
import java.util.Set;
44
45
import sun.datatransfer.DataFlavorUtil;
46
import sun.datatransfer.DesktopDatatransferService;
47
48
/**
49
* The SystemFlavorMap is a configurable map between "natives" (Strings), which
50
* correspond to platform-specific data formats, and "flavors" (DataFlavors),
51
* which correspond to platform-independent MIME types. This mapping is used by
52
* the data transfer subsystem to transfer data between Java and native
53
* applications, and between Java applications in separate VMs.
54
*
55
* @since 1.2
56
*/
57
public final class SystemFlavorMap implements FlavorMap, FlavorTable {
58
59
/**
60
* Constant prefix used to tag Java types converted to native platform type.
61
*/
62
private static String JavaMIME = "JAVA_DATAFLAVOR:";
63
64
private static final Object FLAVOR_MAP_KEY = new Object();
65
66
/**
67
* The list of valid, decoded text flavor representation classes, in order
68
* from best to worst.
69
*/
70
private static final String[] UNICODE_TEXT_CLASSES = {
71
"java.io.Reader", "java.lang.String", "java.nio.CharBuffer", "\"[C\""
72
};
73
74
/**
75
* The list of valid, encoded text flavor representation classes, in order
76
* from best to worst.
77
*/
78
private static final String[] ENCODED_TEXT_CLASSES = {
79
"java.io.InputStream", "java.nio.ByteBuffer", "\"[B\""
80
};
81
82
/**
83
* A String representing text/plain MIME type.
84
*/
85
private static final String TEXT_PLAIN_BASE_TYPE = "text/plain";
86
87
/**
88
* A String representing text/html MIME type.
89
*/
90
private static final String HTML_TEXT_BASE_TYPE = "text/html";
91
92
/**
93
* Maps native Strings to Lists of DataFlavors (or base type Strings for
94
* text DataFlavors).
95
* <p>
96
* Do not use the field directly, use {@link #getNativeToFlavor} instead.
97
*/
98
private final Map<String, LinkedHashSet<DataFlavor>> nativeToFlavor = new HashMap<>();
99
100
/**
101
* Accessor to nativeToFlavor map. Since we use lazy initialization we must
102
* use this accessor instead of direct access to the field which may not be
103
* initialized yet. This method will initialize the field if needed.
104
*
105
* @return nativeToFlavor
106
*/
107
private Map<String, LinkedHashSet<DataFlavor>> getNativeToFlavor() {
108
if (!isMapInitialized) {
109
initSystemFlavorMap();
110
}
111
return nativeToFlavor;
112
}
113
114
/**
115
* Maps DataFlavors (or base type Strings for text DataFlavors) to Lists of
116
* native Strings.
117
* <p>
118
* Do not use the field directly, use {@link #getFlavorToNative} instead.
119
*/
120
private final Map<DataFlavor, LinkedHashSet<String>> flavorToNative = new HashMap<>();
121
122
/**
123
* Accessor to flavorToNative map. Since we use lazy initialization we must
124
* use this accessor instead of direct access to the field which may not be
125
* initialized yet. This method will initialize the field if needed.
126
*
127
* @return flavorToNative
128
*/
129
private synchronized Map<DataFlavor, LinkedHashSet<String>> getFlavorToNative() {
130
if (!isMapInitialized) {
131
initSystemFlavorMap();
132
}
133
return flavorToNative;
134
}
135
136
/**
137
* Maps a text DataFlavor primary mime-type to the native. Used only to
138
* store standard mappings registered in the {@code flavormap.properties}.
139
* <p>
140
* Do not use this field directly, use {@link #getTextTypeToNative} instead.
141
*/
142
private Map<String, LinkedHashSet<String>> textTypeToNative = new HashMap<>();
143
144
/**
145
* Shows if the object has been initialized.
146
*/
147
private boolean isMapInitialized = false;
148
149
/**
150
* An accessor to textTypeToNative map. Since we use lazy initialization we
151
* must use this accessor instead of direct access to the field which may
152
* not be initialized yet. This method will initialize the field if needed.
153
*
154
* @return textTypeToNative
155
*/
156
private synchronized Map<String, LinkedHashSet<String>> getTextTypeToNative() {
157
if (!isMapInitialized) {
158
initSystemFlavorMap();
159
// From this point the map should not be modified
160
textTypeToNative = Collections.unmodifiableMap(textTypeToNative);
161
}
162
return textTypeToNative;
163
}
164
165
/**
166
* Caches the result of getNativesForFlavor(). Maps DataFlavors to
167
* SoftReferences which reference LinkedHashSet of String natives.
168
*/
169
private final SoftCache<DataFlavor, String> nativesForFlavorCache = new SoftCache<>();
170
171
/**
172
* Caches the result getFlavorsForNative(). Maps String natives to
173
* SoftReferences which reference LinkedHashSet of DataFlavors.
174
*/
175
private final SoftCache<String, DataFlavor> flavorsForNativeCache = new SoftCache<>();
176
177
/**
178
* Dynamic mapping generation used for text mappings should not be applied
179
* to the DataFlavors and String natives for which the mappings have been
180
* explicitly specified with {@link #setFlavorsForNative} or
181
* {@link #setNativesForFlavor}. This keeps all such keys.
182
*/
183
private Set<Object> disabledMappingGenerationKeys = new HashSet<>();
184
185
/**
186
* Returns the default FlavorMap for this thread's ClassLoader.
187
*
188
* @return the default FlavorMap for this thread's ClassLoader
189
*/
190
public static FlavorMap getDefaultFlavorMap() {
191
return DataFlavorUtil.getDesktopService().getFlavorMap(SystemFlavorMap::new);
192
}
193
194
private SystemFlavorMap() {
195
}
196
197
/**
198
* Initializes a SystemFlavorMap by reading {@code flavormap.properties}.
199
* For thread-safety must be called under lock on {@code this}.
200
*/
201
private void initSystemFlavorMap() {
202
if (isMapInitialized) {
203
return;
204
}
205
isMapInitialized = true;
206
207
@SuppressWarnings("removal")
208
InputStream is = AccessController.doPrivileged(
209
(PrivilegedAction<InputStream>) () -> {
210
return SystemFlavorMap.class.getResourceAsStream(
211
"/sun/datatransfer/resources/flavormap.properties");
212
});
213
if (is == null) {
214
throw new InternalError("Default flavor mapping not found");
215
}
216
217
try (InputStreamReader isr = new InputStreamReader(is);
218
BufferedReader reader = new BufferedReader(isr)) {
219
String line;
220
while ((line = reader.readLine()) != null) {
221
line = line.trim();
222
if (line.startsWith("#") || line.isEmpty()) continue;
223
while (line.endsWith("\\")) {
224
line = line.substring(0, line.length() - 1) + reader.readLine().trim();
225
}
226
int delimiterPosition = line.indexOf('=');
227
String key = line.substring(0, delimiterPosition).replace("\\ ", " ");
228
String[] values = line.substring(delimiterPosition + 1, line.length()).split(",");
229
for (String value : values) {
230
try {
231
value = loadConvert(value);
232
MimeType mime = new MimeType(value);
233
if ("text".equals(mime.getPrimaryType())) {
234
String charset = mime.getParameter("charset");
235
if (DataFlavorUtil.doesSubtypeSupportCharset(mime.getSubType(), charset))
236
{
237
// We need to store the charset and eoln
238
// parameters, if any, so that the
239
// DataTransferer will have this information
240
// for conversion into the native format.
241
DesktopDatatransferService desktopService =
242
DataFlavorUtil.getDesktopService();
243
if (desktopService.isDesktopPresent()) {
244
desktopService.registerTextFlavorProperties(
245
key, charset,
246
mime.getParameter("eoln"),
247
mime.getParameter("terminators"));
248
}
249
}
250
251
// But don't store any of these parameters in the
252
// DataFlavor itself for any text natives (even
253
// non-charset ones). The SystemFlavorMap will
254
// synthesize the appropriate mappings later.
255
mime.removeParameter("charset");
256
mime.removeParameter("class");
257
mime.removeParameter("eoln");
258
mime.removeParameter("terminators");
259
value = mime.toString();
260
}
261
} catch (MimeTypeParseException e) {
262
e.printStackTrace();
263
continue;
264
}
265
266
DataFlavor flavor;
267
try {
268
flavor = new DataFlavor(value);
269
} catch (Exception e) {
270
try {
271
flavor = new DataFlavor(value, null);
272
} catch (Exception ee) {
273
ee.printStackTrace();
274
continue;
275
}
276
}
277
278
final LinkedHashSet<DataFlavor> dfs = new LinkedHashSet<>();
279
dfs.add(flavor);
280
281
if ("text".equals(flavor.getPrimaryType())) {
282
dfs.addAll(convertMimeTypeToDataFlavors(value));
283
store(flavor.mimeType.getBaseType(), key, getTextTypeToNative());
284
}
285
286
for (DataFlavor df : dfs) {
287
store(df, key, getFlavorToNative());
288
store(key, df, getNativeToFlavor());
289
}
290
}
291
}
292
} catch (IOException e) {
293
throw new InternalError("Error reading default flavor mapping", e);
294
}
295
}
296
297
// Copied from java.util.Properties
298
private static String loadConvert(String theString) {
299
char aChar;
300
int len = theString.length();
301
StringBuilder outBuffer = new StringBuilder(len);
302
303
for (int x = 0; x < len; ) {
304
aChar = theString.charAt(x++);
305
if (aChar == '\\') {
306
aChar = theString.charAt(x++);
307
if (aChar == 'u') {
308
// Read the xxxx
309
int value = 0;
310
for (int i = 0; i < 4; i++) {
311
aChar = theString.charAt(x++);
312
switch (aChar) {
313
case '0': case '1': case '2': case '3': case '4':
314
case '5': case '6': case '7': case '8': case '9': {
315
value = (value << 4) + aChar - '0';
316
break;
317
}
318
case 'a': case 'b': case 'c':
319
case 'd': case 'e': case 'f': {
320
value = (value << 4) + 10 + aChar - 'a';
321
break;
322
}
323
case 'A': case 'B': case 'C':
324
case 'D': case 'E': case 'F': {
325
value = (value << 4) + 10 + aChar - 'A';
326
break;
327
}
328
default: {
329
throw new IllegalArgumentException(
330
"Malformed \\uxxxx encoding.");
331
}
332
}
333
}
334
outBuffer.append((char)value);
335
} else {
336
if (aChar == 't') {
337
aChar = '\t';
338
} else if (aChar == 'r') {
339
aChar = '\r';
340
} else if (aChar == 'n') {
341
aChar = '\n';
342
} else if (aChar == 'f') {
343
aChar = '\f';
344
}
345
outBuffer.append(aChar);
346
}
347
} else {
348
outBuffer.append(aChar);
349
}
350
}
351
return outBuffer.toString();
352
}
353
354
/**
355
* Stores the listed object under the specified hash key in map. Unlike a
356
* standard map, the listed object will not replace any object already at
357
* the appropriate Map location, but rather will be appended to a List
358
* stored in that location.
359
*/
360
private <H, L> void store(H hashed, L listed, Map<H, LinkedHashSet<L>> map) {
361
LinkedHashSet<L> list = map.get(hashed);
362
if (list == null) {
363
list = new LinkedHashSet<>(1);
364
map.put(hashed, list);
365
}
366
if (!list.contains(listed)) {
367
list.add(listed);
368
}
369
}
370
371
/**
372
* Semantically equivalent to 'nativeToFlavor.get(nat)'. This method handles
373
* the case where 'nat' is not found in 'nativeToFlavor'. In that case, a
374
* new DataFlavor is synthesized, stored, and returned, if and only if the
375
* specified native is encoded as a Java MIME type.
376
*/
377
private LinkedHashSet<DataFlavor> nativeToFlavorLookup(String nat) {
378
LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
379
380
if (nat != null && !disabledMappingGenerationKeys.contains(nat)) {
381
DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService();
382
if (desktopService.isDesktopPresent()) {
383
LinkedHashSet<DataFlavor> platformFlavors =
384
desktopService.getPlatformMappingsForNative(nat);
385
if (!platformFlavors.isEmpty()) {
386
if (flavors != null) {
387
// Prepending the platform-specific mappings ensures
388
// that the flavors added with
389
// addFlavorForUnencodedNative() are at the end of
390
// list.
391
platformFlavors.addAll(flavors);
392
}
393
flavors = platformFlavors;
394
}
395
}
396
}
397
398
if (flavors == null && isJavaMIMEType(nat)) {
399
String decoded = decodeJavaMIMEType(nat);
400
DataFlavor flavor = null;
401
402
try {
403
flavor = new DataFlavor(decoded);
404
} catch (Exception e) {
405
System.err.println("Exception \"" + e.getClass().getName() +
406
": " + e.getMessage() +
407
"\"while constructing DataFlavor for: " +
408
decoded);
409
}
410
411
if (flavor != null) {
412
flavors = new LinkedHashSet<>(1);
413
getNativeToFlavor().put(nat, flavors);
414
flavors.add(flavor);
415
flavorsForNativeCache.remove(nat);
416
417
LinkedHashSet<String> natives = getFlavorToNative().get(flavor);
418
if (natives == null) {
419
natives = new LinkedHashSet<>(1);
420
getFlavorToNative().put(flavor, natives);
421
}
422
natives.add(nat);
423
nativesForFlavorCache.remove(flavor);
424
}
425
}
426
427
return (flavors != null) ? flavors : new LinkedHashSet<>(0);
428
}
429
430
/**
431
* Semantically equivalent to 'flavorToNative.get(flav)'. This method
432
* handles the case where 'flav' is not found in 'flavorToNative' depending
433
* on the value of passes 'synthesize' parameter. If 'synthesize' is
434
* SYNTHESIZE_IF_NOT_FOUND a native is synthesized, stored, and returned by
435
* encoding the DataFlavor's MIME type. Otherwise an empty List is returned
436
* and 'flavorToNative' remains unaffected.
437
*/
438
private LinkedHashSet<String> flavorToNativeLookup(final DataFlavor flav,
439
final boolean synthesize) {
440
441
LinkedHashSet<String> natives = getFlavorToNative().get(flav);
442
443
if (flav != null && !disabledMappingGenerationKeys.contains(flav)) {
444
DesktopDatatransferService desktopService = DataFlavorUtil.getDesktopService();
445
if (desktopService.isDesktopPresent()) {
446
LinkedHashSet<String> platformNatives =
447
desktopService.getPlatformMappingsForFlavor(flav);
448
if (!platformNatives.isEmpty()) {
449
if (natives != null) {
450
// Prepend the platform-specific mappings to ensure
451
// that the natives added with
452
// addUnencodedNativeForFlavor() are at the end of
453
// list.
454
platformNatives.addAll(natives);
455
}
456
natives = platformNatives;
457
}
458
}
459
}
460
461
if (natives == null) {
462
if (synthesize) {
463
String encoded = encodeDataFlavor(flav);
464
natives = new LinkedHashSet<>(1);
465
getFlavorToNative().put(flav, natives);
466
natives.add(encoded);
467
468
LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(encoded);
469
if (flavors == null) {
470
flavors = new LinkedHashSet<>(1);
471
getNativeToFlavor().put(encoded, flavors);
472
}
473
flavors.add(flav);
474
475
nativesForFlavorCache.remove(flav);
476
flavorsForNativeCache.remove(encoded);
477
} else {
478
natives = new LinkedHashSet<>(0);
479
}
480
}
481
482
return new LinkedHashSet<>(natives);
483
}
484
485
/**
486
* Returns a {@code List} of {@code String} natives to which the specified
487
* {@code DataFlavor} can be translated by the data transfer subsystem. The
488
* {@code List} will be sorted from best native to worst. That is, the first
489
* native will best reflect data in the specified flavor to the underlying
490
* native platform.
491
* <p>
492
* If the specified {@code DataFlavor} is previously unknown to the data
493
* transfer subsystem and the data transfer subsystem is unable to translate
494
* this {@code DataFlavor} to any existing native, then invoking this method
495
* will establish a mapping in both directions between the specified
496
* {@code DataFlavor} and an encoded version of its MIME type as its native.
497
*
498
* @param flav the {@code DataFlavor} whose corresponding natives should be
499
* returned. If {@code null} is specified, all natives currently
500
* known to the data transfer subsystem are returned in a
501
* non-deterministic order.
502
* @return a {@code java.util.List} of {@code java.lang.String} objects
503
* which are platform-specific representations of platform-specific
504
* data formats
505
* @see #encodeDataFlavor
506
* @since 1.4
507
*/
508
@Override
509
public synchronized List<String> getNativesForFlavor(DataFlavor flav) {
510
LinkedHashSet<String> retval = nativesForFlavorCache.check(flav);
511
if (retval != null) {
512
return new ArrayList<>(retval);
513
}
514
515
if (flav == null) {
516
retval = new LinkedHashSet<>(getNativeToFlavor().keySet());
517
} else if (disabledMappingGenerationKeys.contains(flav)) {
518
// In this case we shouldn't synthesize a native for this flavor,
519
// since its mappings were explicitly specified.
520
retval = flavorToNativeLookup(flav, false);
521
} else if (DataFlavorUtil.isFlavorCharsetTextType(flav)) {
522
retval = new LinkedHashSet<>(0);
523
524
// For text/* flavors, flavor-to-native mappings specified in
525
// flavormap.properties are stored per flavor's base type.
526
if ("text".equals(flav.getPrimaryType())) {
527
LinkedHashSet<String> textTypeNatives =
528
getTextTypeToNative().get(flav.mimeType.getBaseType());
529
if (textTypeNatives != null) {
530
retval.addAll(textTypeNatives);
531
}
532
}
533
534
// Also include text/plain natives, but don't duplicate Strings
535
LinkedHashSet<String> textTypeNatives =
536
getTextTypeToNative().get(TEXT_PLAIN_BASE_TYPE);
537
if (textTypeNatives != null) {
538
retval.addAll(textTypeNatives);
539
}
540
541
if (retval.isEmpty()) {
542
retval = flavorToNativeLookup(flav, true);
543
} else {
544
// In this branch it is guaranteed that natives explicitly
545
// listed for flav's MIME type were added with
546
// addUnencodedNativeForFlavor(), so they have lower priority.
547
retval.addAll(flavorToNativeLookup(flav, false));
548
}
549
} else if (DataFlavorUtil.isFlavorNoncharsetTextType(flav)) {
550
retval = getTextTypeToNative().get(flav.mimeType.getBaseType());
551
552
if (retval == null || retval.isEmpty()) {
553
retval = flavorToNativeLookup(flav, true);
554
} else {
555
// In this branch it is guaranteed that natives explicitly
556
// listed for flav's MIME type were added with
557
// addUnencodedNativeForFlavor(), so they have lower priority.
558
retval.addAll(flavorToNativeLookup(flav, false));
559
}
560
} else {
561
retval = flavorToNativeLookup(flav, true);
562
}
563
564
nativesForFlavorCache.put(flav, retval);
565
// Create a copy, because client code can modify the returned list.
566
return new ArrayList<>(retval);
567
}
568
569
/**
570
* Returns a {@code List} of {@code DataFlavor}s to which the specified
571
* {@code String} native can be translated by the data transfer subsystem.
572
* The {@code List} will be sorted from best {@code DataFlavor} to worst.
573
* That is, the first {@code DataFlavor} will best reflect data in the
574
* specified native to a Java application.
575
* <p>
576
* If the specified native is previously unknown to the data transfer
577
* subsystem, and that native has been properly encoded, then invoking this
578
* method will establish a mapping in both directions between the specified
579
* native and a {@code DataFlavor} whose MIME type is a decoded version of
580
* the native.
581
* <p>
582
* If the specified native is not a properly encoded native and the mappings
583
* for this native have not been altered with {@code setFlavorsForNative},
584
* then the contents of the {@code List} is platform dependent, but
585
* {@code null} cannot be returned.
586
*
587
* @param nat the native whose corresponding {@code DataFlavor}s should be
588
* returned. If {@code null} is specified, all {@code DataFlavor}s
589
* currently known to the data transfer subsystem are returned in a
590
* non-deterministic order.
591
* @return a {@code java.util.List} of {@code DataFlavor} objects into which
592
* platform-specific data in the specified, platform-specific native
593
* can be translated
594
* @see #encodeJavaMIMEType
595
* @since 1.4
596
*/
597
@Override
598
public synchronized List<DataFlavor> getFlavorsForNative(String nat) {
599
LinkedHashSet<DataFlavor> returnValue = flavorsForNativeCache.check(nat);
600
if (returnValue != null) {
601
return new ArrayList<>(returnValue);
602
} else {
603
returnValue = new LinkedHashSet<>();
604
}
605
606
if (nat == null) {
607
for (String n : getNativesForFlavor(null)) {
608
returnValue.addAll(getFlavorsForNative(n));
609
}
610
} else {
611
final LinkedHashSet<DataFlavor> flavors = nativeToFlavorLookup(nat);
612
if (disabledMappingGenerationKeys.contains(nat)) {
613
return new ArrayList<>(flavors);
614
}
615
616
final LinkedHashSet<DataFlavor> flavorsWithSynthesized =
617
nativeToFlavorLookup(nat);
618
619
for (DataFlavor df : flavorsWithSynthesized) {
620
returnValue.add(df);
621
if ("text".equals(df.getPrimaryType())) {
622
String baseType = df.mimeType.getBaseType();
623
returnValue.addAll(convertMimeTypeToDataFlavors(baseType));
624
}
625
}
626
}
627
flavorsForNativeCache.put(nat, returnValue);
628
return new ArrayList<>(returnValue);
629
}
630
631
@SuppressWarnings("deprecation")
632
private static Set<DataFlavor> convertMimeTypeToDataFlavors(
633
final String baseType) {
634
635
final Set<DataFlavor> returnValue = new LinkedHashSet<>();
636
637
String subType = null;
638
639
try {
640
final MimeType mimeType = new MimeType(baseType);
641
subType = mimeType.getSubType();
642
} catch (MimeTypeParseException mtpe) {
643
// Cannot happen, since we checked all mappings
644
// on load from flavormap.properties.
645
}
646
647
if (DataFlavorUtil.doesSubtypeSupportCharset(subType, null)) {
648
if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
649
{
650
returnValue.add(DataFlavor.stringFlavor);
651
}
652
653
for (String unicodeClassName : UNICODE_TEXT_CLASSES) {
654
final String mimeType = baseType + ";charset=Unicode;class=" +
655
unicodeClassName;
656
657
final LinkedHashSet<String> mimeTypes =
658
handleHtmlMimeTypes(baseType, mimeType);
659
for (String mt : mimeTypes) {
660
DataFlavor toAdd = null;
661
try {
662
toAdd = new DataFlavor(mt);
663
} catch (ClassNotFoundException cannotHappen) {
664
}
665
returnValue.add(toAdd);
666
}
667
}
668
669
for (String charset : DataFlavorUtil.standardEncodings()) {
670
671
for (String encodedTextClass : ENCODED_TEXT_CLASSES) {
672
final String mimeType =
673
baseType + ";charset=" + charset +
674
";class=" + encodedTextClass;
675
676
final LinkedHashSet<String> mimeTypes =
677
handleHtmlMimeTypes(baseType, mimeType);
678
679
for (String mt : mimeTypes) {
680
681
DataFlavor df = null;
682
683
try {
684
df = new DataFlavor(mt);
685
// Check for equality to plainTextFlavor so
686
// that we can ensure that the exact charset of
687
// plainTextFlavor, not the canonical charset
688
// or another equivalent charset with a
689
// different name, is used.
690
if (df.equals(DataFlavor.plainTextFlavor)) {
691
df = DataFlavor.plainTextFlavor;
692
}
693
} catch (ClassNotFoundException cannotHappen) {
694
}
695
696
returnValue.add(df);
697
}
698
}
699
}
700
701
if (TEXT_PLAIN_BASE_TYPE.equals(baseType))
702
{
703
returnValue.add(DataFlavor.plainTextFlavor);
704
}
705
} else {
706
// Non-charset text natives should be treated as
707
// opaque, 8-bit data in any of its various
708
// representations.
709
for (String encodedTextClassName : ENCODED_TEXT_CLASSES) {
710
DataFlavor toAdd = null;
711
try {
712
toAdd = new DataFlavor(baseType +
713
";class=" + encodedTextClassName);
714
} catch (ClassNotFoundException cannotHappen) {
715
}
716
returnValue.add(toAdd);
717
}
718
}
719
return returnValue;
720
}
721
722
private static final String [] htmlDocumentTypes =
723
new String [] {"all", "selection", "fragment"};
724
725
private static LinkedHashSet<String> handleHtmlMimeTypes(String baseType,
726
String mimeType) {
727
728
LinkedHashSet<String> returnValues = new LinkedHashSet<>();
729
730
if (HTML_TEXT_BASE_TYPE.equals(baseType)) {
731
for (String documentType : htmlDocumentTypes) {
732
returnValues.add(mimeType + ";document=" + documentType);
733
}
734
} else {
735
returnValues.add(mimeType);
736
}
737
738
return returnValues;
739
}
740
741
/**
742
* Returns a {@code Map} of the specified {@code DataFlavor}s to their most
743
* preferred {@code String} native. Each native value will be the same as
744
* the first native in the List returned by {@code getNativesForFlavor} for
745
* the specified flavor.
746
* <p>
747
* If a specified {@code DataFlavor} is previously unknown to the data
748
* transfer subsystem, then invoking this method will establish a mapping in
749
* both directions between the specified {@code DataFlavor} and an encoded
750
* version of its MIME type as its native.
751
*
752
* @param flavors an array of {@code DataFlavor}s which will be the key set
753
* of the returned {@code Map}. If {@code null} is specified, a
754
* mapping of all {@code DataFlavor}s known to the data transfer
755
* subsystem to their most preferred {@code String} natives will be
756
* returned.
757
* @return a {@code java.util.Map} of {@code DataFlavor}s to {@code String}
758
* natives
759
* @see #getNativesForFlavor
760
* @see #encodeDataFlavor
761
*/
762
@Override
763
public synchronized Map<DataFlavor,String> getNativesForFlavors(DataFlavor[] flavors)
764
{
765
// Use getNativesForFlavor to generate extra natives for text flavors
766
// and stringFlavor
767
768
if (flavors == null) {
769
List<DataFlavor> flavor_list = getFlavorsForNative(null);
770
flavors = new DataFlavor[flavor_list.size()];
771
flavor_list.toArray(flavors);
772
}
773
774
Map<DataFlavor, String> retval = new HashMap<>(flavors.length, 1.0f);
775
for (DataFlavor flavor : flavors) {
776
List<String> natives = getNativesForFlavor(flavor);
777
String nat = (natives.isEmpty()) ? null : natives.get(0);
778
retval.put(flavor, nat);
779
}
780
781
return retval;
782
}
783
784
/**
785
* Returns a {@code Map} of the specified {@code String} natives to their
786
* most preferred {@code DataFlavor}. Each {@code DataFlavor} value will be
787
* the same as the first {@code DataFlavor} in the List returned by
788
* {@code getFlavorsForNative} for the specified native.
789
* <p>
790
* If a specified native is previously unknown to the data transfer
791
* subsystem, and that native has been properly encoded, then invoking this
792
* method will establish a mapping in both directions between the specified
793
* native and a {@code DataFlavor} whose MIME type is a decoded version of
794
* the native.
795
*
796
* @param natives an array of {@code String}s which will be the key set of
797
* the returned {@code Map}. If {@code null} is specified, a mapping
798
* of all supported {@code String} natives to their most preferred
799
* {@code DataFlavor}s will be returned.
800
* @return a {@code java.util.Map} of {@code String} natives to
801
* {@code DataFlavor}s
802
* @see #getFlavorsForNative
803
* @see #encodeJavaMIMEType
804
*/
805
@Override
806
public synchronized Map<String,DataFlavor> getFlavorsForNatives(String[] natives)
807
{
808
// Use getFlavorsForNative to generate extra flavors for text natives
809
if (natives == null) {
810
List<String> nativesList = getNativesForFlavor(null);
811
natives = new String[nativesList.size()];
812
nativesList.toArray(natives);
813
}
814
815
Map<String, DataFlavor> retval = new HashMap<>(natives.length, 1.0f);
816
for (String aNative : natives) {
817
List<DataFlavor> flavors = getFlavorsForNative(aNative);
818
DataFlavor flav = (flavors.isEmpty())? null : flavors.get(0);
819
retval.put(aNative, flav);
820
}
821
return retval;
822
}
823
824
/**
825
* Adds a mapping from the specified {@code DataFlavor} (and all
826
* {@code DataFlavor}s equal to the specified {@code DataFlavor}) to the
827
* specified {@code String} native. Unlike {@code getNativesForFlavor}, the
828
* mapping will only be established in one direction, and the native will
829
* not be encoded. To establish a two-way mapping, call
830
* {@code addFlavorForUnencodedNative} as well. The new mapping will be of
831
* lower priority than any existing mapping. This method has no effect if a
832
* mapping from the specified or equal {@code DataFlavor} to the specified
833
* {@code String} native already exists.
834
*
835
* @param flav the {@code DataFlavor} key for the mapping
836
* @param nat the {@code String} native value for the mapping
837
* @throws NullPointerException if flav or nat is {@code null}
838
* @see #addFlavorForUnencodedNative
839
* @since 1.4
840
*/
841
public synchronized void addUnencodedNativeForFlavor(DataFlavor flav,
842
String nat) {
843
Objects.requireNonNull(nat, "Null native not permitted");
844
Objects.requireNonNull(flav, "Null flavor not permitted");
845
846
LinkedHashSet<String> natives = getFlavorToNative().get(flav);
847
if (natives == null) {
848
natives = new LinkedHashSet<>(1);
849
getFlavorToNative().put(flav, natives);
850
}
851
natives.add(nat);
852
nativesForFlavorCache.remove(flav);
853
}
854
855
/**
856
* Discards the current mappings for the specified {@code DataFlavor} and
857
* all {@code DataFlavor}s equal to the specified {@code DataFlavor}, and
858
* creates new mappings to the specified {@code String} natives. Unlike
859
* {@code getNativesForFlavor}, the mappings will only be established in one
860
* direction, and the natives will not be encoded. To establish two-way
861
* mappings, call {@code setFlavorsForNative} as well. The first native in
862
* the array will represent the highest priority mapping. Subsequent natives
863
* will represent mappings of decreasing priority.
864
* <p>
865
* If the array contains several elements that reference equal
866
* {@code String} natives, this method will establish new mappings for the
867
* first of those elements and ignore the rest of them.
868
* <p>
869
* It is recommended that client code not reset mappings established by the
870
* data transfer subsystem. This method should only be used for
871
* application-level mappings.
872
*
873
* @param flav the {@code DataFlavor} key for the mappings
874
* @param natives the {@code String} native values for the mappings
875
* @throws NullPointerException if flav or natives is {@code null} or if
876
* natives contains {@code null} elements
877
* @see #setFlavorsForNative
878
* @since 1.4
879
*/
880
public synchronized void setNativesForFlavor(DataFlavor flav,
881
String[] natives) {
882
Objects.requireNonNull(natives, "Null natives not permitted");
883
Objects.requireNonNull(flav, "Null flavors not permitted");
884
885
getFlavorToNative().remove(flav);
886
for (String aNative : natives) {
887
addUnencodedNativeForFlavor(flav, aNative);
888
}
889
disabledMappingGenerationKeys.add(flav);
890
nativesForFlavorCache.remove(flav);
891
}
892
893
/**
894
* Adds a mapping from a single {@code String} native to a single
895
* {@code DataFlavor}. Unlike {@code getFlavorsForNative}, the mapping will
896
* only be established in one direction, and the native will not be encoded.
897
* To establish a two-way mapping, call {@code addUnencodedNativeForFlavor}
898
* as well. The new mapping will be of lower priority than any existing
899
* mapping. This method has no effect if a mapping from the specified
900
* {@code String} native to the specified or equal {@code DataFlavor}
901
* already exists.
902
*
903
* @param nat the {@code String} native key for the mapping
904
* @param flav the {@code DataFlavor} value for the mapping
905
* @throws NullPointerException if {@code nat} or {@code flav} is
906
* {@code null}
907
* @see #addUnencodedNativeForFlavor
908
* @since 1.4
909
*/
910
public synchronized void addFlavorForUnencodedNative(String nat,
911
DataFlavor flav) {
912
Objects.requireNonNull(nat, "Null native not permitted");
913
Objects.requireNonNull(flav, "Null flavor not permitted");
914
915
LinkedHashSet<DataFlavor> flavors = getNativeToFlavor().get(nat);
916
if (flavors == null) {
917
flavors = new LinkedHashSet<>(1);
918
getNativeToFlavor().put(nat, flavors);
919
}
920
flavors.add(flav);
921
flavorsForNativeCache.remove(nat);
922
}
923
924
/**
925
* Discards the current mappings for the specified {@code String} native,
926
* and creates new mappings to the specified {@code DataFlavor}s. Unlike
927
* {@code getFlavorsForNative}, the mappings will only be established in one
928
* direction, and the natives need not be encoded. To establish two-way
929
* mappings, call {@code setNativesForFlavor} as well. The first
930
* {@code DataFlavor} in the array will represent the highest priority
931
* mapping. Subsequent {@code DataFlavor}s will represent mappings of
932
* decreasing priority.
933
* <p>
934
* If the array contains several elements that reference equal
935
* {@code DataFlavor}s, this method will establish new mappings for the
936
* first of those elements and ignore the rest of them.
937
* <p>
938
* It is recommended that client code not reset mappings established by the
939
* data transfer subsystem. This method should only be used for
940
* application-level mappings.
941
*
942
* @param nat the {@code String} native key for the mappings
943
* @param flavors the {@code DataFlavor} values for the mappings
944
* @throws NullPointerException if {@code nat} or {@code flavors} is
945
* {@code null} or if {@code flavors} contains {@code null} elements
946
* @see #setNativesForFlavor
947
* @since 1.4
948
*/
949
public synchronized void setFlavorsForNative(String nat,
950
DataFlavor[] flavors) {
951
Objects.requireNonNull(nat, "Null native not permitted");
952
Objects.requireNonNull(flavors, "Null flavors not permitted");
953
954
getNativeToFlavor().remove(nat);
955
for (DataFlavor flavor : flavors) {
956
addFlavorForUnencodedNative(nat, flavor);
957
}
958
disabledMappingGenerationKeys.add(nat);
959
flavorsForNativeCache.remove(nat);
960
}
961
962
/**
963
* Encodes a MIME type for use as a {@code String} native. The format of an
964
* encoded representation of a MIME type is implementation-dependent. The
965
* only restrictions are:
966
* <ul>
967
* <li>The encoded representation is {@code null} if and only if the MIME
968
* type {@code String} is {@code null}</li>
969
* <li>The encoded representations for two non-{@code null} MIME type
970
* {@code String}s are equal if and only if these {@code String}s are
971
* equal according to {@code String.equals(Object)}</li>
972
* </ul>
973
* The reference implementation of this method returns the specified MIME
974
* type {@code String} prefixed with {@code JAVA_DATAFLAVOR:}.
975
*
976
* @param mimeType the MIME type to encode
977
* @return the encoded {@code String}, or {@code null} if {@code mimeType}
978
* is {@code null}
979
*/
980
public static String encodeJavaMIMEType(String mimeType) {
981
return (mimeType != null)
982
? JavaMIME + mimeType
983
: null;
984
}
985
986
/**
987
* Encodes a {@code DataFlavor} for use as a {@code String} native. The
988
* format of an encoded {@code DataFlavor} is implementation-dependent. The
989
* only restrictions are:
990
* <ul>
991
* <li>The encoded representation is {@code null} if and only if the
992
* specified {@code DataFlavor} is {@code null} or its MIME type
993
* {@code String} is {@code null}</li>
994
* <li>The encoded representations for two non-{@code null}
995
* {@code DataFlavor}s with non-{@code null} MIME type {@code String}s
996
* are equal if and only if the MIME type {@code String}s of these
997
* {@code DataFlavor}s are equal according to
998
* {@code String.equals(Object)}</li>
999
* </ul>
1000
* The reference implementation of this method returns the MIME type
1001
* {@code String} of the specified {@code DataFlavor} prefixed with
1002
* {@code JAVA_DATAFLAVOR:}.
1003
*
1004
* @param flav the {@code DataFlavor} to encode
1005
* @return the encoded {@code String}, or {@code null} if {@code flav} is
1006
* {@code null} or has a {@code null} MIME type
1007
*/
1008
public static String encodeDataFlavor(DataFlavor flav) {
1009
return (flav != null)
1010
? SystemFlavorMap.encodeJavaMIMEType(flav.getMimeType())
1011
: null;
1012
}
1013
1014
/**
1015
* Returns whether the specified {@code String} is an encoded Java MIME
1016
* type.
1017
*
1018
* @param str the {@code String} to test
1019
* @return {@code true} if the {@code String} is encoded; {@code false}
1020
* otherwise
1021
*/
1022
public static boolean isJavaMIMEType(String str) {
1023
return (str != null && str.startsWith(JavaMIME, 0));
1024
}
1025
1026
/**
1027
* Decodes a {@code String} native for use as a Java MIME type.
1028
*
1029
* @param nat the {@code String} to decode
1030
* @return the decoded Java MIME type, or {@code null} if {@code nat} is not
1031
* an encoded {@code String} native
1032
*/
1033
public static String decodeJavaMIMEType(String nat) {
1034
return (isJavaMIMEType(nat))
1035
? nat.substring(JavaMIME.length(), nat.length()).trim()
1036
: null;
1037
}
1038
1039
/**
1040
* Decodes a {@code String} native for use as a {@code DataFlavor}.
1041
*
1042
* @param nat the {@code String} to decode
1043
* @return the decoded {@code DataFlavor}, or {@code null} if {@code nat} is
1044
* not an encoded {@code String} native
1045
* @throws ClassNotFoundException if the class of the data flavor is not
1046
* loaded
1047
*/
1048
public static DataFlavor decodeDataFlavor(String nat)
1049
throws ClassNotFoundException
1050
{
1051
String retval_str = SystemFlavorMap.decodeJavaMIMEType(nat);
1052
return (retval_str != null)
1053
? new DataFlavor(retval_str)
1054
: null;
1055
}
1056
1057
private static final class SoftCache<K, V> {
1058
Map<K, SoftReference<LinkedHashSet<V>>> cache;
1059
1060
public void put(K key, LinkedHashSet<V> value) {
1061
if (cache == null) {
1062
cache = new HashMap<>(1);
1063
}
1064
cache.put(key, new SoftReference<>(value));
1065
}
1066
1067
public void remove(K key) {
1068
if (cache == null) return;
1069
cache.remove(null);
1070
cache.remove(key);
1071
}
1072
1073
public LinkedHashSet<V> check(K key) {
1074
if (cache == null) return null;
1075
SoftReference<LinkedHashSet<V>> ref = cache.get(key);
1076
if (ref != null) {
1077
return ref.get();
1078
}
1079
return null;
1080
}
1081
}
1082
}
1083
1084