FileDocCategorySizeDatePackage
ParameterList.javaAPI DocGlassfish v2 API25035Mon May 14 15:28:50 BST 2007javax.mail.internet

ParameterList

public class ParameterList extends Object
This class holds MIME parameters (attribute-value pairs). The mail.mime.encodeparameters and mail.mime.decodeparameters System properties control whether encoded parameters, as specified by RFC 2231, are supported. By default, such encoded parameters are not supported.

Also, in the current implementation, setting the System property mail.mime.decodeparameters.strict to "true" will cause a ParseException to be thrown for errors detected while decoding encoded parameters. By default, if any decoding errors occur, the original (undecoded) string is used.

version
1.17, 07/05/04
author
John Mani
author
Bill Shannon

Fields Summary
private Map
list
The map of name, value pairs. The value object is either a String, for unencoded values, or a Value object, for encoded values, or a MultiValue object, for multi-segment parameters. We use a LinkedHashMap so that parameters are (as much as possible) kept in the original order. Note however that multi-segment parameters (see below) will appear in the position of the first seen segment and orphan segments will all move to the end.
private Set
multisegmentNames
A set of names for multi-segment parameters that we haven't processed yet. Normally such names are accumulated during the inital parse and processed at the end of the parse, but such names can also be set via the set method when the IMAP provider accumulates pre-parsed pieces of a parameter list. (A special call to the set method tells us when the IMAP provider is done setting parameters.) A multi-segment parameter is defined by RFC 2231. For example, "title*0=part1; title*1=part2", which represents a parameter named "title" with value "part1part2". Note also that each segment of the value might or might not be encoded, indicated by a trailing "*" on the parameter name. If any segment is encoded, the first segment must be encoded. Only the first segment contains the charset and language information needed to decode any encoded segments. RFC 2231 introduces many possible failure modes, which we try to handle as gracefully as possible. Generally, a failure to decode a parameter value causes the non-decoded parameter value to be used instead. Missing segments cause all later segments to be appear as independent parameters with names that include the segment number. For example, "title*0=part1; title*1=part2; title*3=part4" appears as two parameters named "title" and "title*3".
private Map
slist
A map containing the segments for all not-yet-processed multi-segment parameters. The map is indexed by "name*seg". The value object is either a String or a Value object. The Value object is not decoded during the initial parse because the segments may appear in any order and until the first segment appears we don't know what charset to use to decode any encoded segments. The segments are decoded in order in the combineMultisegmentNames method.
private String
lastName
MWB 3BView: The name of the last parameter added to the map. Used for the AppleMail hack.
private static boolean
encodeParameters
private static boolean
decodeParameters
private static boolean
decodeParametersStrict
private static boolean
applehack
private static final char[]
hex
Constructors Summary
public ParameterList()
No-arg Constructor.

 
	// initialize other collections only if they'll be needed
	if (decodeParameters) {
	    multisegmentNames = new HashSet();
	    slist = new HashMap();
	}
    
public ParameterList(String s)
Constructor that takes a parameter-list string. The String is parsed and the parameters are collected and stored internally. A ParseException is thrown if the parse fails. Note that an empty parameter-list string is valid and will be parsed into an empty ParameterList.

