001/* 002 * Copyright (C) 2011 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.net; 016 017import static com.google.common.base.CharMatcher.ascii; 018import static com.google.common.base.CharMatcher.javaIsoControl; 019import static com.google.common.base.Charsets.UTF_8; 020import static com.google.common.base.Preconditions.checkArgument; 021import static com.google.common.base.Preconditions.checkNotNull; 022import static com.google.common.base.Preconditions.checkState; 023 024import com.google.common.annotations.Beta; 025import com.google.common.annotations.GwtCompatible; 026import com.google.common.base.Ascii; 027import com.google.common.base.CharMatcher; 028import com.google.common.base.Function; 029import com.google.common.base.Joiner; 030import com.google.common.base.Joiner.MapJoiner; 031import com.google.common.base.MoreObjects; 032import com.google.common.base.Objects; 033import com.google.common.base.Optional; 034import com.google.common.collect.ImmutableListMultimap; 035import com.google.common.collect.ImmutableMultiset; 036import com.google.common.collect.ImmutableSet; 037import com.google.common.collect.Maps; 038import com.google.common.collect.Multimap; 039import com.google.common.collect.Multimaps; 040import com.google.errorprone.annotations.Immutable; 041import com.google.errorprone.annotations.concurrent.LazyInit; 042import java.nio.charset.Charset; 043import java.nio.charset.IllegalCharsetNameException; 044import java.nio.charset.UnsupportedCharsetException; 045import java.util.Collection; 046import java.util.Map; 047import java.util.Map.Entry; 048import org.checkerframework.checker.nullness.qual.Nullable; 049 050/** 051 * Represents an <a href="http://en.wikipedia.org/wiki/Internet_media_type">Internet Media Type</a> 052 * (also known as a MIME Type or Content Type). This class also supports the concept of media ranges 053 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">defined by HTTP/1.1</a>. 054 * As such, the {@code *} character is treated as a wildcard and is used to represent any acceptable 055 * type or subtype value. A media type may not have wildcard type with a declared subtype. The 056 * {@code *} character has no special meaning as part of a parameter. All values for type, subtype, 057 * parameter attributes or parameter values must be valid according to RFCs <a 058 * href="https://tools.ietf.org/html/rfc2045">2045</a> and <a 059 * href="https://tools.ietf.org/html/rfc2046">2046</a>. 060 * 061 * <p>All portions of the media type that are case-insensitive (type, subtype, parameter attributes) 062 * are normalized to lowercase. The value of the {@code charset} parameter is normalized to 063 * lowercase, but all others are left as-is. 064 * 065 * <p>Note that this specifically does <strong>not</strong> represent the value of the MIME {@code 066 * Content-Type} header and as such has no support for header-specific considerations such as line 067 * folding and comments. 068 * 069 * <p>For media types that take a charset the predefined constants default to UTF-8 and have a 070 * "_UTF_8" suffix. To get a version without a character set, use {@link #withoutParameters}. 071 * 072 * @since 12.0 073 * @author Gregory Kick 074 */ 075@Beta 076@GwtCompatible 077@Immutable 078public final class MediaType { 079 private static final String CHARSET_ATTRIBUTE = "charset"; 080 private static final ImmutableListMultimap<String, String> UTF_8_CONSTANT_PARAMETERS = 081 ImmutableListMultimap.of(CHARSET_ATTRIBUTE, Ascii.toLowerCase(UTF_8.name())); 082 083 /** Matcher for type, subtype and attributes. */ 084 private static final CharMatcher TOKEN_MATCHER = 085 ascii() 086 .and(javaIsoControl().negate()) 087 .and(CharMatcher.isNot(' ')) 088 .and(CharMatcher.noneOf("()<>@,;:\\\"/[]?=")); 089 090 private static final CharMatcher QUOTED_TEXT_MATCHER = ascii().and(CharMatcher.noneOf("\"\\\r")); 091 092 /* 093 * This matches the same characters as linear-white-space from RFC 822, but we make no effort to 094 * enforce any particular rules with regards to line folding as stated in the class docs. 095 */ 096 private static final CharMatcher LINEAR_WHITE_SPACE = CharMatcher.anyOf(" \t\r\n"); 097 098 // TODO(gak): make these public? 099 private static final String APPLICATION_TYPE = "application"; 100 private static final String AUDIO_TYPE = "audio"; 101 private static final String IMAGE_TYPE = "image"; 102 private static final String TEXT_TYPE = "text"; 103 private static final String VIDEO_TYPE = "video"; 104 105 private static final String WILDCARD = "*"; 106 107 private static final Map<MediaType, MediaType> KNOWN_TYPES = Maps.newHashMap(); 108 109 private static MediaType createConstant(String type, String subtype) { 110 MediaType mediaType = 111 addKnownType(new MediaType(type, subtype, ImmutableListMultimap.<String, String>of())); 112 mediaType.parsedCharset = Optional.absent(); 113 return mediaType; 114 } 115 116 private static MediaType createConstantUtf8(String type, String subtype) { 117 MediaType mediaType = addKnownType(new MediaType(type, subtype, UTF_8_CONSTANT_PARAMETERS)); 118 mediaType.parsedCharset = Optional.of(UTF_8); 119 return mediaType; 120 } 121 122 private static MediaType addKnownType(MediaType mediaType) { 123 KNOWN_TYPES.put(mediaType, mediaType); 124 return mediaType; 125 } 126 127 /* 128 * The following constants are grouped by their type and ordered alphabetically by the constant 129 * name within that type. The constant name should be a sensible identifier that is closest to the 130 * "common name" of the media. This is often, but not necessarily the same as the subtype. 131 * 132 * Be sure to declare all constants with the type and subtype in all lowercase. For types that 133 * take a charset (e.g. all text/* types), default to UTF-8 and suffix the constant name with 134 * "_UTF_8". 135 */ 136 137 public static final MediaType ANY_TYPE = createConstant(WILDCARD, WILDCARD); 138 public static final MediaType ANY_TEXT_TYPE = createConstant(TEXT_TYPE, WILDCARD); 139 public static final MediaType ANY_IMAGE_TYPE = createConstant(IMAGE_TYPE, WILDCARD); 140 public static final MediaType ANY_AUDIO_TYPE = createConstant(AUDIO_TYPE, WILDCARD); 141 public static final MediaType ANY_VIDEO_TYPE = createConstant(VIDEO_TYPE, WILDCARD); 142 public static final MediaType ANY_APPLICATION_TYPE = createConstant(APPLICATION_TYPE, WILDCARD); 143 144 /* text types */ 145 public static final MediaType CACHE_MANIFEST_UTF_8 = 146 createConstantUtf8(TEXT_TYPE, "cache-manifest"); 147 public static final MediaType CSS_UTF_8 = createConstantUtf8(TEXT_TYPE, "css"); 148 public static final MediaType CSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "csv"); 149 public static final MediaType HTML_UTF_8 = createConstantUtf8(TEXT_TYPE, "html"); 150 public static final MediaType I_CALENDAR_UTF_8 = createConstantUtf8(TEXT_TYPE, "calendar"); 151 public static final MediaType PLAIN_TEXT_UTF_8 = createConstantUtf8(TEXT_TYPE, "plain"); 152 153 /** 154 * <a href="http://www.rfc-editor.org/rfc/rfc4329.txt">RFC 4329</a> declares {@link 155 * #JAVASCRIPT_UTF_8 application/javascript} to be the correct media type for JavaScript, but this 156 * may be necessary in certain situations for compatibility. 157 */ 158 public static final MediaType TEXT_JAVASCRIPT_UTF_8 = createConstantUtf8(TEXT_TYPE, "javascript"); 159 /** 160 * <a href="http://www.iana.org/assignments/media-types/text/tab-separated-values">Tab separated 161 * values</a>. 162 * 163 * @since 15.0 164 */ 165 public static final MediaType TSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "tab-separated-values"); 166 167 public static final MediaType VCARD_UTF_8 = createConstantUtf8(TEXT_TYPE, "vcard"); 168 169 /** 170 * UTF-8 encoded <a href="https://en.wikipedia.org/wiki/Wireless_Markup_Language">Wireless Markup 171 * Language</a>. 172 * 173 * @since 13.0 174 */ 175 public static final MediaType WML_UTF_8 = createConstantUtf8(TEXT_TYPE, "vnd.wap.wml"); 176 177 /** 178 * As described in <a href="http://www.ietf.org/rfc/rfc3023.txt">RFC 3023</a>, this constant 179 * ({@code text/xml}) is used for XML documents that are "readable by casual users." {@link 180 * #APPLICATION_XML_UTF_8} is provided for documents that are intended for applications. 181 */ 182 public static final MediaType XML_UTF_8 = createConstantUtf8(TEXT_TYPE, "xml"); 183 184 /** 185 * As described in <a href="https://w3c.github.io/webvtt/#iana-text-vtt">the VTT spec</a>, this is 186 * used for Web Video Text Tracks (WebVTT) files, used with the HTML5 track element. 187 * 188 * @since 20.0 189 */ 190 public static final MediaType VTT_UTF_8 = createConstantUtf8(TEXT_TYPE, "vtt"); 191 192 /** 193 * <a href="https://en.wikipedia.org/wiki/BMP_file_format">Bitmap file format</a> ({@code bmp} 194 * files). 195 * 196 * @since 13.0 197 */ 198 public static final MediaType BMP = createConstant(IMAGE_TYPE, "bmp"); 199 200 /** 201 * The <a href="https://en.wikipedia.org/wiki/Camera_Image_File_Format">Canon Image File 202 * Format</a> ({@code crw} files), a widely-used "raw image" format for cameras. It is found in 203 * {@code /etc/mime.types}, e.g. in <a href= 204 * "http://anonscm.debian.org/gitweb/?p=collab-maint/mime-support.git;a=blob;f=mime.types;hb=HEAD" 205 * >Debian 3.48-1</a>. 206 * 207 * @since 15.0 208 */ 209 public static final MediaType CRW = createConstant(IMAGE_TYPE, "x-canon-crw"); 210 211 public static final MediaType GIF = createConstant(IMAGE_TYPE, "gif"); 212 public static final MediaType ICO = createConstant(IMAGE_TYPE, "vnd.microsoft.icon"); 213 public static final MediaType JPEG = createConstant(IMAGE_TYPE, "jpeg"); 214 public static final MediaType PNG = createConstant(IMAGE_TYPE, "png"); 215 216 /** 217 * The Photoshop File Format ({@code psd} files) as defined by <a 218 * href="http://www.iana.org/assignments/media-types/image/vnd.adobe.photoshop">IANA</a>, and 219 * found in {@code /etc/mime.types}, e.g. <a 220 * href="http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types"></a> of the 221 * Apache <a href="http://httpd.apache.org/">HTTPD project</a>; for the specification, see <a 222 * href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm"> 223 * Adobe Photoshop Document Format</a> and <a 224 * href="http://en.wikipedia.org/wiki/Adobe_Photoshop#File_format">Wikipedia</a>; this is the 225 * regular output/input of Photoshop (which can also export to various image formats; note that 226 * files with extension "PSB" are in a distinct but related format). 227 * 228 * <p>This is a more recent replacement for the older, experimental type {@code x-photoshop}: <a 229 * href="http://tools.ietf.org/html/rfc2046#section-6">RFC-2046.6</a>. 230 * 231 * @since 15.0 232 */ 233 public static final MediaType PSD = createConstant(IMAGE_TYPE, "vnd.adobe.photoshop"); 234 235 public static final MediaType SVG_UTF_8 = createConstantUtf8(IMAGE_TYPE, "svg+xml"); 236 public static final MediaType TIFF = createConstant(IMAGE_TYPE, "tiff"); 237 238 /** 239 * <a href="https://en.wikipedia.org/wiki/WebP">WebP image format</a>. 240 * 241 * @since 13.0 242 */ 243 public static final MediaType WEBP = createConstant(IMAGE_TYPE, "webp"); 244 245 /** 246 * <a href="https://www.iana.org/assignments/media-types/image/heif">HEIF image format</a>. 247 * 248 * @since 28.1 249 */ 250 public static final MediaType HEIF = createConstant(IMAGE_TYPE, "heif"); 251 252 /** 253 * <a href="https://tools.ietf.org/html/rfc3745">JP2K image format</a>. 254 * 255 * @since 28.1 256 */ 257 public static final MediaType JP2K = createConstant(IMAGE_TYPE, "jp2"); 258 259 /* audio types */ 260 public static final MediaType MP4_AUDIO = createConstant(AUDIO_TYPE, "mp4"); 261 public static final MediaType MPEG_AUDIO = createConstant(AUDIO_TYPE, "mpeg"); 262 public static final MediaType OGG_AUDIO = createConstant(AUDIO_TYPE, "ogg"); 263 public static final MediaType WEBM_AUDIO = createConstant(AUDIO_TYPE, "webm"); 264 265 /** 266 * L16 audio, as defined by <a href="https://tools.ietf.org/html/rfc2586">RFC 2586</a>. 267 * 268 * @since 24.1 269 */ 270 public static final MediaType L16_AUDIO = createConstant(AUDIO_TYPE, "l16"); 271 272 /** 273 * L24 audio, as defined by <a href="https://tools.ietf.org/html/rfc3190">RFC 3190</a>. 274 * 275 * @since 20.0 276 */ 277 public static final MediaType L24_AUDIO = createConstant(AUDIO_TYPE, "l24"); 278 279 /** 280 * Basic Audio, as defined by <a href="http://tools.ietf.org/html/rfc2046#section-4.3">RFC 281 * 2046</a>. 282 * 283 * @since 20.0 284 */ 285 public static final MediaType BASIC_AUDIO = createConstant(AUDIO_TYPE, "basic"); 286 287 /** 288 * Advanced Audio Coding. For more information, see <a 289 * href="https://en.wikipedia.org/wiki/Advanced_Audio_Coding">Advanced Audio Coding</a>. 290 * 291 * @since 20.0 292 */ 293 public static final MediaType AAC_AUDIO = createConstant(AUDIO_TYPE, "aac"); 294 295 /** 296 * Vorbis Audio, as defined by <a href="http://tools.ietf.org/html/rfc5215">RFC 5215</a>. 297 * 298 * @since 20.0 299 */ 300 public static final MediaType VORBIS_AUDIO = createConstant(AUDIO_TYPE, "vorbis"); 301 302 /** 303 * Windows Media Audio. For more information, see <a 304 * href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd562994(v=vs.85).aspx">file 305 * name extensions for Windows Media metafiles</a>. 306 * 307 * @since 20.0 308 */ 309 public static final MediaType WMA_AUDIO = createConstant(AUDIO_TYPE, "x-ms-wma"); 310 311 /** 312 * Windows Media metafiles. For more information, see <a 313 * href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd562994(v=vs.85).aspx">file 314 * name extensions for Windows Media metafiles</a>. 315 * 316 * @since 20.0 317 */ 318 public static final MediaType WAX_AUDIO = createConstant(AUDIO_TYPE, "x-ms-wax"); 319 320 /** 321 * Real Audio. For more information, see <a 322 * href="http://service.real.com/help/faq/rp8/configrp8win.html">this link</a>. 323 * 324 * @since 20.0 325 */ 326 public static final MediaType VND_REAL_AUDIO = createConstant(AUDIO_TYPE, "vnd.rn-realaudio"); 327 328 /** 329 * WAVE format, as defined by <a href="https://tools.ietf.org/html/rfc2361">RFC 2361</a>. 330 * 331 * @since 20.0 332 */ 333 public static final MediaType VND_WAVE_AUDIO = createConstant(AUDIO_TYPE, "vnd.wave"); 334 335 /* video types */ 336 public static final MediaType MP4_VIDEO = createConstant(VIDEO_TYPE, "mp4"); 337 public static final MediaType MPEG_VIDEO = createConstant(VIDEO_TYPE, "mpeg"); 338 public static final MediaType OGG_VIDEO = createConstant(VIDEO_TYPE, "ogg"); 339 public static final MediaType QUICKTIME = createConstant(VIDEO_TYPE, "quicktime"); 340 public static final MediaType WEBM_VIDEO = createConstant(VIDEO_TYPE, "webm"); 341 public static final MediaType WMV = createConstant(VIDEO_TYPE, "x-ms-wmv"); 342 343 /** 344 * Flash video. For more information, see <a href= 345 * "http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7d48.html" 346 * >this link</a>. 347 * 348 * @since 20.0 349 */ 350 public static final MediaType FLV_VIDEO = createConstant(VIDEO_TYPE, "x-flv"); 351 352 /** 353 * The 3GP multimedia container format. For more information, see <a 354 * href="ftp://www.3gpp.org/tsg_sa/TSG_SA/TSGS_23/Docs/PDF/SP-040065.pdf#page=10">3GPP TS 355 * 26.244</a>. 356 * 357 * @since 20.0 358 */ 359 public static final MediaType THREE_GPP_VIDEO = createConstant(VIDEO_TYPE, "3gpp"); 360 361 /** 362 * The 3G2 multimedia container format. For more information, see <a 363 * href="http://www.3gpp2.org/Public_html/specs/C.S0050-B_v1.0_070521.pdf#page=16">3GPP2 364 * C.S0050-B</a>. 365 * 366 * @since 20.0 367 */ 368 public static final MediaType THREE_GPP2_VIDEO = createConstant(VIDEO_TYPE, "3gpp2"); 369 370 /* application types */ 371 /** 372 * As described in <a href="http://www.ietf.org/rfc/rfc3023.txt">RFC 3023</a>, this constant 373 * ({@code application/xml}) is used for XML documents that are "unreadable by casual users." 374 * {@link #XML_UTF_8} is provided for documents that may be read by users. 375 * 376 * @since 14.0 377 */ 378 public static final MediaType APPLICATION_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xml"); 379 380 public static final MediaType ATOM_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "atom+xml"); 381 public static final MediaType BZIP2 = createConstant(APPLICATION_TYPE, "x-bzip2"); 382 383 /** 384 * Files in the <a href="https://www.dartlang.org/articles/embedding-in-html/">dart</a> 385 * programming language. 386 * 387 * @since 19.0 388 */ 389 public static final MediaType DART_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "dart"); 390 391 /** 392 * <a href="https://goo.gl/2QoMvg">Apple Passbook</a>. 393 * 394 * @since 19.0 395 */ 396 public static final MediaType APPLE_PASSBOOK = 397 createConstant(APPLICATION_TYPE, "vnd.apple.pkpass"); 398 399 /** 400 * <a href="http://en.wikipedia.org/wiki/Embedded_OpenType">Embedded OpenType</a> fonts. This is 401 * <a href="http://www.iana.org/assignments/media-types/application/vnd.ms-fontobject">registered 402 * </a> with the IANA. 403 * 404 * @since 17.0 405 */ 406 public static final MediaType EOT = createConstant(APPLICATION_TYPE, "vnd.ms-fontobject"); 407 408 /** 409 * As described in the <a href="http://idpf.org/epub">International Digital Publishing Forum</a> 410 * EPUB is the distribution and interchange format standard for digital publications and 411 * documents. This media type is defined in the <a 412 * href="http://www.idpf.org/epub/30/spec/epub30-ocf.html">EPUB Open Container Format</a> 413 * specification. 414 * 415 * @since 15.0 416 */ 417 public static final MediaType EPUB = createConstant(APPLICATION_TYPE, "epub+zip"); 418 419 public static final MediaType FORM_DATA = 420 createConstant(APPLICATION_TYPE, "x-www-form-urlencoded"); 421 422 /** 423 * As described in <a href="https://www.rsa.com/rsalabs/node.asp?id=2138">PKCS #12: Personal 424 * Information Exchange Syntax Standard</a>, PKCS #12 defines an archive file format for storing 425 * many cryptography objects as a single file. 426 * 427 * @since 15.0 428 */ 429 public static final MediaType KEY_ARCHIVE = createConstant(APPLICATION_TYPE, "pkcs12"); 430 431 /** 432 * This is a non-standard media type, but is commonly used in serving hosted binary files as it is 433 * <a href="http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors"> 434 * known not to trigger content sniffing in current browsers</a>. It <i>should not</i> be used in 435 * other situations as it is not specified by any RFC and does not appear in the <a 436 * href="http://www.iana.org/assignments/media-types">/IANA MIME Media Types</a> list. Consider 437 * {@link #OCTET_STREAM} for binary data that is not being served to a browser. 438 * 439 * @since 14.0 440 */ 441 public static final MediaType APPLICATION_BINARY = createConstant(APPLICATION_TYPE, "binary"); 442 443 /** 444 * Media type for the <a href="https://tools.ietf.org/html/rfc7946">GeoJSON Format</a>, a 445 * geospatial data interchange format based on JSON. 446 * 447 * @since 28.0 448 */ 449 public static final MediaType GEO_JSON = createConstant(APPLICATION_TYPE, "geo+json"); 450 451 public static final MediaType GZIP = createConstant(APPLICATION_TYPE, "x-gzip"); 452 453 /** 454 * <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08#section-3">JSON Hypertext 455 * Application Language (HAL) documents</a>. 456 * 457 * @since 26.0 458 */ 459 public static final MediaType HAL_JSON = createConstant(APPLICATION_TYPE, "hal+json"); 460 461 /** 462 * <a href="http://www.rfc-editor.org/rfc/rfc4329.txt">RFC 4329</a> declares this to be the 463 * correct media type for JavaScript, but {@link #TEXT_JAVASCRIPT_UTF_8 text/javascript} may be 464 * necessary in certain situations for compatibility. 465 */ 466 public static final MediaType JAVASCRIPT_UTF_8 = 467 createConstantUtf8(APPLICATION_TYPE, "javascript"); 468 469 /** 470 * For <a href="https://tools.ietf.org/html/rfc7515">JWS or JWE objects using the Compact 471 * Serialization</a>. 472 * 473 * @since 27.1 474 */ 475 public static final MediaType JOSE = createConstant(APPLICATION_TYPE, "jose"); 476 477 /** 478 * For <a href="https://tools.ietf.org/html/rfc7515">JWS or JWE objects using the JSON 479 * Serialization</a>. 480 * 481 * @since 27.1 482 */ 483 public static final MediaType JOSE_JSON = createConstant(APPLICATION_TYPE, "jose+json"); 484 485 public static final MediaType JSON_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "json"); 486 487 /** 488 * The <a href="http://www.w3.org/TR/appmanifest/">Manifest for a web application</a>. 489 * 490 * @since 19.0 491 */ 492 public static final MediaType MANIFEST_JSON_UTF_8 = 493 createConstantUtf8(APPLICATION_TYPE, "manifest+json"); 494 495 /** 496 * <a href="http://www.opengeospatial.org/standards/kml/">OGC KML (Keyhole Markup Language)</a>. 497 */ 498 public static final MediaType KML = createConstant(APPLICATION_TYPE, "vnd.google-earth.kml+xml"); 499 500 /** 501 * <a href="http://www.opengeospatial.org/standards/kml/">OGC KML (Keyhole Markup Language)</a>, 502 * compressed using the ZIP format into KMZ archives. 503 */ 504 public static final MediaType KMZ = createConstant(APPLICATION_TYPE, "vnd.google-earth.kmz"); 505 506 /** 507 * The <a href="https://tools.ietf.org/html/rfc4155">mbox database format</a>. 508 * 509 * @since 13.0 510 */ 511 public static final MediaType MBOX = createConstant(APPLICATION_TYPE, "mbox"); 512 513 /** 514 * <a href="http://goo.gl/1pGBFm">Apple over-the-air mobile configuration profiles</a>. 515 * 516 * @since 18.0 517 */ 518 public static final MediaType APPLE_MOBILE_CONFIG = 519 createConstant(APPLICATION_TYPE, "x-apple-aspen-config"); 520 521 /** <a href="http://goo.gl/XDQ1h2">Microsoft Excel</a> spreadsheets. */ 522 public static final MediaType MICROSOFT_EXCEL = createConstant(APPLICATION_TYPE, "vnd.ms-excel"); 523 524 /** 525 * <a href="http://goo.gl/XrTEqG">Microsoft Outlook</a> items. 526 * 527 * @since 27.1 528 */ 529 public static final MediaType MICROSOFT_OUTLOOK = 530 createConstant(APPLICATION_TYPE, "vnd.ms-outlook"); 531 532 /** <a href="http://goo.gl/XDQ1h2">Microsoft Powerpoint</a> presentations. */ 533 public static final MediaType MICROSOFT_POWERPOINT = 534 createConstant(APPLICATION_TYPE, "vnd.ms-powerpoint"); 535 536 /** <a href="http://goo.gl/XDQ1h2">Microsoft Word</a> documents. */ 537 public static final MediaType MICROSOFT_WORD = createConstant(APPLICATION_TYPE, "msword"); 538 539 /** 540 * Media type for <a 541 * href="https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP">Dynamic Adaptive 542 * Streaming over HTTP (DASH)</a>. This is <a 543 * href="https://www.iana.org/assignments/media-types/application/dash+xml">registered</a> with 544 * the IANA. 545 * 546 * @since 28.2 547 */ 548 public static final MediaType MEDIA_PRESENTATION_DESCRIPTION = 549 createConstant(APPLICATION_TYPE, "dash+xml"); 550 551 /** 552 * WASM applications. For more information see <a href="https://webassembly.org/">the Web Assembly 553 * overview</a>. 554 * 555 * @since 27.0 556 */ 557 public static final MediaType WASM_APPLICATION = createConstant(APPLICATION_TYPE, "wasm"); 558 559 /** 560 * NaCl applications. For more information see <a 561 * href="https://developer.chrome.com/native-client/devguide/coding/application-structure">the 562 * Developer Guide for Native Client Application Structure</a>. 563 * 564 * @since 20.0 565 */ 566 public static final MediaType NACL_APPLICATION = createConstant(APPLICATION_TYPE, "x-nacl"); 567 568 /** 569 * NaCl portable applications. For more information see <a 570 * href="https://developer.chrome.com/native-client/devguide/coding/application-structure">the 571 * Developer Guide for Native Client Application Structure</a>. 572 * 573 * @since 20.0 574 */ 575 public static final MediaType NACL_PORTABLE_APPLICATION = 576 createConstant(APPLICATION_TYPE, "x-pnacl"); 577 578 public static final MediaType OCTET_STREAM = createConstant(APPLICATION_TYPE, "octet-stream"); 579 580 public static final MediaType OGG_CONTAINER = createConstant(APPLICATION_TYPE, "ogg"); 581 public static final MediaType OOXML_DOCUMENT = 582 createConstant( 583 APPLICATION_TYPE, "vnd.openxmlformats-officedocument.wordprocessingml.document"); 584 public static final MediaType OOXML_PRESENTATION = 585 createConstant( 586 APPLICATION_TYPE, "vnd.openxmlformats-officedocument.presentationml.presentation"); 587 public static final MediaType OOXML_SHEET = 588 createConstant(APPLICATION_TYPE, "vnd.openxmlformats-officedocument.spreadsheetml.sheet"); 589 public static final MediaType OPENDOCUMENT_GRAPHICS = 590 createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.graphics"); 591 public static final MediaType OPENDOCUMENT_PRESENTATION = 592 createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.presentation"); 593 public static final MediaType OPENDOCUMENT_SPREADSHEET = 594 createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.spreadsheet"); 595 public static final MediaType OPENDOCUMENT_TEXT = 596 createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.text"); 597 598 /** 599 * <a href="https://tools.ietf.org/id/draft-ellermann-opensearch-01.html">OpenSearch</a> 600 * Description files are XML files that describe how a website can be used as a search engine by 601 * consumers (e.g. web browsers). 602 * 603 * @since 28.2 604 */ 605 public static final MediaType OPENSEARCH_DESCRIPTION_UTF_8 = 606 createConstantUtf8(APPLICATION_TYPE, "opensearchdescription+xml"); 607 608 public static final MediaType PDF = createConstant(APPLICATION_TYPE, "pdf"); 609 public static final MediaType POSTSCRIPT = createConstant(APPLICATION_TYPE, "postscript"); 610 611 /** 612 * <a href="http://tools.ietf.org/html/draft-rfernando-protocol-buffers-00">Protocol buffers</a> 613 * 614 * @since 15.0 615 */ 616 public static final MediaType PROTOBUF = createConstant(APPLICATION_TYPE, "protobuf"); 617 618 /** 619 * <a href="https://en.wikipedia.org/wiki/RDF/XML">RDF/XML</a> documents, which are XML 620 * serializations of <a 621 * href="https://en.wikipedia.org/wiki/Resource_Description_Framework">Resource Description 622 * Framework</a> graphs. 623 * 624 * @since 14.0 625 */ 626 public static final MediaType RDF_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "rdf+xml"); 627 628 public static final MediaType RTF_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "rtf"); 629 630 /** 631 * SFNT fonts (which includes <a href="http://en.wikipedia.org/wiki/TrueType/">TrueType</a> and <a 632 * href="http://en.wikipedia.org/wiki/OpenType/">OpenType</a> fonts). This is <a 633 * href="http://www.iana.org/assignments/media-types/application/font-sfnt">registered</a> with 634 * the IANA. 635 * 636 * @since 17.0 637 */ 638 public static final MediaType SFNT = createConstant(APPLICATION_TYPE, "font-sfnt"); 639 640 public static final MediaType SHOCKWAVE_FLASH = 641 createConstant(APPLICATION_TYPE, "x-shockwave-flash"); 642 643 /** 644 * {@code skp} files produced by the 3D Modeling software <a 645 * href="https://www.sketchup.com/">SketchUp</a> 646 * 647 * @since 13.0 648 */ 649 public static final MediaType SKETCHUP = createConstant(APPLICATION_TYPE, "vnd.sketchup.skp"); 650 651 /** 652 * As described in <a href="http://www.ietf.org/rfc/rfc3902.txt">RFC 3902</a>, this constant 653 * ({@code application/soap+xml}) is used to identify SOAP 1.2 message envelopes that have been 654 * serialized with XML 1.0. 655 * 656 * <p>For SOAP 1.1 messages, see {@code XML_UTF_8} per <a 657 * href="http://www.w3.org/TR/2000/NOTE-SOAP-20000508/">W3C Note on Simple Object Access Protocol 658 * (SOAP) 1.1</a> 659 * 660 * @since 20.0 661 */ 662 public static final MediaType SOAP_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "soap+xml"); 663 664 public static final MediaType TAR = createConstant(APPLICATION_TYPE, "x-tar"); 665 666 /** 667 * <a href="http://en.wikipedia.org/wiki/Web_Open_Font_Format">Web Open Font Format</a> (WOFF) <a 668 * href="http://www.w3.org/TR/WOFF/">defined</a> by the W3C. This is <a 669 * href="http://www.iana.org/assignments/media-types/application/font-woff">registered</a> with 670 * the IANA. 671 * 672 * @since 17.0 673 */ 674 public static final MediaType WOFF = createConstant(APPLICATION_TYPE, "font-woff"); 675 676 /** 677 * <a href="http://en.wikipedia.org/wiki/Web_Open_Font_Format">Web Open Font Format</a> (WOFF) 678 * version 2 <a href="https://www.w3.org/TR/WOFF2/">defined</a> by the W3C. 679 * 680 * @since 20.0 681 */ 682 public static final MediaType WOFF2 = createConstant(APPLICATION_TYPE, "font-woff2"); 683 684 public static final MediaType XHTML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xhtml+xml"); 685 686 /** 687 * Extensible Resource Descriptors. This is not yet registered with the IANA, but it is specified 688 * by OASIS in the <a href="http://docs.oasis-open.org/xri/xrd/v1.0/cd02/xrd-1.0-cd02.html">XRD 689 * definition</a> and implemented in projects such as <a 690 * href="http://code.google.com/p/webfinger/">WebFinger</a>. 691 * 692 * @since 14.0 693 */ 694 public static final MediaType XRD_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xrd+xml"); 695 696 public static final MediaType ZIP = createConstant(APPLICATION_TYPE, "zip"); 697 698 private final String type; 699 private final String subtype; 700 private final ImmutableListMultimap<String, String> parameters; 701 702 @LazyInit private String toString; 703 704 @LazyInit private int hashCode; 705 706 @LazyInit private Optional<Charset> parsedCharset; 707 708 private MediaType(String type, String subtype, ImmutableListMultimap<String, String> parameters) { 709 this.type = type; 710 this.subtype = subtype; 711 this.parameters = parameters; 712 } 713 714 /** Returns the top-level media type. For example, {@code "text"} in {@code "text/plain"}. */ 715 public String type() { 716 return type; 717 } 718 719 /** Returns the media subtype. For example, {@code "plain"} in {@code "text/plain"}. */ 720 public String subtype() { 721 return subtype; 722 } 723 724 /** Returns a multimap containing the parameters of this media type. */ 725 public ImmutableListMultimap<String, String> parameters() { 726 return parameters; 727 } 728 729 private Map<String, ImmutableMultiset<String>> parametersAsMap() { 730 return Maps.transformValues( 731 parameters.asMap(), 732 new Function<Collection<String>, ImmutableMultiset<String>>() { 733 @Override 734 public ImmutableMultiset<String> apply(Collection<String> input) { 735 return ImmutableMultiset.copyOf(input); 736 } 737 }); 738 } 739 740 /** 741 * Returns an optional charset for the value of the charset parameter if it is specified. 742 * 743 * @throws IllegalStateException if multiple charset values have been set for this media type 744 * @throws IllegalCharsetNameException if a charset value is present, but illegal 745 * @throws UnsupportedCharsetException if a charset value is present, but no support is available 746 * in this instance of the Java virtual machine 747 */ 748 public Optional<Charset> charset() { 749 // racy single-check idiom, this is safe because Optional is immutable. 750 Optional<Charset> local = parsedCharset; 751 if (local == null) { 752 String value = null; 753 local = Optional.absent(); 754 for (String currentValue : parameters.get(CHARSET_ATTRIBUTE)) { 755 if (value == null) { 756 value = currentValue; 757 local = Optional.of(Charset.forName(value)); 758 } else if (!value.equals(currentValue)) { 759 throw new IllegalStateException( 760 "Multiple charset values defined: " + value + ", " + currentValue); 761 } 762 } 763 parsedCharset = local; 764 } 765 return local; 766 } 767 768 /** 769 * Returns a new instance with the same type and subtype as this instance, but without any 770 * parameters. 771 */ 772 public MediaType withoutParameters() { 773 return parameters.isEmpty() ? this : create(type, subtype); 774 } 775 776 /** 777 * <em>Replaces</em> all parameters with the given parameters. 778 * 779 * @throws IllegalArgumentException if any parameter or value is invalid 780 */ 781 public MediaType withParameters(Multimap<String, String> parameters) { 782 return create(type, subtype, parameters); 783 } 784 785 /** 786 * <em>Replaces</em> all parameters with the given attribute with parameters using the given 787 * values. If there are no values, any existing parameters with the given attribute are removed. 788 * 789 * @throws IllegalArgumentException if either {@code attribute} or {@code values} is invalid 790 * @since 24.0 791 */ 792 public MediaType withParameters(String attribute, Iterable<String> values) { 793 checkNotNull(attribute); 794 checkNotNull(values); 795 String normalizedAttribute = normalizeToken(attribute); 796 ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder(); 797 for (Entry<String, String> entry : parameters.entries()) { 798 String key = entry.getKey(); 799 if (!normalizedAttribute.equals(key)) { 800 builder.put(key, entry.getValue()); 801 } 802 } 803 for (String value : values) { 804 builder.put(normalizedAttribute, normalizeParameterValue(normalizedAttribute, value)); 805 } 806 MediaType mediaType = new MediaType(type, subtype, builder.build()); 807 // if the attribute isn't charset, we can just inherit the current parsedCharset 808 if (!normalizedAttribute.equals(CHARSET_ATTRIBUTE)) { 809 mediaType.parsedCharset = this.parsedCharset; 810 } 811 // Return one of the constants if the media type is a known type. 812 return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); 813 } 814 815 /** 816 * <em>Replaces</em> all parameters with the given attribute with a single parameter with the 817 * given value. If multiple parameters with the same attributes are necessary use {@link 818 * #withParameters(String, Iterable)}. Prefer {@link #withCharset} for setting the {@code charset} 819 * parameter when using a {@link Charset} object. 820 * 821 * @throws IllegalArgumentException if either {@code attribute} or {@code value} is invalid 822 */ 823 public MediaType withParameter(String attribute, String value) { 824 return withParameters(attribute, ImmutableSet.of(value)); 825 } 826 827 /** 828 * Returns a new instance with the same type and subtype as this instance, with the {@code 829 * charset} parameter set to the {@link Charset#name name} of the given charset. Only one {@code 830 * charset} parameter will be present on the new instance regardless of the number set on this 831 * one. 832 * 833 * <p>If a charset must be specified that is not supported on this JVM (and thus is not 834 * representable as a {@link Charset} instance, use {@link #withParameter}. 835 */ 836 public MediaType withCharset(Charset charset) { 837 checkNotNull(charset); 838 MediaType withCharset = withParameter(CHARSET_ATTRIBUTE, charset.name()); 839 // precache the charset so we don't need to parse it 840 withCharset.parsedCharset = Optional.of(charset); 841 return withCharset; 842 } 843 844 /** Returns true if either the type or subtype is the wildcard. */ 845 public boolean hasWildcard() { 846 return WILDCARD.equals(type) || WILDCARD.equals(subtype); 847 } 848 849 /** 850 * Returns {@code true} if this instance falls within the range (as defined by <a 851 * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">the HTTP Accept header</a>) given 852 * by the argument according to three criteria: 853 * 854 * <ol> 855 * <li>The type of the argument is the wildcard or equal to the type of this instance. 856 * <li>The subtype of the argument is the wildcard or equal to the subtype of this instance. 857 * <li>All of the parameters present in the argument are present in this instance. 858 * </ol> 859 * 860 * <p>For example: 861 * 862 * <pre>{@code 863 * PLAIN_TEXT_UTF_8.is(PLAIN_TEXT_UTF_8) // true 864 * PLAIN_TEXT_UTF_8.is(HTML_UTF_8) // false 865 * PLAIN_TEXT_UTF_8.is(ANY_TYPE) // true 866 * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE) // true 867 * PLAIN_TEXT_UTF_8.is(ANY_IMAGE_TYPE) // false 868 * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_8)) // true 869 * PLAIN_TEXT_UTF_8.withoutParameters().is(ANY_TEXT_TYPE.withCharset(UTF_8)) // false 870 * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_16)) // false 871 * }</pre> 872 * 873 * <p>Note that while it is possible to have the same parameter declared multiple times within a 874 * media type this method does not consider the number of occurrences of a parameter. For example, 875 * {@code "text/plain; charset=UTF-8"} satisfies {@code "text/plain; charset=UTF-8; 876 * charset=UTF-8"}. 877 */ 878 public boolean is(MediaType mediaTypeRange) { 879 return (mediaTypeRange.type.equals(WILDCARD) || mediaTypeRange.type.equals(this.type)) 880 && (mediaTypeRange.subtype.equals(WILDCARD) || mediaTypeRange.subtype.equals(this.subtype)) 881 && this.parameters.entries().containsAll(mediaTypeRange.parameters.entries()); 882 } 883 884 /** 885 * Creates a new media type with the given type and subtype. 886 * 887 * @throws IllegalArgumentException if type or subtype is invalid or if a wildcard is used for the 888 * type, but not the subtype. 889 */ 890 public static MediaType create(String type, String subtype) { 891 MediaType mediaType = create(type, subtype, ImmutableListMultimap.<String, String>of()); 892 mediaType.parsedCharset = Optional.absent(); 893 return mediaType; 894 } 895 896 private static MediaType create( 897 String type, String subtype, Multimap<String, String> parameters) { 898 checkNotNull(type); 899 checkNotNull(subtype); 900 checkNotNull(parameters); 901 String normalizedType = normalizeToken(type); 902 String normalizedSubtype = normalizeToken(subtype); 903 checkArgument( 904 !WILDCARD.equals(normalizedType) || WILDCARD.equals(normalizedSubtype), 905 "A wildcard type cannot be used with a non-wildcard subtype"); 906 ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder(); 907 for (Entry<String, String> entry : parameters.entries()) { 908 String attribute = normalizeToken(entry.getKey()); 909 builder.put(attribute, normalizeParameterValue(attribute, entry.getValue())); 910 } 911 MediaType mediaType = new MediaType(normalizedType, normalizedSubtype, builder.build()); 912 // Return one of the constants if the media type is a known type. 913 return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); 914 } 915 916 /** 917 * Creates a media type with the "application" type and the given subtype. 918 * 919 * @throws IllegalArgumentException if subtype is invalid 920 */ 921 static MediaType createApplicationType(String subtype) { 922 return create(APPLICATION_TYPE, subtype); 923 } 924 925 /** 926 * Creates a media type with the "audio" type and the given subtype. 927 * 928 * @throws IllegalArgumentException if subtype is invalid 929 */ 930 static MediaType createAudioType(String subtype) { 931 return create(AUDIO_TYPE, subtype); 932 } 933 934 /** 935 * Creates a media type with the "image" type and the given subtype. 936 * 937 * @throws IllegalArgumentException if subtype is invalid 938 */ 939 static MediaType createImageType(String subtype) { 940 return create(IMAGE_TYPE, subtype); 941 } 942 943 /** 944 * Creates a media type with the "text" type and the given subtype. 945 * 946 * @throws IllegalArgumentException if subtype is invalid 947 */ 948 static MediaType createTextType(String subtype) { 949 return create(TEXT_TYPE, subtype); 950 } 951 952 /** 953 * Creates a media type with the "video" type and the given subtype. 954 * 955 * @throws IllegalArgumentException if subtype is invalid 956 */ 957 static MediaType createVideoType(String subtype) { 958 return create(VIDEO_TYPE, subtype); 959 } 960 961 private static String normalizeToken(String token) { 962 checkArgument(TOKEN_MATCHER.matchesAllOf(token)); 963 checkArgument(!token.isEmpty()); 964 return Ascii.toLowerCase(token); 965 } 966 967 private static String normalizeParameterValue(String attribute, String value) { 968 checkNotNull(value); // for GWT 969 checkArgument(ascii().matchesAllOf(value), "parameter values must be ASCII: %s", value); 970 return CHARSET_ATTRIBUTE.equals(attribute) ? Ascii.toLowerCase(value) : value; 971 } 972 973 /** 974 * Parses a media type from its string representation. 975 * 976 * @throws IllegalArgumentException if the input is not parsable 977 */ 978 public static MediaType parse(String input) { 979 checkNotNull(input); 980 Tokenizer tokenizer = new Tokenizer(input); 981 try { 982 String type = tokenizer.consumeToken(TOKEN_MATCHER); 983 tokenizer.consumeCharacter('/'); 984 String subtype = tokenizer.consumeToken(TOKEN_MATCHER); 985 ImmutableListMultimap.Builder<String, String> parameters = ImmutableListMultimap.builder(); 986 while (tokenizer.hasMore()) { 987 tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); 988 tokenizer.consumeCharacter(';'); 989 tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); 990 String attribute = tokenizer.consumeToken(TOKEN_MATCHER); 991 tokenizer.consumeCharacter('='); 992 final String value; 993 if ('"' == tokenizer.previewChar()) { 994 tokenizer.consumeCharacter('"'); 995 StringBuilder valueBuilder = new StringBuilder(); 996 while ('"' != tokenizer.previewChar()) { 997 if ('\\' == tokenizer.previewChar()) { 998 tokenizer.consumeCharacter('\\'); 999 valueBuilder.append(tokenizer.consumeCharacter(ascii())); 1000 } else { 1001 valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER)); 1002 } 1003 } 1004 value = valueBuilder.toString(); 1005 tokenizer.consumeCharacter('"'); 1006 } else { 1007 value = tokenizer.consumeToken(TOKEN_MATCHER); 1008 } 1009 parameters.put(attribute, value); 1010 } 1011 return create(type, subtype, parameters.build()); 1012 } catch (IllegalStateException e) { 1013 throw new IllegalArgumentException("Could not parse '" + input + "'", e); 1014 } 1015 } 1016 1017 private static final class Tokenizer { 1018 final String input; 1019 int position = 0; 1020 1021 Tokenizer(String input) { 1022 this.input = input; 1023 } 1024 1025 String consumeTokenIfPresent(CharMatcher matcher) { 1026 checkState(hasMore()); 1027 int startPosition = position; 1028 position = matcher.negate().indexIn(input, startPosition); 1029 return hasMore() ? input.substring(startPosition, position) : input.substring(startPosition); 1030 } 1031 1032 String consumeToken(CharMatcher matcher) { 1033 int startPosition = position; 1034 String token = consumeTokenIfPresent(matcher); 1035 checkState(position != startPosition); 1036 return token; 1037 } 1038 1039 char consumeCharacter(CharMatcher matcher) { 1040 checkState(hasMore()); 1041 char c = previewChar(); 1042 checkState(matcher.matches(c)); 1043 position++; 1044 return c; 1045 } 1046 1047 char consumeCharacter(char c) { 1048 checkState(hasMore()); 1049 checkState(previewChar() == c); 1050 position++; 1051 return c; 1052 } 1053 1054 char previewChar() { 1055 checkState(hasMore()); 1056 return input.charAt(position); 1057 } 1058 1059 boolean hasMore() { 1060 return (position >= 0) && (position < input.length()); 1061 } 1062 } 1063 1064 @Override 1065 public boolean equals(@Nullable Object obj) { 1066 if (obj == this) { 1067 return true; 1068 } else if (obj instanceof MediaType) { 1069 MediaType that = (MediaType) obj; 1070 return this.type.equals(that.type) 1071 && this.subtype.equals(that.subtype) 1072 // compare parameters regardless of order 1073 && this.parametersAsMap().equals(that.parametersAsMap()); 1074 } else { 1075 return false; 1076 } 1077 } 1078 1079 @Override 1080 public int hashCode() { 1081 // racy single-check idiom 1082 int h = hashCode; 1083 if (h == 0) { 1084 h = Objects.hashCode(type, subtype, parametersAsMap()); 1085 hashCode = h; 1086 } 1087 return h; 1088 } 1089 1090 private static final MapJoiner PARAMETER_JOINER = Joiner.on("; ").withKeyValueSeparator("="); 1091 1092 /** 1093 * Returns the string representation of this media type in the format described in <a 1094 * href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>. 1095 */ 1096 @Override 1097 public String toString() { 1098 // racy single-check idiom, safe because String is immutable 1099 String result = toString; 1100 if (result == null) { 1101 result = computeToString(); 1102 toString = result; 1103 } 1104 return result; 1105 } 1106 1107 private String computeToString() { 1108 StringBuilder builder = new StringBuilder().append(type).append('/').append(subtype); 1109 if (!parameters.isEmpty()) { 1110 builder.append("; "); 1111 Multimap<String, String> quotedParameters = 1112 Multimaps.transformValues( 1113 parameters, 1114 new Function<String, String>() { 1115 @Override 1116 public String apply(String value) { 1117 return (TOKEN_MATCHER.matchesAllOf(value) && !value.isEmpty()) 1118 ? value 1119 : escapeAndQuote(value); 1120 } 1121 }); 1122 PARAMETER_JOINER.appendTo(builder, quotedParameters.entries()); 1123 } 1124 return builder.toString(); 1125 } 1126 1127 private static String escapeAndQuote(String value) { 1128 StringBuilder escaped = new StringBuilder(value.length() + 16).append('"'); 1129 for (int i = 0; i < value.length(); i++) { 1130 char ch = value.charAt(i); 1131 if (ch == '\r' || ch == '\\' || ch == '"') { 1132 escaped.append('\\'); 1133 } 1134 escaped.append(ch); 1135 } 1136 return escaped.append('"').toString(); 1137 } 1138}