1. /*
  2. * @(#)MimeTypeParameterList.java 1.13 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.datatransfer;
  8. import java.util.Enumeration;
  9. import java.util.Hashtable;
  10. import java.util.Iterator;
  11. import java.util.Map;
  12. import java.util.Set;
  13. /**
  14. * An object that encapsualtes the parameter list of a MimeType
  15. * as defined in RFC 2045 and 2046.
  16. *
  17. * @version 1.13, 01/23/03
  18. * @author jeff.dunn@eng.sun.com
  19. */
  20. class MimeTypeParameterList implements Cloneable {
  21. /**
  22. * Default constructor.
  23. */
  24. public MimeTypeParameterList() {
  25. parameters = new Hashtable();
  26. }
  27. public MimeTypeParameterList(String rawdata)
  28. throws MimeTypeParseException
  29. {
  30. parameters = new Hashtable();
  31. // now parse rawdata
  32. parse(rawdata);
  33. }
  34. public int hashCode() {
  35. int code = Integer.MAX_VALUE45; // "random" value for empty lists
  36. String paramName = null;
  37. Enumeration enum = this.getNames();
  38. while (enum.hasMoreElements()) {
  39. paramName = (String)enum.nextElement();
  40. code += paramName.hashCode();
  41. code += this.get(paramName).hashCode();
  42. }
  43. return code;
  44. } // hashCode()
  45. /**
  46. * Two parameter lists are considered equal if they have exactly
  47. * the same set of parameter names and associated values. The
  48. * order of the parameters is not considered.
  49. */
  50. public boolean equals(Object thatObject) {
  51. //System.out.println("MimeTypeParameterList.equals("+this+","+thatObject+")");
  52. if (!(thatObject instanceof MimeTypeParameterList)) {
  53. return false;
  54. }
  55. MimeTypeParameterList that = (MimeTypeParameterList)thatObject;
  56. if (this.size() != that.size()) {
  57. return false;
  58. }
  59. String name = null;
  60. String thisValue = null;
  61. String thatValue = null;
  62. Set entries = parameters.entrySet();
  63. Iterator iterator = entries.iterator();
  64. Map.Entry entry = null;
  65. while (iterator.hasNext()) {
  66. entry = (Map.Entry)iterator.next();
  67. name = (String)entry.getKey();
  68. thisValue = (String)entry.getValue();
  69. thatValue = (String)that.parameters.get(name);
  70. if ((thisValue == null) || (thatValue == null)) {
  71. // both null -> equal, only one null -> not equal
  72. if (thisValue != thatValue) {
  73. return false;
  74. }
  75. } else if (!thisValue.equals(thatValue)) {
  76. return false;
  77. }
  78. } // while iterator
  79. return true;
  80. } // equals()
  81. /**
  82. * A routine for parsing the parameter list out of a String.
  83. */
  84. protected void parse(String rawdata) throws MimeTypeParseException {
  85. int length = rawdata.length();
  86. if(length > 0) {
  87. int currentIndex = skipWhiteSpace(rawdata, 0);
  88. int lastIndex = 0;
  89. if(currentIndex < length) {
  90. char currentChar = rawdata.charAt(currentIndex);
  91. while ((currentIndex < length) && (currentChar == ';')) {
  92. String name;
  93. String value;
  94. boolean foundit;
  95. // eat the ';'
  96. ++currentIndex;
  97. // now parse the parameter name
  98. // skip whitespace
  99. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  100. if(currentIndex < length) {
  101. // find the end of the token char run
  102. lastIndex = currentIndex;
  103. currentChar = rawdata.charAt(currentIndex);
  104. while((currentIndex < length) && isTokenChar(currentChar)) {
  105. ++currentIndex;
  106. currentChar = rawdata.charAt(currentIndex);
  107. }
  108. name = rawdata.substring(lastIndex, currentIndex).toLowerCase();
  109. // now parse the '=' that separates the name from the value
  110. // skip whitespace
  111. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  112. if((currentIndex < length) && (rawdata.charAt(currentIndex) == '=')) {
  113. // eat it and parse the parameter value
  114. ++currentIndex;
  115. // skip whitespace
  116. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  117. if(currentIndex < length) {
  118. // now find out whether or not we have a quoted value
  119. currentChar = rawdata.charAt(currentIndex);
  120. if(currentChar == '"') {
  121. // yup it's quoted so eat it and capture the quoted string
  122. ++currentIndex;
  123. lastIndex = currentIndex;
  124. if(currentIndex < length) {
  125. // find the next unescqped quote
  126. foundit = false;
  127. while((currentIndex < length) && !foundit) {
  128. currentChar = rawdata.charAt(currentIndex);
  129. if(currentChar == '\\') {
  130. // found an escape sequence so pass this and the next character
  131. currentIndex += 2;
  132. } else if(currentChar == '"') {
  133. // foundit!
  134. foundit = true;
  135. } else {
  136. ++currentIndex;
  137. }
  138. }
  139. if(currentChar == '"') {
  140. value = unquote(rawdata.substring(lastIndex, currentIndex));
  141. // eat the quote
  142. ++currentIndex;
  143. } else {
  144. throw new MimeTypeParseException("Encountered unterminated quoted parameter value.");
  145. }
  146. } else {
  147. throw new MimeTypeParseException("Encountered unterminated quoted parameter value.");
  148. }
  149. } else if(isTokenChar(currentChar)) {
  150. // nope it's an ordinary token so it ends with a non-token char
  151. lastIndex = currentIndex;
  152. foundit = false;
  153. while((currentIndex < length) && !foundit) {
  154. currentChar = rawdata.charAt(currentIndex);
  155. if(isTokenChar(currentChar)) {
  156. ++currentIndex;
  157. } else {
  158. foundit = true;
  159. }
  160. }
  161. value = rawdata.substring(lastIndex, currentIndex);
  162. } else {
  163. // it ain't a value
  164. throw new MimeTypeParseException("Unexpected character encountered at index " + currentIndex);
  165. }
  166. // now put the data into the hashtable
  167. parameters.put(name, value);
  168. } else {
  169. throw new MimeTypeParseException("Couldn't find a value for parameter named " + name);
  170. }
  171. } else {
  172. throw new MimeTypeParseException("Couldn't find the '=' that separates a parameter name from its value.");
  173. }
  174. } else {
  175. throw new MimeTypeParseException("Couldn't find parameter name");
  176. }
  177. // setup the next iteration
  178. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  179. if(currentIndex < length) {
  180. currentChar = rawdata.charAt(currentIndex);
  181. }
  182. }
  183. if(currentIndex < length) {
  184. throw new MimeTypeParseException("More characters encountered in input than expected.");
  185. }
  186. }
  187. }
  188. }
  189. /**
  190. * return the number of name-value pairs in this list.
  191. */
  192. public int size() {
  193. return parameters.size();
  194. }
  195. /**
  196. * Determine whether or not this list is empty.
  197. */
  198. public boolean isEmpty() {
  199. return parameters.isEmpty();
  200. }
  201. /**
  202. * Retrieve the value associated with the given name, or null if there
  203. * is no current association.
  204. */
  205. public String get(String name) {
  206. return (String)parameters.get(name.trim().toLowerCase());
  207. }
  208. /**
  209. * Set the value to be associated with the given name, replacing
  210. * any previous association.
  211. */
  212. public void set(String name, String value) {
  213. parameters.put(name.trim().toLowerCase(), value);
  214. }
  215. /**
  216. * Remove any value associated with the given name.
  217. */
  218. public void remove(String name) {
  219. parameters.remove(name.trim().toLowerCase());
  220. }
  221. /**
  222. * Retrieve an enumeration of all the names in this list.
  223. */
  224. public Enumeration getNames() {
  225. return parameters.keys();
  226. }
  227. public String toString() {
  228. StringBuffer buffer = new StringBuffer();
  229. buffer.ensureCapacity(parameters.size() * 16); // heuristic: 8 characters per field
  230. Enumeration keys = parameters.keys();
  231. while(keys.hasMoreElements())
  232. {
  233. buffer.append("; ");
  234. String key = (String)keys.nextElement();
  235. buffer.append(key);
  236. buffer.append('=');
  237. buffer.append(quote((String)parameters.get(key)));
  238. }
  239. return buffer.toString();
  240. }
  241. /**
  242. * @return a clone of this object
  243. */
  244. public Object clone() {
  245. MimeTypeParameterList newObj = null;
  246. try {
  247. newObj = (MimeTypeParameterList)super.clone();
  248. } catch (CloneNotSupportedException cannotHappen) {
  249. }
  250. newObj.parameters = (Hashtable)parameters.clone();
  251. return newObj;
  252. }
  253. private Hashtable parameters;
  254. // below here be scary parsing related things
  255. /**
  256. * Determine whether or not a given character belongs to a legal token.
  257. */
  258. private static boolean isTokenChar(char c) {
  259. return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
  260. }
  261. /**
  262. * return the index of the first non white space character in
  263. * rawdata at or after index i.
  264. */
  265. private static int skipWhiteSpace(String rawdata, int i) {
  266. int length = rawdata.length();
  267. if (i < length) {
  268. char c = rawdata.charAt(i);
  269. while ((i < length) && Character.isWhitespace(c)) {
  270. ++i;
  271. c = rawdata.charAt(i);
  272. }
  273. }
  274. return i;
  275. }
  276. /**
  277. * A routine that knows how and when to quote and escape the given value.
  278. */
  279. private static String quote(String value) {
  280. boolean needsQuotes = false;
  281. // check to see if we actually have to quote this thing
  282. int length = value.length();
  283. for(int i = 0; (i < length) && !needsQuotes; ++i) {
  284. needsQuotes = !isTokenChar(value.charAt(i));
  285. }
  286. if(needsQuotes) {
  287. StringBuffer buffer = new StringBuffer();
  288. buffer.ensureCapacity((int)(length * 1.5));
  289. // add the initial quote
  290. buffer.append('"');
  291. // add the properly escaped text
  292. for(int i = 0; i < length; ++i) {
  293. char c = value.charAt(i);
  294. if((c == '\\') || (c == '"')) {
  295. buffer.append('\\');
  296. }
  297. buffer.append(c);
  298. }
  299. // add the closing quote
  300. buffer.append('"');
  301. return buffer.toString();
  302. }
  303. else
  304. {
  305. return value;
  306. }
  307. }
  308. /**
  309. * A routine that knows how to strip the quotes and escape sequences from the given value.
  310. */
  311. private static String unquote(String value) {
  312. int valueLength = value.length();
  313. StringBuffer buffer = new StringBuffer();
  314. buffer.ensureCapacity(valueLength);
  315. boolean escaped = false;
  316. for(int i = 0; i < valueLength; ++i) {
  317. char currentChar = value.charAt(i);
  318. if(!escaped && (currentChar != '\\')) {
  319. buffer.append(currentChar);
  320. } else if(escaped) {
  321. buffer.append(currentChar);
  322. escaped = false;
  323. } else {
  324. escaped = true;
  325. }
  326. }
  327. return buffer.toString();
  328. }
  329. /**
  330. * A string that holds all the special chars.
  331. */
  332. private static final String TSPECIALS = "()<>@,;:\\\"/[]?=";
  333. }