Skip to content

Commit 0fa4d74

Browse files
author
Miel Vander Sande
committed
Added MIMEParse class
1 parent 586bfd9 commit 0fa4d74

File tree

1 file changed

+262
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)