001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.http;
018
019import java.io.Serializable;
020import java.nio.charset.Charset;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028
029import org.springframework.util.Assert;
030import org.springframework.util.CollectionUtils;
031import org.springframework.util.InvalidMimeTypeException;
032import org.springframework.util.MimeType;
033import org.springframework.util.MimeTypeUtils;
034import org.springframework.util.StringUtils;
035import org.springframework.util.comparator.CompoundComparator;
036
037/**
038 * A subclass of {@link MimeType} that adds support for quality parameters
039 * as defined in the HTTP specification.
040 *
041 * @author Arjen Poutsma
042 * @author Juergen Hoeller
043 * @author Rossen Stoyanchev
044 * @author Sebastien Deleuze
045 * @since 3.0
046 * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.1">
047 *     HTTP 1.1: Semantics and Content, section 3.1.1.1</a>
048 */
049public class MediaType extends MimeType implements Serializable {
050
051        private static final long serialVersionUID = 2069937152339670231L;
052
053        /**
054         * Public constant media type that includes all media ranges (i.e. "&#42;/&#42;").
055         */
056        public static final MediaType ALL;
057
058        /**
059         * A String equivalent of {@link MediaType#ALL}.
060         */
061        public static final String ALL_VALUE = "*/*";
062
063        /**
064         *  Public constant media type for {@code application/atom+xml}.
065         */
066        public final static MediaType APPLICATION_ATOM_XML;
067
068        /**
069         * A String equivalent of {@link MediaType#APPLICATION_ATOM_XML}.
070         */
071        public final static String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
072
073        /**
074         * Public constant media type for {@code application/x-www-form-urlencoded}.
075         */
076        public final static MediaType APPLICATION_FORM_URLENCODED;
077
078        /**
079         * A String equivalent of {@link MediaType#APPLICATION_FORM_URLENCODED}.
080         */
081        public final static String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
082
083        /**
084         * Public constant media type for {@code application/json}.
085         */
086        public final static MediaType APPLICATION_JSON;
087
088        /**
089         * A String equivalent of {@link MediaType#APPLICATION_JSON}.
090         * @see #APPLICATION_JSON_UTF8_VALUE
091         */
092        public final static String APPLICATION_JSON_VALUE = "application/json";
093
094        /**
095         * Public constant media type for {@code application/json;charset=UTF-8}.
096         */
097        public final static MediaType APPLICATION_JSON_UTF8;
098
099        /**
100         * A String equivalent of {@link MediaType#APPLICATION_JSON_UTF8}.
101         */
102        public final static String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8";
103
104        /**
105         * Public constant media type for {@code application/octet-stream}.
106         */
107        public final static MediaType APPLICATION_OCTET_STREAM;
108
109        /**
110         * A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}.
111         */
112        public final static String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
113
114        /**
115         * Public constant media type for {@code application/pdf}.
116         * @since 4.3
117         */
118        public final static MediaType APPLICATION_PDF;
119
120        /**
121         * A String equivalent of {@link MediaType#APPLICATION_PDF}.
122         * @since 4.3
123         */
124        public final static String APPLICATION_PDF_VALUE = "application/pdf";
125
126        /**
127         * Public constant media type for {@code application/rss+xml}.
128         * @since 4.3.6
129         */
130        public final static MediaType APPLICATION_RSS_XML;
131
132        /**
133         * A String equivalent of {@link MediaType#APPLICATION_RSS_XML}.
134         * @since 4.3.6
135         */
136        public final static String APPLICATION_RSS_XML_VALUE = "application/rss+xml";
137
138        /**
139         * Public constant media type for {@code application/xhtml+xml}.
140         */
141        public final static MediaType APPLICATION_XHTML_XML;
142
143        /**
144         * A String equivalent of {@link MediaType#APPLICATION_XHTML_XML}.
145         */
146        public final static String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
147
148        /**
149         * Public constant media type for {@code application/xml}.
150         */
151        public final static MediaType APPLICATION_XML;
152
153        /**
154         * A String equivalent of {@link MediaType#APPLICATION_XML}.
155         */
156        public final static String APPLICATION_XML_VALUE = "application/xml";
157
158        /**
159         * Public constant media type for {@code image/gif}.
160         */
161        public final static MediaType IMAGE_GIF;
162
163        /**
164         * A String equivalent of {@link MediaType#IMAGE_GIF}.
165         */
166        public final static String IMAGE_GIF_VALUE = "image/gif";
167
168        /**
169         * Public constant media type for {@code image/jpeg}.
170         */
171        public final static MediaType IMAGE_JPEG;
172
173        /**
174         * A String equivalent of {@link MediaType#IMAGE_JPEG}.
175         */
176        public final static String IMAGE_JPEG_VALUE = "image/jpeg";
177
178        /**
179         * Public constant media type for {@code image/png}.
180         */
181        public final static MediaType IMAGE_PNG;
182
183        /**
184         * A String equivalent of {@link MediaType#IMAGE_PNG}.
185         */
186        public final static String IMAGE_PNG_VALUE = "image/png";
187
188        /**
189         * Public constant media type for {@code multipart/form-data}.
190         */
191        public final static MediaType MULTIPART_FORM_DATA;
192
193        /**
194         * A String equivalent of {@link MediaType#MULTIPART_FORM_DATA}.
195         */
196        public final static String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
197
198        /**
199         * Public constant media type for {@code text/event-stream}.
200         * @since 4.3.6
201         * @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
202         */
203        public final static MediaType TEXT_EVENT_STREAM;
204
205        /**
206         * A String equivalent of {@link MediaType#TEXT_EVENT_STREAM}.
207         * @since 4.3.6
208         */
209        public final static String TEXT_EVENT_STREAM_VALUE = "text/event-stream";
210
211        /**
212         * Public constant media type for {@code text/html}.
213         */
214        public final static MediaType TEXT_HTML;
215
216        /**
217         * A String equivalent of {@link MediaType#TEXT_HTML}.
218         */
219        public final static String TEXT_HTML_VALUE = "text/html";
220
221        /**
222         * Public constant media type for {@code text/markdown}.
223         * @since 4.3
224         */
225        public final static MediaType TEXT_MARKDOWN;
226
227        /**
228         * A String equivalent of {@link MediaType#TEXT_MARKDOWN}.
229         * @since 4.3
230         */
231        public final static String TEXT_MARKDOWN_VALUE = "text/markdown";
232
233        /**
234         * Public constant media type for {@code text/plain}.
235         */
236        public final static MediaType TEXT_PLAIN;
237
238        /**
239         * A String equivalent of {@link MediaType#TEXT_PLAIN}.
240         */
241        public final static String TEXT_PLAIN_VALUE = "text/plain";
242
243        /**
244         * Public constant media type for {@code text/xml}.
245         */
246        public final static MediaType TEXT_XML;
247
248        /**
249         * A String equivalent of {@link MediaType#TEXT_XML}.
250         */
251        public final static String TEXT_XML_VALUE = "text/xml";
252
253        private static final String PARAM_QUALITY_FACTOR = "q";
254
255
256        static {
257                ALL = valueOf(ALL_VALUE);
258                APPLICATION_ATOM_XML = valueOf(APPLICATION_ATOM_XML_VALUE);
259                APPLICATION_FORM_URLENCODED = valueOf(APPLICATION_FORM_URLENCODED_VALUE);
260                APPLICATION_JSON = valueOf(APPLICATION_JSON_VALUE);
261                APPLICATION_JSON_UTF8 = valueOf(APPLICATION_JSON_UTF8_VALUE);
262                APPLICATION_OCTET_STREAM = valueOf(APPLICATION_OCTET_STREAM_VALUE);
263                APPLICATION_PDF = valueOf(APPLICATION_PDF_VALUE);
264                APPLICATION_RSS_XML = valueOf(APPLICATION_RSS_XML_VALUE);
265                APPLICATION_XHTML_XML = valueOf(APPLICATION_XHTML_XML_VALUE);
266                APPLICATION_XML = valueOf(APPLICATION_XML_VALUE);
267                IMAGE_GIF = valueOf(IMAGE_GIF_VALUE);
268                IMAGE_JPEG = valueOf(IMAGE_JPEG_VALUE);
269                IMAGE_PNG = valueOf(IMAGE_PNG_VALUE);
270                MULTIPART_FORM_DATA = valueOf(MULTIPART_FORM_DATA_VALUE);
271                TEXT_EVENT_STREAM = valueOf(TEXT_EVENT_STREAM_VALUE);
272                TEXT_HTML = valueOf(TEXT_HTML_VALUE);
273                TEXT_MARKDOWN = valueOf(TEXT_MARKDOWN_VALUE);
274                TEXT_PLAIN = valueOf(TEXT_PLAIN_VALUE);
275                TEXT_XML = valueOf(TEXT_XML_VALUE);
276        }
277
278
279        /**
280         * Create a new {@code MediaType} for the given primary type.
281         * <p>The {@linkplain #getSubtype() subtype} is set to "&#42;", parameters empty.
282         * @param type the primary type
283         * @throws IllegalArgumentException if any of the parameters contain illegal characters
284         */
285        public MediaType(String type) {
286                super(type);
287        }
288
289        /**
290         * Create a new {@code MediaType} for the given primary type and subtype.
291         * <p>The parameters are empty.
292         * @param type the primary type
293         * @param subtype the subtype
294         * @throws IllegalArgumentException if any of the parameters contain illegal characters
295         */
296        public MediaType(String type, String subtype) {
297                super(type, subtype, Collections.<String, String>emptyMap());
298        }
299
300        /**
301         * Create a new {@code MediaType} for the given type, subtype, and character set.
302         * @param type the primary type
303         * @param subtype the subtype
304         * @param charset the character set
305         * @throws IllegalArgumentException if any of the parameters contain illegal characters
306         */
307        public MediaType(String type, String subtype, Charset charset) {
308                super(type, subtype, charset);
309        }
310
311        /**
312         * Create a new {@code MediaType} for the given type, subtype, and quality value.
313         * @param type the primary type
314         * @param subtype the subtype
315         * @param qualityValue the quality value
316         * @throws IllegalArgumentException if any of the parameters contain illegal characters
317         */
318        public MediaType(String type, String subtype, double qualityValue) {
319                this(type, subtype, Collections.singletonMap(PARAM_QUALITY_FACTOR, Double.toString(qualityValue)));
320        }
321
322        /**
323         * Copy-constructor that copies the type, subtype and parameters of the given
324         * {@code MediaType}, and allows to set the specified character set.
325         * @param other the other media type
326         * @param charset the character set
327         * @throws IllegalArgumentException if any of the parameters contain illegal characters
328         * @since 4.3
329         */
330        public MediaType(MediaType other, Charset charset) {
331                super(other, charset);
332        }
333
334        /**
335         * Copy-constructor that copies the type and subtype of the given {@code MediaType},
336         * and allows for different parameters.
337         * @param other the other media type
338         * @param parameters the parameters, may be {@code null}
339         * @throws IllegalArgumentException if any of the parameters contain illegal characters
340         */
341        public MediaType(MediaType other, Map<String, String> parameters) {
342                super(other.getType(), other.getSubtype(), parameters);
343        }
344
345        /**
346         * Create a new {@code MediaType} for the given type, subtype, and parameters.
347         * @param type the primary type
348         * @param subtype the subtype
349         * @param parameters the parameters, may be {@code null}
350         * @throws IllegalArgumentException if any of the parameters contain illegal characters
351         */
352        public MediaType(String type, String subtype, Map<String, String> parameters) {
353                super(type, subtype, parameters);
354        }
355
356
357        @Override
358        protected void checkParameters(String attribute, String value) {
359                super.checkParameters(attribute, value);
360                if (PARAM_QUALITY_FACTOR.equals(attribute)) {
361                        value = unquote(value);
362                        double d = Double.parseDouble(value);
363                        Assert.isTrue(d >= 0D && d <= 1D,
364                                        "Invalid quality value \"" + value + "\": should be between 0.0 and 1.0");
365                }
366        }
367
368        /**
369         * Return the quality factor, as indicated by a {@code q} parameter, if any.
370         * Defaults to {@code 1.0}.
371         * @return the quality factor as double value
372         */
373        public double getQualityValue() {
374                String qualityFactor = getParameter(PARAM_QUALITY_FACTOR);
375                return (qualityFactor != null ? Double.parseDouble(unquote(qualityFactor)) : 1D);
376        }
377
378        /**
379         * Indicate whether this {@code MediaType} includes the given media type.
380         * <p>For instance, {@code text/*} includes {@code text/plain} and {@code text/html},
381         * and {@code application/*+xml} includes {@code application/soap+xml}, etc.
382         * This method is <b>not</b> symmetric.
383         * <p>Simply calls {@link MimeType#includes(MimeType)} but declared with a
384         * {@code MediaType} parameter for binary backwards compatibility.
385         * @param other the reference media type with which to compare
386         * @return {@code true} if this media type includes the given media type;
387         * {@code false} otherwise
388         */
389        public boolean includes(MediaType other) {
390                return super.includes(other);
391        }
392
393        /**
394         * Indicate whether this {@code MediaType} is compatible with the given media type.
395         * <p>For instance, {@code text/*} is compatible with {@code text/plain},
396         * {@code text/html}, and vice versa. In effect, this method is similar to
397         * {@link #includes}, except that it <b>is</b> symmetric.
398         * <p>Simply calls {@link MimeType#isCompatibleWith(MimeType)} but declared with a
399         * {@code MediaType} parameter for binary backwards compatibility.
400         * @param other the reference media type with which to compare
401         * @return {@code true} if this media type is compatible with the given media type;
402         * {@code false} otherwise
403         */
404        public boolean isCompatibleWith(MediaType other) {
405                return super.isCompatibleWith(other);
406        }
407
408        /**
409         * Return a replica of this instance with the quality value of the given {@code MediaType}.
410         * @return the same instance if the given MediaType doesn't have a quality value,
411         * or a new one otherwise
412         */
413        public MediaType copyQualityValue(MediaType mediaType) {
414                if (!mediaType.getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
415                        return this;
416                }
417                Map<String, String> params = new LinkedHashMap<String, String>(getParameters());
418                params.put(PARAM_QUALITY_FACTOR, mediaType.getParameters().get(PARAM_QUALITY_FACTOR));
419                return new MediaType(this, params);
420        }
421
422        /**
423         * Return a replica of this instance with its quality value removed.
424         * @return the same instance if the media type doesn't contain a quality value,
425         * or a new one otherwise
426         */
427        public MediaType removeQualityValue() {
428                if (!getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
429                        return this;
430                }
431                Map<String, String> params = new LinkedHashMap<String, String>(getParameters());
432                params.remove(PARAM_QUALITY_FACTOR);
433                return new MediaType(this, params);
434        }
435
436
437        /**
438         * Parse the given String value into a {@code MediaType} object,
439         * with this method name following the 'valueOf' naming convention
440         * (as supported by {@link org.springframework.core.convert.ConversionService}.
441         * @param value the string to parse
442         * @throws InvalidMediaTypeException if the media type value cannot be parsed
443         * @see #parseMediaType(String)
444         */
445        public static MediaType valueOf(String value) {
446                return parseMediaType(value);
447        }
448
449        /**
450         * Parse the given String into a single {@code MediaType}.
451         * @param mediaType the string to parse
452         * @return the media type
453         * @throws InvalidMediaTypeException if the media type value cannot be parsed
454         */
455        public static MediaType parseMediaType(String mediaType) {
456                MimeType type;
457                try {
458                        type = MimeTypeUtils.parseMimeType(mediaType);
459                }
460                catch (InvalidMimeTypeException ex) {
461                        throw new InvalidMediaTypeException(ex);
462                }
463                try {
464                        return new MediaType(type.getType(), type.getSubtype(), type.getParameters());
465                }
466                catch (IllegalArgumentException ex) {
467                        throw new InvalidMediaTypeException(mediaType, ex.getMessage());
468                }
469        }
470
471        /**
472         * Parse the comma-separated string into a list of {@code MediaType} objects.
473         * <p>This method can be used to parse an Accept or Content-Type header.
474         * @param mediaTypes the string to parse
475         * @return the list of media types
476         * @throws InvalidMediaTypeException if the media type value cannot be parsed
477         */
478        public static List<MediaType> parseMediaTypes(String mediaTypes) {
479                if (!StringUtils.hasLength(mediaTypes)) {
480                        return Collections.emptyList();
481                }
482                String[] tokens = StringUtils.tokenizeToStringArray(mediaTypes, ",");
483                List<MediaType> result = new ArrayList<MediaType>(tokens.length);
484                for (String token : tokens) {
485                        result.add(parseMediaType(token));
486                }
487                return result;
488        }
489
490        /**
491         * Parse the given list of (potentially) comma-separated strings into a
492         * list of {@code MediaType} objects.
493         * <p>This method can be used to parse an Accept or Content-Type header.
494         * @param mediaTypes the string to parse
495         * @return the list of media types
496         * @throws InvalidMediaTypeException if the media type value cannot be parsed
497         * @since 4.3.2
498         */
499        public static List<MediaType> parseMediaTypes(List<String> mediaTypes) {
500                if (CollectionUtils.isEmpty(mediaTypes)) {
501                        return Collections.<MediaType>emptyList();
502                }
503                else if (mediaTypes.size() == 1) {
504                        return parseMediaTypes(mediaTypes.get(0));
505                }
506                else {
507                        List<MediaType> result = new ArrayList<MediaType>(8);
508                        for (String mediaType : mediaTypes) {
509                                result.addAll(parseMediaTypes(mediaType));
510                        }
511                        return result;
512                }
513        }
514
515        /**
516         * Return a string representation of the given list of {@code MediaType} objects.
517         * <p>This method can be used to for an {@code Accept} or {@code Content-Type} header.
518         * @param mediaTypes the media types to create a string representation for
519         * @return the string representation
520         */
521        public static String toString(Collection<MediaType> mediaTypes) {
522                return MimeTypeUtils.toString(mediaTypes);
523        }
524
525        /**
526         * Sorts the given list of {@code MediaType} objects by specificity.
527         * <p>Given two media types:
528         * <ol>
529         * <li>if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the
530         * wildcard is ordered before the other.</li>
531         * <li>if the two media types have different {@linkplain #getType() types}, then they are considered equal and
532         * remain their current order.</li>
533         * <li>if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without
534         * the wildcard is sorted before the other.</li>
535         * <li>if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal
536         * and remain their current order.</li>
537         * <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
538         * with the highest quality value is ordered before the other.</li>
539         * <li>if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the
540         * media type with the most parameters is ordered before the other.</li>
541         * </ol>
542         * <p>For example:
543         * <blockquote>audio/basic &lt; audio/* &lt; *&#047;*</blockquote>
544         * <blockquote>audio/* &lt; audio/*;q=0.7; audio/*;q=0.3</blockquote>
545         * <blockquote>audio/basic;level=1 &lt; audio/basic</blockquote>
546         * <blockquote>audio/basic == text/html</blockquote>
547         * <blockquote>audio/basic == audio/wave</blockquote>
548         * @param mediaTypes the list of media types to be sorted
549         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">HTTP 1.1: Semantics
550         * and Content, section 5.3.2</a>
551         */
552        public static void sortBySpecificity(List<MediaType> mediaTypes) {
553                Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
554                if (mediaTypes.size() > 1) {
555                        Collections.sort(mediaTypes, SPECIFICITY_COMPARATOR);
556                }
557        }
558
559        /**
560         * Sorts the given list of {@code MediaType} objects by quality value.
561         * <p>Given two media types:
562         * <ol>
563         * <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
564         * with the highest quality value is ordered before the other.</li>
565         * <li>if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the
566         * wildcard is ordered before the other.</li>
567         * <li>if the two media types have different {@linkplain #getType() types}, then they are considered equal and
568         * remain their current order.</li>
569         * <li>if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without
570         * the wildcard is sorted before the other.</li>
571         * <li>if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal
572         * and remain their current order.</li>
573         * <li>if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the
574         * media type with the most parameters is ordered before the other.</li>
575         * </ol>
576         * @param mediaTypes the list of media types to be sorted
577         * @see #getQualityValue()
578         */
579        public static void sortByQualityValue(List<MediaType> mediaTypes) {
580                Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
581                if (mediaTypes.size() > 1) {
582                        Collections.sort(mediaTypes, QUALITY_VALUE_COMPARATOR);
583                }
584        }
585
586        /**
587         * Sorts the given list of {@code MediaType} objects by specificity as the
588         * primary criteria and quality value the secondary.
589         * @see MediaType#sortBySpecificity(List)
590         * @see MediaType#sortByQualityValue(List)
591         */
592        public static void sortBySpecificityAndQuality(List<MediaType> mediaTypes) {
593                Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
594                if (mediaTypes.size() > 1) {
595                        Collections.sort(mediaTypes, new CompoundComparator<MediaType>(
596                                        MediaType.SPECIFICITY_COMPARATOR, MediaType.QUALITY_VALUE_COMPARATOR));
597                }
598        }
599
600
601        /**
602         * Comparator used by {@link #sortByQualityValue(List)}.
603         */
604        public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = new Comparator<MediaType>() {
605
606                @Override
607                public int compare(MediaType mediaType1, MediaType mediaType2) {
608                        double quality1 = mediaType1.getQualityValue();
609                        double quality2 = mediaType2.getQualityValue();
610                        int qualityComparison = Double.compare(quality2, quality1);
611                        if (qualityComparison != 0) {
612                                return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
613                        }
614                        else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/*
615                                return 1;
616                        }
617                        else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */*
618                                return -1;
619                        }
620                        else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html
621                                return 0;
622                        }
623                        else { // mediaType1.getType().equals(mediaType2.getType())
624                                if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) { // audio/* < audio/basic
625                                        return 1;
626                                }
627                                else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) { // audio/basic > audio/*
628                                        return -1;
629                                }
630                                else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave
631                                        return 0;
632                                }
633                                else {
634                                        int paramsSize1 = mediaType1.getParameters().size();
635                                        int paramsSize2 = mediaType2.getParameters().size();
636                                        // audio/basic;level=1 < audio/basic
637                                        return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1));
638                                }
639                        }
640                }
641        };
642
643
644        /**
645         * Comparator used by {@link #sortBySpecificity(List)}.
646         */
647        public static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new SpecificityComparator<MediaType>() {
648
649                @Override
650                protected int compareParameters(MediaType mediaType1, MediaType mediaType2) {
651                        double quality1 = mediaType1.getQualityValue();
652                        double quality2 = mediaType2.getQualityValue();
653                        int qualityComparison = Double.compare(quality2, quality1);
654                        if (qualityComparison != 0) {
655                                return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
656                        }
657                        return super.compareParameters(mediaType1, mediaType2);
658                }
659        };
660
661}