001// Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
002// All rights reserved.  Use of this class is limited.
003// Please see the LICENSE for more information.
004
005package com.oreilly.servlet;
006
007import java.io.*;
008import java.util.*;
009import javax.servlet.*;
010
011/** 
012 * A class to simplify parameter handling.  It can return parameters of
013 * any primitive type (no casting or parsing required), can throw an 
014 * exception when a parameter is not found (simplifying error handling),
015 * and can accept default values (eliminating error handling).
016 * <p>
017 * It is used like this:
018 * <blockquote><pre>
019 * ParameterParser parser = new ParameterParser(req);
020 * &nbsp;
021 * float ratio = parser.getFloatParameter("ratio", 1.0);
022 * &nbsp;
023 * int count = 0;
024 * try {
025 *   count = parser.getIntParameter("count");
026 * }
027 * catch (NumberFormatException e) {
028 *   handleMalformedCount();
029 * }
030 * catch (ParameterNotFoundException e) {
031 *   handleNoCount();
032 * }
033 * </pre></blockquote>
034 *
035 * There's also a capability to find out if any required parameters are
036 * missing from a request:
037 * <blockquote><pre>
038 * ParameterParser parser = new ParameterParser(req);
039 * String[] required = { "fname", "lname", "account" };
040 * String[] missing = parser.getMissingParameters(required);
041 * </pre></blockquote>
042 *
043 * The default charset for input parameters is ISO-8859-1 (Latin-1).  
044 * If the parameter values are encoded in another format, specify that using
045 * setCharacterEncoding() before parsing.  The parameter names currently
046 * have to be in the Latin-1 character set:
047 * <blockquote><pre>
048 * ParameterParser parser = new ParameterParser(req);
049 * parser.setCharacterEncoding("Shift_JIS");
050 * String japaneseValue = parser.getStringParameter("latinName");
051 * </pre></blockquote>
052 *
053 * @see com.oreilly.servlet.ParameterNotFoundException
054 *
055 * @author <b>Jason Hunter</b>, Copyright &#169; 1998, 1999
056 * @version 1.4, 2000/12/14, better checking the selected encoding is valid in 
057 *                           setCharacterEncoding() thanks to Dewayne McNair
058 * @version 1.3, 2000/05/17, added setCharacterEncoding()
059 * @version 1.2, 2000/05/17, getBooleanParameter() now recognizes "on" and "yes"
060 * @version 1.1, 1999/12/20, added getMissingParameters() method
061 * @version 1.0, 1998/09/18
062 */
063public class ParameterParser {
064
065  private ServletRequest req;
066  private String encoding;
067
068  /**
069   * Constructs a new ParameterParser to handle the parameters of the
070   * given request.
071   *
072   * @param req the servlet request
073   */
074  public ParameterParser(ServletRequest req) {
075    this.req = req;
076  }
077
078  /**
079   * Sets the character encoding (charset) of the request to help the parser 
080   * properly decode parameter values.  The default is to return undecoded values,
081   * the same as would be returned by getParameter().
082   *
083   * @param encoding the charset of the request
084   * @exception UnsupportedEncodingException if the charset is not supported 
085   * on this sytem
086   */
087  public void setCharacterEncoding(String encoding) 
088                 throws UnsupportedEncodingException {
089    // Test the encoding is valid
090    new String("".getBytes("8859_1"), encoding);
091    // Getting here means we're valid, so set the encoding
092    this.encoding = encoding;
093  }
094
095  /**
096   * Gets the named parameter value as a String
097   *
098   * @param name the parameter name
099   * @return the parameter value as a String
100   * @exception ParameterNotFoundException if the parameter was not found
101   * or was the empty string
102   */
103  public String getStringParameter(String name)
104      throws ParameterNotFoundException {
105    String[] values = req.getParameterValues(name);
106    if (values == null) {
107      throw new ParameterNotFoundException(name + " not found");
108    }
109    else if (values[0].length() == 0) {
110      throw new ParameterNotFoundException(name + " was empty");
111    }
112    else {
113      if (encoding == null) {
114        return values[0];
115      }
116      else {
117        try {
118          return new String(values[0].getBytes("8859_1"), encoding);
119        }
120        catch (UnsupportedEncodingException e) {
121          return values[0];  // should never happen
122        }
123      }
124    }
125  }
126
127  /**
128   * Gets the named parameter value as a String, with a default.
129   * Returns the default value if the parameter is not found or 
130   * is the empty string.
131   * 
132   * @param name the parameter name
133   * @param def the default parameter value
134   * @return the parameter value as a String, or the default
135   */
136  public String getStringParameter(String name, String def) {
137    try { return getStringParameter(name); }
138    catch (Exception e) { return def; }
139  }
140
141  /**
142   * Gets the named parameter value as a boolean, with true indicated by
143   * "true", "on", or "yes" in any letter case, false indicated by "false", 
144   * "off", or "no" in any letter case.
145   *
146   * @param name the parameter name
147   * @return the parameter value as a boolean
148   * @exception ParameterNotFoundException if the parameter was not found
149   * @exception NumberFormatException if the parameter could not be converted 
150   * to a boolean
151   */
152  public boolean getBooleanParameter(String name)
153      throws ParameterNotFoundException, NumberFormatException {
154    String value = getStringParameter(name).toLowerCase();
155    if ((value.equalsIgnoreCase("true")) ||
156        (value.equalsIgnoreCase("on")) ||
157        (value.equalsIgnoreCase("yes"))) {
158        return true;
159    }
160    else if ((value.equalsIgnoreCase("false")) ||
161             (value.equalsIgnoreCase("off")) ||
162             (value.equalsIgnoreCase("no"))) {
163        return false;
164    }
165    else {
166      throw new NumberFormatException("Parameter " + name + " value " + value + 
167                                      " is not a boolean");
168    }
169  }
170
171  /**
172   * Gets the named parameter value as a boolean, with a default.
173   * Returns the default value if the parameter is not found.
174   * 
175   * @param name the parameter name
176   * @param def the default parameter value
177   * @return the parameter value as a boolean, or the default
178   */
179  public boolean getBooleanParameter(String name, boolean def) {
180    try { return getBooleanParameter(name); }
181    catch (Exception e) { return def; }
182  }
183
184  /**
185   * Gets the named parameter value as a byte
186   *
187   * @param name the parameter name
188   * @return the parameter value as a byte
189   * @exception ParameterNotFoundException if the parameter was not found
190   * @exception NumberFormatException if the parameter value could not
191   * be converted to a byte
192   */
193  public byte getByteParameter(String name)
194      throws ParameterNotFoundException, NumberFormatException {
195    return Byte.parseByte(getStringParameter(name));
196  }
197
198  /**
199   * Gets the named parameter value as a byte, with a default.
200   * Returns the default value if the parameter is not found or cannot
201   * be converted to a byte.
202   * 
203   * @param name the parameter name
204   * @param def the default parameter value
205   * @return the parameter value as a byte, or the default
206   */
207  public byte getByteParameter(String name, byte def) {
208    try { return getByteParameter(name); }
209    catch (Exception e) { return def; }
210  }
211
212  /**
213   * Gets the named parameter value as a char
214   *
215   * @param name the parameter name
216   * @return the parameter value as a char
217   * @exception ParameterNotFoundException if the parameter was not found
218   * or was the empty string
219   */
220  public char getCharParameter(String name)
221      throws ParameterNotFoundException {
222    String param = getStringParameter(name);
223    if (param.length() == 0)
224      throw new ParameterNotFoundException(name + " is empty string");
225    else
226      return (param.charAt(0));
227  }
228
229  /**
230   * Gets the named parameter value as a char, with a default.
231   * Returns the default value if the parameter is not found.
232   * 
233   * @param name the parameter name
234   * @param def the default parameter value
235   * @return the parameter value as a char, or the default
236   */
237  public char getCharParameter(String name, char def) {
238    try { return getCharParameter(name); }
239    catch (Exception e) { return def; }
240  }
241
242  /**
243   * Gets the named parameter value as a double
244   *
245   * @param name the parameter name
246   * @return the parameter value as a double
247   * @exception ParameterNotFoundException if the parameter was not found
248   * @exception NumberFormatException if the parameter could not be converted
249   * to a double
250   */
251  public double getDoubleParameter(String name)
252      throws ParameterNotFoundException, NumberFormatException {
253    return new Double(getStringParameter(name)).doubleValue();
254  }
255
256  /**
257   * Gets the named parameter value as a double, with a default.
258   * Returns the default value if the parameter is not found.
259   * 
260   * @param name the parameter name
261   * @param def the default parameter value
262   * @return the parameter value as a double, or the default
263   */
264  public double getDoubleParameter(String name, double def) {
265    try { return getDoubleParameter(name); }
266    catch (Exception e) { return def; }
267  }
268
269  /**
270   * Gets the named parameter value as a float
271   *
272   * @param name the parameter name
273   * @return the parameter value as a float
274   * @exception ParameterNotFoundException if the parameter was not found
275   * @exception NumberFormatException if the parameter could not be converted
276   * to a float
277   */
278  public float getFloatParameter(String name)
279      throws ParameterNotFoundException, NumberFormatException {
280    return new Float(getStringParameter(name)).floatValue();
281  }
282
283  /**
284   * Gets the named parameter value as a float, with a default.
285   * Returns the default value if the parameter is not found.
286   * 
287   * @param name the parameter name
288   * @param def the default parameter value
289   * @return the parameter value as a float, or the default
290   */
291  public float getFloatParameter(String name, float def) {
292    try { return getFloatParameter(name); }
293    catch (Exception e) { return def; }
294  }
295
296  /**
297   * Gets the named parameter value as a int
298   *
299   * @param name the parameter name
300   * @return the parameter value as a int
301   * @exception ParameterNotFoundException if the parameter was not found
302   * @exception NumberFormatException if the parameter could not be converted
303   * to a int
304   */
305  public int getIntParameter(String name)
306      throws ParameterNotFoundException, NumberFormatException {
307    return Integer.parseInt(getStringParameter(name));
308  }
309
310  /**
311   * Gets the named parameter value as a int, with a default.
312   * Returns the default value if the parameter is not found.
313   * 
314   * @param name the parameter name
315   * @param def the default parameter value
316   * @return the parameter value as a int, or the default
317   */
318  public int getIntParameter(String name, int def) {
319    try { return getIntParameter(name); }
320    catch (Exception e) { return def; }
321  }
322
323  /**
324   * Gets the named parameter value as a long
325   *
326   * @param name the parameter name
327   * @return the parameter value as a long
328   * @exception ParameterNotFoundException if the parameter was not found
329   * @exception NumberFormatException if the parameter could not be converted
330   * to a long
331   */
332  public long getLongParameter(String name)
333      throws ParameterNotFoundException, NumberFormatException {
334    return Long.parseLong(getStringParameter(name));
335  }
336
337  /**
338   * Gets the named parameter value as a long, with a default.
339   * Returns the default value if the parameter is not found.
340   * 
341   * @param name the parameter name
342   * @param def the default parameter value
343   * @return the parameter value as a long, or the default
344   */
345  public long getLongParameter(String name, long def) {
346    try { return getLongParameter(name); }
347    catch (Exception e) { return def; }
348  }
349
350  /**
351   * Gets the named parameter value as a short
352   *
353   * @param name the parameter name
354   * @return the parameter value as a short
355   * @exception ParameterNotFoundException if the parameter was not found
356   * @exception NumberFormatException if the parameter could not be converted
357   * to a short
358   */
359  public short getShortParameter(String name)
360      throws ParameterNotFoundException, NumberFormatException {
361    return Short.parseShort(getStringParameter(name));
362  }
363
364  /**
365   * Gets the named parameter value as a short, with a default.
366   * Returns the default value if the parameter is not found.
367   * 
368   * @param name the parameter name
369   * @param def the default parameter value
370   * @return the parameter value as a short, or the default
371   */
372  public short getShortParameter(String name, short def) {
373    try { return getShortParameter(name); }
374    catch (Exception e) { return def; }
375  }
376
377  /**
378   * Determines which of the required parameters were missing from the
379   * request.  Returns null if all the parameters are present.
380   * 
381   * @param an array of required parameters
382   * @return an array of missing parameters, or null if none are missing
383   */
384  public String[] getMissingParameters(String[] required) {
385    Vector missing = new Vector();
386    for (int i = 0; i < required.length; i++) {
387      String val = getStringParameter(required[i], null);
388      if (val == null) {
389        missing.addElement(required[i]);
390      }
391    }
392    if (missing.size() == 0) {
393      return null;
394    }
395    else {
396      String[] ret = new String[missing.size()];
397      missing.copyInto(ret);
398      return ret;
399    }
400  }
401}