/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Mar 9, 2011
*/
package com.bigdata.util.httpd;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.log4j.Logger;
import com.bigdata.util.CaseInsensitiveStringComparator;
/**
* Parser for MIME type data. MIME type, subtype, and MIME parameter
* names are case-insensitive. Parameters values are treated as
* case-sensitive by this class, but subclasses may override the
* comparison logic for parameters, e.g., the "q" parameter in {@link
* AcceptMIMEType} has a default value and its values are compared
* after conversion into a floating point number.<p>
*
* @todo Consider requiring all default parameters to be made explicit
* during the constructor / parse of the MIMEType. It might greatly
* simplify the logic if we treated the "default" as a syntactic sugar
* that did NOT survive a round trip. Or survived by virtue of being
* marked with a 'defaultValue' flag on a per-parameter-type (or
* per-parameter-instance) basis by a subclass.<p>
*
* @todo IF MIME is constrained to not have duplicate parameter names
* then (a) we should check for that and throw an error when
* duplicates exist; and (b) we can change the {@link #getParams()}
* return value to an (immutable) {@link Map}. This sort of logic
* already partly exists in {@link #getIntersection( MIMEType other,
* String[] dropParams )}.
*
* @todo Consider migrating the NVPair[] to a Vector of NVPair in
* order to provide for a mutable implementation of {@link MIMEType},
* in which case we will also need a wrapper that provides an
* immutable view. However, I don't necessarily want to expose a
* mutable iterface as fat as {@link Vector}, so this may not be a
* good idea. See {@link AcceptMIMEType}.
*
* @todo Add support for mutable parameters, including addParam(
* String name, String value ), removeParam( String name ), and
* setParam( String name, String value ). If duplicate parameter
* names are not permitted by MIME, then perhaps we can simplify this
* interface. Derive an ImmutableMIMEType class for people who don't
* want their MIMEType expressions to be changable.
*/
public class MIMEType
{
/**
* The {@link Logger} for {@link MIMEType} operations. The {@link
* Logger} is named for this class.
*/
protected static final Logger log = Logger.getLogger
( MIMEType.class
);
String m_type;
String m_subtype;
NVPair[] m_params = EMPTY;
// Split out base value and parameters. These
// guidelines were extracted from 3.6 and 3.7 of the
// HTTP/1.1 specification.
//
// No LWS between type and subtype, both are required.
// Each parameter must specify an attribute and a
// value. No LWS between the attribute and the value.
// type/subtype [ ';' parameter ]*
//
// type := token
// subtype := token
// parameter := attribute '=' value
// attribute := token
// value := token | quoted-string
//
/**
* An empty NVPair[] used by the parser when there are no MIME
* parameters.
*/
final static NVPair[] EMPTY = new NVPair[]{};
/**
* Pattern used to match the type/subtype of a MIME expression.
* The matched groups are numbered by the opening parentheses in
* the pattern. The different matching groups are:<ul>
*
* <li> group(0) : the matched input.
*
* <li> group(1) : type
*
* <li> group(2) : subtype
*
* <li> group(3) : the rest of the input, to which you then apply
* {@link #m_p2}.
*
* </ul>
*/
static protected Pattern m_p1 = null;
/**
* Pattern used to match the optional parameters of a MIME
* expression. The matched groups are numbered by the opening
* parentheses in the pattern. The source for the pattern is the
* parameters as identified by {@link #m_p1}. The different
* matching groups are:<ul>
*
* <li> group( 0 ) : parameter ( attribute=value ).
*
* <li> group( 1 ) : attribute (parameter name).
*
* <li> group( 2 ) : value for that parameter.
*
* </ul>
*
* Note: You should match this pattern repeatedly until the input
* is exhausted.<p>
*/
static protected Pattern m_p2 = null;
/**
* Initialization for shared {@link Pattern} object that matches
* valid MIME type expressions.
*/
static protected void init()
{
try {
String tok = HTTPHeaderUtility.httpTokenPattern;
String qs = HTTPHeaderUtility.httpQuotedStringPattern;
if( m_p1 != null && m_p2 != null ) return;
m_p1 = Pattern.compile
( "^("+tok+")/("+tok+")(.*)$"
);
m_p2 = Pattern.compile
( "\\s*;\\s*("+tok+")=("+tok+"|"+qs+")\\s*"
);
}
catch( PatternSyntaxException ex ) {
/* Masquerade this exception so that it does not show up
* on method signatures throughout this and other
* packages. The decision to use java.util.regex here is
* an implementation decision and its exception signatures
* should not be propagated.
*/
AssertionError err = new AssertionError
( "Could not compile regex patterns."
);
err.initCause( ex );
throw err;
}
}
/**
* Returns the names of all default parameters recognized by this
* class for the MIME type and subtype represented by this {@link
* MIMEType} instances.
*
* @return The default implementation always returns an empty
* {@link String}[] since it does not recognize any default MIME
* type parameters. Implementors that subclass {@link MIMEType}
* MUST extend this method if the subclass recognizes any default
* MIME type parameters.
*/
public String[] getDefaultParamNames()
{
return new String[]{};
}
/**
* Returns true iff the named parameter should be ignored by the
* <code>spans</code> logic for MIME type and subtype represented
* by this {@link MIMEType} instance.
*
* @return The default implementation always returns
* <code>false</code>. Implementors that subclass {@link
* MIMEType} MUST extend this method if the subclass contains any
* parameters that should be ignored by the <code>spans</code>
* logic.
*/
public boolean isIgnoredParam
( String name
)
{
return false;
}
/**
* Clones the specified {@link MIMEType}.
*/
public MIMEType( MIMEType mt )
{
this( mt.toString() );
}
/**
* Constructs a {@link MIMEType} object from its component parts.
* The {@link MIMEType} will not have any MIME attributes.
*/
public MIMEType( String type, String subtype )
{
this( type, subtype, EMPTY );
}
/**
* Constructs a {@link MIMEType} object from its component
* parts.<p>
*
* @param type The media type or "*" (if the <i>subtype</i> if
* also "*").
*
* @param subtype The media subtype or "*".
*
* @param params An array of name-value pairs specifying the MIME
* attributes.
*
* @exception IllegalArgumentException If the method is unable to
* validate the syntax of the type, subtype and params.
* Everything must be an HTTP <code>token</code> except the
* attribute value, which is interchanged as an HTTP
* <code>quoted-string</code> any may contain pretty much any
* character sequence. Also thrown if <i>params</i> is
* <code>null</code>.
*/
public MIMEType
( String type,
String subtype,
NVPair[] params
)
throws IllegalArgumentException
{
init();
m_type = type;
m_subtype = subtype;
m_params = params;
if( ! HTTPHeaderUtility.isHttpToken( type ) ) {
throw new IllegalArgumentException
( "MIME type is not an HTTP token"+
" : '"+type+"'"
);
}
if( ! HTTPHeaderUtility.isHttpToken( subtype ) ) {
throw new IllegalArgumentException
( "MIME subtype is not an HTTP token"+
" : '"+subtype+"'"
);
}
if( params == null ) {
throw new IllegalArgumentException
( "params may not be null."
);
}
for( int i=0; i<params.length; i++ ) {
String attribute = params[i].getName();
String value = params[i].getValue();
if( ! HTTPHeaderUtility.isHttpToken( attribute ) ) {
throw new IllegalArgumentException
( "MIME attribute is not an HTTP token"+
" : '"+attribute+"'"
);
}
}
}
/**
* Constructor parses the string as a MIME Internet Type
* expression. The results of the parse are available from the
* various methods on the constructed object.<p>
*
* From the HTTP/1.1 specification: The type, subtype, and
* parameter attribute names are case-insensitive. Parameter
* values might or might not be casesensitive, depending on the
* semantics of the parameter name. Linear white space (LWS) MUST
* NOT be used between the type and subtype, nor between an
* attribute and its value.<p>
*
* @exception IllegalArgumentException Indicates that the
* string could not be parsed as a valid MIME expression.
*/
public MIMEType( String s )
throws IllegalArgumentException
{
init();
log.debug
( "Parsing: '"+s+"'"
);
Matcher m1 = m_p1.matcher( s );
if( ! m1.matches() ) {
throw new IllegalArgumentException
( "Can not parse '"+s+"' as a MIME string."
);
}
m_type = m1.group( 1 );
log.debug
( "type = '"+m_type+"'"
);
m_subtype = m1.group( 2 );
log.debug
( "subtype = '"+m_subtype+"'"
);
if( m_type.equals("*") && ! m_subtype.equals("*") ) {
throw new IllegalArgumentException
( "The mime type wildcard is '*/*'"+
", not "+m_type+"/"+m_subtype
);
}
String parameters = m1.group( 3 );
log.debug
( "parameters = '"+parameters+"'"
);
/* If there are no parameters then we are done.
*
* Note: leftover whitespace at this point is illegal.
*/
if( parameters.length() == 0 ) {
m_params = EMPTY;
return;
}
/* Repeatedly apply a pattern that matches on parameter each
* time.
*/
Vector v = new Vector();
Matcher m2 = m_p2.matcher( parameters );
int nextStart = 0;
log.debug
( "input length = "+parameters.length()
);
while( m2.find() ) {
log.debug
( "nextStart = "+nextStart+"\n"+
" start = "+m2.start()+"\n"+
" end = "+m2.end()+"\n"+
" match = '"+m2.group(0)+"'\n"+
"attribute = '"+m2.group(1)+"'\n"+
" value = '"+m2.group(2)+"'\n"
);
if( m2.start() != nextStart ) {
throw new AssertionError
( "Pattern matches are not contiguous"+
", data='"+s+"'"+
", position="+nextStart
);
}
nextStart = m2.end();
String attribute = m2.group( 1 );
String value = m2.group( 2 );
value = HTTPHeaderUtility.unquoteString( value );
log.debug
( "parameter : "+attribute+"="+value
);
v.add( new NVPair( attribute, value ) );
}
/* Make sure that nothing is left over.
*/
if( nextStart != parameters.length() ) {
throw new IllegalArgumentException
( "Pattern did not completely absorb input"+
", data='"+s+"'"+
", nextStart="+nextStart+
", inputLength="+parameters.length()
);
}
m_params = (NVPair[])v.toArray( EMPTY );
}
/**
* Returns the MIME type, eg, "text" for "text/plain".
*/
public String getType() {return m_type;}
/**
* Returns the MIME subtype, eg, "plain" for "text/plain".
*/
public String getSubtype() {return m_subtype;}
/**
* Returns true iff the MIME type is "*", indicating a MIME type
* wildcard.
*/
public boolean isTypeWildcard() {
return getType().equals("*");
}
/**
* Returns true iff the MIME subtype is "*", indicating a MIME
* wildcard.
*
* @deprecated Use {@link #isSubtypeWildcard()} instead.
*/
public boolean isWildcard() {
return isSubtypeWildcard();
}
/**
* Returns true iff the MIME subtype is "*", indicating a MIME
* subtype wildcard.
*/
public boolean isSubtypeWildcard() {
return getSubtype().equals("*");
}
/**
* Returns the MIME type and subtype as "type/subtype", but
* does not format in any MIME parameters.<p>
*
* Note: There is no LWS (linear whitespace) between the type
* and the subtype for a MIME string.
*
* @see #toString()
*/
public String getMimeType() {
return getType()+"/"+getSubtype();
}
/**
* Convenience method for {@link #matches( MIMEType mimeType,
* boolean matchParams )} that does NOT compare the MIME type
* parameters.
*/
public boolean matches( String mimeType )
throws IllegalArgumentException
{
// /* If the two string representations are the same then this is
// * a match. Otherwise we have to look further.
// */
// if( getMimeType().equalsIgnoreCase( mimeType ) ) {
// return true;
// }
/* Otherwise we have to parse the MIME type expression and
* compare the components directly.
*/
return matches( new MIMEType( mimeType ),
false
);
}
/**
* Convenience method for {@link #matches( MIMEType mimeType,
* boolean matchParams )} that does NOT compare the MIME type
* parameters.
*/
public boolean matches( MIMEType otherMimeType )
throws IllegalArgumentException
{
return matches( otherMimeType,
false
);
}
/**
* Convenience method compares type, subtype, and type parameters
*/
public boolean isExactMatch
( MIMEType otherType
)
{
return isExactMatch
( otherType,
true
);
}
/**
* Convenience method compares type, subtype, and type parameters
*/
public boolean isExactMatch
( String otherType
)
throws IllegalArgumentException
{
return isExactMatch
( new MIMEType( otherType ),
true
);
}
/**
* Returns true IFF the two MIME type expressions have the same
* meaning. I.e., they impose the same type and subtype
* constraint and, optionally, they impose the same MIME type
* parameter constraints (if any). The MIME type and subtype and
* MIME parameter names are compared using
* <strong>case-insensitive</strong> comparison.<p>
*
* Note: The MIME parameter values are compared using a
* case-sensitive comparison, which is not correct for all MIME
* types since some explictly provide for case-insensitive
* semantics for their parameter values. Implementors are free to
* further specialize this method in derived subclasses to obtain
* MIME type specific comparision of MIME type parameter values or
* to establish default parameter values.<p>
*
* Note: Defaults may be provided for MIME parameters through
* subclasses by extending {@link #sameParamValue ( String name,
* String realValue, String otherValue )}, {@link
* #getDefaultParamNames()}, and {@link #isIgnoredParamName(
* String name )}.<p>
*/
public boolean isExactMatch
( MIMEType t,
boolean compareParams
)
{
/* Compare the MIME type, MIME subtype since this is quick.
*/
if( ! getType().equalsIgnoreCase( t.getType() ) ) {
return false;
}
if( ! getSubtype().equalsIgnoreCase( t.getSubtype() ) ) {
return false;
}
if( compareParams ) {
/* Comparing parameters is not easy since there can be
* default parameters that are only recognized by specific
* subclasses. Therefore we use the spans semantics to
* conclude that A == B IFF A-spans-B and B-spans-A.
*/
if( this.spans( t, true ) && t.spans( this, true ) ) {
/* @todo Should we explictly test the isIgnoredParams
* here?
*/
return true;
} else {
return false;
}
} else {
return true;
}
// if( compareParams ) {
// /* Note: Do NOT test the count of parameters since a
// * subclass may recognized some default MIME type
// * parameters.
// */
// // /* Make sure that each parameter exists and has the same
// // * value.
// // */
// // if( getParamCount() != t.getParamCount() ) {
// // return false;
// // }
// NVPair[] params = getParams();
// for( int i=0; i<params.length; i++ ) {
// String attribute = params[i].getName();
// String realValue = params[i].getValue();
// String otherValue = t.getParamValue( attribute );
// if( realValue == null ) {
// return false;
// }
// if( ! sameParamValue( attribute, realValue, otherValue ) ) {
// return false;
// }
// }
// }
// return true;
}
/**
* Returns true IFF this {@link MIMEType} has the specified
* parameter value. This method is exposed to account for any
* MIME type specific case-sensitivity for the parameter name or
* parameter value and any default MIME type parameters.<p>
*
* The default implementation uses a case-sensitive comparison of
* the MIME parameter value. Implementors are free to created
* subclasses that extend this method to incorporate MIME type
* specific logic in their comparison.<p>
*
* @param name The name of the parameter whose value is being
* compared. This parameter is ignored by this implementation but
* is provided for subclasses that may have type specific value
* comparison logic and/or parameter value default logic.
*
* @param realValue The value of the parameter on this {@link
* MIMEType} object as discovered, e.g., by {@link #getParamValue(
* String name )}. This may explicitly be <code>null</code>.
* Subclasses that have a default value for this named parameter,
* e.g., {@link AcceptMIMEType} which has a default value for the
* "q" parameter, MUST substitute their default value when
* <i>realValue</i> is <code>null</code>.
*
* @param otberValue The value that is being compared to the
* <i>foundValue</i>. This parameter MAY be <code>null</code>, in
* which case a subclass MAY choose to declare a default value to
* be used in this comparison.
*
* @todo Consider defining this as spansParamValue so that it can
* be used to test constraints that define a range of values
* rather than just a point value.
*/
public boolean sameParamValue
( String name,
String realValue,
String otherValue
)
throws IllegalArgumentException
{
if( realValue == null && otherValue != null ) {
return false;
}
return realValue.equals // case-sensitive comparison.
( otherValue
);
}
/**
* This method may be extended to produce a specific
* represensation of the value of the indicated parameter. The
* representation returned will still be quoted iff necessary for
* HTTP by {@link #toString()} when serializing the entire header
* value.
*/
public String toString
( String name,
String value
)
{
return value;
}
//************************************************************
//************************************************************
//************************************************************
/**
* Returns the intersection of the this media-range and the given
* media-range. The resulting {@link MIMEType} MAY be a fully
* determined media type, but it MAY be undetermined, e.g., has a
* type or subtype wildcard, does not specify a content encoding,
* language family, etc. If this is NOT a fully determined media
* type, then the server MUST make it into a concrete media type.
*
* @param other The other media-range whose intersection with this
* media-range will be return.
*
* @param dropParams An array of zero or more parameter names that
* will be dropped out of the intersected media-ranges. For
* example, this is used to drop the "q" parameter during content
* negotiation when computing the negotiated joint media-range.
*
* @return The intersection of the two media-ranges or
* <code>null</code> if the intersection does not exist. For
* example, if the two media-ranges each specify different
* concrete subtypes, such as "text/xml" vs "text/plain".
*/
public MIMEType getIntersection
( MIMEType other,
String[] dropParams
)
{
if( other == null ) {
throw new IllegalArgumentException
( "The 'other' parameter may not be null."
);
}
if( dropParams == null ) {
throw new IllegalArgumentException
( "The 'dropParams' parameter may not be null."
);
}
/* If the types agree, then choose either one. If either's
* type is a wildcard, then choose the other's type.
* Otherwise there is no intersection.
*/
String type;
if( getType().equalsIgnoreCase( other.getType() ) ) {
type = getType();
} else if( isTypeWildcard() ) {
type = other.getType();
} else if( other.isTypeWildcard() ) {
type = getType();
} else {
return null; // no intersection !
}
/* If the subtypes agree, then choose either one. If either's
* subtype is a wildcard, then choose the other's subtype.
* Otherwise there is no intersection.
*/
String subtype;
if( getSubtype().equalsIgnoreCase( other.getSubtype() ) ) {
subtype = getSubtype();
} else if( isSubtypeWildcard() ) {
subtype = other.getSubtype();
} else if( other.isSubtypeWildcard() ) {
subtype = getSubtype();
} else {
return null; // no intersection !
}
/* Place all parameters into a shared Map, except any
* parameters that are being dropped. If the same parameter
* name occurs in both media-ranges, then the parameters MUST
* have the same value otherwise the intersection is
* null.
*/
Map params = new TreeMap // case-insensitive !!!
( new CaseInsensitiveStringComparator()
);
// Copy parameters from this media-range.
if( true ) { // local variables.
Iterator itr = Arrays.asList
( getParams()
).iterator()
;
while( itr.hasNext() ) {
NVPair param = (NVPair)itr.next();
String name = param.getName();
boolean dropThisParam = false;
for( int i=0; i<dropParams.length; i++ ) {
if( name.equalsIgnoreCase( dropParams[ i ] ) ) {
dropThisParam = true;
break;
}
}
if( ! dropThisParam ) {
params.put
( name,
param
);
}
}
}
/* Copy parameters from the 'other' media-range. The
* intersection fails if any shared parameter does not have
* the same value.
*/
if( true ) { // local variables.
Iterator itr = Arrays.asList
( other.getParams()
).iterator()
;
while( itr.hasNext() ) {
NVPair param = (NVPair)itr.next();
String name = param.getName();
boolean dropThisParam = false;
for( int i=0; i<dropParams.length; i++ ) {
if( name.equalsIgnoreCase( dropParams[ i ] ) ) {
dropThisParam = true;
break;
}
}
if( ! dropThisParam ) {
NVPair existingParam = (NVPair)params.get
( name
);
/* Make sure that this value does not create a
* conflict with any existing value for the same
* parameter.
*/
if( existingParam != null &&
! sameParamValue
( name,
existingParam.getValue(),
param.getValue()
) ) {
return null; // null intersection.
}
params.put
( name,
param
);
}
}
}
/* Build the new MIMEType from the intersection of the
* parameters.
*/
StringBuffer sb = new StringBuffer();
if( true ) { // local variables.
sb.append( type + "/" + subtype );
Iterator itr = params.entrySet().iterator();
while( itr.hasNext() ) {
Map.Entry entry = (Map.Entry)itr.next();
String name = (String)entry.getKey();
NVPair param = (NVPair)entry.getValue();
sb.append
( ";"+param.getName()+"="+param.getValue()
);
}
}
return new MIMEType
( sb.toString()
);
}
//************************************************************
//********************* SPAN LOGIC ***************************
//************************************************************
/**
* Convenience method compares type, subtype, and type parameters
*/
public boolean spans
( String other
)
{
return spans( other, true );
}
/**
* Convenience method compares type, subtype, and type parameters
*/
public boolean spans
( MIMEType other
)
{
return spans( other, true );
}
/**
* Convenience method compares type, subtype, and optionally any
* type parameters.
*/
public boolean spans
( String other,
boolean compareParams
)
{
return spans( new MIMEType( other ),
compareParams
);
}
/**
* Returns true IFF this {@link MIMEType} expresses a constraint
* that spans the <i>other</i> {@link MIMEType}. The semantics of
* "spans" are that the <i>other</i> may be either the same {@link
* MIMEType} or may be a more constrained (aka specific) {@link
* MIMEType}.
*
* @param compareParams IFF true the MIME type parameters are also
* compared by this method. The specific parameter values are
* compared using {@link #sameParamValue( String name, String
* realValue, String otherValue )}.
*/
public boolean spans
( MIMEType other,
boolean compareParams
)
{
/* A type wildcard (* / *) spans everything.
*/
if( isTypeWildcard() ) {
return true;
}
if( getType().equalsIgnoreCase( other.getType() ) &&
( getSubtype().equalsIgnoreCase( other.getSubtype() ) ||
isSubtypeWildcard()
)
) {
if( compareParams ) {
/* 1. This MIMEType may NOT specify any constraints
* that are not also found on the "other" MIMEType.
* E.g., "text/xml;a=b" does NOT span "text/xml".
*
* 2. The "other" MIMEType may specify additional
* constraints (as type parameters), but it may not
* specify any type parameter that is given a
* different value by this MIMEType.
*
* Note: We can NOT compare the #of parameters since
* subclasses may recognize default parameters.
*/
// if( getParamCount() > other.getParamCount() ) {
// return false;
// }
NVPair[] params = getParams();
for( int i=0; i<params.length; i++ ) {
String name = params[ i ].getName();
if( isIgnoredParam( name ) ) {
continue;
}
String value = params[ i ].getValue();
String otherValue = other.getParamValue
( name
);
// if( value == null && otherValue == null ) {
// continue; // only a flag without a value.
// }
if( ! sameParamValue( name, value, otherValue ) ) {
return false;
}
}
/* Now check each recognized default parameter name.
* If the default parameter is explictly bound then
* this was already checked about. However, it the
* parameter was not explicitly bound, then it needs
* to be tested here.
*/
String[] defaultNames = getDefaultParamNames();
for( int i=0; i<defaultNames.length; i++ ) {
String name = defaultNames[ i ];
if( isIgnoredParam( name ) ) {
continue;
}
String value = getParamValue
( name
);
String otherValue = other.getParamValue
( name
);
if( ! sameParamValue( name, value, otherValue ) ) {
return false;
}
}
}
return true;
}
return false;
}
/**
* Convenience method for <code>new MIMEType( other ).spans( this,
* true );</code>
*/
public boolean isSpannedBy
( String other
)
{
return new MIMEType( other ).spans( this, true );
}
/**
* Convenience method for <code>other.spans( this, true );</code>
*/
public boolean isSpannedBy
( MIMEType other
)
{
return other.spans( this, true );
}
/**
* Convenience method for <code>other.spans( this, compareParams );</code>
*/
public boolean isSpannedBy
( MIMEType other,
boolean compareParams
)
{
return other.spans( this, compareParams );
}
/**
* Matches if this {@link MIMEType} has the same type and subtype
* or if the types match and <i>this</i> {@link MIMEType} is a
* wildcard that covers the given <i>mimeType</i>. The parameters
* are optionally compared by this method.<p>
*
* @param compareParams IFF true the MIME type parameters are also
* compared by this method. The specific parameter values are
* compared using {@link #sameParamValue( String name, String
* realValue, String otherValue )}.
*
* Note: This is a convenience for <code>spans( other, false
* );</code>.
*
* @see #spans( MIMEType other, boolean compareParams )
*/
public boolean matches
( MIMEType other,
boolean compareParams
)
{
return spans( other, false );
// if( getType().equalsIgnoreCase( mimeType.getType() ) &&
// ( getSubtype().equalsIgnoreCase( mimeType.getSubtype() ) ||
// isSubtypeWildcard()
// )
// ) {
// return true;
// }
// return false;
}
/**
* Returns true iff the MIME type, MIME subtype and any MIME
* parameters are all the same as determined by {@link
* #isExactMatch( MIMEType other, boolean compareParams )} when
* <i>compareParams</i> is <code>true</code>.
*
* @param o Must be a {@link String} or {@link MIMEType} object.
*/
public boolean equals( Object o )
{
MIMEType t = null;
if( o instanceof String ) {
t = new MIMEType( (String) o );
} else if( o instanceof MIMEType ) {
t = (MIMEType) o;
} else {
throw new IllegalArgumentException
( "Can't compare "+o.getClass()
);
}
return isExactMatch
( t,
true
);
}
// /**
// * Returns an immutable {@link Iterator} that visits the MIME
// * parameters, each of which is a name-value pair represented
// * using an {@link NVPair} instance.<p>
// *
// * Note: If a subclass declares any default parameters, then it
// * MAY choose to return them here.<p>
// *
// * @return An immutable {@link Iterator}. If there are no MIME
// * parameters, then the {@link Iterator} will visit zero elements.
// */
// public Iterator getParams()
// {
// return Arrays.asList( m_params ).iterator();
// }
/**
* An array of the explicitly declared MIME type parameters.
* Default parameters are NOT represented in the returned array.
*
* @todo This returns the backing array. Should this interface be
* changed to increase the encapsulation? We MUST change this
* implementation if we want to enforce an immutable interface.
*/
public NVPair[] getParams()
{
return m_params;
}
/**
* Returns the nth MIME type parameters, origin zero.
*/
public NVPair getParam( int i ) {return m_params[ i ];}
/**
* Returns the #of MIME parameters.
*/
public int getParamCount() {return m_params.length;}
/**
* Returns the value for the indicated name and <code>null</code>
* if there is no MIME parameter with that name.<p>
*
* @param name The name of a MIME parameter, which is
* case-insensitive. Implementors can create subclasses if a
* specific MIME type declares some case-sensitive MIME type
* parameter names.<p>
*/
public String getParamValue
( String name
)
{
if( name == null ) {
throw new IllegalArgumentException
( "name may not be null."
);
}
NVPair[] params = getParams();
for( int i=0; i<params.length; i++ ) {
String attribute = params[i].getName();
if( name.equalsIgnoreCase( attribute ) ) {
return params[i].getValue();
}
}
return null;
}
/**
* Generates a MIME type string from the parsed data. If any
* parameter value is not a valid HTTP <code>token</code> then it
* is serialized as an HTTP <code>quoted-string</code>.<p>
*
* Note: There is no LWS (linear whitespace) between the type and
* the subtype for a MIME string. Also, there is no LWS between
* the attribute name and the attribute value, e.g., "charset=xxx"
* but not "charset = xxx".
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append( getMimeType() );
NVPair[] params = getParams();
for( int i=0; i<params.length; i++ ) {
String attribute = params[i].getName();
// Note: value represented as quoted-string iff
// necessary.
String value = HTTPHeaderUtility.quoteString
( params[i].getValue(),
false
);
// Note: no LWS between attribute and value.
sb.append( "; "+attribute+"="+value );
}
return sb.toString();
}
}