001    /*--------------------------------------------------------------------------+
002    $Id: Options.java 26268 2010-02-18 10:44:30Z juergens $
003    |                                                                          |
004    | Copyright 2005-2010 Technische Universitaet Muenchen                     |
005    |                                                                          |
006    | Licensed under the Apache License, Version 2.0 (the "License");          |
007    | you may not use this file except in compliance with the License.         |
008    | You may obtain a copy of the License at                                  |
009    |                                                                          |
010    |    http://www.apache.org/licenses/LICENSE-2.0                            |
011    |                                                                          |
012    | Unless required by applicable law or agreed to in writing, software      |
013    | distributed under the License is distributed on an "AS IS" BASIS,        |
014    | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
015    | See the License for the specific language governing permissions and      |
016    | limitations under the License.                                           |
017    +--------------------------------------------------------------------------*/
018    package edu.tum.cs.commons.options;
019    
020    import java.io.FileInputStream;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.util.ArrayList;
024    import java.util.Iterator;
025    import java.util.Properties;
026    
027    import edu.tum.cs.commons.enums.EnumUtils;
028    
029    /**
030     * This class offers a safe and flexible interface to Java properties files.
031     * 
032     * Property files must follow the format for Java properties files. See Javadoc
033     * of {@link java.util.Properties} for details.
034     * 
035     * @author Florian Deissenboeck
036     * @author Axel Gerster
037     * @author $Author: juergens $
038     * 
039     * @version $Rev: 26268 $
040     * @levd.rating GREEN Hash: 9DD87B7C40B4E1B84D1F15B00CA3798A
041     * 
042     * @see java.util.Properties
043     */
044    public class Options {
045    
046        /**
047         * Returned by <code>countValues</code> when trying to count values of a
048         * non-present option.
049         */
050        public static final int OPTION_NOT_PRESENT = -1;
051    
052        /**
053         * This implementation is back by a <code>Properties</code> object.
054         */
055        private Properties properties;
056    
057        /**
058         * Construct a new <code>Option</code> object holding now options. Use
059         * methods {@link #init(String)}or {@link #setOption(String, String)}to
060         * store options.
061         */
062        public Options() {
063            init();
064        }
065    
066        /**
067         * This initalizes the <code>Options</code> object by reading a properties
068         * file.
069         * 
070         * @param filename
071         *            full-qualified name of the properties file
072         * @throws IOException
073         *             Thrown if an I/O problem is encountered while reading
074         *             properties file.
075         */
076        public void init(String filename) throws IOException {
077            properties = new Properties();
078            InputStream inputStream = new FileInputStream(filename);
079            properties.load(inputStream);
080            inputStream.close();
081        }
082    
083        /**
084         * Init empty <code>Options</code> object. Existing options are cleared.
085         */
086        public void init() {
087            properties = new Properties();
088        }
089    
090        /**
091         * Sets and option. Setting an already existing option overwrites current
092         * value.
093         * 
094         * @param option
095         *            name of the option
096         * @param value
097         *            option's value, must have same format as defined in the
098         *            properties file
099         * @return <code>true</code> if option was alreay present,
100         *         <code>false</code> otherwise
101         */
102        public boolean setOption(String option, String value) {
103            boolean overriden = hasOption(option);
104            properties.setProperty(option, value);
105            return overriden;
106        }
107    
108        /**
109         * Gets the value for a specified option.
110         * 
111         * @param option
112         *            the name of the option
113         * @return the option's value, if the option is not present or has a
114         *         <code>null</code> value <code>null</code> is returned. If the
115         *         option has a space separated value list, the whole list is
116         *         returned. Use {@link #getValues(String)}to access single values.
117         */
118        public String getValue(String option) {
119            if (!hasOption(option)) {
120                return null;
121            }
122            String value = properties.getProperty(option);
123    
124            if ("".equals(value)) {
125                return null;
126            }
127            return value;
128        }
129    
130        /**
131         * Return the value for a specified option or a default value if option is
132         * not present.
133         * 
134         * @param option
135         *            name of the option
136         * @param defaultValue
137         *            default value to use, if option is not present
138         * @return the option's value or the default value
139         */
140    
141        public String getValue(String option, String defaultValue) {
142            if (hasOption(option)) {
143                return getValue(option);
144            }
145            return defaultValue;
146        }
147    
148        /**
149         * Returns the space separated value of an option as array. A option might
150         * have more the one space separated value. This method returns them as an
151         * array. To allow values containing spaces use double quotes.
152         * <p>
153         * <i>Example: </i> For the following line in a properties file
154         * <p>
155         * <code>option=value1 value2 "value 3" value4</code>
156         * <p>
157         * the method returns this array <br/>
158         * 
159         * <code><br/>
160         * a[0] = &quot;value1&quot;<br/>
161         * a[1] = &quot;value2&quot;<br/>
162         * a[2] = &quot;value 3&quot;<br/>
163         * a[3] = &quot;value4&quot;<br/>
164         * </code>
165         * 
166         * @param option
167         *            name of the option
168         * @return the array as desribed above
169         */
170        public String[] getValues(String option) {
171            if (!hasOption(option)) {
172                return null;
173            }
174            String values = properties.getProperty(option);
175    
176            if ("".equals(values)) {
177                return null;
178            }
179    
180            return parse(values);
181        }
182    
183        /**
184         * Checks if the specified option is present and has a boolean value.
185         * Boolean values are <code>true</code>,<code>false</code>,
186         * <code>yes</code> and <code>no</code>
187         * 
188         * @param option
189         *            name of the option
190         * @return if the option is present and has a boolean value
191         *         <code>true</code> is returned, otherwise <code>false</code>
192         */
193        public boolean hasBooleanValue(String option) {
194            if (!hasValue(option)) {
195                return false;
196            }
197    
198            String value = getValue(option);
199    
200            return checkTrue(value) || checkFalse(value);
201        }
202    
203        /**
204         * Get the value for an option as <code>boolean</code>.
205         * 
206         * @param option
207         *            name of the option
208         * @return the value of this option
209         * @throws ValueConversionException
210         *             if the option doesn't have a boolean value. Use
211         *             {@link #hasBooleanValue(String)}method or default value
212         *             enabled version {@link #getBooleanValue(String, boolean)}of
213         *             this method to avoid conversion problems.
214         */
215        public boolean getBooleanValue(String option)
216                throws ValueConversionException {
217            if (!hasBooleanValue(option)) {
218                throw new ValueConversionException(option);
219            }
220    
221            String value = getValue(option);
222    
223            if (checkTrue(value)) {
224                return true;
225            }
226    
227            return false;
228        }
229    
230        /**
231         * Get the value for an option as instance of an enumeration. Enumeration
232         * names are matched in non case-sensitive way. Dashes in values are
233         * replaced by underscores.
234         * <p>
235         * Typical usage is:
236         * 
237         * <pre>
238         * Colors color = options.getEnumValue(&quot;enum1&quot;, Colors.class);
239         * </pre>
240         * 
241         * where <code>Colors</code> is an enumeration.
242         * 
243         * @param <T>
244         *            the enumeration
245         * @param option
246         *            the name of the option
247         * @param enumType
248         *            the enumeration type
249         * @return the enumeration entry
250         * @throws ValueConversionException
251         *             if the option doesn't have a value of the specified
252         *             enumeration. Use {@link #hasEnumValue(String, Class)}method
253         *             or default value enabled version
254         *             {@link #getEnumValue(String, Enum, Class)}of this method to
255         *             avoid conversion problems.
256         */
257        public <T extends Enum<T>> T getEnumValue(String option, Class<T> enumType)
258                throws ValueConversionException {
259            if (!hasEnumValue(option, enumType)) {
260                throw new ValueConversionException(option);
261            }
262    
263            String value = getValue(option);
264    
265            return EnumUtils.valueOfIgnoreCase(enumType,
266                    normalizeEnumConstantName(value));
267        }
268    
269        /**
270         * Same as {@link #getEnumValue(String, Class)} but allows to specify
271         * default value.
272         * 
273         * @param <T>
274         *            the enumeration
275         * @param option
276         *            the name of the option
277         * @param enumType
278         *            the enumeration type
279         * @return the enumeration entry
280         * 
281         */
282        public <T extends Enum<T>> T getEnumValue(String option, T defaultValue,
283                Class<T> enumType) {
284    
285            try {
286                return getEnumValue(option, enumType);
287            } catch (ValueConversionException e) {
288                return defaultValue;
289            }
290    
291        }
292    
293        /**
294         * Checks if the specified option is present and has a legal value.
295         * 
296         * @param option
297         *            name of the option
298         * @return if the option is present and has a legal value <code>true</code>
299         *         is returned, otherwise <code>false</code>
300         */
301        public <T extends Enum<T>> boolean hasEnumValue(String option,
302                Class<T> enumType) {
303            if (!hasValue(option)) {
304                return false;
305            }
306    
307            String value = getValue(option);
308    
309            return checkEnum(value, enumType);
310        }
311    
312        /**
313         * Check if value describe an an element of the enumeration (case-insenstive
314         * match).
315         * 
316         */
317        private <T extends Enum<T>> boolean checkEnum(String value,
318                Class<T> enumType) {
319            T t = EnumUtils.valueOfIgnoreCase(enumType,
320                    normalizeEnumConstantName(value));
321            if (t == null) {
322                return false;
323            }
324            return true;
325        }
326    
327        /**
328         * Get the value for an option as <code>int</code>.
329         * 
330         * @param option
331         *            name of the option
332         * @return the value of this option
333         * @throws ValueConversionException
334         * @throws ValueConversionException
335         *             if the option doesn't have a <code>int</code> value. Use
336         *             {@link #hasIntValue(String)}method or default value enabled
337         *             version {@link #getIntValue(String, int)}of this method to
338         *             avoid conversion problems.
339         */
340        public int getIntValue(String option) throws ValueConversionException {
341            if (!hasIntValue(option)) {
342                throw new ValueConversionException(option);
343            }
344    
345            String value = getValue(option);
346    
347            return Integer.parseInt(value);
348        }
349    
350        /**
351         * Checks if the specified option is present and has a <code>int</code>
352         * value.
353         * 
354         * @param option
355         *            name of the option
356         * @return if the option is present and has a <code>int</code> value
357         *         <code>true</code> is returned, otherwise <code>false</code>
358         */
359        public boolean hasIntValue(String option) {
360            if (!hasValue(option)) {
361                return false;
362            }
363    
364            String value = getValue(option);
365    
366            return checkInt(value);
367        }
368    
369        /**
370         * Same as {@link #getBooleanValue(String)}but allows to specify a default
371         * value.
372         * 
373         * @param option
374         *            name of the option
375         * @param defaultValue
376         *            default value
377         * @return return the value of the option if option is present and has a
378         *         boolean value, otherwise the default value is returned
379         */
380        public boolean getBooleanValue(String option, boolean defaultValue) {
381    
382            try {
383                return getBooleanValue(option);
384            } catch (ValueConversionException e) {
385                return defaultValue;
386            }
387    
388        }
389    
390        /**
391         * Same as {@link #getIntValue(String)}but allows to specify a default
392         * value.
393         * 
394         * @param option
395         *            name of the option
396         * @param defaultValue
397         *            default value
398         * @return return the value of the option if option is present and has an
399         *         integer value, otherwise the default value is returned
400         */
401        public int getIntValue(String option, int defaultValue) {
402            try {
403                return getIntValue(option);
404            } catch (ValueConversionException e) {
405                return defaultValue;
406            }
407        }
408    
409        /**
410         * Checks if a given string represent an integer.
411         * 
412         * @param value -
413         *            the string to check
414         * @return <code>true</code> if the string represents an integer,
415         *         <code>false</code> otherwise
416         */
417        private boolean checkInt(String value) {
418            try {
419                Integer.parseInt(value);
420            } catch (NumberFormatException ex) {
421                return false;
422            }
423            return true;
424        }
425    
426        /**
427         * Checks if the string is a boolean literal with value <code>false</code>.
428         * Literals <code>false</code> and <code>no</code> are allowed.
429         * 
430         * @param value
431         *            the string to check
432         * @return <code>true</code> if the string represents a booleean literal
433         *         with value <code>false</code>,<code>false</code> otherwise
434         */
435        private boolean checkFalse(String value) {
436            value = value.trim();
437    
438            if (value.toLowerCase().equals("false")) {
439                return true;
440            }
441            if (value.toLowerCase().equals("no")) {
442                return true;
443            }
444    
445            return false;
446        }
447    
448        /**
449         * Checks if the string is a boolean literal with value <code>true</code>.
450         * Literals <code>true</code> and <code>yes</code> are allowed.
451         * 
452         * @param value
453         *            the string to check
454         * @return <code>true</code> if the string represents a booleean literal
455         *         with value <code>true</code>,<code>false</code> otherwise
456         */
457        private boolean checkTrue(String value) {
458            value = value.trim();
459    
460            if (value.toLowerCase().equals("true")) {
461                return true;
462            }
463            if (value.toLowerCase().equals("yes")) {
464                return true;
465            }
466    
467            return false;
468        }
469    
470        /**
471         * Parses a space separated value list. To use values with spaces, use
472         * double quotes.
473         * 
474         * @param string
475         *            the value list to parse
476         * @return an array containing the values, quotes are omitted
477         */
478        private String[] parse(String string) {
479            string = string.trim();
480            int length = string.length();
481            char[] content = new char[length];
482            string.getChars(0, length, content, 0);
483    
484            ArrayList<String> list = new ArrayList<String>();
485    
486            int i = 0;
487    
488            int lastPos = 0;
489            boolean inQM = false;
490            boolean inToken = false;
491    
492            while (i < length) {
493                switch (content[i]) {
494                case ' ':
495                case '\t':
496                    if (inToken && !inQM) {
497                        // parameter found
498                        String parameter = string.substring(lastPos, i).trim();
499                        parameter = parameter.replaceAll("\"", "");
500                        list.add(parameter);
501                        lastPos = i;
502                    }
503    
504                    inToken = false;
505                    // lastPos++;
506                    break;
507                case '\"':
508                    inQM = !inQM;
509                    break;
510                default:
511                    inToken = true;
512                }
513                i++;
514            }
515    
516            String parameter = string.substring(lastPos, i).trim();
517            parameter = parameter.replaceAll("\"", "");
518            list.add(parameter);
519    
520            String[] result = new String[list.size()];
521            list.toArray(result);
522    
523            return result;
524        }
525    
526        /**
527         * Checks if a specified option is present.
528         * 
529         * @param option
530         *            name of the option
531         * @return <code>true</code> if option is present, <code>false</code>
532         *         otherwise
533         */
534        public boolean hasOption(String option) {
535            return !(properties.getProperty(option) == null);
536        }
537    
538        /**
539         * Checks if specified option has a value.
540         * 
541         * @param option
542         *            name of the option
543         * @return <code>true</code> if option is present and has a value,
544         *         <code>false</code> otherwise (even if option is present but
545         *         doesn't have a value)
546         */
547        public boolean hasValue(String option) {
548            return countValues(option) > 0;
549        }
550    
551        /**
552         * Count the space separated values of an option. Double quotes are taken
553         * into account.
554         * 
555         * @param option
556         *            name of the option
557         * @return value count
558         */
559        public int countValues(String option) {
560            if (!hasOption(option)) {
561                return OPTION_NOT_PRESENT;
562            }
563    
564            String[] values = getValues(option);
565    
566            if (values == null) {
567                return 0;
568            }
569    
570            return values.length;
571        }
572    
573        /**
574         * Returns a list with key-value-pairs as string.
575         * 
576         * @return key-value-pairs as string
577         */
578        @Override
579            public String toString() {
580            StringBuffer buffer = new StringBuffer();
581            Iterator<Object> it = properties.keySet().iterator();
582            while (it.hasNext()) {
583                String key = (String) it.next();
584                String value = properties.getProperty(key);
585                buffer.append(key + " = " + value);
586                if (it.hasNext()) {
587                    buffer.append(System.getProperty("line.separator"));
588                }
589            }
590            return buffer.toString();
591        }
592    
593        /**
594         * Exception objects of this class are possibly returned by
595         * {@link Options#getBooleanValue(String)}and
596         * {@link Options#getIntValue(String)}, if corresponding options don't have
597         * a boolean respectively integer value.
598         * 
599         */
600        @SuppressWarnings("serial")
601        public static class ValueConversionException extends Exception {
602    
603            /**
604             * Construct new conversion exception.
605             * 
606             * @param option
607             *            name of the option causing the exception
608             */
609            public ValueConversionException(String option) {
610                super("Option: " + option);
611            }
612        }
613    
614        /**
615         * Get the value for an option as <code>float</code>.
616         * 
617         * @param option
618         *            name of the option
619         * @return the value of this option
620         * @throws ValueConversionException
621         *             if the option doesn't have a float value.
622         */
623        public float getFloatValue(String option) throws ValueConversionException {
624            if (!hasFloatValue(option)) {
625                throw new ValueConversionException(option);
626            }
627    
628            String value = getValue(option);
629    
630            return Float.parseFloat(value);
631        }
632    
633        /**
634         * Checks if the specified option is present and has a float value.
635         * 
636         * 
637         * @param option
638         *            name of the option
639         * @return if the option is present and has a float value <code>true</code>
640         *         is returned, otherwise <code>false</code>
641         */
642        public boolean hasFloatValue(String option) {
643            if (!hasValue(option)) {
644                return false;
645            }
646    
647            String value = getValue(option);
648    
649            return checkFloat(value);
650        }
651    
652        /**
653         * Checks if a string contains a float.
654         */
655        private boolean checkFloat(String value) {
656            try {
657                Float.parseFloat(value);
658            } catch (NumberFormatException ex) {
659                return false;
660            }
661            return true;
662        }
663    
664        /** Normalize enum constant name. This replaces all dashes with underscores. */
665        private String normalizeEnumConstantName(String constantName) {
666            return constantName.replaceAll("-", "_");
667        }
668    }