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