MediaType.java
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.http;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.TypeHint;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ImmutableArgumentConversionContext;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.OptionalValues;
import io.micronaut.http.annotation.Produces;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
/**
* Represents a media type.
* See https://www.iana.org/assignments/media-types/media-types.xhtml and https://tools.ietf.org/html/rfc2046
*
* @author Graeme Rocher
* @since 1.0
*/
@TypeHint(value = MediaType[].class)
public class MediaType implements CharSequence {
/**
* Default file extension used for JSON.
*/
public static final String EXTENSION_JSON = "json";
/**
* Default file extension used for XML.
*/
public static final String EXTENSION_XML = "xml";
/**
* Default file extension used for PDF.
*/
public static final String EXTENSION_PDF = "pdf";
/**
* File extension used for Microsoft Excel Open XML Spreadsheet (XLSX).
*/
public static final String EXTENSION_XLSX = "xlsx";
/**
* File extension for Microsoft Excel's workbook files in use between 97-2003.
*/
public static final String EXTENSION_XLS = "xls";
/**
* File extension for OpenDocument spreadsheets.
*/
public static final String EXTENSION_ODS = "ods";
/**
* File extension used for Microsoft Word Open XML document (DOCX).
*/
public static final String EXTENSION_DOCX = "docx";
/**
* File extension for Microsoft Word document files in use between 97-2003.
*/
public static final String EXTENSION_DOC = "doc";
/**
* File extension for OpenDocument text files.
*/
public static final String EXTENSION_ODT = "odt";
/**
* File extension used for Microsoft Powerpoint Open XML document (PPTX).
*/
public static final String EXTENSION_PPTX = "pptx";
/**
* File extension for Microsoft Powerpoint files in use between 97-2003.
*/
public static final String EXTENSION_PPT = "ppt";
/**
* File extension for OpenDocument presentation files.
*/
public static final String EXTENSION_ODP = "odp";
/**
* File extension for GPS Exchange Format files.
*/
public static final String EXTENSION_GPX = "gpx";
/**
* File extension for ZIP archive files.
*/
public static final String EXTENSION_ZIP = "zip";
/**
* File extension for GZIP compressed files.
*/
public static final String EXTENSION_GZIP = "gz";
/**
* Default empty media type array.
*/
public static final MediaType[] EMPTY_ARRAY = new MediaType[0];
/**
* A wildcard media type representing all types.
*/
public static final String ALL = "*/*";
/**
* A wildcard media type representing all types.
*/
public static final MediaType ALL_TYPE = new MediaType(ALL, "all");
/**
* Multi part form data: multipart/form-data.
*/
public static final String MULTIPART_FORM_DATA = "multipart/form-data";
/**
* Multi part form data: multipart/form-data.
*/
public static final MediaType MULTIPART_FORM_DATA_TYPE = new MediaType(MULTIPART_FORM_DATA);
/**
* Form encoded data: application/x-www-form-urlencoded.
*/
public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
/**
* Form encoded data: application/x-www-form-urlencoded.
*/
public static final MediaType APPLICATION_FORM_URLENCODED_TYPE = new MediaType(APPLICATION_FORM_URLENCODED);
/**
* Shortcut for {@link #APPLICATION_FORM_URLENCODED_TYPE}.
*/
public static final MediaType FORM = APPLICATION_FORM_URLENCODED_TYPE;
/**
* XHTML: application/xhtml+xml.
*/
public static final String APPLICATION_XHTML = "application/xhtml+xml";
/**
* XHTML: application/xhtml+xml.
*/
public static final MediaType APPLICATION_XHTML_TYPE = new MediaType(APPLICATION_XHTML, "html");
/**
* XML: application/xml.
*/
public static final String APPLICATION_XML = "application/xml";
/**
* XML: application/xml.
*/
public static final MediaType APPLICATION_XML_TYPE = new MediaType(APPLICATION_XML);
/**
* JSON GitHub: application/vnd.github+json.
*/
public static final String APPLICATION_JSON_GITHUB = "application/vnd.github+json";
/**
* JSON GitHub: application/vnd.github+json.
*/
public static final MediaType APPLICATION_JSON_GITHUB_TYPE = new MediaType(MediaType.APPLICATION_JSON_GITHUB);
/**
* JSON Feed: application/feed+json.
*/
public static final String APPLICATION_JSON_FEED = "application/feed+json";
/**
* JSON Feed: application/feed+json.
*/
public static final MediaType APPLICATION_JSON_FEED_TYPE = new MediaType(MediaType.APPLICATION_JSON_FEED);
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc6902/">JSON Patch</a>
* JSON Patch: application/json-patch+json.
*/
public static final String APPLICATION_JSON_PATCH = "application/json-patch+json";
/**
* JSON Patch: application/json-patch+json.
*/
public static final MediaType APPLICATION_JSON_PATCH_TYPE = new MediaType(MediaType.APPLICATION_JSON_PATCH);
/**
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7386">JSON Merge Patch</a>
* JSON Merge Patch: application/merge-patch+json
*/
public static final String APPLICATION_JSON_MERGE_PATCH = "application/merge-patch+json";
/**
* JSON Merge Patch: application/merge-patch+json.
*/
public static final MediaType APPLICATION_JSON_MERGE_PATCH_TYPE = new MediaType(MediaType.APPLICATION_JSON_MERGE_PATCH);
/**
* JSON Feed: application/problem+json.
*/
public static final String APPLICATION_JSON_PROBLEM = "application/problem+json";
/**
* JSON Feed: application/problem+json.
*/
public static final MediaType APPLICATION_JSON_PROBLEM_TYPE = new MediaType(MediaType.APPLICATION_JSON_PROBLEM);
/**
* JSON Schema: application/schema+json.
*/
public static final String APPLICATION_JSON_SCHEMA = "application/schema+json";
/**
* JSON Schema: application/schema+json.
*/
public static final MediaType APPLICATION_JSON_SCHEMA_TYPE = new MediaType(MediaType.APPLICATION_JSON_SCHEMA);
/**
* JSON: application/json.
*/
public static final String APPLICATION_JSON = "application/json";
/**
* JSON: application/json.
*/
public static final MediaType APPLICATION_JSON_TYPE = new MediaType(MediaType.APPLICATION_JSON);
/**
* YAML: application/x-yaml.
*/
public static final String APPLICATION_YAML = "application/x-yaml";
/**
* YAML: application/x-yaml.
*/
public static final MediaType APPLICATION_YAML_TYPE = new MediaType(MediaType.APPLICATION_YAML);
/**
* XML: Microsoft Excel Open XML Spreadsheet (XLSX).
*/
public static final String MICROSOFT_EXCEL_OPEN_XML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
/**
* XML: Microsoft Excel Open XML Spreadsheet (XLSX).
*/
public static final MediaType MICROSOFT_EXCEL_OPEN_XML_TYPE = new MediaType(MICROSOFT_EXCEL_OPEN_XML, EXTENSION_XLSX);
/**
* Microsoft Excel's workbook files in use between 97-2003.
*/
public static final String MICROSOFT_EXCEL = "application/vnd.ms-excel";
/**
* Microsoft Excel's workbook files in use between 97-2003.
*/
public static final MediaType MICROSOFT_EXCEL_TYPE = new MediaType(MICROSOFT_EXCEL, EXTENSION_XLS);
/**
* OpenDocument spreadsheet: application/vnd.oasis.opendocument.spreadsheet.
*/
public static final String OPEN_DOCUMENT_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet";
/**
* OpenDocument spreadsheet: application/vnd.oasis.opendocument.spreadsheet.
*/
public static final MediaType OPEN_DOCUMENT_SPREADSHEET_TYPE = new MediaType(OPEN_DOCUMENT_SPREADSHEET, EXTENSION_ODS);
/**
* XML: Microsoft Word Open XML (DOCX).
*/
public static final String MICROSOFT_WORD_OPEN_XML = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
/**
* XML: Microsoft Word Open XML (DOCX).
*/
public static final MediaType MICROSOFT_WORD_OPEN_XML_TYPE = new MediaType(MICROSOFT_WORD_OPEN_XML, EXTENSION_DOCX);
/**
* Microsoft Word files in use between 97-2003.
*/
public static final String MICROSOFT_WORD = "application/msword";
/**
* Microsoft Word files in use between 97-2003.
*/
public static final MediaType MICROSOFT_WORD_TYPE = new MediaType(MICROSOFT_WORD, EXTENSION_DOC);
/**
* OpenDocument text: application/vnd.oasis.opendocument.text.
*/
public static final String OPEN_DOCUMENT_TEXT = "application/vnd.oasis.opendocument.text";
/**
* OpenDocument text: application/vnd.oasis.opendocument.text.
*/
public static final MediaType OPEN_DOCUMENT_TEXT_TYPE = new MediaType(OPEN_DOCUMENT_TEXT, EXTENSION_ODT);
/**
* XML: Microsoft Powerpoint XML (PPTX).
*/
public static final String MICROSOFT_POWERPOINT_OPEN_XML = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
/**
* XML: Microsoft Powerpoint Open XML (PPTX).
*/
public static final MediaType MICROSOFT_POWERPOINT_OPEN_XML_TYPE = new MediaType(MICROSOFT_WORD_OPEN_XML, EXTENSION_PPTX);
/**
* Microsoft Powerpoint files in use between 97-2003.
*/
public static final String MICROSOFT_POWERPOINT = "application/vnd.ms-powerpoint";
/**
* Microsoft Powerpoint files in use between 97-2003.
*/
public static final MediaType MICROSOFT_POWERPOINT_TYPE = new MediaType(MICROSOFT_POWERPOINT, EXTENSION_PPT);
/**
* OpenDocument presentation: application/vnd.oasis.opendocument.presentation.
*/
public static final String OPEN_DOCUMENT_PRESENTATION = "application/vnd.oasis.opendocument.presentation";
/**
* OpenDocument presentation: application/vnd.oasis.opendocument.presentation.
*/
public static final MediaType OPEN_DOCUMENT_PRESENTATION_TYPE = new MediaType(OPEN_DOCUMENT_PRESENTATION, EXTENSION_ODP);
/**
* HAL JSON: application/hal+json.
*/
public static final String APPLICATION_HAL_JSON = "application/hal+json";
/**
* HAL JSON: application/hal+json.
*/
public static final MediaType APPLICATION_HAL_JSON_TYPE = new MediaType(APPLICATION_HAL_JSON);
/**
* HAL XML: application/hal+xml.
*/
public static final String APPLICATION_HAL_XML = "application/hal+xml";
/**
* HAL XML: application/hal+xml.
*/
public static final MediaType APPLICATION_HAL_XML_TYPE = new MediaType(APPLICATION_HAL_XML);
/**
* Atom: application/atom+xml.
*/
public static final String APPLICATION_ATOM_XML = "application/atom+xml";
/**
* Atom: application/atom+xml.
*/
public static final MediaType APPLICATION_ATOM_XML_TYPE = new MediaType(APPLICATION_ATOM_XML);
/**
* VND Error: application/vnd.error+json.
*/
public static final String APPLICATION_VND_ERROR = "application/vnd.error+json";
/**
* VND Error: application/vnd.error+json.
*/
public static final MediaType APPLICATION_VND_ERROR_TYPE = new MediaType(APPLICATION_VND_ERROR);
/**
* JSON Stream: application/x-json-stream.
*/
public static final String APPLICATION_JSON_STREAM = "application/x-json-stream";
/**
* JSON Stream: application/x-json-stream.
*/
public static final MediaType APPLICATION_JSON_STREAM_TYPE = new MediaType(APPLICATION_JSON_STREAM);
/**
* BINARY: application/octet-stream.
*/
public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
/**
* BINARY: application/octet-stream.
*/
public static final MediaType APPLICATION_OCTET_STREAM_TYPE = new MediaType(APPLICATION_OCTET_STREAM);
/**
* GraphQL: application/graphql.
*/
public static final String APPLICATION_GRAPHQL = "application/graphql";
/**
* GraphQL: application/graphql.
*/
public static final MediaType APPLICATION_GRAPHQL_TYPE = new MediaType(APPLICATION_GRAPHQL);
/**
* GPS Exchange Format: application/gpx+xml.
*/
public static final String APPLICATION_GPX_XML = "application/gpx+xml";
/**
* GPS Exchange Format: application/gpx+xml.
*/
public static final MediaType GPX_XML_TYPE = new MediaType(APPLICATION_GPX_XML, EXTENSION_GPX);
/**
* ZIP archive format: application/zip.
*/
public static final String APPLICATION_ZIP = "application/zip";
/**
* ZIP archive format: application/zip.
*/
public static final MediaType ZIP_TYPE = new MediaType(APPLICATION_ZIP);
/**
* GZip compressed data: application/gzip.
*/
public static final String APPLICATION_GZIP = "application/gzip";
/**
* GZip compressed data: application/gzip.
*/
public static final MediaType GZIP_TYPE = new MediaType(APPLICATION_GZIP);
/**
* YANG format data: application/yang.
*/
public static final String APPLICATION_YANG = "application/yang";
/**
* YANG format data: application/yang.
*/
public static final MediaType YANG_TYPE = new MediaType(APPLICATION_YANG);
/**
* CUE format data: application/x-cue.
*/
public static final String APPLICATION_CUE = "application/x-cue";
/**
* CUE format data: application/x-cue.
*/
public static final MediaType CUE_TYPE = new MediaType(APPLICATION_CUE);
/**
* TOML format data: application/toml.
*/
public static final String APPLICATION_TOML = "application/toml";
/**
* TOML format data: application/toml.
*/
public static final MediaType TOML_TYPE = new MediaType(APPLICATION_TOML);
/**
* RTF format data: application/rtf.
*/
public static final String APPLICATION_RTF = "application/rtf";
/**
* RTF format data: application/rtf.
*/
public static final MediaType RTF_TYPE = new MediaType(APPLICATION_RTF);
/**
* Zlib compressed data: application/zlib.
*/
public static final String APPLICATION_ZLIB = "application/zlib";
/**
* Zlib compressed data: application/zlib.
*/
public static final MediaType ZLIB_TYPE = new MediaType(APPLICATION_ZLIB);
/**
* Zstd compressed data: application/zstd.
*/
public static final String APPLICATION_ZSTD = "application/zstd";
/**
* Zstd compressed data: application/zstd.
*/
public static final MediaType ZSTD_TYPE = new MediaType(APPLICATION_ZSTD);
/**
* PDF: application/pdf.
*/
public static final String APPLICATION_PDF = "application/pdf";
/**
* PDF: application/pdf.
*/
public static final MediaType APPLICATION_PDF_TYPE = new MediaType(APPLICATION_PDF);
/**
* HTML: text/html.
*/
public static final String TEXT_HTML = "text/html";
/**
* HTML: text/html.
*/
public static final MediaType TEXT_HTML_TYPE = new MediaType(TEXT_HTML);
/**
* CSV: text/csv.
*/
public static final String TEXT_CSV = "text/csv";
/**
* CSV: text/csv.
*/
public static final MediaType TEXT_CSV_TYPE = new MediaType(TEXT_CSV);
/**
* CSS: text/css.
*/
public static final String TEXT_CSS = "text/css";
/**
* CSS: text/css.
*/
public static final MediaType TEXT_CSS_TYPE = new MediaType(TEXT_CSS);
/**
* XML: text/xml.
*/
public static final String TEXT_XML = "text/xml";
/**
* XML: text/xml.
*/
public static final MediaType TEXT_XML_TYPE = new MediaType(TEXT_XML);
/**
* JSON: text/json.
*/
public static final String TEXT_JSON = "text/json";
/**
* JSON: text/json.
*/
public static final MediaType TEXT_JSON_TYPE = new MediaType(TEXT_JSON);
/**
* Text javascript: text/javascript.
*/
public static final String TEXT_JAVASCRIPT = "text/javascript";
/**
* Text javascript: text/javascript.
*/
public static final MediaType TEXT_JAVASCRIPT_TYPE = new MediaType(TEXT_JAVASCRIPT);
/**
* Text ecmascript: text/ecmascript.
*/
public static final String TEXT_ECMASCRIPT = "text/ecmascript";
/**
* Text ecmascript: text/ecmascript.
*/
public static final MediaType TEXT_ECMASCRIPT_TYPE = new MediaType(TEXT_ECMASCRIPT);
/**
* Plain Text: text/plain.
*/
public static final String TEXT_PLAIN = "text/plain";
/**
* Plain Text: text/plain.
*/
public static final MediaType TEXT_PLAIN_TYPE = new MediaType(TEXT_PLAIN);
/**
* Markdown: text/markdown.
*/
public static final String TEXT_MARKDOWN = "text/markdown";
/**
* Markdown: text/markdown.
*/
public static final MediaType TEXT_MARKDOWN_TYPE = new MediaType(TEXT_MARKDOWN);
/**
* Server Sent Event: text/event-stream.
*/
public static final String TEXT_EVENT_STREAM = "text/event-stream";
/**
* Server Sent Event: text/event-stream.
*/
public static final MediaType TEXT_EVENT_STREAM_TYPE = new MediaType(TEXT_EVENT_STREAM);
/**
* Animated Portable Network Graphics (APNG): image/apng.
*/
public static final String IMAGE_APNG = "image/apng";
/**
* Animated Portable Network Graphics (APNG): image/apng.
*/
public static final MediaType IMAGE_APNG_TYPE = new MediaType(IMAGE_APNG);
/**
* Bitmap file: image/bmp.
*/
public static final String IMAGE_BMP = "image/bmp";
/**
* Bitmap file: image/bmp.
*/
public static final MediaType IMAGE_BMP_TYPE = new MediaType(IMAGE_BMP);
/**
* Microsoft Icon: image/x-icon.
*/
public static final String IMAGE_X_ICON = "image/x-icon";
/**
* Microsoft Icon: image/x-icon.
*/
public static final MediaType IMAGE_X_ICON_TYPE = new MediaType(IMAGE_X_ICON);
/**
* Tagged Image File Format: image/tiff.
*/
public static final String IMAGE_TIFF = "image/tiff";
/**
* Tagged Image File Format: image/tiff.
*/
public static final MediaType IMAGE_TIFF_TYPE = new MediaType(IMAGE_TIFF);
/**
* AV1 Image File Format (AVIF): image/avif.
*/
public static final String IMAGE_AVIF = "image/avif";
/**
* AV1 Image File Format (AVIF): image/avif.
*/
public static final MediaType IMAGE_AVIF_TYPE = new MediaType(IMAGE_AVIF);
/**
* Scalable Vector Graphics (SVG): image/svg+xml.
*/
public static final String IMAGE_SVG = "image/svg+xml";
/**
* Scalable Vector Graphics (SVG): image/svg+xml.
*/
public static final MediaType IMAGE_SVG_TYPE = new MediaType(IMAGE_SVG);
/**
* X Window System Bitmap file (XBM): image/xbm.
*/
public static final String IMAGE_XBM = "image/xbm";
/**
* X Window System Bitmap file (XBM): image/xbm.
*/
public static final MediaType IMAGE_XBM_TYPE = new MediaType(IMAGE_XBM);
/**
* Png Image: image/png.
*/
public static final String IMAGE_PNG = "image/png";
/**
* Png Image: image/png.
*/
public static final MediaType IMAGE_PNG_TYPE = new MediaType(IMAGE_PNG);
/**
* Jpeg Image: image/jpeg.
*/
public static final String IMAGE_JPEG = "image/jpeg";
/**
* Jpeg Image: image/jpeg.
*/
public static final MediaType IMAGE_JPEG_TYPE = new MediaType(IMAGE_JPEG);
/**
* Gif Image: image/gif.
*/
public static final String IMAGE_GIF = "image/gif";
/**
* Gif Image: image/gif.
*/
public static final MediaType IMAGE_GIF_TYPE = new MediaType(IMAGE_GIF);
/**
* Webp Image: image/webp.
*/
public static final String IMAGE_WEBP = "image/webp";
/**
* Webp Image: image/webp.
*/
public static final MediaType IMAGE_WEBP_TYPE = new MediaType(IMAGE_WEBP);
/**
* WMF Image: image/wmf.
*/
public static final String IMAGE_WMF = "image/wmf";
/**
* WMF Image: image/wmf.
*/
public static final MediaType IMAGE_WMF_TYPE = new MediaType(IMAGE_WMF);
/**
* Parameter {@code "charset"}.
*/
public static final String CHARSET_PARAMETER = "charset";
/**
* Parameter {@code "q"}.
*/
public static final String Q_PARAMETER = "q";
/**
* Parameter {@code "v"}.
*/
public static final String V_PARAMETER = "v";
@Internal
static final Argument<MediaType> ARGUMENT = Argument.of(MediaType.class);
@Internal
static final ArgumentConversionContext<MediaType> CONVERSION_CONTEXT = ImmutableArgumentConversionContext.of(ARGUMENT);
private static final char SEMICOLON = ';';
private static final String WILDCARD = "*";
@SuppressWarnings("ConstantName")
private static final String MIME_TYPES_FILE_NAME = "META-INF/http/mime.types";
private static Map<String, String> mediaTypeFileExtensions;
@SuppressWarnings("ConstantName")
private static final List<Pattern> textTypePatterns = new ArrayList<>(4);
protected final String name;
protected final String subtype;
protected final String type;
protected final String extension;
protected final Map<CharSequence, String> parameters;
private final String strRepr;
private final String lowerName;
private BigDecimal qualityNumberField = BigDecimal.ONE;
private boolean valid;
static {
textTypePatterns.add(Pattern.compile("^text/.*$"));
textTypePatterns.add(Pattern.compile("^.*\\+json$"));
textTypePatterns.add(Pattern.compile("^.*\\+text$"));
textTypePatterns.add(Pattern.compile("^.*\\+xml$"));
textTypePatterns.add(Pattern.compile("^application/javascript$"));
}
/**
* Constructs a new media type for the given string.
*
* @param name The name of the media type. For example application/json
*/
public MediaType(@NonNull String name) {
this(name, null, Collections.emptyMap());
}
/**
* Constructs a new media type for the given string and parameters.
*
* @param name The name of the media type. For example application/json
* @param params The parameters
*/
public MediaType(@NonNull String name, @Nullable Map<String, String> params) {
this(name, null, params);
}
/**
* Constructs a new media type for the given string and extension.
*
* @param name The name of the media type. For example application/json
* @param extension The extension of the file using this media type if it differs from the subtype
*/
public MediaType(@NonNull String name, @Nullable String extension) {
this(name, extension, Collections.emptyMap());
}
/**
* Constructs a new media type for the given string and extension.
*
* @param name The name of the media type. For example application/json
* @param extension The extension of the file using this media type if it differs from the subtype
* @param params The parameters
*/
public MediaType(@NonNull String name, @Nullable String extension, @Nullable Map<String, String> params) {
if (name == null) {
throw new IllegalArgumentException("Argument [name] cannot be null");
}
name = name.trim();
String withoutArgs;
Iterator<String> splitIt = StringUtils.splitOmitEmptyStringsIterator(name, SEMICOLON);
if (splitIt.hasNext()) {
withoutArgs = splitIt.next();
if (splitIt.hasNext()) {
Map<CharSequence, String> parameters = null;
while (splitIt.hasNext()) {
String paramExpression = splitIt.next();
int i = paramExpression.indexOf('=');
if (i > -1) {
String paramName = paramExpression.substring(0, i).trim();
String paramValue = paramExpression.substring(i + 1).trim();
if ("q".equals(paramName)) {
qualityNumberField = new BigDecimal(paramValue);
}
if (parameters == null) {
parameters = new LinkedHashMap<>();
}
parameters.put(paramName, paramValue);
}
}
if (parameters == null) {
parameters = Collections.emptyMap();
}
this.parameters = parameters;
} else if (params == null) {
this.parameters = Collections.emptyMap();
} else {
this.parameters = (Map) params;
}
} else {
if (params == null) {
this.parameters = Collections.emptyMap();
} else {
this.parameters = (Map) params;
}
withoutArgs = name;
}
this.name = withoutArgs;
this.lowerName = withoutArgs.toLowerCase(Locale.ROOT);
int i = withoutArgs.indexOf('/');
if (i > -1) {
this.type = withoutArgs.substring(0, i);
this.subtype = withoutArgs.substring(i + 1);
} else {
throw new IllegalArgumentException("Invalid mime type: " + name);
}
if (extension != null) {
this.extension = extension;
} else {
int j = subtype.indexOf('+');
if (j > -1) {
this.extension = subtype.substring(j + 1);
} else {
this.extension = subtype;
}
}
if (params == null || params.isEmpty()) {
this.strRepr = name;
} else {
this.strRepr = toString0();
}
}
/**
* Create a new or get a {@link MediaType} from the given text.
*
* @param mediaType The text
* @return The {@link MediaType}
*/
public static MediaType of(String mediaType) {
return switch (mediaType) {
case ALL -> ALL_TYPE;
case APPLICATION_FORM_URLENCODED -> APPLICATION_FORM_URLENCODED_TYPE;
case APPLICATION_XHTML -> APPLICATION_XHTML_TYPE;
case APPLICATION_XML -> APPLICATION_XML_TYPE;
case APPLICATION_JSON -> APPLICATION_JSON_TYPE;
case APPLICATION_JSON_FEED -> APPLICATION_JSON_FEED_TYPE;
case APPLICATION_JSON_GITHUB -> APPLICATION_JSON_GITHUB_TYPE;
case APPLICATION_JSON_PATCH -> APPLICATION_JSON_PATCH_TYPE;
case APPLICATION_JSON_MERGE_PATCH -> APPLICATION_JSON_MERGE_PATCH_TYPE;
case APPLICATION_JSON_PROBLEM -> APPLICATION_JSON_PROBLEM_TYPE;
case APPLICATION_JSON_SCHEMA -> APPLICATION_JSON_SCHEMA_TYPE;
case APPLICATION_YAML -> APPLICATION_YAML_TYPE;
case APPLICATION_HAL_JSON -> APPLICATION_HAL_JSON_TYPE;
case APPLICATION_HAL_XML -> APPLICATION_HAL_XML_TYPE;
case APPLICATION_ATOM_XML -> APPLICATION_ATOM_XML_TYPE;
case APPLICATION_VND_ERROR -> APPLICATION_VND_ERROR_TYPE;
case APPLICATION_JSON_STREAM -> APPLICATION_JSON_STREAM_TYPE;
case APPLICATION_OCTET_STREAM -> APPLICATION_OCTET_STREAM_TYPE;
case APPLICATION_GRAPHQL -> APPLICATION_GRAPHQL_TYPE;
case APPLICATION_PDF -> APPLICATION_PDF_TYPE;
case APPLICATION_GPX_XML -> GPX_XML_TYPE;
case APPLICATION_GZIP -> GZIP_TYPE;
case APPLICATION_ZIP -> ZIP_TYPE;
case MICROSOFT_EXCEL_OPEN_XML -> MICROSOFT_EXCEL_OPEN_XML_TYPE;
case MICROSOFT_EXCEL -> MICROSOFT_EXCEL_TYPE;
case OPEN_DOCUMENT_SPREADSHEET -> OPEN_DOCUMENT_SPREADSHEET_TYPE;
case MICROSOFT_WORD_OPEN_XML -> MICROSOFT_WORD_OPEN_XML_TYPE;
case MICROSOFT_WORD -> MICROSOFT_WORD_TYPE;
case OPEN_DOCUMENT_TEXT -> OPEN_DOCUMENT_TEXT_TYPE;
case MICROSOFT_POWERPOINT -> MICROSOFT_POWERPOINT_TYPE;
case MICROSOFT_POWERPOINT_OPEN_XML -> MICROSOFT_POWERPOINT_OPEN_XML_TYPE;
case OPEN_DOCUMENT_PRESENTATION -> OPEN_DOCUMENT_PRESENTATION_TYPE;
case APPLICATION_YANG -> YANG_TYPE;
case APPLICATION_CUE -> CUE_TYPE;
case APPLICATION_TOML -> TOML_TYPE;
case APPLICATION_RTF -> RTF_TYPE;
case APPLICATION_ZLIB -> ZLIB_TYPE;
case APPLICATION_ZSTD -> ZSTD_TYPE;
case MULTIPART_FORM_DATA -> MULTIPART_FORM_DATA_TYPE;
case TEXT_HTML -> TEXT_HTML_TYPE;
case TEXT_CSV -> TEXT_CSV_TYPE;
case TEXT_XML -> TEXT_XML_TYPE;
case TEXT_JSON -> TEXT_JSON_TYPE;
case TEXT_PLAIN -> TEXT_PLAIN_TYPE;
case TEXT_EVENT_STREAM -> TEXT_EVENT_STREAM_TYPE;
case TEXT_MARKDOWN -> TEXT_MARKDOWN_TYPE;
case TEXT_CSS -> TEXT_CSS_TYPE;
case TEXT_JAVASCRIPT -> TEXT_JAVASCRIPT_TYPE;
case TEXT_ECMASCRIPT -> TEXT_ECMASCRIPT_TYPE;
case IMAGE_APNG -> IMAGE_APNG_TYPE;
case IMAGE_BMP -> IMAGE_BMP_TYPE;
case IMAGE_X_ICON -> IMAGE_X_ICON_TYPE;
case IMAGE_TIFF -> IMAGE_TIFF_TYPE;
case IMAGE_AVIF -> IMAGE_AVIF_TYPE;
case IMAGE_SVG -> IMAGE_SVG_TYPE;
case IMAGE_XBM -> IMAGE_XBM_TYPE;
case IMAGE_PNG -> IMAGE_PNG_TYPE;
case IMAGE_JPEG -> IMAGE_JPEG_TYPE;
case IMAGE_GIF -> IMAGE_GIF_TYPE;
case IMAGE_WEBP -> IMAGE_WEBP_TYPE;
case IMAGE_WMF -> IMAGE_WMF_TYPE;
default -> new MediaType(mediaType);
};
}
/**
* Determine if this requested content type can be satisfied by a given content type. e.g. text/* will be satisfied by test/html.
*
* @param expectedContentType Content type to match against
* @return if successful match
*/
public boolean matches(@NonNull MediaType expectedContentType) {
//noinspection ConstantConditions
if (expectedContentType == null) {
return false;
}
if (expectedContentType == this) {
return true;
}
return matchesType(expectedContentType.getType()) && matchesSubtype(expectedContentType.getSubtype());
}
/**
* Check if the subtype matches.
*
* @param matchSubtype The subtype to match
* @return true if matches
* @since 4.6.3
*/
public boolean matchesSubtype(String matchSubtype) {
return subtype.equals(WILDCARD) || subtype.equalsIgnoreCase(matchSubtype);
}
/**
* Check if the type matches.
* @param matchType The type to match
* @return true if matches
* @since 4.6.3
*/
public boolean matchesType(String matchType) {
return type.equals(WILDCARD) || type.equalsIgnoreCase(matchType);
}
/**
* Check if the extension matches.
* @param matchExtension The extension to match
* @return true if matches
* @since 4.7.0
*/
public boolean matchesAllOrWildcardOrExtension(String matchExtension) {
return extension.equalsIgnoreCase(ALL_TYPE.extension) || extension.equals(WILDCARD) || matchesExtension(matchExtension);
}
/**
* Check if the extension matches.
* @param matchExtension The extension to match
* @return true if matches
* @since 4.6.3
*/
public boolean matchesExtension(String matchExtension) {
return extension.equalsIgnoreCase(matchExtension);
}
/**
* @return The name of the mime type without any parameters
*/
public String getName() {
return name;
}
/**
* @return The type of the media type. For example for application/hal+json this would return "application"
*/
public String getType() {
return this.type;
}
/**
* @return The subtype. For example for application/hal+json this would return "hal+json"
*/
public String getSubtype() {
return this.subtype;
}
/**
* @return The extension. For example for application/hal+json this would return "json"
*/
public String getExtension() {
return extension;
}
/**
* @return The parameters of the media type
*/
public OptionalValues<String> getParameters() {
return OptionalValues.of(String.class, parameters);
}
/**
* @return The parameters map of the media type
* @since 4.8
*/
@NonNull
public Map<CharSequence, String> getParametersMap() {
if (parameters == null) {
return Collections.emptyMap();
}
return Collections.unmodifiableMap(parameters);
}
/**
* @return The quality of the Mime type
*/
public String getQuality() {
return qualityNumberField.toString();
}
/**
* @return The quality in BigDecimal form
*/
public BigDecimal getQualityAsNumber() {
return this.qualityNumberField;
}
/**
* @return The version of the Mime type
*/
public String getVersion() {
return parameters.getOrDefault(V_PARAMETER, null);
}
/**
* @return The charset of the media type if specified
*/
public Optional<Charset> getCharset() {
String charset = parameters.get(CHARSET_PARAMETER);
if (charset == null) {
return Optional.empty();
}
return Optional.of(Charset.forName(charset));
}
@Override
public int length() {
return strRepr.length();
}
@Override
public char charAt(int index) {
return strRepr.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return strRepr.subSequence(start, end);
}
/**
* @return Whether the media type is text based
*/
public boolean isTextBased() {
boolean matches = textTypePatterns.stream().anyMatch(p -> p.matcher(name).matches());
if (!matches) {
matches = subtype.equalsIgnoreCase("json")
|| subtype.equalsIgnoreCase("xml")
|| subtype.equalsIgnoreCase("x-yaml")
|| subtype.equalsIgnoreCase("graphql")
|| subtype.equalsIgnoreCase("yang")
|| subtype.equalsIgnoreCase("toml")
|| subtype.equalsIgnoreCase("x-cue")
;
}
return matches;
}
/**
* @param contentType The content type
* @return Whether the content type is text based
*/
public static boolean isTextBased(String contentType) {
if (StringUtils.isEmpty(contentType)) {
return false;
}
try {
return of(contentType).isTextBased();
} catch (IllegalArgumentException e) {
return false;
}
}
/**
* Validate this media type for sending as an HTTP header. This is an optimization to only run
* the validation once if possible. If the validation function does not throw, future calls to
* this method will not call the validation function again.
*
* @param r Validation function
*/
@Internal
public void validate(Runnable r) {
if (!valid) {
r.run();
valid = true;
}
}
@Override
public String toString() {
return strRepr;
}
private String toString0() {
if (parameters.isEmpty()) {
return name;
} else {
StringBuilder sb = new StringBuilder(name);
parameters.forEach((name, value) -> {
sb.append(';');
sb.append(name);
sb.append('=');
sb.append(value);
});
return sb.toString();
}
}
/**
* {@inheritDoc}
* <p>
* Only the name is matched. Parameters are not included.
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MediaType mediaType = (MediaType) o;
return lowerName.equals(mediaType.lowerName);
}
@Override
public int hashCode() {
return lowerName.hashCode();
}
/**
* Returns the ordered media types for the given values.
* @param values The values
* @return The media types.
* @since 1.3.3
*/
public static List<MediaType> orderedOf(CharSequence... values) {
return orderedOf(Arrays.asList(values));
}
/**
* Returns the ordered media types for the given values.
* @param values The values
* @return The media types.
* @since 1.3.3
*/
public static List<MediaType> orderedOf(List<? extends CharSequence> values) {
if (values == null) {
return Collections.emptyList();
}
int headerCount = values.size();
if (headerCount == 0) {
return Collections.emptyList();
}
if (headerCount == 1) {
// fast path for single header with single media type
String singleHeader = values.get(0).toString();
if (singleHeader.indexOf(',') == -1) {
try {
return List.of(MediaType.of(singleHeader));
} catch (IllegalArgumentException ignored) {
}
}
}
var mediaTypes = new ArrayList<MediaType>(values.size());
for (CharSequence value : values) {
for (String token : StringUtils.splitOmitEmptyStrings(value, ',')) {
try {
mediaTypes.add(MediaType.of(token));
} catch (IllegalArgumentException e) {
// ignore
}
}
}
mediaTypes.sort(MediaType::naturalSort);
return Collections.unmodifiableList(mediaTypes);
}
private static int naturalSort(MediaType o1, MediaType o2) {
//The */* type is always last
boolean fullWildcard1 = o1.type.equals(WILDCARD);
boolean fullWildcard2 = o2.type.equals(WILDCARD);
if (fullWildcard1 && fullWildcard2) {
return 0;
} else if (fullWildcard1) {
return 1;
} else if (fullWildcard2) {
return -1;
}
if (o2.subtype.equals(WILDCARD) && !o1.subtype.equals(WILDCARD)) {
return -1;
} else if (o1.subtype.equals(WILDCARD) && !o2.subtype.equals(WILDCARD)) {
return 1;
}
return o2.getQualityAsNumber().compareTo(o1.getQualityAsNumber());
}
/**
* Create a new {@link MediaType} from the given text.
*
* @param mediaType The text
* @return The {@link MediaType}
*/
public static MediaType of(CharSequence mediaType) {
return MediaType.of(mediaType.toString());
}
/**
* Create a new {@link MediaType} from the given text.
*
* @param mediaType The text
* @return The {@link MediaType}
*/
public static MediaType[] of(CharSequence... mediaType) {
var types = new MediaType[mediaType.length];
for (int i = 0; i < mediaType.length; i++) {
types[i] = MediaType.of(mediaType[i].toString());
}
return types;
}
/**
* Resolve the {@link MediaType} produced by the given type based on the {@link Produces} annotation.
*
* @param type The type
* @return An {@link Optional} {@link MediaType}
*/
public static Optional<MediaType> fromType(Class<?> type) {
Produces producesAnn = type.getAnnotation(Produces.class);
if (producesAnn != null) {
String[] value = producesAnn.value();
if (ArrayUtils.isNotEmpty(value)) {
return Optional.of(MediaType.of(value[0]));
}
}
return Optional.empty();
}
/**
* Resolve the {@link MediaType} for the given file extension.
*
* @param extension The file extension
* @return The {@link MediaType}
*/
public static Optional<MediaType> forExtension(String extension) {
if (StringUtils.isNotEmpty(extension)) {
Map<String, String> extensions = getMediaTypeFileExtensions();
if (extensions != null) {
String type = extensions.get(extension);
if (type != null) {
return Optional.of(new MediaType(type, extension));
}
}
}
return Optional.empty();
}
/**
* Resolve the {@link MediaType} for the given file name. Defaults
* to text/plain.
*
* @param filename The file name
* @return The {@link MediaType}
*/
public static MediaType forFilename(String filename) {
if (StringUtils.isNotEmpty(filename)) {
return forExtension(NameUtils.extension(filename)).orElse(MediaType.TEXT_PLAIN_TYPE);
}
return MediaType.TEXT_PLAIN_TYPE;
}
@SuppressWarnings("MagicNumber")
private static Map<String, String> getMediaTypeFileExtensions() {
Map<String, String> extensions = mediaTypeFileExtensions;
if (extensions == null) {
synchronized (MediaType.class) { // double check
extensions = mediaTypeFileExtensions;
if (extensions == null) {
try {
extensions = loadMimeTypes();
mediaTypeFileExtensions = extensions;
} catch (Exception e) {
mediaTypeFileExtensions = Collections.emptyMap();
}
}
}
}
return extensions;
}
@SuppressWarnings("MagicNumber")
private static Map<String, String> loadMimeTypes() {
try (InputStream is = MediaType.class.getClassLoader().getResourceAsStream(MIME_TYPES_FILE_NAME)) {
var reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII));
var result = new LinkedHashMap<String, String>(100);
String line;
while ((line = reader.readLine()) != null) {
if (line.isEmpty() || line.charAt(0) == '#') {
continue;
}
String formattedLine = line.trim().replaceAll("\\s{2,}", " ").replaceAll("\\s", "|");
String[] tokens = formattedLine.split("\\|");
for (int i = 1; i < tokens.length; i++) {
String fileExtension = tokens[i].toLowerCase(Locale.ENGLISH);
result.put(fileExtension, tokens[0]);
}
}
return result;
} catch (IOException ex) {
Logger logger = LoggerFactory.getLogger(MediaType.class);
if (logger.isWarnEnabled()) {
logger.warn("Failed to load mime types for file extension detection!");
}
}
return Collections.emptyMap();
}
}