001    package org.apache.fulcrum.parser;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.beans.IndexedPropertyDescriptor;
023    import java.beans.Introspector;
024    import java.beans.PropertyDescriptor;
025    import java.io.UnsupportedEncodingException;
026    import java.lang.reflect.Method;
027    import java.math.BigDecimal;
028    import java.text.DateFormat;
029    import java.text.NumberFormat;
030    import java.text.ParseException;
031    import java.text.ParsePosition;
032    import java.util.Date;
033    import java.util.Hashtable;
034    import java.util.Iterator;
035    import java.util.Locale;
036    import java.util.Set;
037    
038    import org.apache.avalon.framework.logger.LogEnabled;
039    import org.apache.avalon.framework.logger.Logger;
040    import org.apache.commons.collections.iterators.ArrayIterator;
041    import org.apache.commons.lang.ArrayUtils;
042    import org.apache.commons.lang.StringUtils;
043    import org.apache.fulcrum.pool.Recyclable;
044    
045    /**
046     * BaseValueParser is a base class for classes that need to parse
047     * name/value Parameters, for example GET/POST data or Cookies
048     * (DefaultParameterParser and DefaultCookieParser)
049     *
050     * <p>It can also be used standalone, for an example see DataStreamParser.
051     *
052     * <p>NOTE: The name= portion of a name=value pair may be converted
053     * to lowercase or uppercase when the object is initialized and when
054     * new data is added.  This behaviour is determined by the url.case.folding
055     * property in TurbineResources.properties.  Adding a name/value pair may
056     * overwrite existing name=value pairs if the names match:
057     *
058     * <pre>
059     * ValueParser vp = new BaseValueParser();
060     * vp.add("ERROR",1);
061     * vp.add("eRrOr",2);
062     * int result = vp.getInt("ERROR");
063     * </pre>
064     *
065     * In the above example, result is 2.
066     *
067     * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
068     * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
069     * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
070     * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
071     * @author <a href="mailto:jh@byteaction.de">J&#252;rgen Hoffmann</a>
072     * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
073     * @version $Id: BaseValueParser.java 659983 2008-05-25 13:56:48Z tv $
074     */
075    public class BaseValueParser
076        implements ValueParser,
077                   Recyclable, ParserServiceSupport, LogEnabled
078    {
079        /** The ParserService instance to query for conversion and configuration */
080        protected ParserService parserService;
081    
082        /** A convenience logger */
083        private Logger logger;
084    
085        /** String values which would evaluate to Boolean.TRUE */
086        private static String[] trueValues = {"TRUE","T","YES","Y","1","ON"};
087    
088        /** String values which would evaluate to Boolean.FALSE */
089        private static String[] falseValues = {"FALSE","F","NO","N","0","OFF"};
090    
091        /**
092         * The character encoding to use when converting to byte arrays
093         */
094        private String characterEncoding = DEFAULT_CHARACTER_ENCODING;
095    
096        /**
097         * Random access storage for parameter data.
098         */
099        protected Hashtable parameters = new Hashtable();
100    
101        /** The locale to use when converting dates, floats and decimals */
102        private Locale locale = Locale.getDefault();
103        
104        /** The DateFormat to use for converting dates */
105        private DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
106        
107        /** The NumberFormat to use when converting floats and decimals */
108        private NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
109        
110        public BaseValueParser()
111        {
112            this(DEFAULT_CHARACTER_ENCODING);
113        }
114    
115        /**
116         * Constructor that takes a character encoding
117         */
118        public BaseValueParser(String characterEncoding)
119        {
120            this(characterEncoding, Locale.getDefault());
121        }
122    
123        /**
124         * Constructor that takes a character encoding and a locale
125         */
126        public BaseValueParser(String characterEncoding, Locale locale)
127        {
128            super();
129            recycle(characterEncoding);
130            setLocale(locale);
131        }
132        
133        /**
134         * Set a ParserService instance
135         */
136        public void setParserService(ParserService parserService)
137        {
138            this.parserService = parserService;
139            
140        }
141    
142        /**
143         * @see org.apache.avalon.framework.logger.LogEnabled#enableLogging(org.apache.avalon.framework.logger.Logger)
144         */
145        public void enableLogging(Logger logger)
146        {
147            this.logger = logger;
148        }
149    
150        /**
151         * Provide an Avalon logger to the derived classes
152         * 
153         * @return An Avalon logger instance
154         */
155        protected Logger getLogger()
156        {
157            return logger;
158        }
159        
160        /**
161         * Recycles the parser.
162         */
163        public void recycle()
164        {
165            recycle(DEFAULT_CHARACTER_ENCODING);
166        }
167    
168        /**
169         * Recycles the parser with a character encoding.
170         *
171         * @param characterEncoding the character encoding.
172         */
173        public void recycle(String characterEncoding)
174        {
175            setCharacterEncoding(characterEncoding);
176        }
177    
178        /**
179         * Disposes the parser.
180         */
181        public void dispose()
182        {
183            clear();
184            disposed = true;
185        }
186    
187        /**
188         * Clear all name/value pairs out of this object.
189         */
190        public void clear()
191        {
192            parameters.clear();
193        }
194    
195        /**
196         * Set the character encoding that will be used by this ValueParser.
197         */
198        public void setCharacterEncoding(String s)
199        {
200            characterEncoding = s;
201        }
202    
203        /**
204         * Get the character encoding that will be used by this ValueParser.
205         */
206        public String getCharacterEncoding()
207        {
208            return characterEncoding;
209        }
210    
211        /**
212         * Set the locale that will be used by this ValueParser.
213         */
214        public void setLocale(Locale l)
215        {
216            locale = l;
217            setDateFormat(DateFormat.getDateInstance(DateFormat.SHORT, locale));
218            setNumberFormat(NumberFormat.getNumberInstance(locale));
219        }
220        
221        /**
222         * Get the locale that will be used by this ValueParser.
223         */
224        public Locale getLocale()
225        {
226            return locale;
227        }
228    
229        /**
230         * Set the date format that will be used by this ValueParser.
231         */
232        public void setDateFormat(DateFormat df)
233        {
234            dateFormat = df;
235        }
236    
237        /**
238         * Get the date format that will be used by this ValueParser.
239         */
240        public DateFormat getDateFormat()
241        {
242            return dateFormat;
243        }
244    
245        /**
246         * Set the number format that will be used by this ValueParser.
247         */
248        public void setNumberFormat(NumberFormat nf)
249        {
250            numberFormat = nf;
251        }
252    
253        /**
254         * Get the number format that will be used by this ValueParser.
255         */
256        public NumberFormat getNumberFormat()
257        {
258            return numberFormat;
259        }
260        
261        /**
262         * Add a name/value pair into this object.
263         *
264         * @param name A String with the name.
265         * @param value A double with the value.
266         */
267        public void add(String name, double value)
268        {
269            add(name, numberFormat.format(value));
270        }
271    
272        /**
273         * Add a name/value pair into this object.
274         *
275         * @param name A String with the name.
276         * @param value An int with the value.
277         */
278        public void add(String name, int value)
279        {
280            add(name, (long)value);
281        }
282    
283        /**
284         * Add a name/value pair into this object.
285         *
286         * @param name A String with the name.
287         * @param value An Integer with the value.
288         */
289        public void add(String name, Integer value)
290        {
291            if (value != null)
292            {
293                add(name, value.intValue());
294            }
295        }
296    
297        /**
298         * Add a name/value pair into this object.
299         *
300         * @param name A String with the name.
301         * @param value A long with the value.
302         */
303        public void add(String name, long value)
304        {
305            add(name, Long.toString(value));
306        }
307    
308        /**
309         * Add a name/value pair into this object.
310         *
311         * @param name A String with the name.
312         * @param value A long with the value.
313         */
314        public void add(String name, String value)
315        {
316            if (value != null)
317            {
318                String [] items = getParam(name);
319                items = (String []) ArrayUtils.add(items, value);
320                putParam(name, items);
321            }
322        }
323    
324        /**
325         * Add an array of Strings for a key. This
326         * is simply adding all the elements in the
327         * array one by one.
328         *
329         * @param name A String with the name.
330         * @param value A String Array.
331         */
332        public void add(String name, String [] value)
333        {
334            // ArrayUtils.addAll() looks promising but it would also add
335            // null values into the parameters array, so we can't use that.
336            if (value != null)
337            {
338                for (int i = 0 ; i < value.length; i++)
339                {
340                    if (value[i] != null)
341                    {
342                        add(name, value[i]);
343                    }
344                }
345            }
346        }
347    
348        /**
349         * Removes the named parameter from the contained hashtable. Wraps to the
350         * contained <code>Map.remove()</code>.
351         *
352         * @return The value that was mapped to the key (a <code>String[]</code>)
353         *         or <code>null</code> if the key was not mapped.
354         */
355        public Object remove(String name)
356        {
357            return parameters.remove(convert(name));
358        }
359    
360        /**
361         * Trims the string data and applies the conversion specified in
362         * the property given by URL_CASE_FOLDING.  It returns a new
363         * string so that it does not destroy the value data.
364         *
365         * @param value A String to be processed.
366         * @return A new String converted to lowercase and trimmed.
367         */
368        public String convert(String value)
369        {
370            return convertAndTrim(value);
371        }
372    
373        /**
374         * Determine whether a given key has been inserted.  All keys are
375         * stored in lowercase strings, so override method to account for
376         * this.
377         *
378         * @param key An Object with the key to search for.
379         * @return True if the object is found.
380         */
381        public boolean containsKey(Object key)
382        {
383            return parameters.containsKey(convert(String.valueOf(key)));
384        }
385    
386        /**
387         * Gets the set of keys
388         *
389         * @return A <code>Set</code> of the keys.
390         */
391        public Set keySet()
392        {
393            return parameters.keySet();
394        }
395    
396        /**
397         * Returns all the available parameter names.
398         *
399         * @return A object array with the keys.
400         */
401        public Object[] getKeys()
402        {
403            return keySet().toArray();
404        }
405        
406        /**
407         * Returns a Boolean object for the given string. If the value
408         * can not be parsed as a boolean, null is returned.
409         * <p>
410         * Valid values for true: true, t, on, 1, yes, y<br>
411         * Valid values for false: false, f, off, 0, no, n<br>
412         * <p>
413         * The string is compared without reguard to case.
414         *
415         * @param string A String with the value.
416         * @return A Boolean.
417         */
418        private Boolean parseBoolean(String string)
419        {
420            Boolean result = null;
421            String value = StringUtils.trim(string);
422            
423            if (StringUtils.isNotEmpty(value))
424            {
425                for (int cnt = 0;
426                cnt < Math.max(trueValues.length, falseValues.length); cnt++)
427                {
428                    // Short-cut evaluation or bust!
429                    if ((cnt < trueValues.length) &&
430                       value.equalsIgnoreCase(trueValues[cnt]))
431                    {
432                        result = Boolean.TRUE;
433                        break;
434                    }
435    
436                    if ((cnt < falseValues.length) &&
437                       value.equalsIgnoreCase(falseValues[cnt]))
438                    {
439                        result = Boolean.FALSE;
440                        break;
441                    }
442                }
443                
444                if (result == null)
445                {
446                    if (getLogger().isWarnEnabled())
447                    {
448                        getLogger().warn("Parameter with value of ("
449                                + value + ") could not be converted to a Boolean");
450                    }
451                }
452            }
453            
454            return result;
455        }
456    
457        /**
458         * Return a boolean for the given name.  If the name does not
459         * exist, return defaultValue.
460         *
461         * @param name A String with the name.
462         * @param defaultValue The default value.
463         * @return A boolean.
464         */
465        public boolean getBoolean(String name, boolean defaultValue)
466        {
467            Boolean result = getBooleanObject(name);
468            return (result == null ? defaultValue : result.booleanValue());
469        }
470    
471        /**
472         * Return a boolean for the given name.  If the name does not
473         * exist, return false.
474         *
475         * @param name A String with the name.
476         * @return A boolean.
477         */
478        public boolean getBoolean(String name)
479        {
480            return getBoolean(name, false);
481        }
482    
483        /**
484         * Return an array of booleans for the given name.  If the name does
485         * not exist, return null.
486         *
487         * @param name A String with the name.
488         * @return A boolean[].
489         */
490        public boolean[] getBooleans(String name)
491        {
492            boolean[] result = null;
493            String value[] = getParam(name);
494            if (value != null)
495            {
496                result = new boolean[value.length];
497                for (int i = 0; i < value.length; i++)
498                {
499                    Boolean bool = parseBoolean(value[i]);
500                    result[i] = (bool == null ? false : bool.booleanValue());
501                }
502            }
503            return result;
504        }
505        
506        /**
507         * Returns a Boolean object for the given name.  If the parameter
508         * does not exist or can not be parsed as a boolean, null is returned.
509         * <p>
510         * Valid values for true: true, on, 1, yes<br>
511         * Valid values for false: false, off, 0, no<br>
512         * <p>
513         * The string is compared without reguard to case.
514         *
515         * @param name A String with the name.
516         * @return A Boolean.
517         */
518        public Boolean getBooleanObject(String name)
519        {
520            return parseBoolean(getString(name));
521        }
522    
523        /**
524         * Returns a Boolean object for the given name.  If the parameter
525         * does not exist or can not be parsed as a boolean, null is returned.
526         * <p>
527         * Valid values for true: true, on, 1, yes<br>
528         * Valid values for false: false, off, 0, no<br>
529         * <p>
530         * The string is compared without reguard to case.
531         *
532         * @param name A String with the name.
533         * @param defaultValue The default value.
534         * @return A Boolean.
535         */
536        public Boolean getBooleanObject(String name, Boolean defaultValue)
537        {
538            Boolean result = getBooleanObject(name);
539            return (result == null ? defaultValue : result);
540        }
541    
542        /**
543         * Return an array of Booleans for the given name.  If the name does
544         * not exist, return null.
545         *
546         * @param name A String with the name.
547         * @return A Boolean[].
548         */
549        public Boolean[] getBooleanObjects(String name)
550        {
551            Boolean[] result = null;
552            String value[] = getParam(name);
553            if (value != null)
554            {
555                result = new Boolean[value.length];
556                for (int i = 0; i < value.length; i++)
557                {
558                    result[i] = parseBoolean(value[i]);
559                }
560            }
561            return result;
562        }
563    
564        /**
565         * Return a {@link Number} for the given string.
566         *
567         * @param string A String with the value.
568         * @return A Number.
569         * 
570         */
571        private Number parseNumber(String string)
572        {
573            Number result = null;
574            String value = StringUtils.trim(string);
575    
576            if (StringUtils.isNotEmpty(value))
577            {
578                ParsePosition pos = new ParsePosition(0);
579                Number number = numberFormat.parse(value, pos);
580                
581                if (pos.getIndex() == value.length())
582                {
583                    // completely parsed
584                    result = number;
585                }
586                else
587                {
588                    if (getLogger().isWarnEnabled())
589                    {
590                        getLogger().warn("Parameter with value of ("
591                                + value + ") could not be converted to a Number at position " + pos.getIndex());
592                    }
593                }
594            }
595            
596            return result;
597        }
598    
599        /**
600         * Return a {@link Number} for the given name.  If the name does not
601         * exist, return null. This is the base function for all numbers.
602         *
603         * @param name A String with the name.
604         * @return A Number.
605         * 
606         */
607        private Number getNumber(String name)
608        {
609            return parseNumber(getString(name));
610        }
611    
612        /**
613         * Return a double for the given name.  If the name does not
614         * exist, return defaultValue.
615         *
616         * @param name A String with the name.
617         * @param defaultValue The default value.
618         * @return A double.
619         */
620        public double getDouble(String name, double defaultValue)
621        {
622            Number number = getNumber(name);
623            return (number == null ? defaultValue : number.doubleValue());
624        }
625    
626        /**
627         * Return a double for the given name.  If the name does not
628         * exist, return 0.0.
629         *
630         * @param name A String with the name.
631         * @return A double.
632         */
633        public double getDouble(String name)
634        {
635            return getDouble(name, 0.0);
636        }
637    
638        /**
639         * Return an array of doubles for the given name.  If the name does
640         * not exist, return null.
641         *
642         * @param name A String with the name.
643         * @return A double[].
644         */
645        public double[] getDoubles(String name)
646        {
647            double[] result = null;
648            String value[] = getParam(name);
649            if (value != null)
650            {
651                result = new double[value.length];
652                for (int i = 0; i < value.length; i++)
653                {
654                    Number number = parseNumber(value[i]);
655                    result[i] = (number == null ? 0.0 : number.doubleValue());
656                }
657            }
658            return result;
659        }
660    
661        /**
662         * Return a Double for the given name.  If the name does not
663         * exist, return defaultValue.
664         *
665         * @param name A String with the name.
666         * @param defaultValue The default value.
667         * @return A double.
668         */
669        public Double getDoubleObject(String name, Double defaultValue)
670        {
671            Number result = getNumber(name);
672            return (result == null ? defaultValue : new Double(result.doubleValue()));
673        }
674    
675        /**
676         * Return a Double for the given name.  If the name does not
677         * exist, return null.
678         *
679         * @param name A String with the name.
680         * @return A double.
681         */
682        public Double getDoubleObject(String name)
683        {
684            return getDoubleObject(name, null);
685        }
686    
687        /**
688         * Return an array of doubles for the given name.  If the name does
689         * not exist, return null.
690         *
691         * @param name A String with the name.
692         * @return A double[].
693         */
694        public Double[] getDoubleObjects(String name)
695        {
696            Double[] result = null;
697            String value[] = getParam(name);
698            if (value != null)
699            {
700                result = new Double[value.length];
701                for (int i = 0; i < value.length; i++)
702                {
703                    Number number = parseNumber(value[i]);
704                    result[i] = (number == null ? null : new Double(number.doubleValue()));
705                }
706            }
707            return result;
708        }
709    
710        /**
711         * Return a float for the given name.  If the name does not
712         * exist, return defaultValue.
713         *
714         * @param name A String with the name.
715         * @param defaultValue The default value.
716         * @return A float.
717         */
718        public float getFloat(String name, float defaultValue)
719        {
720            Number number = getNumber(name);
721            return (number == null ? defaultValue : number.floatValue());
722        }
723    
724        /**
725         * Return a float for the given name.  If the name does not
726         * exist, return 0.0.
727         *
728         * @param name A String with the name.
729         * @return A float.
730         */
731        public float getFloat(String name)
732        {
733            return getFloat(name, 0.0f);
734        }
735    
736        /**
737         * Return an array of floats for the given name.  If the name does
738         * not exist, return null.
739         *
740         * @param name A String with the name.
741         * @return A float[].
742         */
743        public float[] getFloats(String name)
744        {
745            float[] result = null;
746            String value[] = getParam(name);
747            if (value != null)
748            {
749                result = new float[value.length];
750                for (int i = 0; i < value.length; i++)
751                {
752                    Number number = parseNumber(value[i]);
753                    result[i] = (number == null ? 0.0f : number.floatValue());
754                }
755            }
756            return result;
757        }
758    
759        /**
760         * Return a Float for the given name.  If the name does not
761         * exist, return defaultValue.
762         *
763         * @param name A String with the name.
764         * @param defaultValue The default value.
765         * @return A Float.
766         */
767        public Float getFloatObject(String name, Float defaultValue)
768        {
769            Number result = getNumber(name);
770            return (result == null ? defaultValue : new Float(result.floatValue()));
771        }
772    
773        /**
774         * Return a float for the given name.  If the name does not
775         * exist, return null.
776         *
777         * @param name A String with the name.
778         * @return A Float.
779         */
780        public Float getFloatObject(String name)
781        {
782            return getFloatObject(name, null);
783        }
784    
785        /**
786         * Return an array of floats for the given name.  If the name does
787         * not exist, return null.
788         *
789         * @param name A String with the name.
790         * @return A float[].
791         */
792        public Float[] getFloatObjects(String name)
793        {
794            Float[] result = null;
795            String value[] = getParam(name);
796            if (value != null)
797            {
798                result = new Float[value.length];
799                for (int i = 0; i < value.length; i++)
800                {
801                    Number number = parseNumber(value[i]);
802                    result[i] = (number == null ? null : new Float(number.floatValue()));
803                }
804            }
805            return result;
806        }
807    
808        /**
809         * Return a BigDecimal for the given name.  If the name does not
810         * exist, return defaultValue.
811         *
812         * @param name A String with the name.
813         * @param defaultValue The default value.
814         * @return A BigDecimal.
815         */
816        public BigDecimal getBigDecimal(String name, BigDecimal defaultValue)
817        {
818            Number result = getNumber(name);
819            return (result == null ? defaultValue : new BigDecimal(result.doubleValue()));
820        }
821    
822        /**
823         * Return a BigDecimal for the given name.  If the name does not
824         * exist, return null.
825         *
826         * @param name A String with the name.
827         * @return A BigDecimal.
828         */
829        public BigDecimal getBigDecimal(String name)
830        {
831            return getBigDecimal(name, null);
832        }
833    
834        /**
835         * Return an array of BigDecimals for the given name.  If the name
836         * does not exist, return null.
837         *
838         * @param name A String with the name.
839         * @return A BigDecimal[].
840         */
841        public BigDecimal[] getBigDecimals(String name)
842        {
843            BigDecimal[] result = null;
844            String value[] = getParam(name);
845            if (value != null)
846            {
847                result = new BigDecimal[value.length];
848                for (int i = 0; i < value.length; i++)
849                {
850                    Number number = parseNumber(value[i]);
851                    result[i] = (number == null ? null : new BigDecimal(number.doubleValue()));
852                }
853            }
854            return result;
855        }
856    
857        /**
858         * Return an int for the given name.  If the name does not exist,
859         * return defaultValue.
860         *
861         * @param name A String with the name.
862         * @param defaultValue The default value.
863         * @return An int.
864         */
865        public int getInt(String name, int defaultValue)
866        {
867            Number result = getNumber(name);
868            return ((result == null || result instanceof Double) ? defaultValue : result.intValue());
869        }
870    
871        /**
872         * Return an int for the given name.  If the name does not exist,
873         * return 0.
874         *
875         * @param name A String with the name.
876         * @return An int.
877         */
878        public int getInt(String name)
879        {
880            return getInt(name, 0);
881        }
882    
883        /**
884         * Return an array of ints for the given name.  If the name does
885         * not exist, return null.
886         *
887         * @param name A String with the name.
888         * @return An int[].
889         */
890        public int[] getInts(String name)
891        {
892            int[] result = null;
893            String value[] = getParam(name);
894            if (value != null)
895            {
896                result = new int[value.length];
897                for (int i = 0; i < value.length; i++)
898                {
899                    Number number = parseNumber(value[i]);
900                    result[i] = ((number == null || number instanceof Double) ? 0 : number.intValue());
901                }
902            }
903            return result;
904        }
905    
906        /**
907         * Return an Integer for the given name.  If the name does not exist,
908         * return defaultValue.
909         *
910         * @param name A String with the name.
911         * @param defaultValue The default value.
912         * @return An Integer.
913         */
914        public Integer getIntObject(String name, Integer defaultValue)
915        {
916            Number result = getNumber(name);
917            return ((result == null || result instanceof Double) ? defaultValue : new Integer(result.intValue()));
918        }
919    
920        /**
921         * Return an Integer for the given name.  If the name does not exist,
922         * return null.
923         *
924         * @param name A String with the name.
925         * @return An Integer.
926         */
927        public Integer getIntObject(String name)
928        {
929            return getIntObject(name, null);
930        }
931    
932        /**
933         * Return an array of Integers for the given name.  If the name
934         * does not exist, return null.
935         *
936         * @param name A String with the name.
937         * @return An Integer[].
938         */
939        public Integer[] getIntObjects(String name)
940        {
941            Integer[] result = null;
942            String value[] = getParam(name);
943            if (value != null)
944            {
945                result = new Integer[value.length];
946                for (int i = 0; i < value.length; i++)
947                {
948                    Number number = parseNumber(value[i]);
949                    result[i] = ((number == null || number instanceof Double) ? null : new Integer(number.intValue()));
950                }
951            }
952            return result;
953        }
954    
955        /**
956         * Return a long for the given name.  If the name does not exist,
957         * return defaultValue.
958         *
959         * @param name A String with the name.
960         * @param defaultValue The default value.
961         * @return A long.
962         */
963        public long getLong(String name, long defaultValue)
964        {
965            Number result = getNumber(name);
966            return ((result == null || result instanceof Double) ? defaultValue : result.longValue());
967        }
968    
969        /**
970         * Return a long for the given name.  If the name does not exist,
971         * return 0.
972         *
973         * @param name A String with the name.
974         * @return A long.
975         */
976        public long getLong(String name)
977        {
978            return getLong(name, 0);
979        }
980    
981        /**
982         * Return an array of longs for the given name.  If the name does
983         * not exist, return null.
984         *
985         * @param name A String with the name.
986         * @return A long[].
987         */
988        public long[] getLongs(String name)
989        {
990            long[] result = null;
991            String value[] = getParam(name);
992            if (value != null)
993            {
994                result = new long[value.length];
995                for (int i = 0; i < value.length; i++)
996                {
997                    Number number = parseNumber(value[i]);
998                    result[i] = ((number == null || number instanceof Double) ? 0L : number.longValue());
999                }
1000            }
1001            return result;
1002        }
1003    
1004        /**
1005         * Return an array of Longs for the given name.  If the name does
1006         * not exist, return null.
1007         *
1008         * @param name A String with the name.
1009         * @return A Long[].
1010         */
1011        public Long[] getLongObjects(String name)
1012        {
1013            Long[] result = null;
1014            String value[] = getParam(name);
1015            if (value != null)
1016            {
1017                result = new Long[value.length];
1018                for (int i = 0; i < value.length; i++)
1019                {
1020                    Number number = parseNumber(value[i]);
1021                    result[i] = ((number == null || number instanceof Double) ? null : new Long(number.longValue()));
1022                }
1023            }
1024            return result;
1025        }
1026    
1027        /**
1028         * Return a Long for the given name.  If the name does
1029         * not exist, return null.
1030         *
1031         * @param name A String with the name.
1032         * @return A Long.
1033         */
1034        public Long getLongObject(String name)
1035        {
1036            return getLongObject(name, null);
1037        }
1038    
1039        /**
1040         * Return a Long for the given name.  If the name does
1041         * not exist, return the default value.
1042         *
1043         * @param name A String with the name.
1044         * @param defaultValue The default value.
1045         * @return A Long.
1046         */
1047        public Long getLongObject(String name, Long defaultValue)
1048        {
1049            Number result = getNumber(name);
1050            return ((result == null || result instanceof Double) ? defaultValue : new Long(result.longValue()));
1051        }
1052    
1053        /**
1054         * Return a byte for the given name.  If the name does not exist,
1055         * return defaultValue.
1056         *
1057         * @param name A String with the name.
1058         * @param defaultValue The default value.
1059         * @return A byte.
1060         */
1061        public byte getByte(String name, byte defaultValue)
1062        {
1063            Number result = getNumber(name);
1064            return ((result == null || result instanceof Double) ? defaultValue : result.byteValue());
1065        }
1066    
1067        /**
1068         * Return a byte for the given name.  If the name does not exist,
1069         * return 0.
1070         *
1071         * @param name A String with the name.
1072         * @return A byte.
1073         */
1074        public byte getByte(String name)
1075        {
1076            return getByte(name, (byte) 0);
1077        }
1078    
1079        /**
1080         * Return an array of bytes for the given name.  If the name does
1081         * not exist, return null. The array is returned according to the
1082         * HttpRequest's character encoding.
1083         *
1084         * @param name A String with the name.
1085         * @return A byte[].
1086         * @exception UnsupportedEncodingException
1087         */
1088        public byte[] getBytes(String name)
1089                throws UnsupportedEncodingException
1090        {
1091            byte result[] = null;
1092            String value = getString(name);
1093            if (value != null)
1094            {
1095                result = value.getBytes(getCharacterEncoding());
1096            }
1097            return result;
1098        }
1099    
1100        /**
1101         * Return a byte for the given name.  If the name does not exist,
1102         * return defaultValue.
1103         *
1104         * @param name A String with the name.
1105         * @param defaultValue The default value.
1106         * @return A byte.
1107         */
1108        public Byte getByteObject(String name, Byte defaultValue)
1109        {
1110            Number result = getNumber(name);
1111            return ((result == null || result instanceof Double) ? defaultValue : new Byte(result.byteValue()));
1112        }
1113    
1114        /**
1115         * Return a byte for the given name.  If the name does not exist,
1116         * return 0.
1117         *
1118         * @param name A String with the name.
1119         * @return A byte.
1120         */
1121        public Byte getByteObject(String name)
1122        {
1123            return getByteObject(name, null);
1124        }
1125    
1126        /**
1127         * Return a String for the given name.  If the name does not
1128         * exist, return null.
1129         *
1130         * @param name A String with the name.
1131         * @return A String or null if the key is unknown.
1132         */
1133        public String getString(String name)
1134        {
1135            String [] value = getParam(name);
1136    
1137            return (value == null
1138                    || value.length == 0)
1139                    ? null : value[0];
1140        }
1141    
1142        /**
1143         * Return a String for the given name.  If the name does not
1144         * exist, return null. It is the same as the getString() method
1145         * however has been added for simplicity when working with
1146         * template tools such as Velocity which allow you to do
1147         * something like this:
1148         *
1149         * <code>$data.Parameters.form_variable_name</code>
1150         *
1151         * @param name A String with the name.
1152         * @return A String.
1153         */
1154        public String get(String name)
1155        {
1156            return getString(name);
1157        }
1158    
1159        /**
1160         * Return a String for the given name.  If the name does not
1161         * exist, return the defaultValue.
1162         *
1163         * @param name A String with the name.
1164         * @param defaultValue The default value.
1165         * @return A String.
1166         */
1167        public String getString(String name, String defaultValue)
1168        {
1169            String value = getString(name);
1170    
1171            return (StringUtils.isEmpty(value) ? defaultValue : value );
1172        }
1173    
1174        /**
1175         * Set a parameter to a specific value.
1176         *
1177         * This is useful if you want your action to override the values
1178         * of the parameters for the screen to use.
1179         * @param name The name of the parameter.
1180         * @param value The value to set.
1181         */
1182        public void setString(String name, String value)
1183        {
1184            if (value != null)
1185            {
1186                putParam(name, new String[]{value});
1187            }
1188        }
1189    
1190        /**
1191         * Return an array of Strings for the given name.  If the name
1192         * does not exist, return null.
1193         *
1194         * @param name A String with the name.
1195         * @return A String[].
1196         */
1197        public String[] getStrings(String name)
1198        {
1199            return getParam(name);
1200        }
1201    
1202        /**
1203         * Return an array of Strings for the given name.  If the name
1204         * does not exist, return the defaultValue.
1205         *
1206         * @param name A String with the name.
1207         * @param defaultValue The default value.
1208         * @return A String[].
1209         */
1210        public String[] getStrings(String name, String[] defaultValue)
1211        {
1212            String[] value = getParam(name);
1213    
1214            return (value == null || value.length == 0)
1215                ? defaultValue : value;
1216        }
1217    
1218        /**
1219         * Set a parameter to a specific value.
1220         *
1221         * This is useful if you want your action to override the values
1222         * of the parameters for the screen to use.
1223         * @param name The name of the parameter.
1224         * @param values The value to set.
1225         */
1226        public void setStrings(String name, String[] values)
1227        {
1228            if (values != null)
1229            {
1230                putParam(name, values);
1231            }
1232        }
1233    
1234        /**
1235         * Return an Object for the given name.  If the name does not
1236         * exist, return null.
1237         *
1238         * @param name A String with the name.
1239         * @return An Object.
1240         */
1241        public Object getObject(String name)
1242        {
1243            return getString(name);
1244        }
1245    
1246        /**
1247         * Return an array of Objects for the given name.  If the name
1248         * does not exist, return null.
1249         *
1250         * @param name A String with the name.
1251         * @return An Object[].
1252         */
1253        public Object[] getObjects(String name)
1254        {
1255            return getParam(name);
1256        }
1257    
1258        /**
1259         * Returns a {@link java.util.Date} object.  String is parsed by supplied
1260         * DateFormat.  If the name does not exist or the value could not be
1261         * parsed into a date return the defaultValue.
1262         *
1263         * @param name A String with the name.
1264         * @param df A DateFormat.
1265         * @param defaultValue The default value.
1266         * @return A Date.
1267         */
1268        public Date getDate(String name, DateFormat df, Date defaultValue)
1269        {
1270            Date result = defaultValue;
1271            String value = StringUtils.trim(getString(name));
1272    
1273            if (StringUtils.isNotEmpty(value))
1274            {
1275                try
1276                {
1277                    // Reject invalid dates.
1278                    df.setLenient(false);
1279                    result = df.parse(value);
1280                }
1281                catch (ParseException e)
1282                {
1283                    logConversionFailure(name, value, "Date");
1284                }
1285            }
1286    
1287            return result;
1288        }
1289    
1290        /**
1291         * Returns a {@link java.util.Date} object.  If there are DateSelector or
1292         * TimeSelector style parameters then these are used.  If not and there
1293         * is a parameter 'name' then this is parsed by DateFormat.  If the
1294         * name does not exist, return null.
1295         *
1296         * @param name A String with the name.
1297         * @return A Date.
1298         */
1299        public Date getDate(String name)
1300        {
1301            return getDate(name, dateFormat, null);
1302        }
1303    
1304        /**
1305         * Returns a {@link java.util.Date} object.  String is parsed by supplied
1306         * DateFormat.  If the name does not exist, return null.
1307         *
1308         * @param name A String with the name.
1309         * @param df A DateFormat.
1310         * @return A Date.
1311         */
1312        public Date getDate(String name, DateFormat df)
1313        {
1314            return getDate(name, df, null);
1315        }
1316    
1317        /**
1318         * Uses bean introspection to set writable properties of bean from
1319         * the parameters, where a (case-insensitive) name match between
1320         * the bean property and the parameter is looked for.
1321         *
1322         * @param bean An Object.
1323         * @exception Exception a generic exception.
1324         */
1325        public void setProperties(Object bean) throws Exception
1326        {
1327            Class beanClass = bean.getClass();
1328            PropertyDescriptor[] props
1329                    = Introspector.getBeanInfo(beanClass).getPropertyDescriptors();
1330    
1331            for (int i = 0; i < props.length; i++)
1332            {
1333                String propname = props[i].getName();
1334                Method setter = props[i].getWriteMethod();
1335                if (setter != null && containsKey(propname))
1336                {
1337                    setProperty(bean, props[i]);
1338                }
1339            }
1340        }
1341    
1342        /**
1343         * Simple method that attempts to get a textual representation of
1344         * this object's name/value pairs.  String[] handling is currently
1345         * a bit rough.
1346         *
1347         * @return A textual representation of the parsed name/value pairs.
1348         */
1349        public String toString()
1350        {
1351            StringBuffer sb = new StringBuffer();
1352            for (Iterator iter = keySet().iterator(); iter.hasNext();)
1353            {
1354                String name = (String) iter.next();
1355    
1356                sb.append('{');
1357                sb.append(name);
1358                sb.append('=');
1359                Object [] params = getToStringParam(name);
1360    
1361                if (params == null)
1362                {
1363                    sb.append("unknown?");
1364                }
1365                else if (params.length == 0)
1366                {
1367                    sb.append("empty");
1368                }
1369                else
1370                {
1371                    sb.append('[');
1372                    for (Iterator it = new ArrayIterator(params); it.hasNext(); )
1373                    {
1374                        sb.append(it.next());
1375                        if (it.hasNext())
1376                        {
1377                            sb.append(", ");
1378                        }
1379                    }
1380                    sb.append(']');
1381                }
1382                sb.append("}\n");
1383            }
1384    
1385            return sb.toString();
1386        }
1387    
1388        /**
1389         * This method is only used in toString() and can be used by
1390         * derived classes to add their local parameters to the toString()
1391    
1392         * @param name A string with the name
1393         *
1394         * @return the value object array or null if not set
1395         */
1396        protected Object [] getToStringParam(final String name)
1397        {
1398            return getParam(name);
1399        }
1400    
1401        /**
1402         * Set the property 'prop' in the bean to the value of the
1403         * corresponding parameters.  Supports all types supported by
1404         * getXXX methods plus a few more that come for free because
1405         * primitives have to be wrapped before being passed to invoke
1406         * anyway.
1407         *
1408         * @param bean An Object.
1409         * @param prop A PropertyDescriptor.
1410         * @exception Exception a generic exception.
1411         */
1412        protected void setProperty(Object bean,
1413                                   PropertyDescriptor prop)
1414                throws Exception
1415        {
1416            if (prop instanceof IndexedPropertyDescriptor)
1417            {
1418                throw new Exception(prop.getName() +
1419                        " is an indexed property (not supported)");
1420            }
1421    
1422            Method setter = prop.getWriteMethod();
1423            if (setter == null)
1424            {
1425                throw new Exception(prop.getName() +
1426                        " is a read only property");
1427            }
1428    
1429            Class propclass = prop.getPropertyType();
1430            Object[] args = {null};
1431    
1432            if (propclass == String.class)
1433            {
1434                args[0] = getString(prop.getName());
1435            }
1436            else if (propclass == Byte.class || propclass == Byte.TYPE)
1437            {
1438                args[0] = getByteObject(prop.getName());
1439            }
1440            else if (propclass == Integer.class || propclass == Integer.TYPE)
1441            {
1442                args[0] = getIntObject(prop.getName());
1443            }
1444            else if (propclass == Long.class || propclass == Long.TYPE)
1445            {
1446                args[0] = getLongObject(prop.getName());
1447            }
1448            else if (propclass == Boolean.class || propclass == Boolean.TYPE)
1449            {
1450                args[0] = getBooleanObject(prop.getName());
1451            }
1452            else if (propclass == Double.class || propclass == Double.TYPE)
1453            {
1454                args[0] = getDoubleObject(prop.getName());
1455            }
1456            else if (propclass == Float.class || propclass == Float.TYPE)
1457            {
1458                args[0] = getFloatObject(prop.getName());
1459            }
1460            else if (propclass == BigDecimal.class)
1461            {
1462                args[0] = getBigDecimal(prop.getName());
1463            }
1464            else if (propclass == String[].class)
1465            {
1466                args[0] = getStrings(prop.getName());
1467            }
1468            else if (propclass == Object.class)
1469            {
1470                args[0] = getObject(prop.getName());
1471            }
1472            else if (propclass == int[].class)
1473            {
1474                args[0] = getInts(prop.getName());
1475            }
1476            else if (propclass == Integer[].class)
1477            {
1478                args[0] = getIntObjects(prop.getName());
1479            }
1480            else if (propclass == Date.class)
1481            {
1482                args[0] = getDate(prop.getName());
1483            }
1484            else
1485            {
1486                throw new Exception("property "
1487                        + prop.getName()
1488                        + " is of unsupported type "
1489                        + propclass.toString());
1490            }
1491    
1492            setter.invoke(bean, args);
1493        }
1494    
1495        /**
1496         * Puts a key into the parameters map. Makes sure that the name is always
1497         * mapped correctly. This method also enforces the usage of arrays for the
1498         * parameters.
1499         *
1500         * @param name A String with the name.
1501         * @param value An array of Objects with the values.
1502         *
1503         */
1504        protected void putParam(final String name, final String [] value)
1505        {
1506            String key = convert(name);
1507            if (key != null)
1508            {
1509                parameters.put(key, value);
1510            }
1511        }
1512    
1513        /**
1514         * fetches a key from the parameters map. Makes sure that the name is
1515         * always mapped correctly.
1516         *
1517         * @param name A string with the name
1518         *
1519         * @return the value object array or null if not set
1520         */
1521        protected String [] getParam(final String name)
1522        {
1523            String key = convert(name);
1524            Object value = parameters.get(key);
1525            
1526            // todo sgoeschl 20070405 quick fix for Scott's test case - need to
1527            // be reworked for proper functioning according to TV
1528            if(value instanceof String[])
1529            {
1530                return (String []) parameters.get(key);
1531            }
1532            else
1533            {
1534                return null;
1535            }
1536        }
1537    
1538    
1539        /** recyclable support **/
1540    
1541        /**
1542         * The disposed flag.
1543         */
1544        private boolean disposed;
1545    
1546        /**
1547         * Checks whether the object is disposed.
1548         *
1549         * @return true, if the object is disposed.
1550         */
1551        public boolean isDisposed()
1552        {
1553            return disposed;
1554        }
1555    
1556        /**
1557         * Writes a log message about a conversion failure.
1558         *
1559         * @param paramName name of the parameter which could not be converted
1560         * @param value value of the parameter
1561         * @param type target data type.
1562         */
1563        private void logConversionFailure(String paramName,
1564                                          String value, String type)
1565        {
1566            getLogger().warn("Parameter (" + paramName
1567                    + ") with value of ("
1568                    + value + ") could not be converted to a " + type);
1569        }
1570    
1571        /**
1572         * Convert a String value according to the url-case-folding property.
1573         *
1574         * @param value the String to convert
1575         *
1576         * @return a new String.
1577         *
1578         */
1579        public String convertAndTrim(String value)
1580        {
1581            return parserService.convertAndTrim(value);
1582        }
1583    
1584        /**
1585         * A convert method, which trims the string data and applies the 
1586         * conversion specified in the parameter given. It returns a new
1587         * string so that it does not destroy the value data.
1588         *
1589         * @param value A String to be processed.
1590         * @param fold The parameter folding to be applied 
1591         * (see {@link ParserService})
1592         * @return A new String converted to the correct case and trimmed.
1593         */
1594        public String convertAndTrim(String value, int fold)
1595        {
1596            return parserService.convertAndTrim(value, fold);
1597        }
1598    
1599        /**
1600         * Gets the folding value from the ParserService configuration
1601         *
1602         * @return The current Folding Value
1603         */
1604        public int getUrlFolding()
1605        {
1606            return parserService.getUrlFolding();
1607        }
1608    }