/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.civilian-framework.org/license.txt
*
* 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 org.civilian.content;
import java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import org.civilian.annotation.Consumes;
import org.civilian.annotation.Produces;
import org.civilian.util.Check;
/**
* ContentType represents the
* <a href="https://en.wikipedia.org/wiki/Internet_media_type">https://en.wikipedia.org/wiki/Internet_media_type</a>
* of request or response content.
*/
public class ContentType
{
private static final HashMap<String,ContentType> BUILT_INS = new HashMap<>();
/**
* The Types interface defines constants for the main part of important content types.
*/
public static interface Types
{
public static final String APPLICATION = "application";
public static final String AUDIO = "audio";
public static final String IMAGE = "image";
public static final String TEXT = "text";
public static final String VIDEO = "video";
}
/**
* The Strings interface defines constants for often used ContentType strings.
* You can use these in {@link Produces} or {@link Consumes} annotations.
*/
public static interface Strings
{
public static final String ANY = "*/*";
public static final String APPLICATION_EXCEL = Types.APPLICATION + '/' + "vnd.ms-excel";
public static final String APPLICATION_OCTET_STREAM = Types.APPLICATION + '/' + "octet-stream";
public static final String APPLICATION_JAVASCRIPT = Types.APPLICATION + '/' + "javascript";
public static final String APPLICATION_JSON = Types.APPLICATION + '/' + "json";
public static final String APPLICATION_PDF = Types.APPLICATION + '/' + "pdf";
public static final String APPLICATION_XML = Types.APPLICATION + '/' + "xml";
public static final String APPLICATION_X_MPEG = Types.APPLICATION + '/' + "x-mpeg";
public static final String APPLICATION_X_FLASH = Types.APPLICATION + '/' + "x-flash";
public static final String APPLICATION_X_WWW_FORM_URLENCODED
= Types.APPLICATION + '/' + "x-www-form-urlencoded";
public static final String TEXT_CSS = Types.TEXT + '/' + "css";
public static final String TEXT_HTML = Types.TEXT + '/' + "html";
public static final String TEXT_PLAIN = Types.TEXT + '/' + "plain";
public static final String TEXT_XML = Types.TEXT + '/' + "xml";
public static final String TEXT_X_TEMPLATE = Types.TEXT + '/' + "x-template";
public static final String TEXT_X_VCARD = Types.TEXT + '/' + "x-vcard";
public static final String IMAGE_GIF = Types.IMAGE + '/' + "gif";
public static final String IMAGE_PNG = Types.IMAGE + '/' + "png";
public static final String IMAGE_JPEG = Types.IMAGE + '/' + "jpeg";
public static final String VIDEO_X_FLV = Types.VIDEO + '/' + "x-flv";
}
/**
* ContentType("*/*")
*/
public static final ContentType ANY = builtin(Strings.ANY);
/**
* ContentType("application/vnd.ms-excel")
*/
public static final ContentType APPLICATION_EXCEL = builtin(Strings.APPLICATION_EXCEL);
/**
* ContentType("application/javascript")
*/
public static final ContentType APPLICATION_JAVASCRIPT = builtin(Strings.APPLICATION_JAVASCRIPT);
/**
* ContentType("application/json")
*/
public static final ContentType APPLICATION_JSON = builtin(Strings.APPLICATION_JSON);
/**
* ContentType("application/octet-stream")
*/
public static final ContentType APPLICATION_OCTET_STREAM = builtin(Strings.APPLICATION_OCTET_STREAM);
/**
* ContentType("application/pdf")
*/
public static final ContentType APPLICATION_PDF = builtin(Strings.APPLICATION_PDF);
/**
* ContentType("application/xml")
*/
public static final ContentType APPLICATION_XML = builtin(Strings.APPLICATION_XML);
/**
* ContentType("application/x-mpeg")
*/
public static final ContentType APPLICATION_X_MPEG = builtin(Strings.APPLICATION_X_MPEG);
/**
* ContentType("application/x-flash")
*/
public static final ContentType APPLICATION_X_FLASH = builtin(Strings.APPLICATION_X_FLASH);
/**
* ContentType("application/x-www-form-urlencoded")
*/
public static final ContentType APPLICATION_X_WWW_FORM_URLENCODED = builtin(Strings.APPLICATION_X_WWW_FORM_URLENCODED);
/**
* ContentType("text/css")
*/
public static final ContentType TEXT_CSS = builtin(Strings.TEXT_CSS);
/**
* ContentType("text/html")
*/
public static final ContentType TEXT_HTML = builtin(Strings.TEXT_HTML);
/**
* ContentType("text/plain")
*/
public static final ContentType TEXT_PLAIN = builtin(Strings.TEXT_PLAIN);
/**
* ContentType("text/xml")
*/
public static final ContentType TEXT_XML = builtin(Strings.TEXT_XML);
/**
* ContentType("text/x-template")
*/
public static final ContentType TEXT_X_TEMPLATE = builtin(Strings.TEXT_X_TEMPLATE);
/**
* ContentType("text/x-vcard")
*/
public static final ContentType TEXT_X_VCARD = builtin(Strings.TEXT_X_VCARD);
/**
* ContentType("image/gif")
*/
public static final ContentType IMAGE_GIF = builtin(Strings.IMAGE_GIF);
/**
* ContentType("image/png")
*/
public static final ContentType IMAGE_PNG = builtin(Strings.IMAGE_PNG);
/**
* ContentType("image/jpeg")
*/
public static final ContentType IMAGE_JPEG = builtin(Strings.IMAGE_JPEG);
/**
* ContentType("video/x-flv")
*/
public static final ContentType VIDEO_X_FLV = builtin(Strings.VIDEO_X_FLV);
/**
* Compare defines ContentType comparators.
*/
public static interface Compare
{
/**
* A comparator to sort ContentTypes by their quality, highest coming first.
*/
public static final Comparator<ContentType> BY_QUALITY = new QualityComparator();
/**
* A comparator to sort ContentTypes by how specific they are
* "main/sub" < "main/*" < "*/sub" < "*/*"
*/
public static final Comparator<ContentType> BY_SPECIFICITY = new SpecificityComparator();
}
/**
* Registers a builtin content type.
*/
private static final ContentType builtin(String s)
{
ContentType type = new ContentType(s, true);
BUILT_INS.put(s, type);
return type;
}
/**
* Returns a ContentType for the given string.
* If the string is null, then null is returned.
* If it is one of the predefined content types, the corresponding constant
* is returned, else a new content-type object is created.
*/
public static ContentType getContentType(String s)
{
ContentType contentType = null;
if (s != null)
{
contentType = BUILT_INS.get(s);
if (contentType == null)
contentType = new ContentType(s);
}
return contentType;
}
/**
* Creates a ContentType for the given string representation.
* @param value a string containing a slash, separating the type and subtype.
* The wildcard '*' is allowed for both type and subtype.
*/
public ContentType(String value)
{
this(value, false);
}
private ContentType(String value, boolean reuseValue)
{
Check.notNull(value, "value");
int p = value.indexOf('/');
if (p == -1)
throw new IllegalArgumentException("not a content type: " + value);
init(value.substring(0, p), value.substring(p + 1), reuseValue ? value : null);
}
/**
* Creates a ContentType for the given main part and sub part.
* '*' or null values are allowed and interpreted as wildcard.
*/
public ContentType(String mainPart, String subPart)
{
init(mainPart, subPart, null);
}
/**
* Creates a ContentType for the given main part, sub part and quality.
* @param quality a value >= 0.0
*/
public ContentType(String mainPart, String subPart, double quality)
{
this(mainPart, subPart);
quality_ = checkQuality(quality);
}
/**
* Creates a copy of a ContentType with the given quality.
*/
public ContentType(ContentType contentType, double quality)
{
mainPart_ = contentType.mainPart_;
subPart_ = contentType.subPart_;
value_ = contentType.value_;
quality_ = checkQuality(quality);
}
private void init(String mainPart, String subPart, String value)
{
mainPart_ = normPart(mainPart);
subPart_ = normPart(subPart);
if (value != null)
value_ = value;
else
value_ = (mainPart_ == null ? "*" : mainPart) + '/' + (subPart_ == null ? "*" : subPart_);
}
private static String normPart(String part)
{
if (part != null)
{
part = part.trim();
if ("*".equals(part))
part = null;
}
return part;
}
/**
* Returns the string representation of the ContentType.
*/
public String getValue()
{
return value_;
}
/**
* Returns if the string representation of the ContentType
* equals the given string.
*/
public boolean hasValue(String contentType)
{
return value_.equals(contentType);
}
/**
* Returns the main part of the ContentType, i.e. the part before the '/'.
* @return the main type or null, if not specified or specified as wildcard '*'.
*/
public String getMainPart()
{
return mainPart_;
}
/**
* Returns the sub part of the ContentType, i.e. the part after the '/'.
* @return the subtype or null, if not specified or specified as wildcard '*'.
*/
public String getSubPart()
{
return subPart_;
}
/**
* Returns if the main part of this ContentType matches the given string, i.e.
* if either this type or the given type is the wildcard or they are equal.
*/
public boolean matchesMainPart(String part)
{
return matchesPart(mainPart_, part);
}
/**
* Returns if the subtype of this ContentType matches the given subtype, i.e.
* if either this subtype or the given subtype is the wildcard or if they are equal.
*/
public boolean matchesSubPart(String part)
{
return matchesPart(subPart_, part);
}
/**
* Returns if the type and subtype of this ContentType and the other ContentType match.
*/
public boolean matches(ContentType contentType)
{
return matchesMainPart(contentType.getMainPart()) && matchesSubPart(contentType.getSubPart());
}
private static boolean matchesPart(String thisPart, String otherPart)
{
return (thisPart == otherPart) || (thisPart == null) || (otherPart == null) || thisPart.equals(otherPart) || normPart(otherPart) == null;
}
/**
* Returns if neither the main part nor sub part represent the wildcard.
*/
public boolean isConcrete()
{
return (mainPart_ != null) && (subPart_ != null);
}
/**
* Returns the quality (degradation) factor.
* For content-types used in a HTTP Accept header the quality is a number between 0 and 1. It
* codes the willigness of the client to accept a response with such content-type.
* For content-types used on the server side (when specified in a {@link Produces @Produces} annotation,
* it is a number >= 0. It can be used to boost specific content-types which have the same
* client quality.
*/
public double getQuality()
{
return quality_;
}
/**
* Returns a new ContentType with the given quality.
* @param quality a number >= 0.
*/
public ContentType withQuality(double quality)
{
return quality_ == quality ? this : new ContentType(this, quality);
}
protected double checkQuality(double quality)
{
if (quality < 0.0)
throw new IllegalArgumentException("quality must >= 0.0, but is " + quality);
return quality;
}
/**
* Returns the specificity of the content-type.
* @see #getSpecificity(String, String)
*/
public int getSpecificity()
{
return getSpecificity(mainPart_, subPart_);
}
/**
* Returns the specificity of a content-type with the given parts.
* It returns 0 for content-types */*<br>
* It returns 1 for content-types x/*<br>
* It returns 2 for content-types */y<br>
* It returns 3 for content-types x/y<br>
* @param mainPart the main part of a ContentType or null, if wildcard "*"
* @param subPart the sub part of a ContentType or null, if wildcard "*"
*/
public static int getSpecificity(String mainPart, String subPart)
{
if (mainPart == null)
return subPart == null ? 0 : 2;
else
return subPart == null ? 1 : 3;
}
/**
* Returns a hash code.
*/
@Override public int hashCode()
{
return value_.hashCode();
}
/**
* Tests if the given object represents the same content type string,
* but ignoring differences in quality or parameters.
*/
@Override public boolean equals(Object other)
{
return (other instanceof ContentType) && equals((ContentType)other, false);
}
/**
* Tests if the given ContentType represents the same content type string,
* @param compareQuality if true then the quality value of the ContentType is also
* included, to test if they are equal
*/
public boolean equals(ContentType other, boolean compareQuality)
{
return
(other != null) &&
value_.equals(other.value_) &&
(!compareQuality || (quality_ == other.quality_));
}
/**
* Returns the string representation of the ContentType. If the quality is not 1.0,
* the string representation contains a quality parameter.
*/
@Override public String toString()
{
String s = value_;
if (quality_ != 1.0)
s += "; q=" + quality_;
return s;
}
@SuppressWarnings("serial")
private static class QualityComparator implements Comparator<ContentType>, Serializable
{
@Override public int compare(ContentType t1, ContentType t2)
{
return (int)Math.signum(t2.getQuality() - t1.getQuality());
}
}
@SuppressWarnings("serial")
private static class SpecificityComparator implements Comparator<ContentType>, Serializable
{
@Override public int compare(ContentType t1, ContentType t2)
{
if ((t1.getMainPart() == null) != (t2.getMainPart() == null))
return t1.getMainPart() == null ? 1 : -1;
if ((t1.getSubPart() == null) != (t2.getSubPart() == null))
return t1.getSubPart() == null ? 1 : -1;
return 0;
}
}
private String mainPart_;
private String subPart_;
private String value_;
private double quality_ = 1.0;
}