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