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.nio.charset.StandardCharsets;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.LinkedHashMap;
027import java.util.List;
028import java.util.Map;
029
030import org.springframework.lang.Nullable;
031import org.springframework.util.Assert;
032import org.springframework.util.CollectionUtils;
033import org.springframework.util.InvalidMimeTypeException;
034import org.springframework.util.MimeType;
035import org.springframework.util.MimeTypeUtils;
036import org.springframework.util.StringUtils;
037
038/**
039 * A subclass of {@link MimeType} that adds support for quality parameters
040 * as defined in the HTTP specification.
041 *
042 * @author Arjen Poutsma
043 * @author Juergen Hoeller
044 * @author Rossen Stoyanchev
045 * @author Sebastien Deleuze
046 * @author Kazuki Shimizu
047 * @author Sam Brannen
048 * @since 3.0
049 * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.1">
050 *     HTTP 1.1: Semantics and Content, section 3.1.1.1</a>
051 */
052public class MediaType extends MimeType implements Serializable {
053
054        private static final long serialVersionUID = 2069937152339670231L;
055
056        /**
057         * Public constant media type that includes all media ranges (i.e. "&#42;/&#42;").
058         */
059        public static final MediaType ALL;
060
061        /**
062         * A String equivalent of {@link MediaType#ALL}.
063         */
064        public static final String ALL_VALUE = "*/*";
065
066        /**
067         *  Public constant media type for {@code application/atom+xml}.
068         */
069        public static final MediaType APPLICATION_ATOM_XML;
070
071        /**
072         * A String equivalent of {@link MediaType#APPLICATION_ATOM_XML}.
073         */
074        public static final String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
075
076        /**
077         * Public constant media type for {@code application/cbor}.
078         * @since 5.2
079         */
080        public static final MediaType APPLICATION_CBOR;
081
082        /**
083         * A String equivalent of {@link MediaType#APPLICATION_CBOR}.
084         * @since 5.2
085         */
086        public static final String APPLICATION_CBOR_VALUE = "application/cbor";
087
088        /**
089         * Public constant media type for {@code application/x-www-form-urlencoded}.
090         */
091        public static final MediaType APPLICATION_FORM_URLENCODED;
092
093        /**
094         * A String equivalent of {@link MediaType#APPLICATION_FORM_URLENCODED}.
095         */
096        public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
097
098        /**
099         * Public constant media type for {@code application/json}.
100         */
101        public static final MediaType APPLICATION_JSON;
102
103        /**
104         * A String equivalent of {@link MediaType#APPLICATION_JSON}.
105         * @see #APPLICATION_JSON_UTF8_VALUE
106         */
107        public static final String APPLICATION_JSON_VALUE = "application/json";
108
109        /**
110         * Public constant media type for {@code application/json;charset=UTF-8}.
111         * @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON}
112         * since major browsers like Chrome
113         * <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
114         * now comply with the specification</a> and interpret correctly UTF-8 special
115         * characters without requiring a {@code charset=UTF-8} parameter.
116         */
117        @Deprecated
118        public static final MediaType APPLICATION_JSON_UTF8;
119
120        /**
121         * A String equivalent of {@link MediaType#APPLICATION_JSON_UTF8}.
122         * @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON_VALUE}
123         * since major browsers like Chrome
124         * <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
125         * now comply with the specification</a> and interpret correctly UTF-8 special
126         * characters without requiring a {@code charset=UTF-8} parameter.
127         */
128        @Deprecated
129        public static final String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8";
130
131        /**
132         * Public constant media type for {@code application/octet-stream}.
133         */
134        public static final MediaType APPLICATION_OCTET_STREAM;
135
136        /**
137         * A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}.
138         */
139        public static final String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
140
141        /**
142         * Public constant media type for {@code application/pdf}.
143         * @since 4.3
144         */
145        public static final MediaType APPLICATION_PDF;
146
147        /**
148         * A String equivalent of {@link MediaType#APPLICATION_PDF}.
149         * @since 4.3
150         */
151        public static final String APPLICATION_PDF_VALUE = "application/pdf";
152
153        /**
154         * Public constant media type for {@code application/problem+json}.
155         * @since 5.0
156         * @see <a href="https://tools.ietf.org/html/rfc7807#section-6.1">
157         *     Problem Details for HTTP APIs, 6.1. application/problem+json</a>
158         */
159        public static final MediaType APPLICATION_PROBLEM_JSON;
160
161        /**
162         * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON}.
163         * @since 5.0
164         */
165        public static final String APPLICATION_PROBLEM_JSON_VALUE = "application/problem+json";
166
167        /**
168         * Public constant media type for {@code application/problem+json}.
169         * @since 5.0
170         * @see <a href="https://tools.ietf.org/html/rfc7807#section-6.1">
171         *     Problem Details for HTTP APIs, 6.1. application/problem+json</a>
172         * @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON}
173         * since major browsers like Chrome
174         * <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
175         * now comply with the specification</a> and interpret correctly UTF-8 special
176         * characters without requiring a {@code charset=UTF-8} parameter.
177         */
178        @Deprecated
179        public static final MediaType APPLICATION_PROBLEM_JSON_UTF8;
180
181        /**
182         * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON_UTF8}.
183         * @since 5.0
184         * @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON_VALUE}
185         * since major browsers like Chrome
186         * <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
187         * now comply with the specification</a> and interpret correctly UTF-8 special
188         * characters without requiring a {@code charset=UTF-8} parameter.
189         */
190        @Deprecated
191        public static final String APPLICATION_PROBLEM_JSON_UTF8_VALUE = "application/problem+json;charset=UTF-8";
192
193        /**
194         * Public constant media type for {@code application/problem+xml}.
195         * @since 5.0
196         * @see <a href="https://tools.ietf.org/html/rfc7807#section-6.2">
197         *     Problem Details for HTTP APIs, 6.2. application/problem+xml</a>
198         */
199        public static final MediaType APPLICATION_PROBLEM_XML;
200
201        /**
202         * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_XML}.
203         * @since 5.0
204         */
205        public static final String APPLICATION_PROBLEM_XML_VALUE = "application/problem+xml";
206
207        /**
208         * Public constant media type for {@code application/rss+xml}.
209         * @since 4.3.6
210         */
211        public static final MediaType APPLICATION_RSS_XML;
212
213        /**
214         * A String equivalent of {@link MediaType#APPLICATION_RSS_XML}.
215         * @since 4.3.6
216         */
217        public static final String APPLICATION_RSS_XML_VALUE = "application/rss+xml";
218
219        /**
220         * Public constant media type for {@code application/stream+json}.
221         * @since 5.0
222         */
223        public static final MediaType APPLICATION_STREAM_JSON;
224
225        /**
226         * A String equivalent of {@link MediaType#APPLICATION_STREAM_JSON}.
227         * @since 5.0
228         */
229        public static final String APPLICATION_STREAM_JSON_VALUE = "application/stream+json";
230
231        /**
232         * Public constant media type for {@code application/xhtml+xml}.
233         */
234        public static final MediaType APPLICATION_XHTML_XML;
235
236        /**
237         * A String equivalent of {@link MediaType#APPLICATION_XHTML_XML}.
238         */
239        public static final String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
240
241        /**
242         * Public constant media type for {@code application/xml}.
243         */
244        public static final MediaType APPLICATION_XML;
245
246        /**
247         * A String equivalent of {@link MediaType#APPLICATION_XML}.
248         */
249        public static final String APPLICATION_XML_VALUE = "application/xml";
250
251        /**
252         * Public constant media type for {@code image/gif}.
253         */
254        public static final MediaType IMAGE_GIF;
255
256        /**
257         * A String equivalent of {@link MediaType#IMAGE_GIF}.
258         */
259        public static final String IMAGE_GIF_VALUE = "image/gif";
260
261        /**
262         * Public constant media type for {@code image/jpeg}.
263         */
264        public static final MediaType IMAGE_JPEG;
265
266        /**
267         * A String equivalent of {@link MediaType#IMAGE_JPEG}.
268         */
269        public static final String IMAGE_JPEG_VALUE = "image/jpeg";
270
271        /**
272         * Public constant media type for {@code image/png}.
273         */
274        public static final MediaType IMAGE_PNG;
275
276        /**
277         * A String equivalent of {@link MediaType#IMAGE_PNG}.
278         */
279        public static final String IMAGE_PNG_VALUE = "image/png";
280
281        /**
282         * Public constant media type for {@code multipart/form-data}.
283         */
284        public static final MediaType MULTIPART_FORM_DATA;
285
286        /**
287         * A String equivalent of {@link MediaType#MULTIPART_FORM_DATA}.
288         */
289        public static final String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
290
291        /**
292         * Public constant media type for {@code multipart/mixed}.
293         * @since 5.2
294         */
295        public static final MediaType MULTIPART_MIXED;
296
297        /**
298         * A String equivalent of {@link MediaType#MULTIPART_MIXED}.
299         * @since 5.2
300         */
301        public static final String MULTIPART_MIXED_VALUE = "multipart/mixed";
302
303        /**
304         * Public constant media type for {@code multipart/related}.
305         * @since 5.2.5
306         */
307        public static final MediaType MULTIPART_RELATED;
308
309        /**
310         * A String equivalent of {@link MediaType#MULTIPART_RELATED}.
311         * @since 5.2.5
312         */
313        public static final String MULTIPART_RELATED_VALUE = "multipart/related";
314
315        /**
316         * Public constant media type for {@code text/event-stream}.
317         * @since 4.3.6
318         * @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
319         */
320        public static final MediaType TEXT_EVENT_STREAM;
321
322        /**
323         * A String equivalent of {@link MediaType#TEXT_EVENT_STREAM}.
324         * @since 4.3.6
325         */
326        public static final String TEXT_EVENT_STREAM_VALUE = "text/event-stream";
327
328        /**
329         * Public constant media type for {@code text/html}.
330         */
331        public static final MediaType TEXT_HTML;
332
333        /**
334         * A String equivalent of {@link MediaType#TEXT_HTML}.
335         */
336        public static final String TEXT_HTML_VALUE = "text/html";
337
338        /**
339         * Public constant media type for {@code text/markdown}.
340         * @since 4.3
341         */
342        public static final MediaType TEXT_MARKDOWN;
343
344        /**
345         * A String equivalent of {@link MediaType#TEXT_MARKDOWN}.
346         * @since 4.3
347         */
348        public static final String TEXT_MARKDOWN_VALUE = "text/markdown";
349
350        /**
351         * Public constant media type for {@code text/plain}.
352         */
353        public static final MediaType TEXT_PLAIN;
354
355        /**
356         * A String equivalent of {@link MediaType#TEXT_PLAIN}.
357         */
358        public static final String TEXT_PLAIN_VALUE = "text/plain";
359
360        /**
361         * Public constant media type for {@code text/xml}.
362         */
363        public static final MediaType TEXT_XML;
364
365        /**
366         * A String equivalent of {@link MediaType#TEXT_XML}.
367         */
368        public static final String TEXT_XML_VALUE = "text/xml";
369
370        private static final String PARAM_QUALITY_FACTOR = "q";
371
372
373        static {
374                // Not using "valueOf' to avoid static init cost
375                ALL = new MediaType("*", "*");
376                APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
377                APPLICATION_CBOR = new MediaType("application", "cbor");
378                APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
379                APPLICATION_JSON = new MediaType("application", "json");
380                APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
381                APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream");
382                APPLICATION_PDF = new MediaType("application", "pdf");
383                APPLICATION_PROBLEM_JSON = new MediaType("application", "problem+json");
384                APPLICATION_PROBLEM_JSON_UTF8 = new MediaType("application", "problem+json", StandardCharsets.UTF_8);
385                APPLICATION_PROBLEM_XML = new MediaType("application", "problem+xml");
386                APPLICATION_RSS_XML = new MediaType("application", "rss+xml");
387                APPLICATION_STREAM_JSON = new MediaType("application", "stream+json");
388                APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml");
389                APPLICATION_XML = new MediaType("application", "xml");
390                IMAGE_GIF = new MediaType("image", "gif");
391                IMAGE_JPEG = new MediaType("image", "jpeg");
392                IMAGE_PNG = new MediaType("image", "png");
393                MULTIPART_FORM_DATA = new MediaType("multipart", "form-data");
394                MULTIPART_MIXED = new MediaType("multipart", "mixed");
395                MULTIPART_RELATED = new MediaType("multipart", "related");
396                TEXT_EVENT_STREAM = new MediaType("text", "event-stream");
397                TEXT_HTML = new MediaType("text", "html");
398                TEXT_MARKDOWN = new MediaType("text", "markdown");
399                TEXT_PLAIN = new MediaType("text", "plain");
400                TEXT_XML = new MediaType("text", "xml");
401        }
402
403
404        /**
405         * Create a new {@code MediaType} for the given primary type.
406         * <p>The {@linkplain #getSubtype() subtype} is set to "&#42;", parameters empty.
407         * @param type the primary type
408         * @throws IllegalArgumentException if any of the parameters contain illegal characters
409         */
410        public MediaType(String type) {
411                super(type);
412        }
413
414        /**
415         * Create a new {@code MediaType} for the given primary type and subtype.
416         * <p>The parameters are empty.
417         * @param type the primary type
418         * @param subtype the subtype
419         * @throws IllegalArgumentException if any of the parameters contain illegal characters
420         */
421        public MediaType(String type, String subtype) {
422                super(type, subtype, Collections.emptyMap());
423        }
424
425        /**
426         * Create a new {@code MediaType} for the given type, subtype, and character set.
427         * @param type the primary type
428         * @param subtype the subtype
429         * @param charset the character set
430         * @throws IllegalArgumentException if any of the parameters contain illegal characters
431         */
432        public MediaType(String type, String subtype, Charset charset) {
433                super(type, subtype, charset);
434        }
435
436        /**
437         * Create a new {@code MediaType} for the given type, subtype, and quality value.
438         * @param type the primary type
439         * @param subtype the subtype
440         * @param qualityValue the quality value
441         * @throws IllegalArgumentException if any of the parameters contain illegal characters
442         */
443        public MediaType(String type, String subtype, double qualityValue) {
444                this(type, subtype, Collections.singletonMap(PARAM_QUALITY_FACTOR, Double.toString(qualityValue)));
445        }
446
447        /**
448         * Copy-constructor that copies the type, subtype and parameters of the given
449         * {@code MediaType}, and allows to set the specified character set.
450         * @param other the other media type
451         * @param charset the character set
452         * @throws IllegalArgumentException if any of the parameters contain illegal characters
453         * @since 4.3
454         */
455        public MediaType(MediaType other, Charset charset) {
456                super(other, charset);
457        }
458
459        /**
460         * Copy-constructor that copies the type and subtype of the given {@code MediaType},
461         * and allows for different parameters.
462         * @param other the other media type
463         * @param parameters the parameters, may be {@code null}
464         * @throws IllegalArgumentException if any of the parameters contain illegal characters
465         */
466        public MediaType(MediaType other, @Nullable Map<String, String> parameters) {
467                super(other.getType(), other.getSubtype(), parameters);
468        }
469
470        /**
471         * Create a new {@code MediaType} for the given type, subtype, and parameters.
472         * @param type the primary type
473         * @param subtype the subtype
474         * @param parameters the parameters, may be {@code null}
475         * @throws IllegalArgumentException if any of the parameters contain illegal characters
476         */
477        public MediaType(String type, String subtype, @Nullable Map<String, String> parameters) {
478                super(type, subtype, parameters);
479        }
480
481
482        @Override
483        protected void checkParameters(String parameter, String value) {
484                super.checkParameters(parameter, value);
485                if (PARAM_QUALITY_FACTOR.equals(parameter)) {
486                        value = unquote(value);
487                        double d = Double.parseDouble(value);
488                        Assert.isTrue(d >= 0D && d <= 1D,
489                                        "Invalid quality value \"" + value + "\": should be between 0.0 and 1.0");
490                }
491        }
492
493        /**
494         * Return the quality factor, as indicated by a {@code q} parameter, if any.
495         * Defaults to {@code 1.0}.
496         * @return the quality factor as double value
497         */
498        public double getQualityValue() {
499                String qualityFactor = getParameter(PARAM_QUALITY_FACTOR);
500                return (qualityFactor != null ? Double.parseDouble(unquote(qualityFactor)) : 1D);
501        }
502
503        /**
504         * Indicate whether this {@code MediaType} includes the given media type.
505         * <p>For instance, {@code text/*} includes {@code text/plain} and {@code text/html},
506         * and {@code application/*+xml} includes {@code application/soap+xml}, etc.
507         * This method is <b>not</b> symmetric.
508         * <p>Simply calls {@link MimeType#includes(MimeType)} but declared with a
509         * {@code MediaType} parameter for binary backwards compatibility.
510         * @param other the reference media type with which to compare
511         * @return {@code true} if this media type includes the given media type;
512         * {@code false} otherwise
513         */
514        public boolean includes(@Nullable MediaType other) {
515                return super.includes(other);
516        }
517
518        /**
519         * Indicate whether this {@code MediaType} is compatible with the given media type.
520         * <p>For instance, {@code text/*} is compatible with {@code text/plain},
521         * {@code text/html}, and vice versa. In effect, this method is similar to
522         * {@link #includes}, except that it <b>is</b> symmetric.
523         * <p>Simply calls {@link MimeType#isCompatibleWith(MimeType)} but declared with a
524         * {@code MediaType} parameter for binary backwards compatibility.
525         * @param other the reference media type with which to compare
526         * @return {@code true} if this media type is compatible with the given media type;
527         * {@code false} otherwise
528         */
529        public boolean isCompatibleWith(@Nullable MediaType other) {
530                return super.isCompatibleWith(other);
531        }
532
533        /**
534         * Return a replica of this instance with the quality value of the given {@code MediaType}.
535         * @return the same instance if the given MediaType doesn't have a quality value,
536         * or a new one otherwise
537         */
538        public MediaType copyQualityValue(MediaType mediaType) {
539                if (!mediaType.getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
540                        return this;
541                }
542                Map<String, String> params = new LinkedHashMap<>(getParameters());
543                params.put(PARAM_QUALITY_FACTOR, mediaType.getParameters().get(PARAM_QUALITY_FACTOR));
544                return new MediaType(this, params);
545        }
546
547        /**
548         * Return a replica of this instance with its quality value removed.
549         * @return the same instance if the media type doesn't contain a quality value,
550         * or a new one otherwise
551         */
552        public MediaType removeQualityValue() {
553                if (!getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
554                        return this;
555                }
556                Map<String, String> params = new LinkedHashMap<>(getParameters());
557                params.remove(PARAM_QUALITY_FACTOR);
558                return new MediaType(this, params);
559        }
560
561
562        /**
563         * Parse the given String value into a {@code MediaType} object,
564         * with this method name following the 'valueOf' naming convention
565         * (as supported by {@link org.springframework.core.convert.ConversionService}.
566         * @param value the string to parse
567         * @throws InvalidMediaTypeException if the media type value cannot be parsed
568         * @see #parseMediaType(String)
569         */
570        public static MediaType valueOf(String value) {
571                return parseMediaType(value);
572        }
573
574        /**
575         * Parse the given String into a single {@code MediaType}.
576         * @param mediaType the string to parse
577         * @return the media type
578         * @throws InvalidMediaTypeException if the media type value cannot be parsed
579         */
580        public static MediaType parseMediaType(String mediaType) {
581                MimeType type;
582                try {
583                        type = MimeTypeUtils.parseMimeType(mediaType);
584                }
585                catch (InvalidMimeTypeException ex) {
586                        throw new InvalidMediaTypeException(ex);
587                }
588                try {
589                        return new MediaType(type.getType(), type.getSubtype(), type.getParameters());
590                }
591                catch (IllegalArgumentException ex) {
592                        throw new InvalidMediaTypeException(mediaType, ex.getMessage());
593                }
594        }
595
596        /**
597         * Parse the comma-separated string into a list of {@code MediaType} objects.
598         * <p>This method can be used to parse an Accept or Content-Type header.
599         * @param mediaTypes the string to parse
600         * @return the list of media types
601         * @throws InvalidMediaTypeException if the media type value cannot be parsed
602         */
603        public static List<MediaType> parseMediaTypes(@Nullable String mediaTypes) {
604                if (!StringUtils.hasLength(mediaTypes)) {
605                        return Collections.emptyList();
606                }
607                // Avoid using java.util.stream.Stream in hot paths
608                List<String> tokenizedTypes = MimeTypeUtils.tokenize(mediaTypes);
609                List<MediaType> result = new ArrayList<>(tokenizedTypes.size());
610                for (String type : tokenizedTypes) {
611                        if (StringUtils.hasText(type)) {
612                                result.add(parseMediaType(type));
613                        }
614                }
615                return result;
616        }
617
618        /**
619         * Parse the given list of (potentially) comma-separated strings into a
620         * list of {@code MediaType} objects.
621         * <p>This method can be used to parse an Accept or Content-Type header.
622         * @param mediaTypes the string to parse
623         * @return the list of media types
624         * @throws InvalidMediaTypeException if the media type value cannot be parsed
625         * @since 4.3.2
626         */
627        public static List<MediaType> parseMediaTypes(@Nullable List<String> mediaTypes) {
628                if (CollectionUtils.isEmpty(mediaTypes)) {
629                        return Collections.emptyList();
630                }
631                else if (mediaTypes.size() == 1) {
632                        return parseMediaTypes(mediaTypes.get(0));
633                }
634                else {
635                        List<MediaType> result = new ArrayList<>(8);
636                        for (String mediaType : mediaTypes) {
637                                result.addAll(parseMediaTypes(mediaType));
638                        }
639                        return result;
640                }
641        }
642
643        /**
644         * Re-create the given mime types as media types.
645         * @since 5.0
646         */
647        public static List<MediaType> asMediaTypes(List<MimeType> mimeTypes) {
648                List<MediaType> mediaTypes = new ArrayList<>(mimeTypes.size());
649                for (MimeType mimeType : mimeTypes) {
650                        mediaTypes.add(MediaType.asMediaType(mimeType));
651                }
652                return mediaTypes;
653        }
654
655        /**
656         * Re-create the given mime type as a media type.
657         * @since 5.0
658         */
659        public static MediaType asMediaType(MimeType mimeType) {
660                if (mimeType instanceof MediaType) {
661                        return (MediaType) mimeType;
662                }
663                return new MediaType(mimeType.getType(), mimeType.getSubtype(), mimeType.getParameters());
664        }
665
666        /**
667         * Return a string representation of the given list of {@code MediaType} objects.
668         * <p>This method can be used to for an {@code Accept} or {@code Content-Type} header.
669         * @param mediaTypes the media types to create a string representation for
670         * @return the string representation
671         */
672        public static String toString(Collection<MediaType> mediaTypes) {
673                return MimeTypeUtils.toString(mediaTypes);
674        }
675
676        /**
677         * Sorts the given list of {@code MediaType} objects by specificity.
678         * <p>Given two media types:
679         * <ol>
680         * <li>if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the
681         * wildcard is ordered before the other.</li>
682         * <li>if the two media types have different {@linkplain #getType() types}, then they are considered equal and
683         * remain their current order.</li>
684         * <li>if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without
685         * the wildcard is sorted before the other.</li>
686         * <li>if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal
687         * and remain their current order.</li>
688         * <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
689         * with the highest quality value is ordered before the other.</li>
690         * <li>if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the
691         * media type with the most parameters is ordered before the other.</li>
692         * </ol>
693         * <p>For example:
694         * <blockquote>audio/basic &lt; audio/* &lt; *&#047;*</blockquote>
695         * <blockquote>audio/* &lt; audio/*;q=0.7; audio/*;q=0.3</blockquote>
696         * <blockquote>audio/basic;level=1 &lt; audio/basic</blockquote>
697         * <blockquote>audio/basic == text/html</blockquote>
698         * <blockquote>audio/basic == audio/wave</blockquote>
699         * @param mediaTypes the list of media types to be sorted
700         * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">HTTP 1.1: Semantics
701         * and Content, section 5.3.2</a>
702         */
703        public static void sortBySpecificity(List<MediaType> mediaTypes) {
704                Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
705                if (mediaTypes.size() > 1) {
706                        mediaTypes.sort(SPECIFICITY_COMPARATOR);
707                }
708        }
709
710        /**
711         * Sorts the given list of {@code MediaType} objects by quality value.
712         * <p>Given two media types:
713         * <ol>
714         * <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
715         * with the highest quality value is ordered before the other.</li>
716         * <li>if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the
717         * wildcard is ordered before the other.</li>
718         * <li>if the two media types have different {@linkplain #getType() types}, then they are considered equal and
719         * remain their current order.</li>
720         * <li>if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without
721         * the wildcard is sorted before the other.</li>
722         * <li>if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal
723         * and remain their current order.</li>
724         * <li>if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the
725         * media type with the most parameters is ordered before the other.</li>
726         * </ol>
727         * @param mediaTypes the list of media types to be sorted
728         * @see #getQualityValue()
729         */
730        public static void sortByQualityValue(List<MediaType> mediaTypes) {
731                Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
732                if (mediaTypes.size() > 1) {
733                        mediaTypes.sort(QUALITY_VALUE_COMPARATOR);
734                }
735        }
736
737        /**
738         * Sorts the given list of {@code MediaType} objects by specificity as the
739         * primary criteria and quality value the secondary.
740         * @see MediaType#sortBySpecificity(List)
741         * @see MediaType#sortByQualityValue(List)
742         */
743        public static void sortBySpecificityAndQuality(List<MediaType> mediaTypes) {
744                Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
745                if (mediaTypes.size() > 1) {
746                        mediaTypes.sort(MediaType.SPECIFICITY_COMPARATOR.thenComparing(MediaType.QUALITY_VALUE_COMPARATOR));
747                }
748        }
749
750
751        /**
752         * Comparator used by {@link #sortByQualityValue(List)}.
753         */
754        public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> {
755                double quality1 = mediaType1.getQualityValue();
756                double quality2 = mediaType2.getQualityValue();
757                int qualityComparison = Double.compare(quality2, quality1);
758                if (qualityComparison != 0) {
759                        return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
760                }
761                else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) {  // */* < audio/*
762                        return 1;
763                }
764                else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) {  // audio/* > */*
765                        return -1;
766                }
767                else if (!mediaType1.getType().equals(mediaType2.getType())) {  // audio/basic == text/html
768                        return 0;
769                }
770                else {  // mediaType1.getType().equals(mediaType2.getType())
771                        if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) {  // audio/* < audio/basic
772                                return 1;
773                        }
774                        else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) {  // audio/basic > audio/*
775                                return -1;
776                        }
777                        else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) {  // audio/basic == audio/wave
778                                return 0;
779                        }
780                        else {
781                                int paramsSize1 = mediaType1.getParameters().size();
782                                int paramsSize2 = mediaType2.getParameters().size();
783                                return Integer.compare(paramsSize2, paramsSize1);  // audio/basic;level=1 < audio/basic
784                        }
785                }
786        };
787
788
789        /**
790         * Comparator used by {@link #sortBySpecificity(List)}.
791         */
792        public static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new SpecificityComparator<MediaType>() {
793
794                @Override
795                protected int compareParameters(MediaType mediaType1, MediaType mediaType2) {
796                        double quality1 = mediaType1.getQualityValue();
797                        double quality2 = mediaType2.getQualityValue();
798                        int qualityComparison = Double.compare(quality2, quality1);
799                        if (qualityComparison != 0) {
800                                return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
801                        }
802                        return super.compareParameters(mediaType1, mediaType2);
803                }
804        };
805
806}