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 }