param
s the parameter-list string.
exception
ParseException if the parse fails.

	this();

	HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
	for (;;) {
	    HeaderTokenizer.Token tk = h.next();
	    int type = tk.getType();
	    String name, value;

	    if (type == HeaderTokenizer.Token.EOF) // done
		break;

	    if ((char)type == ';") {
		// expect parameter name
		tk = h.next();
		// tolerate trailing semicolon, even though it violates the spec
		if (tk.getType() == HeaderTokenizer.Token.EOF)
		    break;
		// parameter name must be a MIME Atom
		if (tk.getType() != HeaderTokenizer.Token.ATOM)
		    throw new ParseException("Expected parameter name, " +
					    "got \"" + tk.getValue() + "\"");
		name = tk.getValue().toLowerCase(Locale.ENGLISH);

		// expect '='
		tk = h.next();
		if ((char)tk.getType() != '=")
		    throw new ParseException("Expected '=', " +
					    "got \"" + tk.getValue() + "\"");

		// expect parameter value
		tk = h.next();
		type = tk.getType();
		// parameter value must be a MIME Atom or Quoted String
		if (type != HeaderTokenizer.Token.ATOM &&
		    type != HeaderTokenizer.Token.QUOTEDSTRING)
		    throw new ParseException("Expected parameter value, " +
					    "got \"" + tk.getValue() + "\"");

		value = tk.getValue();
		lastName = name;
		if (decodeParameters)
		    putEncodedName(name, value);
		else
		    list.put(name, value);
            } else {
		// MWB 3BView new code to add in filenames generated by 
		// AppleMail.
		// Note - one space is assumed between name elements.
		// This may not be correct but it shouldn't matter too much.
		// Note: AppleMail encodes filenames with non-ascii characters 
		// correctly, so we don't need to worry about the name* subkeys.
		if (applehack && type == HeaderTokenizer.Token.ATOM &&
			lastName != null &&
			(lastName.equals("name") ||
			 lastName.equals("filename"))) {
		    // Add value to previous value
		    String lastValue = (String)list.get(lastName);
		    value = lastValue + " " + tk.getValue();
		    list.put(lastName, value);
                } else {
		    throw new ParseException("Expected ';', " +
					    "got \"" + tk.getValue() + "\"");
		}
	    }
        }

	if (decodeParameters) {
	    /*
	     * After parsing all the parameters, combine all the
	     * multi-segment parameter values together.
	     */
	    combineMultisegmentNames(false);
	}
    
Methods Summary
private voidcombineMultisegmentNames(boolean keepConsistentOnFailure)
Iterate through the saved set of names of multi-segment parameters, for each parameter find all segments stored in the slist map, decode each segment as needed, combine the segments together into a single decoded value, and save all segments in a MultiValue object in the main list indexed by the parameter name.

	boolean success = false;
	try {
	    Iterator it = multisegmentNames.iterator();
	    while (it.hasNext()) {
		String name = (String)it.next();
		StringBuffer sb = new StringBuffer();
		MultiValue mv = new MultiValue();
		/*
		 * Now find all the segments for this name and
		 * decode each segment as needed.
		 */
		String charset = null;
		int segment;
		for (segment = 0; ; segment++) {
		    String sname = name + "*" + segment;
		    Object v = slist.get(sname);
		    if (v == null)	// out of segments
			break;
		    mv.add(v);
		    String value = null;
		    if (v instanceof Value) {
			try {
			    Value vv = (Value)v;
			    String evalue = vv.encodedValue;
			    value = evalue;		// in case of exception
			    if (segment == 0) {
				// the first segment specified the charset
				// for all other encoded segments
				Value vnew = decodeValue(evalue);
				charset = vv.charset = vnew.charset;
				value = vv.value = vnew.value;
			    } else {
				if (charset == null) {
				    // should never happen
				    multisegmentNames.remove(name);
				    break;
				}
				value = vv.value = decodeBytes(evalue, charset);
			    }
			} catch (NumberFormatException nex) {
			    if (decodeParametersStrict)
				throw new ParseException(nex.toString());
			} catch (UnsupportedEncodingException uex) {
			    if (decodeParametersStrict)
				throw new ParseException(uex.toString());
			} catch (StringIndexOutOfBoundsException ex) {
			    if (decodeParametersStrict)
				throw new ParseException(ex.toString());
			}
			// if anything went wrong decoding the value,
			// we just use the original value (set above)
		    } else {
			value = (String)v;
		    }
		    sb.append(value);
		    slist.remove(sname);
		}
		if (segment == 0) {
		    // didn't find any segments at all
		    list.remove(name);
		} else {
		    mv.value = sb.toString();
		    list.put(name, mv);
		}
	    }
	    success = true;
	} finally {
	    /*
	     * If we get here because of an exception that's going to
	     * be thrown (success == false) from the constructor
	     * (keepConsistentOnFailure == false), this is all wasted effort.
	     */
	    if (keepConsistentOnFailure || success)  {
		// we should never end up with anything in slist,
		// but if we do, add it all to list
		if (slist.size() > 0) {
		    // first, decode any values that we'll add to the list
		    Iterator sit = slist.values().iterator();
		    while (sit.hasNext()) {
			Object v = sit.next();
			if (v instanceof Value) {
			    Value vv = (Value)v;
			    Value vnew = decodeValue(vv.encodedValue);
			    vv.charset = vnew.charset;
			    vv.value = vnew.value;
			}
		    }
		    list.putAll(slist);
		}

		// clear out the set of names and segments
		multisegmentNames.clear();
		slist.clear();
	    }
	}
    
private static java.lang.StringdecodeBytes(java.lang.String value, java.lang.String charset)
Decode the encoded bytes in value using the specified charset.

	/*
	 * Decode the ASCII characters in value
	 * into an array of bytes, and then convert
	 * the bytes to a String using the specified
	 * charset.  We'll never need more bytes than
	 * encoded characters, so use that to size the
	 * array.
	 */
	byte[] b = new byte[value.length()];
	int i, bi;
	for (i = 0, bi = 0; i < value.length(); i++) {
	    char c = value.charAt(i);
	    if (c == '%") {
		String hex = value.substring(i + 1, i + 3);
		c = (char)Integer.parseInt(hex, 16);
		i += 2;
	    }
	    b[bi++] = (byte)c;
	}
	return new String(b, 0, bi, MimeUtility.javaCharset(charset));
    
private static javax.mail.internet.ParameterList$ValuedecodeValue(java.lang.String value)
Decode a parameter value.

	Value v = new Value();
	v.encodedValue = value;
	v.value = value;	// in case we fail to decode it
	try {
	    int i = value.indexOf('\'");
	    if (i <= 0) {
		if (decodeParametersStrict)
		    throw new ParseException(
			"Missing charset in encoded value: " + value);
		return v;	// not encoded correctly?  return as is.
	    }
	    String charset = value.substring(0, i);
	    int li = value.indexOf('\'", i + 1);
	    if (li < 0) {
		if (decodeParametersStrict)
		    throw new ParseException(
			"Missing language in encoded value: " + value);
		return v;	// not encoded correctly?  return as is.
	    }
	    String lang = value.substring(i + 1, li);
	    value = value.substring(li + 1);
	    v.charset = charset;
	    v.value = decodeBytes(value, charset);
	} catch (NumberFormatException nex) {
	    if (decodeParametersStrict)
		throw new ParseException(nex.toString());
	} catch (UnsupportedEncodingException uex) {
	    if (decodeParametersStrict)
		throw new ParseException(uex.toString());
	} catch (StringIndexOutOfBoundsException ex) {
	    if (decodeParametersStrict)
		throw new ParseException(ex.toString());
	}
	return v;
    
private static javax.mail.internet.ParameterList$ValueencodeValue(java.lang.String value, java.lang.String charset)
Encode a parameter value, if necessary. If the value is encoded, a Value object is returned. Otherwise, null is returned. XXX - Could return a MultiValue object if parameter value is too long.


                                          
           
	if (MimeUtility.checkAscii(value) == MimeUtility.ALL_ASCII)
	    return null;	// no need to encode it

	byte[] b;	// charset encoded bytes from the string
	try {
	    b = value.getBytes(MimeUtility.javaCharset(charset));
	} catch (UnsupportedEncodingException ex) {
	    return null;
	}
	StringBuffer sb = new StringBuffer(b.length + charset.length() + 2);
	sb.append(charset).append("''");
	for (int i = 0; i < b.length; i++) {
	    char c = (char)(b[i] & 0xff);
	    // do we need to encode this character?
	    if (c <= ' " || c >= 0x7f || c == '*" || c == '\'" || c == '%" ||
		    HeaderTokenizer.MIME.indexOf(c) >= 0) {
		sb.append('%").append(hex[c>>4]).append(hex[c&0xf]);
	    } else
		sb.append(c);
	}
	Value v = new Value();
	v.charset = charset;
	v.value = value;
	v.encodedValue = sb.toString();
	return v;
    
public java.lang.Stringget(java.lang.String name)
Returns the value of the specified parameter. Note that parameter names are case-insensitive.

param
name parameter name.
return
Value of the parameter. Returns null if the parameter is not present.

	String value;
	Object v = list.get(name.trim().toLowerCase(Locale.ENGLISH));
	if (v instanceof MultiValue)
	    value = ((MultiValue)v).value;
	else if (v instanceof Value)
	    value = ((Value)v).value;
	else
	    value = (String)v;
	return value;
    
public java.util.EnumerationgetNames()
Return an enumeration of the names of all parameters in this list.

return
Enumeration of all parameter names in this list.

	return new ParamEnum(list.keySet().iterator());
    
private voidputEncodedName(java.lang.String name, java.lang.String value)
If the name is an encoded or multi-segment name (or both) handle it appropriately, storing the appropriate String or Value object. Multi-segment names are stored in the main parameter list as an emtpy string as a placeholder, replaced later in combineMultisegmentNames with a MultiValue object. This causes all pieces of the multi-segment parameter to appear in the position of the first seen segment of the parameter.

	int star = name.indexOf('*");
	if (star < 0) {
	    // single parameter, unencoded value
	    list.put(name, value);
	} else if (star == name.length() - 1) {
	    // single parameter, encoded value
	    name = name.substring(0, star);
	    list.put(name, decodeValue(value));
	} else {
	    // multiple segments
	    String rname = name.substring(0, star);
	    multisegmentNames.add(rname);
	    list.put(rname, "");

	    Object v;
	    if (name.endsWith("*")) {
		// encoded value
		v = new Value();
		((Value)v).encodedValue = value;
		((Value)v).value = value;	// default; decoded later
		name = name.substring(0, name.length() - 1);
	    } else {
		// unencoded value
		v = value;
	    }
	    slist.put(name, v);
	}
    
private static java.lang.Stringquote(java.lang.String value)

	return MimeUtility.quote(value, HeaderTokenizer.MIME);
    
public voidremove(java.lang.String name)
Removes the specified parameter from this ParameterList. This method does nothing if the parameter is not present.

param
name name of the parameter.

	list.remove(name.trim().toLowerCase(Locale.ENGLISH));
    
public voidset(java.lang.String name, java.lang.String value)
Set a parameter. If this parameter already exists, it is replaced by this new value.

param
name name of the parameter.
param
value value of the parameter.

	// XXX - an incredible kludge used by the IMAP provider
	// to indicate that it's done setting parameters
	if (name == null && value != null && value.equals("DONE")) {
	    /*
	     * If we've accumulated any multi-segment names from calls to
	     * the set method from the IMAP provider, combine the pieces.
	     * Ignore any parse errors (e.g., from decoding the values)
	     * because it's too late to report them.
	     */
	    if (decodeParameters && multisegmentNames.size() > 0) {
		try {
		    combineMultisegmentNames(true);
		} catch (ParseException pex) {
		    // too late to do anything about it
		}
	    }
	    return;
	}
	name = name.trim().toLowerCase(Locale.ENGLISH);
	if (decodeParameters) {
	    try {
		putEncodedName(name, value);
	    } catch (ParseException pex) {
		// ignore it
		list.put(name, value);
	    }
	} else
	    list.put(name, value);
    
public voidset(java.lang.String name, java.lang.String value, java.lang.String charset)
Set a parameter. If this parameter already exists, it is replaced by this new value. If the mail.mime.encodeparameters System property is true, and the parameter value is non-ASCII, it will be encoded with the specified charset, as specified by RFC 2231.

param
name name of the parameter.
param
value value of the parameter.
param
charset charset of the parameter value.
since
JavaMail 1.4

	if (encodeParameters) {
	    Value ev = encodeValue(value, charset);
	    // was it actually encoded?
	    if (ev != null)
		list.put(name.trim().toLowerCase(Locale.ENGLISH), ev);
	    else
		set(name, value);
	} else
	    set(name, value);
    
public intsize()
Return the number of parameters in this list.

return
number of parameters.

	return list.size();
    
public java.lang.StringtoString()
Convert this ParameterList into a MIME String. If this is an empty list, an empty string is returned.

return
String

	return toString(0);
    
public java.lang.StringtoString(int used)
Convert this ParameterList into a MIME String. If this is an empty list, an empty string is returned. The 'used' parameter specifies the number of character positions already taken up in the field into which the resulting parameter list is to be inserted. It's used to determine where to fold the resulting parameter list.

param
used number of character positions already used, in the field into which the parameter list is to be inserted.
return
String

        ToStringBuffer sb = new ToStringBuffer(used);
        Iterator e = list.keySet().iterator();
 
        while (e.hasNext()) {
            String name = (String)e.next();
	    Object v = list.get(name);
	    if (v instanceof MultiValue) {
		MultiValue vv = (MultiValue)v;
		String ns = name + "*";
		for (int i = 0; i < vv.size(); i++) {
		    Object va = vv.get(i);
		    if (va instanceof Value)
			sb.addNV(ns + i + "*", ((Value)va).encodedValue);
		    else
			sb.addNV(ns + i, (String)va);
		}
	    } else if (v instanceof Value)
		sb.addNV(name + "*", ((Value)v).encodedValue);
	    else
		sb.addNV(name, (String)v);
        }
        return sb.toString();