View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.util;
18  
19  import java.nio.charset.UnsupportedCharsetException;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Comparator;
24  import java.util.Iterator;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.springframework.util.MimeType.SpecificityComparator;
30  
31  /**
32   * Miscellaneous {@link MimeType} utility methods.
33   *
34   * @author Arjen Poutsma
35   * @author Rossen Stoyanchev
36   * @since 4.0
37   */
38  public abstract class MimeTypeUtils {
39  
40  	/**
41  	 * Public constant mime type that includes all media ranges (i.e. "*/*").
42  	 */
43  	public static final MimeType ALL;
44  
45  	/**
46  	 * A String equivalent of {@link MimeTypeUtils#ALL}.
47  	 */
48  	public static final String ALL_VALUE = "*/*";
49  
50  	/**
51  	 *  Public constant mime type for {@code application/atom+xml}.
52  	 */
53  	public final static MimeType APPLICATION_ATOM_XML;
54  
55  	/**
56  	 * A String equivalent of {@link MimeTypeUtils#APPLICATION_ATOM_XML}.
57  	 */
58  	public final static String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
59  
60  	/**
61  	 * Public constant mime type for {@code application/x-www-form-urlencoded}.
62  	 *  */
63  	public final static MimeType APPLICATION_FORM_URLENCODED;
64  
65  	/**
66  	 * A String equivalent of {@link MimeTypeUtils#APPLICATION_FORM_URLENCODED}.
67  	 */
68  	public final static String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
69  
70  	/**
71  	 * Public constant mime type for {@code application/json}.
72  	 * */
73  	public final static MimeType APPLICATION_JSON;
74  
75  	/**
76  	 * A String equivalent of {@link MimeTypeUtils#APPLICATION_JSON}.
77  	 */
78  	public final static String APPLICATION_JSON_VALUE = "application/json";
79  
80  	/**
81  	 * Public constant mime type for {@code application/octet-stream}.
82  	 *  */
83  	public final static MimeType APPLICATION_OCTET_STREAM;
84  
85  	/**
86  	 * A String equivalent of {@link MimeTypeUtils#APPLICATION_OCTET_STREAM}.
87  	 */
88  	public final static String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
89  
90  	/**
91  	 * Public constant mime type for {@code application/xhtml+xml}.
92  	 *  */
93  	public final static MimeType APPLICATION_XHTML_XML;
94  
95  	/**
96  	 * A String equivalent of {@link MimeTypeUtils#APPLICATION_XHTML_XML}.
97  	 */
98  	public final static String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
99  
100 	/**
101 	 * Public constant mime type for {@code application/xml}.
102 	 */
103 	public final static MimeType APPLICATION_XML;
104 
105 	/**
106 	 * A String equivalent of {@link MimeTypeUtils#APPLICATION_XML}.
107 	 */
108 	public final static String APPLICATION_XML_VALUE = "application/xml";
109 
110 	/**
111 	 * Public constant mime type for {@code image/gif}.
112 	 */
113 	public final static MimeType IMAGE_GIF;
114 
115 	/**
116 	 * A String equivalent of {@link MimeTypeUtils#IMAGE_GIF}.
117 	 */
118 	public final static String IMAGE_GIF_VALUE = "image/gif";
119 
120 	/**
121 	 * Public constant mime type for {@code image/jpeg}.
122 	 */
123 	public final static MimeType IMAGE_JPEG;
124 
125 	/**
126 	 * A String equivalent of {@link MimeTypeUtils#IMAGE_JPEG}.
127 	 */
128 	public final static String IMAGE_JPEG_VALUE = "image/jpeg";
129 
130 	/**
131 	 * Public constant mime type for {@code image/png}.
132 	 */
133 	public final static MimeType IMAGE_PNG;
134 
135 	/**
136 	 * A String equivalent of {@link MimeTypeUtils#IMAGE_PNG}.
137 	 */
138 	public final static String IMAGE_PNG_VALUE = "image/png";
139 
140 	/**
141 	 * Public constant mime type for {@code multipart/form-data}.
142 	 *  */
143 	public final static MimeType MULTIPART_FORM_DATA;
144 
145 	/**
146 	 * A String equivalent of {@link MimeTypeUtils#MULTIPART_FORM_DATA}.
147 	 */
148 	public final static String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
149 
150 	/**
151 	 * Public constant mime type for {@code text/html}.
152 	 *  */
153 	public final static MimeType TEXT_HTML;
154 
155 	/**
156 	 * A String equivalent of {@link MimeTypeUtils#TEXT_HTML}.
157 	 */
158 	public final static String TEXT_HTML_VALUE = "text/html";
159 
160 	/**
161 	 * Public constant mime type for {@code text/plain}.
162 	 *  */
163 	public final static MimeType TEXT_PLAIN;
164 
165 	/**
166 	 * A String equivalent of {@link MimeTypeUtils#TEXT_PLAIN}.
167 	 */
168 	public final static String TEXT_PLAIN_VALUE = "text/plain";
169 
170 	/**
171 	 * Public constant mime type for {@code text/xml}.
172 	 *  */
173 	public final static MimeType TEXT_XML;
174 
175 	/**
176 	 * A String equivalent of {@link MimeTypeUtils#TEXT_XML}.
177 	 */
178 	public final static String TEXT_XML_VALUE = "text/xml";
179 
180 
181 	static {
182 		ALL = MimeType.valueOf(ALL_VALUE);
183 		APPLICATION_ATOM_XML = MimeType.valueOf(APPLICATION_ATOM_XML_VALUE);
184 		APPLICATION_FORM_URLENCODED = MimeType.valueOf(APPLICATION_FORM_URLENCODED_VALUE);
185 		APPLICATION_JSON = MimeType.valueOf(APPLICATION_JSON_VALUE);
186 		APPLICATION_OCTET_STREAM = MimeType.valueOf(APPLICATION_OCTET_STREAM_VALUE);
187 		APPLICATION_XHTML_XML = MimeType.valueOf(APPLICATION_XHTML_XML_VALUE);
188 		APPLICATION_XML = MimeType.valueOf(APPLICATION_XML_VALUE);
189 		IMAGE_GIF = MimeType.valueOf(IMAGE_GIF_VALUE);
190 		IMAGE_JPEG = MimeType.valueOf(IMAGE_JPEG_VALUE);
191 		IMAGE_PNG = MimeType.valueOf(IMAGE_PNG_VALUE);
192 		MULTIPART_FORM_DATA = MimeType.valueOf(MULTIPART_FORM_DATA_VALUE);
193 		TEXT_HTML = MimeType.valueOf(TEXT_HTML_VALUE);
194 		TEXT_PLAIN = MimeType.valueOf(TEXT_PLAIN_VALUE);
195 		TEXT_XML = MimeType.valueOf(TEXT_XML_VALUE);
196 	}
197 
198 
199 	/**
200 	 * Parse the given String into a single {@code MimeType}.
201 	 * @param mimeType the string to parse
202 	 * @return the mime type
203 	 * @throws InvalidMimeTypeException if the string cannot be parsed
204 	 */
205 	public static MimeType parseMimeType(String mimeType) {
206 		if (!StringUtils.hasLength(mimeType)) {
207 			throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
208 		}
209 		String[] parts = StringUtils.tokenizeToStringArray(mimeType, ";");
210 
211 		String fullType = parts[0].trim();
212 		// java.net.HttpURLConnection returns a *; q=.2 Accept header
213 		if (MimeType.WILDCARD_TYPE.equals(fullType)) {
214 			fullType = "*/*";
215 		}
216 		int subIndex = fullType.indexOf('/');
217 		if (subIndex == -1) {
218 			throw new InvalidMimeTypeException(mimeType, "does not contain '/'");
219 		}
220 		if (subIndex == fullType.length() - 1) {
221 			throw new InvalidMimeTypeException(mimeType, "does not contain subtype after '/'");
222 		}
223 		String type = fullType.substring(0, subIndex);
224 		String subtype = fullType.substring(subIndex + 1, fullType.length());
225 		if (MimeType.WILDCARD_TYPE.equals(type) && !MimeType.WILDCARD_TYPE.equals(subtype)) {
226 			throw new InvalidMimeTypeException(mimeType, "wildcard type is legal only in '*/*' (all mime types)");
227 		}
228 
229 		Map<String, String> parameters = null;
230 		if (parts.length > 1) {
231 			parameters = new LinkedHashMap<String, String>(parts.length - 1);
232 			for (int i = 1; i < parts.length; i++) {
233 				String parameter = parts[i];
234 				int eqIndex = parameter.indexOf('=');
235 				if (eqIndex != -1) {
236 					String attribute = parameter.substring(0, eqIndex);
237 					String value = parameter.substring(eqIndex + 1, parameter.length());
238 					parameters.put(attribute, value);
239 				}
240 			}
241 		}
242 
243 		try {
244 			return new MimeType(type, subtype, parameters);
245 		}
246 		catch (UnsupportedCharsetException ex) {
247 			throw new InvalidMimeTypeException(mimeType, "unsupported charset '" + ex.getCharsetName() + "'");
248 		}
249 		catch (IllegalArgumentException ex) {
250 			throw new InvalidMimeTypeException(mimeType, ex.getMessage());
251 		}
252 	}
253 
254 	/**
255 	 * Parse the given, comma-separated string into a list of {@code MimeType} objects.
256 	 * @param mimeTypes the string to parse
257 	 * @return the list of mime types
258 	 * @throws IllegalArgumentException if the string cannot be parsed
259 	 */
260 	public static List<MimeType> parseMimeTypes(String mimeTypes) {
261 		if (!StringUtils.hasLength(mimeTypes)) {
262 			return Collections.emptyList();
263 		}
264 		String[] tokens = mimeTypes.split(",\\s*");
265 		List<MimeType> result = new ArrayList<MimeType>(tokens.length);
266 		for (String token : tokens) {
267 			result.add(parseMimeType(token));
268 		}
269 		return result;
270 	}
271 
272 	/**
273 	 * Return a string representation of the given list of {@code MimeType} objects.
274 	 * @param mimeTypes the string to parse
275 	 * @return the list of mime types
276 	 * @throws IllegalArgumentException if the String cannot be parsed
277 	 */
278 	public static String toString(Collection<? extends MimeType> mimeTypes) {
279 		StringBuilder builder = new StringBuilder();
280 		for (Iterator<? extends MimeType> iterator = mimeTypes.iterator(); iterator.hasNext();) {
281 			MimeType mimeType = iterator.next();
282 			mimeType.appendTo(builder);
283 			if (iterator.hasNext()) {
284 				builder.append(", ");
285 			}
286 		}
287 		return builder.toString();
288 	}
289 
290 
291 	/**
292 	 * Sorts the given list of {@code MimeType} objects by specificity.
293 	 * <p>Given two mime types:
294 	 * <ol>
295 	 * <li>if either mime type has a {@linkplain MimeType#isWildcardType() wildcard type},
296 	 * then the mime type without the wildcard is ordered before the other.</li>
297 	 * <li>if the two mime types have different {@linkplain MimeType#getType() types},
298 	 * then they are considered equal and remain their current order.</li>
299 	 * <li>if either mime type has a {@linkplain MimeType#isWildcardSubtype() wildcard subtype}
300 	 * , then the mime type without the wildcard is sorted before the other.</li>
301 	 * <li>if the two mime types have different {@linkplain MimeType#getSubtype() subtypes},
302 	 * then they are considered equal and remain their current order.</li>
303 	 * <li>if the two mime types have a different amount of
304 	 * {@linkplain MimeType#getParameter(String) parameters}, then the mime type with the most
305 	 * parameters is ordered before the other.</li>
306 	 * </ol>
307 	 * <p>For example: <blockquote>audio/basic &lt; audio/* &lt; *&#047;*</blockquote>
308 	 * <blockquote>audio/basic;level=1 &lt; audio/basic</blockquote>
309 	 * <blockquote>audio/basic == text/html</blockquote> <blockquote>audio/basic ==
310 	 * audio/wave</blockquote>
311 	 * @param mimeTypes the list of mime types to be sorted
312 	 * @see <a href="http://tools.ietf.org/html/rfc7231#section-5.3.2">HTTP 1.1: Semantics
313 	 * and Content, section 5.3.2</a>
314 	 */
315 	public static void sortBySpecificity(List<MimeType> mimeTypes) {
316 		Assert.notNull(mimeTypes, "'mimeTypes' must not be null");
317 		if (mimeTypes.size() > 1) {
318 			Collections.sort(mimeTypes, SPECIFICITY_COMPARATOR);
319 		}
320 	}
321 
322 
323 	/**
324 	 * Comparator used by {@link #sortBySpecificity(List)}.
325 	 */
326 	public static final Comparator<MimeType> SPECIFICITY_COMPARATOR = new SpecificityComparator<MimeType>();
327 
328 }