/*
* 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.TypeHint;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.CollectionUtils;
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 javax.annotation.Nonnull;
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.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
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
/**
* 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.
/**
* Default file extension used for JSON.
*/
public static final String EXTENSION_JSON = "json";
Default file extension used for XML.
/**
* Default file extension used for XML.
*/
public static final String EXTENSION_XML = "xml";
Default file extension used for PDF.
/**
* Default file extension used for PDF.
*/
public static final String EXTENSION_PDF = "pdf";
Default empty media type array.
/**
* Default empty media type array.
*/
public static final MediaType[] EMPTY_ARRAY = new MediaType[0];
A wildcard media type representing all types.
/**
* A wildcard media type representing all types.
*/
public static final String ALL = "*/*";
A wildcard media type representing all types.
/**
* A wildcard media type representing all types.
*/
public static final MediaType ALL_TYPE = new MediaType(ALL, "all");
Form encoded data: application/x-www-form-urlencoded.
/**
* 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.
/**
* Form encoded data: application/x-www-form-urlencoded.
*/
public static final MediaType APPLICATION_FORM_URLENCODED_TYPE = new MediaType(APPLICATION_FORM_URLENCODED);
Short cut for APPLICATION_FORM_URLENCODED_TYPE
. /**
* Short cut for {@link #APPLICATION_FORM_URLENCODED_TYPE}.
*/
public static final MediaType FORM = APPLICATION_FORM_URLENCODED_TYPE;
Multi part form data: multipart/form-data.
/**
* Multi part form data: multipart/form-data.
*/
public static final String MULTIPART_FORM_DATA = "multipart/form-data";
Multi part 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);
HTML: text/html.
/**
* HTML: text/html.
*/
public static final String TEXT_HTML = "text/html";
HTML: text/html.
/**
* HTML: text/html.
*/
public static final MediaType TEXT_HTML_TYPE = new MediaType(TEXT_HTML);
XHTML: application/xhtml+xml.
/**
* XHTML: application/xhtml+xml.
*/
public static final String APPLICATION_XHTML = "application/xhtml+xml";
XHTML: application/xhtml+xml.
/**
* XHTML: application/xhtml+xml.
*/
public static final MediaType APPLICATION_XHTML_TYPE = new MediaType(APPLICATION_XHTML, "html");
XML: application/xml.
/**
* XML: application/xml.
*/
public static final String APPLICATION_XML = "application/xml";
XML: application/xml.
/**
* XML: application/xml.
*/
public static final MediaType APPLICATION_XML_TYPE = new MediaType(APPLICATION_XML);
JSON: application/json.
/**
* JSON: application/json.
*/
public static final String APPLICATION_JSON = "application/json";
JSON: application/json.
/**
* JSON: application/json.
*/
public static final MediaType APPLICATION_JSON_TYPE = new MediaType(MediaType.APPLICATION_JSON);
YAML: application/x-yaml.
/**
* YAML: application/x-yaml.
*/
public static final String APPLICATION_YAML = "application/x-yaml";
YAML: application/x-yaml.
/**
* YAML: application/x-yaml.
*/
public static final MediaType APPLICATION_YAML_TYPE = new MediaType(MediaType.APPLICATION_YAML);
XML: text/xml.
/**
* XML: text/xml.
*/
public static final String TEXT_XML = "text/xml";
XML: text/xml.
/**
* XML: text/xml.
*/
public static final MediaType TEXT_XML_TYPE = new MediaType(TEXT_XML);
JSON: text/json.
/**
* JSON: text/json.
*/
public static final String TEXT_JSON = "text/json";
JSON: text/json.
/**
* JSON: text/json.
*/
public static final MediaType TEXT_JSON_TYPE = new MediaType(TEXT_JSON);
Plain Text: text/plain.
/**
* Plain Text: text/plain.
*/
public static final String TEXT_PLAIN = "text/plain";
Plain Text: text/plain.
/**
* Plain Text: text/plain.
*/
public static final MediaType TEXT_PLAIN_TYPE = new MediaType(TEXT_PLAIN);
HAL JSON: application/hal+json.
/**
* HAL JSON: application/hal+json.
*/
public static final String APPLICATION_HAL_JSON = "application/hal+json";
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.
/**
* HAL XML: application/hal+xml.
*/
public static final String APPLICATION_HAL_XML = "application/hal+xml";
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.
/**
* Atom: application/atom+xml.
*/
public static final String APPLICATION_ATOM_XML = "application/atom+xml";
Atom: 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.
/**
* VND Error: application/vnd.error+json.
*/
public static final String APPLICATION_VND_ERROR = "application/vnd.error+json";
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);
Server Sent Event: text/event-stream.
/**
* Server Sent Event: text/event-stream.
*/
public static final String TEXT_EVENT_STREAM = "text/event-stream";
Server Sent Event: text/event-stream.
/**
* Server Sent Event: text/event-stream.
*/
public static final MediaType TEXT_EVENT_STREAM_TYPE = new MediaType(TEXT_EVENT_STREAM);
JSON Stream: application/x-json-stream.
/**
* JSON Stream: application/x-json-stream.
*/
public static final String APPLICATION_JSON_STREAM = "application/x-json-stream";
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.
/**
* BINARY: application/octet-stream.
*/
public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
BINARY: application/octet-stream.
/**
* BINARY: application/octet-stream.
*/
public static final MediaType APPLICATION_OCTET_STREAM_TYPE = new MediaType(APPLICATION_OCTET_STREAM);
GraphQL: application/graphql.
/**
* GraphQL: application/graphql.
*/
public static final String APPLICATION_GRAPHQL = "application/graphql";
GraphQL: application/graphql.
/**
* GraphQL: application/graphql.
*/
public static final MediaType APPLICATION_GRAPHQL_TYPE = new MediaType(APPLICATION_GRAPHQL);
PDF: application/pdf.
/**
* PDF: application/pdf.
*/
public static final String APPLICATION_PDF = "application/pdf";
PDF: application/pdf.
/**
* PDF: application/pdf.
*/
public static final MediaType APPLICATION_PDF_TYPE = new MediaType(APPLICATION_PDF);
Png Image: image/png.
/**
* Png Image: image/png.
*/
public static final String IMAGE_PNG = "image/png";
Png Image: image/png.
/**
* Png Image: image/png.
*/
public static final MediaType IMAGE_PNG_TYPE = new MediaType(IMAGE_PNG);
Jpeg Image: image/jpeg.
/**
* Jpeg Image: image/jpeg.
*/
public static final String IMAGE_JPEG = "image/jpeg";
Jpeg Image: image/jpeg.
/**
* Jpeg Image: image/jpeg.
*/
public static final MediaType IMAGE_JPEG_TYPE = new MediaType(IMAGE_JPEG);
Gif Image: image/gif.
/**
* Gif Image: image/gif.
*/
public static final String IMAGE_GIF = "image/gif";
Gif Image: image/gif.
/**
* Gif Image: image/gif.
*/
public static final MediaType IMAGE_GIF_TYPE = new MediaType(IMAGE_GIF);
Webp Image: image/webp.
/**
* Webp Image: image/webp.
*/
public static final String IMAGE_WEBP = "image/webp";
Webp Image: image/webp.
/**
* Webp Image: image/webp.
*/
public static final MediaType IMAGE_WEBP_TYPE = new MediaType(IMAGE_WEBP);
Parameter "charset"
. /**
* Parameter {@code "charset"}.
*/
public static final String CHARSET_PARAMETER = "charset";
Parameter "q"
. /**
* Parameter {@code "q"}.
*/
public static final String Q_PARAMETER = "q";
Parameter "v"
. /**
* 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 = ConversionContext.of(ARGUMENT);
private static final BigDecimal QUALITY_RATING_NUMBER = new BigDecimal("1.0");
private static final String QUALITY_RATING = "1.0";
private static final String SEMICOLON = ";";
@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 BigDecimal qualityNumberField;
static {
ConversionService.SHARED.addConverter(CharSequence.class, MediaType.class, charSequence -> {
if (StringUtils.isNotEmpty(charSequence)) {
return new MediaType(charSequence.toString());
}
return null;
}
);
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.
Params: - name – The name of the media type. For example application/json
/**
* Constructs a new media type for the given string.
*
* @param name The name of the media type. For example application/json
*/
public MediaType(String name) {
this(name, null, Collections.emptyMap());
}
Constructs a new media type for the given string and parameters.
Params: - name – The name of the media type. For example application/json
- params – The parameters
/**
* 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(String name, Map<String, String> params) {
this(name, null, params);
}
Constructs a new media type for the given string and extension.
Params: - name – The name of the media type. For example application/json
- extension – The extension of the file using this media type if it differs from the subtype
/**
* 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(String name, String extension) {
this(name, extension, Collections.emptyMap());
}
Constructs a new media type for the given string and extension.
Params: - name – The name of the media type. For example application/json
- extension – The extension of the file using this media type if it differs from the subtype
- params – The parameters
/**
* 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(String name, String extension, Map<String, String> params) {
if (name == null) {
throw new IllegalArgumentException("Argument [name] cannot be null");
}
name = name.trim();
String withoutArgs;
this.parameters = new LinkedHashMap<>();
if (name.contains(SEMICOLON)) {
String[] tokenWithArgs = name.split(SEMICOLON);
withoutArgs = tokenWithArgs[0];
String[] paramsList = Arrays.copyOfRange(tokenWithArgs, 1, tokenWithArgs.length);
for (String param : paramsList) {
int i = param.indexOf('=');
if (i > -1) {
parameters.put(param.substring(0, i).trim(), param.substring(i + 1).trim());
}
}
} else {
withoutArgs = name;
}
this.name = withoutArgs;
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) {
parameters.putAll(params);
}
this.strRepr = toString0();
}
Determine if this requested content type can be satisfied by a given content type. e.g. text/* will be satisfied by test/html.
Params: - expectedContentType – Content type to match against
Returns: if successful match
/**
* 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;
}
String expectedType = expectedContentType.getType();
String expectedSubtype = expectedContentType.getSubtype();
boolean typeMatch = type.equals("*") || type.equalsIgnoreCase(expectedType);
boolean subtypeMatch = subtype.equals("*") || subtype.equalsIgnoreCase(expectedSubtype);
return typeMatch && subtypeMatch;
}
Returns: The name of the mime type without any parameters
/**
* @return The name of the mime type without any parameters
*/
public String getName() {
return name;
}
Returns: The type of the media type. For example for application/hal+json this would return "application"
/**
* @return The type of the media type. For example for application/hal+json this would return "application"
*/
public String getType() {
return this.type;
}
Returns: The subtype. For example for application/hal+json this would return "hal+json"
/**
* @return The subtype. For example for application/hal+json this would return "hal+json"
*/
public String getSubtype() {
return this.subtype;
}
Returns: The extension. For example for application/hal+json this would return "json"
/**
* @return The extension. For example for application/hal+json this would return "json"
*/
public String getExtension() {
return extension;
}
Returns: The parameters to the media type
/**
* @return The parameters to the media type
*/
public OptionalValues<String> getParameters() {
return OptionalValues.of(String.class, parameters);
}
Returns: The quality of the Mime type
/**
* @return The quality of the Mime type
*/
public String getQuality() {
return parameters.getOrDefault("q", QUALITY_RATING);
}
Returns: The quality in BigDecimal form
/**
* @return The quality in BigDecimal form
*/
public BigDecimal getQualityAsNumber() {
if (this.qualityNumberField == null) {
this.qualityNumberField = getOrConvertQualityParameterToBigDecimal(this);
}
return this.qualityNumberField;
}
Returns: The version of the Mime type
/**
* @return The version of the Mime type
*/
public String getVersion() {
return parameters.getOrDefault(V_PARAMETER, null);
}
Returns: The charset of the media type if specified
/**
* @return The charset of the media type if specified
*/
public Optional<Charset> getCharset() {
return getParameters().get(CHARSET_PARAMETER).map(Charset::forName);
}
@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);
}
Returns: Whether the media type is text based
/**
* @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");
}
return matches;
}
Params: - contentType – The content type
Returns: Whether the content type is text based
/**
* @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 new MediaType(contentType).isTextBased();
} catch (IllegalArgumentException e) {
return false;
}
}
@Override
public String toString() {
return strRepr;
}
private String toString0() {
if (parameters.isEmpty()) {
return name;
} else {
return name + ";" + parameters.entrySet().stream().map(Object::toString)
.collect(Collectors.joining(";"));
}
}
{@inheritDoc}
Only the name is matched. Parameters are not included.
/**
* {@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 name.equalsIgnoreCase(mediaType.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
Returns the ordered media types for the given values.
Params: - values – The values
Returns: The media types. Since: 1.3.3
/**
* 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.
Params: - values – The values
Returns: The media types. Since: 1.3.3
/**
* 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 (CollectionUtils.isNotEmpty(values)) {
List<MediaType> mediaTypes = new ArrayList<>(values.size());
for (CharSequence value : values) {
final String[] tokens = value.toString().split(",");
for (String token : tokens) {
try {
mediaTypes.add(new MediaType(token));
} catch (IllegalArgumentException e) {
// ignore
}
}
}
mediaTypes.sort((o1, o2) -> {
//The */* type is always last
if (o1.type.equals("*")) {
return 1;
} else if (o2.type.equals("*")) {
return -1;
}
if (o2.subtype.equals("*") && !o1.subtype.equals("*")) {
return -1;
} else if (o1.subtype.equals("*") && !o2.subtype.equals("*")) {
return 1;
}
return o2.getQualityAsNumber().compareTo(o1.getQualityAsNumber());
});
return Collections.unmodifiableList(mediaTypes);
}
return Collections.emptyList();
}
Create a new MediaType
from the given text. Params: - mediaType – The text
Returns: The MediaType
/**
* Create a new {@link MediaType} from the given text.
*
* @param mediaType The text
* @return The {@link MediaType}
*/
public static MediaType of(CharSequence mediaType) {
return new MediaType(mediaType.toString());
}
Create a new MediaType
from the given text. Params: - mediaType – The text
Returns: The MediaType
/**
* Create a new {@link MediaType} from the given text.
*
* @param mediaType The text
* @return The {@link MediaType}
*/
public static MediaType[] of(CharSequence... mediaType) {
return Arrays.stream(mediaType).map(txt -> new MediaType(txt.toString())).toArray(MediaType[]::new);
}
Params: - type – The type
Returns: An Optional
MediaType
/**
* 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) {
return Arrays.stream(producesAnn.value()).findFirst().map(MediaType::new);
}
return Optional.empty();
}
Resolve the MediaType
for the given file extension. Params: - extension – The file extension
Returns: The MediaType
/**
* 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)) {
String type = getMediaTypeFileExtensions().get(extension);
if (type != null) {
return Optional.of(new MediaType(type, extension));
}
}
return Optional.empty();
}
Resolve the MediaType
for the given file name. Defaults to text/plain. Params: - filename – The file name
Returns: The MediaType
/**
* 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;
}
private BigDecimal getOrConvertQualityParameterToBigDecimal(MediaType mt) {
BigDecimal bd;
try {
String q = mt.parameters.getOrDefault(Q_PARAMETER, null);
if (q == null) {
return QUALITY_RATING_NUMBER;
} else {
bd = new BigDecimal(q);
}
return bd;
} catch (NumberFormatException e) {
bd = QUALITY_RATING_NUMBER;
return bd;
}
}
@SuppressWarnings("MagicNumber")
private static Map<String, String> loadMimeTypes() {
try (InputStream is = MediaType.class.getClassLoader().getResourceAsStream(MIME_TYPES_FILE_NAME)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII));
Map<String, String> result = new LinkedHashMap<>(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();
}
}