-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathMediaTypeParser.java
More file actions
265 lines (241 loc) · 8.86 KB
/
MediaTypeParser.java
File metadata and controls
265 lines (241 loc) · 8.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/* Copyright (c) restSQL Project Contributors. Licensed under MIT. */
package org.restsql.core.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
/**
* MIME-Type Parser
*
* This class provides basic functions for handling mime-types. It can handle
* matching mime-types against a list of media-ranges. See section 14.1 of the
* HTTP specification [RFC 2616] for a complete explanation.
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
*
* A port to Java of Joe Gregorio's MIME-Type Parser:
*
* http://code.google.com/p/mimeparse/
*
* Ported by Tom Zellman <tzellman@gmail.com>.
*
* Licensed under MIT (http://www.opensource.org/licenses/mit-license.php)
*/
public final class MediaTypeParser
{
/**
* Parse results container
*/
protected static class ParseResults
{
String type;
String subType;
// !a dictionary of all the parameters for the media range
Map<String, String> params;
@Override
public String toString()
{
StringBuffer s = new StringBuffer("('" + type + "', '" + subType
+ "', {");
for (String k : params.keySet())
s.append("'" + k + "':'" + params.get(k) + "',");
return s.append("})").toString();
}
}
/**
* Carves up a mime-type and returns a ParseResults object
*
* For example, the media range 'application/xhtml;q=0.5' would get parsed
* into:
*
* ('application', 'xhtml', {'q', '0.5'})
*/
protected static ParseResults parseMimeType(String mimeType)
{
String[] parts = StringUtils.split(mimeType, ";");
ParseResults results = new ParseResults();
results.params = new HashMap<String, String>();
for (int i = 1; i < parts.length; ++i)
{
String p = parts[i];
String[] subParts = StringUtils.split(p, '=');
if (subParts.length == 2)
results.params.put(subParts[0].trim(), subParts[1].trim());
}
String fullType = parts[0].trim();
// Java URLConnection class sends an Accept header that includes a
// single "*" - Turn it into a legal wildcard.
if (fullType.equals("*"))
fullType = "*/*";
String[] types = StringUtils.split(fullType, "/");
results.type = types[0].trim();
results.subType = types[1].trim();
return results;
}
/**
* Carves up a media range and returns a ParseResults.
*
* For example, the media range 'application/*;q=0.5' would get parsed into:
*
* ('application', '*', {'q', '0.5'})
*
* In addition this function also guarantees that there is a value for 'q'
* in the params dictionary, filling it in with a proper default if
* necessary.
*
* @param range
*/
protected static ParseResults parseMediaRange(String range)
{
ParseResults results = parseMimeType(range);
String q = results.params.get("q");
float f = NumberUtils.toFloat(q, 1);
if (StringUtils.isBlank(q) || f < 0 || f > 1)
results.params.put("q", "1");
return results;
}
/**
* Structure for holding a fitness/quality combo
*/
protected static class FitnessAndQuality implements
Comparable<FitnessAndQuality>
{
int fitness;
float quality;
String mimeType; // optionally used
public FitnessAndQuality(int fitness, float quality)
{
this.fitness = fitness;
this.quality = quality;
}
public int compareTo(FitnessAndQuality o)
{
if (fitness == o.fitness)
{
if (quality == o.quality)
return 0;
else
return quality < o.quality ? -1 : 1;
}
else
return fitness < o.fitness ? -1 : 1;
}
}
/**
* Find the best match for a given mimeType against a list of media_ranges
* that have already been parsed by MimeParse.parseMediaRange(). Returns a
* tuple of the fitness value and the value of the 'q' quality parameter of
* the best match, or (-1, 0) if no match was found. Just as for
* quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
*
* @param mimeType
* @param parsedRanges
*/
protected static FitnessAndQuality fitnessAndQualityParsed(String mimeType,
Collection<ParseResults> parsedRanges)
{
int bestFitness = -1;
float bestFitQ = 0;
ParseResults target = parseMediaRange(mimeType);
for (ParseResults range : parsedRanges)
{
if ((target.type.equals(range.type) || range.type.equals("*") || target.type
.equals("*"))
&& (target.subType.equals(range.subType)
|| range.subType.equals("*") || target.subType
.equals("*")))
{
for (String k : target.params.keySet())
{
int paramMatches = 0;
if (!k.equals("q") && range.params.containsKey(k)
&& target.params.get(k).equals(range.params.get(k)))
{
paramMatches++;
}
int fitness = (range.type.equals(target.type)) ? 100 : 0;
fitness += (range.subType.equals(target.subType)) ? 10 : 0;
fitness += paramMatches;
if (fitness > bestFitness)
{
bestFitness = fitness;
bestFitQ = NumberUtils
.toFloat(range.params.get("q"), 0);
}
}
}
}
return new FitnessAndQuality(bestFitness, bestFitQ);
}
/**
* Find the best match for a given mime-type against a list of ranges that
* have already been parsed by parseMediaRange(). Returns the 'q' quality
* parameter of the best match, 0 if no match was found. This function
* bahaves the same as quality() except that 'parsed_ranges' must be a list
* of parsed media ranges.
*
* @param mimeType
* @param parsedRanges
* @return quality
*/
protected static float qualityParsed(String mimeType,
Collection<ParseResults> parsedRanges)
{
return fitnessAndQualityParsed(mimeType, parsedRanges).quality;
}
/**
* Returns the quality 'q' of a mime-type when compared against the
* mediaRanges in ranges. For example:
*
* @param mimeType
* @param ranges
* @return quality
*/
public static float quality(String mimeType, String ranges)
{
List<ParseResults> results = new LinkedList<ParseResults>();
for (String r : StringUtils.split(ranges, ','))
results.add(parseMediaRange(r));
return qualityParsed(mimeType, results);
}
/**
* Takes a list of supported mime-types and finds the best match for all the
* media-ranges listed in header. The value of header must be a string that
* conforms to the format of the HTTP Accept: header. The value of
* 'supported' is a list of mime-types.
*
* MimeParse.bestMatch(Arrays.asList(new String[]{"application/xbel+xml",
* "text/xml"}), "text/*;q=0.5,*; q=0.1") 'text/xml'
*
* @param supported
* @param header
* @return mime-type
*/
public static String bestMatch(Collection<String> supported, String header)
{
List<ParseResults> parseResults = new LinkedList<ParseResults>();
List<FitnessAndQuality> weightedMatches = new LinkedList<FitnessAndQuality>();
for (String r : StringUtils.split(header, ','))
parseResults.add(parseMediaRange(r));
for (String s : supported)
{
FitnessAndQuality fitnessAndQuality = fitnessAndQualityParsed(s,
parseResults);
fitnessAndQuality.mimeType = s;
weightedMatches.add(fitnessAndQuality);
}
Collections.sort(weightedMatches);
FitnessAndQuality lastOne = weightedMatches
.get(weightedMatches.size() - 1);
return NumberUtils.compare(lastOne.quality, 0) != 0 ? lastOne.mimeType
: "";
}
// hidden
private MediaTypeParser()
{
}
}