1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.util;
18
19 import java.io.Serializable;
20 import java.nio.charset.Charset;
21 import java.util.BitSet;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.TreeSet;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 public class MimeType implements Comparable<MimeType>, Serializable {
47
48 private static final long serialVersionUID = 4085923477777865903L;
49
50 protected static final String WILDCARD_TYPE = "*";
51
52 private static final BitSet TOKEN;
53
54 private static final String PARAM_CHARSET = "charset";
55
56
57 private final String type;
58
59 private final String subtype;
60
61 private final Map<String, String> parameters;
62
63
64 static {
65
66 BitSet ctl = new BitSet(128);
67 for (int i = 0; i <= 31; i++) {
68 ctl.set(i);
69 }
70 ctl.set(127);
71
72 BitSet separators = new BitSet(128);
73 separators.set('(');
74 separators.set(')');
75 separators.set('<');
76 separators.set('>');
77 separators.set('@');
78 separators.set(',');
79 separators.set(';');
80 separators.set(':');
81 separators.set('\\');
82 separators.set('\"');
83 separators.set('/');
84 separators.set('[');
85 separators.set(']');
86 separators.set('?');
87 separators.set('=');
88 separators.set('{');
89 separators.set('}');
90 separators.set(' ');
91 separators.set('\t');
92
93 TOKEN = new BitSet(128);
94 TOKEN.set(0, 128);
95 TOKEN.andNot(ctl);
96 TOKEN.andNot(separators);
97 }
98
99
100
101
102
103
104
105
106 public MimeType(String type) {
107 this(type, WILDCARD_TYPE);
108 }
109
110
111
112
113
114
115
116
117 public MimeType(String type, String subtype) {
118 this(type, subtype, Collections.<String, String>emptyMap());
119 }
120
121
122
123
124
125
126
127
128 public MimeType(String type, String subtype, Charset charSet) {
129 this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name()));
130 }
131
132
133
134
135
136
137
138
139 public MimeType(MimeType other, Map<String, String> parameters) {
140 this(other.getType(), other.getSubtype(), parameters);
141 }
142
143
144
145
146
147
148
149
150 public MimeType(String type, String subtype, Map<String, String> parameters) {
151 Assert.hasLength(type, "type must not be empty");
152 Assert.hasLength(subtype, "subtype must not be empty");
153 checkToken(type);
154 checkToken(subtype);
155 this.type = type.toLowerCase(Locale.ENGLISH);
156 this.subtype = subtype.toLowerCase(Locale.ENGLISH);
157 if (!CollectionUtils.isEmpty(parameters)) {
158 Map<String, String> map = new LinkedCaseInsensitiveMap<String>(parameters.size(), Locale.ENGLISH);
159 for (Map.Entry<String, String> entry : parameters.entrySet()) {
160 String attribute = entry.getKey();
161 String value = entry.getValue();
162 checkParameters(attribute, value);
163 map.put(attribute, value);
164 }
165 this.parameters = Collections.unmodifiableMap(map);
166 }
167 else {
168 this.parameters = Collections.emptyMap();
169 }
170 }
171
172
173
174
175
176
177
178 private void checkToken(String token) {
179 for (int i=0; i < token.length(); i++ ) {
180 char ch = token.charAt(i);
181 if (!TOKEN.get(ch)) {
182 throw new IllegalArgumentException("Invalid token character '" + ch + "' in token \"" + token + "\"");
183 }
184 }
185 }
186
187 protected void checkParameters(String attribute, String value) {
188 Assert.hasLength(attribute, "parameter attribute must not be empty");
189 Assert.hasLength(value, "parameter value must not be empty");
190 checkToken(attribute);
191 if (PARAM_CHARSET.equals(attribute)) {
192 value = unquote(value);
193 Charset.forName(value);
194 }
195 else if (!isQuotedString(value)) {
196 checkToken(value);
197 }
198 }
199
200 private boolean isQuotedString(String s) {
201 if (s.length() < 2) {
202 return false;
203 }
204 else {
205 return ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'")));
206 }
207 }
208
209 protected String unquote(String s) {
210 if (s == null) {
211 return null;
212 }
213 return isQuotedString(s) ? s.substring(1, s.length() - 1) : s;
214 }
215
216
217
218
219
220 public boolean isWildcardType() {
221 return WILDCARD_TYPE.equals(getType());
222 }
223
224
225
226
227
228
229
230 public boolean isWildcardSubtype() {
231 return WILDCARD_TYPE.equals(getSubtype()) || getSubtype().startsWith("*+");
232 }
233
234
235
236
237
238
239 public boolean isConcrete() {
240 return !isWildcardType() && !isWildcardSubtype();
241 }
242
243
244
245
246 public String getType() {
247 return this.type;
248 }
249
250
251
252
253 public String getSubtype() {
254 return this.subtype;
255 }
256
257
258
259
260
261 public Charset getCharSet() {
262 String charSet = getParameter(PARAM_CHARSET);
263 return (charSet != null ? Charset.forName(unquote(charSet)) : null);
264 }
265
266
267
268
269
270
271 public String getParameter(String name) {
272 return this.parameters.get(name);
273 }
274
275
276
277
278
279 public Map<String, String> getParameters() {
280 return this.parameters;
281 }
282
283
284
285
286
287
288
289
290
291
292 public boolean includes(MimeType other) {
293 if (other == null) {
294 return false;
295 }
296 if (this.isWildcardType()) {
297
298 return true;
299 }
300 else if (getType().equals(other.getType())) {
301 if (getSubtype().equals(other.getSubtype())) {
302 return true;
303 }
304 if (this.isWildcardSubtype()) {
305
306 int thisPlusIdx = getSubtype().indexOf('+');
307 if (thisPlusIdx == -1) {
308 return true;
309 }
310 else {
311
312 int otherPlusIdx = other.getSubtype().indexOf('+');
313 if (otherPlusIdx != -1) {
314 String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx);
315 String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1);
316 String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1);
317 if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) {
318 return true;
319 }
320 }
321 }
322 }
323 }
324 return false;
325 }
326
327
328
329
330
331
332
333
334
335
336 public boolean isCompatibleWith(MimeType other) {
337 if (other == null) {
338 return false;
339 }
340 if (isWildcardType() || other.isWildcardType()) {
341 return true;
342 }
343 else if (getType().equals(other.getType())) {
344 if (getSubtype().equals(other.getSubtype())) {
345 return true;
346 }
347
348 if (this.isWildcardSubtype() || other.isWildcardSubtype()) {
349
350 int thisPlusIdx = getSubtype().indexOf('+');
351 int otherPlusIdx = other.getSubtype().indexOf('+');
352
353 if (thisPlusIdx == -1 && otherPlusIdx == -1) {
354 return true;
355 }
356 else if (thisPlusIdx != -1 && otherPlusIdx != -1) {
357 String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx);
358 String otherSubtypeNoSuffix = other.getSubtype().substring(0, otherPlusIdx);
359
360 String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1);
361 String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1);
362
363 if (thisSubtypeSuffix.equals(otherSubtypeSuffix) &&
364 (WILDCARD_TYPE.equals(thisSubtypeNoSuffix) || WILDCARD_TYPE.equals(otherSubtypeNoSuffix))) {
365 return true;
366 }
367 }
368 }
369 }
370 return false;
371 }
372
373
374
375
376
377
378 @Override
379 public int compareTo(MimeType other) {
380 int comp = getType().compareToIgnoreCase(other.getType());
381 if (comp != 0) {
382 return comp;
383 }
384 comp = getSubtype().compareToIgnoreCase(other.getSubtype());
385 if (comp != 0) {
386 return comp;
387 }
388 comp = getParameters().size() - other.getParameters().size();
389 if (comp != 0) {
390 return comp;
391 }
392 TreeSet<String> thisAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
393 thisAttributes.addAll(getParameters().keySet());
394 TreeSet<String> otherAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
395 otherAttributes.addAll(other.getParameters().keySet());
396 Iterator<String> thisAttributesIterator = thisAttributes.iterator();
397 Iterator<String> otherAttributesIterator = otherAttributes.iterator();
398 while (thisAttributesIterator.hasNext()) {
399 String thisAttribute = thisAttributesIterator.next();
400 String otherAttribute = otherAttributesIterator.next();
401 comp = thisAttribute.compareToIgnoreCase(otherAttribute);
402 if (comp != 0) {
403 return comp;
404 }
405 String thisValue = getParameters().get(thisAttribute);
406 String otherValue = other.getParameters().get(otherAttribute);
407 if (otherValue == null) {
408 otherValue = "";
409 }
410 comp = thisValue.compareTo(otherValue);
411 if (comp != 0) {
412 return comp;
413 }
414 }
415 return 0;
416 }
417
418 @Override
419 public boolean equals(Object other) {
420 if (this == other) {
421 return true;
422 }
423 if (!(other instanceof MimeType)) {
424 return false;
425 }
426 MimeType otherType = (MimeType) other;
427 return (this.type.equalsIgnoreCase(otherType.type) &&
428 this.subtype.equalsIgnoreCase(otherType.subtype) &&
429 this.parameters.equals(otherType.parameters));
430 }
431
432 @Override
433 public int hashCode() {
434 int result = this.type.hashCode();
435 result = 31 * result + this.subtype.hashCode();
436 result = 31 * result + this.parameters.hashCode();
437 return result;
438 }
439
440 @Override
441 public String toString() {
442 StringBuilder builder = new StringBuilder();
443 appendTo(builder);
444 return builder.toString();
445 }
446
447 protected void appendTo(StringBuilder builder) {
448 builder.append(this.type);
449 builder.append('/');
450 builder.append(this.subtype);
451 appendTo(this.parameters, builder);
452 }
453
454 private void appendTo(Map<String, String> map, StringBuilder builder) {
455 for (Map.Entry<String, String> entry : map.entrySet()) {
456 builder.append(';');
457 builder.append(entry.getKey());
458 builder.append('=');
459 builder.append(entry.getValue());
460 }
461 }
462
463
464
465
466
467
468
469 public static MimeType valueOf(String value) {
470 return MimeTypeUtils.parseMimeType(value);
471 }
472
473
474 public static class SpecificityComparator<T extends MimeType> implements Comparator<T> {
475
476 @Override
477 public int compare(T mimeType1, T mimeType2) {
478 if (mimeType1.isWildcardType() && !mimeType2.isWildcardType()) {
479 return 1;
480 }
481 else if (mimeType2.isWildcardType() && !mimeType1.isWildcardType()) {
482 return -1;
483 }
484 else if (!mimeType1.getType().equals(mimeType2.getType())) {
485 return 0;
486 }
487 else {
488 if (mimeType1.isWildcardSubtype() && !mimeType2.isWildcardSubtype()) {
489 return 1;
490 }
491 else if (mimeType2.isWildcardSubtype() && !mimeType1.isWildcardSubtype()) {
492 return -1;
493 }
494 else if (!mimeType1.getSubtype().equals(mimeType2.getSubtype())) {
495 return 0;
496 }
497 else {
498 return compareParameters(mimeType1, mimeType2);
499 }
500 }
501 }
502
503 protected int compareParameters(T mimeType1, T mimeType2) {
504 int paramsSize1 = mimeType1.getParameters().size();
505 int paramsSize2 = mimeType2.getParameters().size();
506 return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1));
507 }
508 }
509
510 }