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] = "value1"<br/>
161 * a[1] = "value2"<br/>
162 * a[2] = "value 3"<br/>
163 * a[3] = "value4"<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("enum1", 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 }