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. "*/*"). 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 "*", 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 < audio/* < */*</blockquote> 695 * <blockquote>audio/* < audio/*;q=0.7; audio/*;q=0.3</blockquote> 696 * <blockquote>audio/basic;level=1 < 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}