1. /*
  2. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  3. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  4. */
  5. package javax.activation;
  6. import java.util.Hashtable;
  7. import java.util.Enumeration;
  8. /**
  9. * A parameter list of a MimeType
  10. * as defined in RFC 2045 and 2046. The Primary type of the
  11. * object must already be stripped off.
  12. *
  13. * @see javax.activation.MimeType
  14. */
  15. public class MimeTypeParameterList {
  16. private Hashtable parameters;
  17. /**
  18. * A string that holds all the special chars.
  19. */
  20. private static final String TSPECIALS = "()<>@,;:/[]?=\\\"";
  21. /**
  22. * Default constructor.
  23. */
  24. public MimeTypeParameterList() {
  25. parameters = new Hashtable();
  26. }
  27. /**
  28. * Constructs a new MimeTypeParameterList with the passed in data.
  29. *
  30. * @param parameterList an RFC 2045, 2046 compliant parameter list.
  31. */
  32. public MimeTypeParameterList(String parameterList)
  33. throws MimeTypeParseException {
  34. parameters = new Hashtable();
  35. // now parse rawdata
  36. parse(parameterList);
  37. }
  38. /**
  39. * A routine for parsing the parameter list out of a String.
  40. *
  41. * @param parameterList an RFC 2045, 2046 compliant parameter list.
  42. */
  43. protected void parse(String parameterList) throws MimeTypeParseException {
  44. if (parameterList == null)
  45. return;
  46. int length = parameterList.length();
  47. if (length <= 0)
  48. return;
  49. int i;
  50. char c;
  51. for (i = skipWhiteSpace(parameterList, 0);
  52. i < length && (c = parameterList.charAt(i)) == ';';
  53. i = skipWhiteSpace(parameterList, i)) {
  54. int lastIndex;
  55. String name;
  56. String value;
  57. // eat the ';'
  58. i++;
  59. // now parse the parameter name
  60. // skip whitespace
  61. i = skipWhiteSpace(parameterList, i);
  62. if (i >= length)
  63. throw new MimeTypeParseException(
  64. "Couldn't find parameter name");
  65. // find the end of the token char run
  66. lastIndex = i;
  67. while ((i < length) && isTokenChar(parameterList.charAt(i)))
  68. i++;
  69. name = parameterList.substring(lastIndex, i).toLowerCase();
  70. // now parse the '=' that separates the name from the value
  71. i = skipWhiteSpace(parameterList, i);
  72. if (i >= length || parameterList.charAt(i) != '=')
  73. throw new MimeTypeParseException(
  74. "Couldn't find the '=' that separates a " +
  75. "parameter name from its value.");
  76. // eat it and parse the parameter value
  77. i++;
  78. i = skipWhiteSpace(parameterList, i);
  79. if (i >= length)
  80. throw new MimeTypeParseException(
  81. "Couldn't find a value for parameter named " + name);
  82. // now find out whether or not we have a quoted value
  83. c = parameterList.charAt(i);
  84. if (c == '"') {
  85. // yup it's quoted so eat it and capture the quoted string
  86. i++;
  87. if (i >= length)
  88. throw new MimeTypeParseException(
  89. "Encountered unterminated quoted parameter value.");
  90. lastIndex = i;
  91. // find the next unescaped quote
  92. while (i < length) {
  93. c = parameterList.charAt(i);
  94. if (c == '"')
  95. break;
  96. if (c == '\\') {
  97. // found an escape sequence
  98. // so skip this and the
  99. // next character
  100. i++;
  101. }
  102. i++;
  103. }
  104. if (c != '"')
  105. throw new MimeTypeParseException(
  106. "Encountered unterminated quoted parameter value.");
  107. value = unquote(parameterList.substring(lastIndex, i));
  108. // eat the quote
  109. i++;
  110. } else if (isTokenChar(c)) {
  111. // nope it's an ordinary token so it
  112. // ends with a non-token char
  113. lastIndex = i;
  114. while (i < length && isTokenChar(parameterList.charAt(i)))
  115. i++;
  116. value = parameterList.substring(lastIndex, i);
  117. } else {
  118. // it ain't a value
  119. throw new MimeTypeParseException(
  120. "Unexpected character encountered at index " + i);
  121. }
  122. // now put the data into the hashtable
  123. parameters.put(name, value);
  124. }
  125. if (i < length) {
  126. throw new MimeTypeParseException(
  127. "More characters encountered in input than expected.");
  128. }
  129. }
  130. /**
  131. * Return the number of name-value pairs in this list.
  132. *
  133. * @return the number of parameters
  134. */
  135. public int size() {
  136. return parameters.size();
  137. }
  138. /**
  139. * Determine whether or not this list is empty.
  140. *
  141. * @return true if there are no parameters
  142. */
  143. public boolean isEmpty() {
  144. return parameters.isEmpty();
  145. }
  146. /**
  147. * Retrieve the value associated with the given name, or null if there
  148. * is no current association.
  149. *
  150. * @param name the parameter name
  151. * @return the parameter's value
  152. */
  153. public String get(String name) {
  154. return (String)parameters.get(name.trim().toLowerCase());
  155. }
  156. /**
  157. * Set the value to be associated with the given name, replacing
  158. * any previous association.
  159. *
  160. * @param name the parameter name
  161. * @param value the parameter's value
  162. */
  163. public void set(String name, String value) {
  164. parameters.put(name.trim().toLowerCase(), value);
  165. }
  166. /**
  167. * Remove any value associated with the given name.
  168. *
  169. * @param name the parameter name
  170. */
  171. public void remove(String name) {
  172. parameters.remove(name.trim().toLowerCase());
  173. }
  174. /**
  175. * Retrieve an enumeration of all the names in this list.
  176. *
  177. * @return an enumeration of all parameter names
  178. */
  179. public Enumeration getNames() {
  180. return parameters.keys();
  181. }
  182. /**
  183. * Return a string representation of this object.
  184. */
  185. public String toString() {
  186. StringBuffer buffer = new StringBuffer();
  187. buffer.ensureCapacity(parameters.size() * 16);
  188. // heuristic: 8 characters per field
  189. Enumeration keys = parameters.keys();
  190. while (keys.hasMoreElements()) {
  191. String key = (String)keys.nextElement();
  192. buffer.append("; ");
  193. buffer.append(key);
  194. buffer.append('=');
  195. buffer.append(quote((String)parameters.get(key)));
  196. }
  197. return buffer.toString();
  198. }
  199. // below here be scary parsing related things
  200. /**
  201. * Determine whether or not a given character belongs to a legal token.
  202. */
  203. private static boolean isTokenChar(char c) {
  204. return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
  205. }
  206. /**
  207. * return the index of the first non white space character in
  208. * rawdata at or after index i.
  209. */
  210. private static int skipWhiteSpace(String rawdata, int i) {
  211. int length = rawdata.length();
  212. while ((i < length) && Character.isWhitespace(rawdata.charAt(i)))
  213. i++;
  214. return i;
  215. }
  216. /**
  217. * A routine that knows how and when to quote and escape the given value.
  218. */
  219. private static String quote(String value) {
  220. boolean needsQuotes = false;
  221. // check to see if we actually have to quote this thing
  222. int length = value.length();
  223. for (int i = 0; (i < length) && !needsQuotes; i++) {
  224. needsQuotes = !isTokenChar(value.charAt(i));
  225. }
  226. if (needsQuotes) {
  227. StringBuffer buffer = new StringBuffer();
  228. buffer.ensureCapacity((int)(length * 1.5));
  229. // add the initial quote
  230. buffer.append('"');
  231. // add the properly escaped text
  232. for (int i = 0; i < length; ++i) {
  233. char c = value.charAt(i);
  234. if ((c == '\\') || (c == '"'))
  235. buffer.append('\\');
  236. buffer.append(c);
  237. }
  238. // add the closing quote
  239. buffer.append('"');
  240. return buffer.toString();
  241. } else {
  242. return value;
  243. }
  244. }
  245. /**
  246. * A routine that knows how to strip the quotes and
  247. * escape sequences from the given value.
  248. */
  249. private static String unquote(String value) {
  250. int valueLength = value.length();
  251. StringBuffer buffer = new StringBuffer();
  252. buffer.ensureCapacity(valueLength);
  253. boolean escaped = false;
  254. for (int i = 0; i < valueLength; ++i) {
  255. char currentChar = value.charAt(i);
  256. if (!escaped && (currentChar != '\\')) {
  257. buffer.append(currentChar);
  258. } else if (escaped) {
  259. buffer.append(currentChar);
  260. escaped = false;
  261. } else {
  262. escaped = true;
  263. }
  264. }
  265. return buffer.toString();
  266. }
  267. }