001package com.thetransactioncompany.util; 002 003 004import java.net.MalformedURLException; 005import java.net.URI; 006import java.net.URISyntaxException; 007import java.net.URL; 008import java.util.*; 009 010 011/** 012 * Provides typed retrieval of {@link java.util.Properties} as {@code boolean}, 013 * {@code int}, {@code long}, {@code float}, {@code double}, 014 * {@link java.lang.String}, {@link java.net.URL}, {@link java.net.URI} or 015 * {@code enum} values. 016 * 017 * @author Vladimir Dzhuvinov 018 */ 019public class PropertyRetriever { 020 021 022 /** 023 * The properties to parse. 024 */ 025 private final Properties props; 026 027 028 /** 029 * {@code true} if system property override has been enabled, 030 * {@code false} if it's disabled. 031 */ 032 private final boolean enableSysPropOverride; 033 034 035 /** 036 * Creates a new retriever for the specified properties. System 037 * property override is disabled. 038 * 039 * @param props The properties. Must not be {@code null}. 040 */ 041 public PropertyRetriever(final Properties props) { 042 043 this(props, false); 044 } 045 046 047 /** 048 * Creates a new retriever for the specified properties. 049 * 050 * @param props The properties. Must not be 051 * {@code null}. 052 * @param enableSysPropOverride If {@code true} if system properties 053 * with the same names are present they 054 * will override the specified properties, 055 * {@code false} to disable this 056 * behaviour. 057 */ 058 public PropertyRetriever(final Properties props, final boolean enableSysPropOverride) { 059 060 this.props = new Properties(); 061 this.props.putAll(props); 062 063 this.enableSysPropOverride = enableSysPropOverride; 064 if (enableSysPropOverride) { 065 // Overwrite 066 this.props.putAll(System.getProperties()); 067 } 068 } 069 070 071 /** 072 * Gets the system property override setting. 073 * 074 * @return {@code true} if system property override has been enabled, 075 * {@code false} if it's disabled. 076 */ 077 public boolean systemPropertyOverrideIsEnabled() { 078 079 return enableSysPropOverride; 080 } 081 082 083 /** 084 * Retrieves a boolean value. 085 * 086 * @param key The property name. 087 * 088 * @return The property as a boolean value. 089 * 090 * @throws PropertyParseException On a missing or invalid property. 091 */ 092 public boolean getBoolean(final String key) 093 throws PropertyParseException { 094 095 String value = props.getProperty(key); 096 097 if (value == null) 098 throw new PropertyParseException("Missing property", key); 099 100 if (value.equalsIgnoreCase("true")) 101 return true; 102 103 else if (value.equalsIgnoreCase("false")) 104 return false; 105 106 else 107 throw new PropertyParseException("Invalid boolean property", key, value); 108 } 109 110 111 /** 112 * Retrieves an optional boolean value. 113 * 114 * @param key The property name. 115 * @param def The default value if the property value is undefined or 116 * empty. 117 * 118 * @return The property as a boolean. 119 * 120 * @throws PropertyParseException On an invalid property. 121 */ 122 public boolean getOptBoolean(final String key, final boolean def) 123 throws PropertyParseException { 124 125 String value = props.getProperty(key); 126 127 if (value == null || value.trim().isEmpty()) 128 return def; 129 130 if (value.equalsIgnoreCase("true")) 131 return true; 132 133 if (value.equalsIgnoreCase("false")) 134 return false; 135 136 throw new PropertyParseException("Invalid boolean property", key, value); 137 } 138 139 140 /** 141 * Retrieves an integer value. 142 * 143 * @param key The property name. 144 * 145 * @return The property as an integer. 146 * 147 * @throws PropertyParseException On a missing or invalid property. 148 */ 149 public int getInt(final String key) 150 throws PropertyParseException { 151 152 String value = props.getProperty(key); 153 154 if (value == null) 155 throw new PropertyParseException("Missing property", key); 156 157 try { 158 return Integer.parseInt(value); 159 160 } catch (NumberFormatException e) { 161 162 throw new PropertyParseException("Invalid int property", key, value); 163 } 164 } 165 166 167 /** 168 * Retrieves an optional integer value. 169 * 170 * @param key The property name. 171 * @param def The default value if the property value is undefined or 172 * empty. 173 * 174 * @return The property as an integer. 175 * 176 * @throws PropertyParseException On an invalid property. 177 */ 178 public int getOptInt(final String key, final int def) 179 throws PropertyParseException { 180 181 String value = props.getProperty(key); 182 183 if (value == null || value.trim().isEmpty()) 184 return def; 185 186 try { 187 return Integer.parseInt(value); 188 189 } catch (NumberFormatException e) { 190 191 throw new PropertyParseException("Invalid int property", key); 192 } 193 } 194 195 196 /** 197 * Retrieves a long value. 198 * 199 * @param key The property name. 200 * 201 * @return The property as a long. 202 * 203 * @throws PropertyParseException On a missing or invalid property. 204 */ 205 public long getLong(final String key) 206 throws PropertyParseException { 207 208 String value = props.getProperty(key); 209 210 if (value == null) 211 throw new PropertyParseException("Missing property", key); 212 213 try { 214 return Long.parseLong(value); 215 216 } catch (NumberFormatException e) { 217 218 throw new PropertyParseException("Invalid long property", key, value); 219 } 220 } 221 222 223 /** 224 * Retrieves an optional long value. 225 * 226 * @param key The property name. 227 * @param def The default value if the property value is undefined or 228 * empty. 229 * 230 * @return The property as a long. 231 * 232 * @throws PropertyParseException On an invalid property. 233 */ 234 public long getOptLong(final String key, final long def) 235 throws PropertyParseException { 236 237 String value = props.getProperty(key); 238 239 if (value == null || value.trim().isEmpty()) 240 return def; 241 242 try { 243 return Long.parseLong(value); 244 245 } catch (NumberFormatException e) { 246 247 throw new PropertyParseException("Invalid long property", key, value); 248 } 249 } 250 251 252 /** 253 * Retrieves a float value. 254 * 255 * @param key The property name. 256 * 257 * @return The property as a float. 258 * 259 * @throws PropertyParseException On a missing or invalid property. 260 */ 261 public float getFloat(final String key) 262 throws PropertyParseException { 263 264 String value = props.getProperty(key); 265 266 if (value == null) 267 throw new PropertyParseException("Missing property", key); 268 269 try { 270 return Float.parseFloat(value); 271 272 } catch (NumberFormatException e) { 273 274 throw new PropertyParseException("Invalid float property", key, value); 275 } 276 } 277 278 279 /** 280 * Retrieves an optional float value. 281 * 282 * @param key The property name. 283 * @param def The default value if the property value is undefined or 284 * empty. 285 * 286 * @return The property as a float. 287 * 288 * @throws PropertyParseException On an invalid property. 289 */ 290 public float getOptFloat(final String key, final float def) 291 throws PropertyParseException { 292 293 String value = props.getProperty(key); 294 295 if (value == null || value.trim().isEmpty()) 296 return def; 297 298 try { 299 return Float.parseFloat(value); 300 301 } catch (NumberFormatException e) { 302 303 throw new PropertyParseException("Invalid float property", key, value); 304 } 305 } 306 307 308 /** 309 * Retrieves a double value. 310 * 311 * @param key The property name. 312 * 313 * @return The property as a double. 314 * 315 * @throws PropertyParseException On a missing or invalid property. 316 */ 317 public double getDouble(final String key) 318 throws PropertyParseException { 319 320 String value = props.getProperty(key); 321 322 if (value == null) 323 throw new PropertyParseException("Missing property", key); 324 325 try { 326 return Double.parseDouble(value); 327 328 } catch (NumberFormatException e) { 329 330 throw new PropertyParseException("Invalid double property", key, value); 331 } 332 } 333 334 335 /** 336 * Retrieves an optional double value. 337 * 338 * @param key The property name. 339 * @param def The default value if the property value is undefined or 340 * empty. 341 * 342 * @return The property as a double. 343 * 344 * @throws PropertyParseException On an invalid property. 345 */ 346 public double getOptDouble(final String key, final double def) 347 throws PropertyParseException { 348 349 String value = props.getProperty(key); 350 351 if (value == null || value.trim().isEmpty()) 352 return def; 353 354 try { 355 return Double.parseDouble(value); 356 357 } catch (NumberFormatException e) { 358 359 throw new PropertyParseException("Invalid double property", key, value); 360 } 361 } 362 363 364 /** 365 * Retrieves a string value. 366 * 367 * @param key The property name. 368 * 369 * @return The property as a string. 370 * 371 * @throws PropertyParseException On a missing or invalid property. 372 */ 373 public String getString(final String key) 374 throws PropertyParseException { 375 376 String value = props.getProperty(key); 377 378 if (value == null) 379 throw new PropertyParseException("Missing property", key); 380 381 return value; 382 } 383 384 385 /** 386 * Retrieves an optional string value. 387 * 388 * @param key The property name. 389 * @param def The default value if the property value is undefined or 390 * empty. 391 * 392 * @return The property as a string. 393 * 394 * @throws PropertyParseException On an invalid property. 395 */ 396 public String getOptString(final String key, final String def) 397 throws PropertyParseException { 398 399 String value = props.getProperty(key); 400 401 if (value == null || value.trim().isEmpty()) 402 return def; 403 404 return value; 405 } 406 407 408 /** 409 * Retrieves an enumerated string value. String case is ignored during 410 * comparison. 411 * 412 * @param key The property name. 413 * @param enums A string array defining the acceptable values. 414 * 415 * @return The property as a string. 416 * 417 * @throws PropertyParseException On a missing or invalid property. 418 */ 419 public String getEnumString(final String key, final String[] enums) 420 throws PropertyParseException { 421 422 String value = props.getProperty(key); 423 424 if (value == null) 425 throw new PropertyParseException("Missing property", key); 426 427 for (String en: enums) { 428 429 if (en.equalsIgnoreCase(value)) 430 return value; 431 } 432 433 throw new PropertyParseException("Invalid enum string property", key, value); 434 } 435 436 437 /** 438 * Retrieves an enumerated string value. String case is ignored during 439 * comparison. 440 * 441 * @param key The property name. 442 * @param enums A string array defining the acceptable values. 443 * @param def The default value if the property value is undefined or 444 * empty. 445 * 446 * @return The property as a string. 447 * 448 * @throws PropertyParseException On an invalid property. 449 */ 450 public String getOptEnumString(final String key, final String[] enums, final String def) 451 throws PropertyParseException { 452 453 String value = props.getProperty(key); 454 455 if (value == null || value.trim().isEmpty()) 456 return def; 457 458 for (String en: enums) { 459 460 if (en.equalsIgnoreCase(value)) 461 return value; 462 } 463 464 throw new PropertyParseException("Invalid enum string property", key, value); 465 } 466 467 468 /** 469 * Retrieves an enumerated constant. String case is ignored during 470 * comparison. 471 * 472 * @param key The property name. 473 * @param enumClass The enumeration class specifying the acceptable 474 * values. 475 * 476 * @return The matching enumerated constant. 477 * 478 * @throws PropertyParseException On a missing or invalid property. 479 */ 480 public <T extends Enum<T>> T getEnum(final String key, final Class<T> enumClass) 481 throws PropertyParseException { 482 483 String value = props.getProperty(key); 484 485 if (value == null) 486 throw new PropertyParseException("Missing property", key); 487 488 for (T en: enumClass.getEnumConstants()) { 489 490 if (en.toString().equalsIgnoreCase(value)) 491 return en; 492 } 493 494 // No match? -> raise exception 495 throw new PropertyParseException("Invalid enum property", key, value); 496 } 497 498 499 /** 500 * Retrieves an optional enumerated constant. String case is ignored 501 * during comparison. 502 * 503 * @param key The property name. 504 * @param enumClass The enumeration class specifying the acceptable 505 * values. 506 * @param def The default value if the property value is 507 * undefined or empty. 508 * 509 * @return The matching enumerated constant. 510 * 511 * @throws PropertyParseException On a missing or invalid property. 512 */ 513 public <T extends Enum<T>> T getOptEnum(final String key, final Class<T> enumClass, final T def) 514 throws PropertyParseException { 515 516 String value = props.getProperty(key); 517 518 if (value == null || value.trim().isEmpty()) 519 return def; 520 521 for (T en: enumClass.getEnumConstants()) { 522 523 if (en.toString().equalsIgnoreCase(value)) 524 return en; 525 } 526 527 // No match? -> raise exception 528 throw new PropertyParseException("Invalid enum property", key, value); 529 } 530 531 532 /** 533 * Retrieves a string list consisting of one or more comma / space 534 * separated items. 535 * 536 * @param key The property name. 537 * 538 * @return The string list with at least one item. 539 * 540 * @throws PropertyParseException On a missing or empty property. 541 */ 542 public List<String> getStringList(final String key) 543 throws PropertyParseException { 544 545 String s = getString(key); 546 547 StringTokenizer st = new StringTokenizer(s, ", "); 548 List<String> out = new LinkedList<>(); 549 while (st.hasMoreElements()) { 550 out.add(st.nextToken()); 551 } 552 553 if (out.isEmpty()) { 554 throw new PropertyParseException("Empty string list", key, s); 555 } 556 557 return out; 558 } 559 560 561 /** 562 * Retrieves an optional string list consisting of zero or more comma / 563 * space separated items. 564 * 565 * @param key The property name. 566 * @param def The default value if the property value is undefined or 567 * empty. 568 * 569 * @return The string list with zero or more items. 570 * 571 * @throws PropertyParseException On a invalid property. 572 */ 573 public List<String> getOptStringList(final String key, final List<String> def) 574 throws PropertyParseException { 575 576 String s = getOptString(key, null); 577 578 if (s == null) { 579 return def; 580 } 581 582 StringTokenizer st = new StringTokenizer(s, ", "); 583 List<String> out = new LinkedList<>(); 584 while (st.hasMoreElements()) { 585 out.add(st.nextToken()); 586 } 587 588 if (out.isEmpty()) { 589 return def; 590 } 591 592 return out; 593 } 594 595 596 /** 597 * Retrieves a string list consisting of one or more comma / space 598 * separated items from a enumeration. 599 * 600 * @param key The property name. 601 * @param enums A string array defining the acceptable values. 602 * 603 * @return The string list with at least one item. 604 * 605 * @throws PropertyParseException On a missing or empty property. 606 */ 607 public List<String> getEnumStringList(final String key, final String[] enums) 608 throws PropertyParseException { 609 610 return getEnumStringList(key, Arrays.asList(enums)); 611 } 612 613 614 /** 615 * Retrieves an optional string list consisting of zero or more comma / 616 * space separated items from a enumeration. 617 * 618 * @param key The property name. 619 * @param enums A string array defining the acceptable values. 620 * @param def The default value if the property value is undefined or 621 * empty. 622 * 623 * @return The string list with zero or more items. 624 * 625 * @throws PropertyParseException On a invalid property. 626 */ 627 public List<String> getOptEnumStringList(final String key, final String[] enums, final List<String> def) 628 throws PropertyParseException { 629 630 return getOptEnumStringList(key, Arrays.asList(enums), def); 631 } 632 633 634 /** 635 * Retrieves a string list consisting of one or more comma / space 636 * separated items from a enumeration. 637 * 638 * @param key The property name. 639 * @param enums A string list defining the acceptable values. 640 * 641 * @return The string list with at least one item. 642 * 643 * @throws PropertyParseException On a missing or empty property. 644 */ 645 public List<String> getEnumStringList(final String key, final List<String> enums) 646 throws PropertyParseException { 647 648 List<String> out = getStringList(key); 649 650 for (String v: out) { 651 if (! enums.contains(v)) { 652 throw new PropertyParseException("Invalid enum string list property: " + v, key, props.getProperty(key)); 653 } 654 } 655 656 return out; 657 } 658 659 660 /** 661 * Retrieves an optional string list consisting of zero or more comma / 662 * space separated items from a enumeration. 663 * 664 * @param key The property name. 665 * @param enums A string list defining the acceptable values. 666 * @param def The default value if the property value is undefined or 667 * empty. 668 * 669 * @return The string list with zero or more items. 670 * 671 * @throws PropertyParseException On a invalid property. 672 */ 673 public List<String> getOptEnumStringList(final String key, final List<String> enums, final List<String> def) 674 throws PropertyParseException { 675 676 List<String> out = getOptStringList(key, def); 677 678 for (String v: out) { 679 if (! enums.contains(v)) { 680 throw new PropertyParseException("Invalid enum string list property: " + v, key, props.getProperty(key)); 681 } 682 } 683 684 return out; 685 } 686 687 688 /** 689 * Retrieves a string list from multiple properties sharing a common 690 * prefix. 691 * 692 * @param commonPrefix The common property name prefix. 693 * 694 * @return The string list with at least one item. 695 * 696 * @throws PropertyParseException On a missing or empty property. 697 */ 698 public List<String> getStringListMulti(final String commonPrefix) 699 throws PropertyParseException { 700 701 List<String> stringList = getOptStringListMulti(commonPrefix, null); 702 703 if (stringList == null) { 704 throw new PropertyParseException("Missing properties", commonPrefix + "*"); 705 } 706 707 return stringList; 708 } 709 710 711 /** 712 * Retrieves an optional string list from multiple properties sharing a 713 * common prefix. 714 * 715 * @param commonPrefix The common property name prefix. 716 * @param def The default value if no non-blank properties 717 * are found. 718 * 719 * @return The string list with at least one item or the default value. 720 */ 721 public List<String> getOptStringListMulti(final String commonPrefix, final List<String> def) { 722 723 Properties filteredProps = PropertyFilter.filterWithPrefix(commonPrefix, props); 724 List<String> stringList = new LinkedList<>(); 725 for (String name: filteredProps.stringPropertyNames()) { 726 String value = filteredProps.getProperty(name); 727 if (value == null || value.trim().isEmpty()) { 728 continue; 729 } 730 stringList.add(value); 731 } 732 733 if (stringList.isEmpty()) { 734 return def; 735 } else { 736 return Collections.unmodifiableList(stringList); 737 } 738 } 739 740 741 /** 742 * Retrieves a URI value. 743 * 744 * @param key The property name. 745 * 746 * @return The property as a URI. 747 * 748 * @throws PropertyParseException On a missing or invalid property. 749 */ 750 public URI getURI(final String key) 751 throws PropertyParseException { 752 753 String value = props.getProperty(key); 754 755 if (value == null) 756 throw new PropertyParseException("Missing property", key); 757 758 try { 759 return new URI(value); 760 761 } catch (URISyntaxException e) { 762 763 throw new PropertyParseException("Invalid URI property: " + e.getMessage(), key, value); 764 } 765 } 766 767 768 /** 769 * Retrieves an optional URI value. 770 * 771 * @param key The property name. 772 * @param def The default value if the property value is undefined or 773 * empty. 774 * 775 * @return The property as a URL. 776 * 777 * @throws PropertyParseException On an invalid property. 778 */ 779 public URI getOptURI(final String key, final URI def) 780 throws PropertyParseException { 781 782 String value = props.getProperty(key); 783 784 if (value == null || value.trim().isEmpty()) 785 return def; 786 787 try { 788 return new URI(value); 789 790 } catch (URISyntaxException e) { 791 792 throw new PropertyParseException("Invalid URI property: " + e.getMessage(), key, value); 793 } 794 } 795 796 797 /** 798 * Retrieves a URL value. 799 * 800 * @param key The property name. 801 * 802 * @return The property as a URL. 803 * 804 * @throws PropertyParseException On a missing or invalid property. 805 */ 806 public URL getURL(final String key) 807 throws PropertyParseException { 808 809 String value = props.getProperty(key); 810 811 if (value == null) 812 throw new PropertyParseException("Missing property", key); 813 814 try { 815 return new URL(value); 816 817 } catch (MalformedURLException e) { 818 819 throw new PropertyParseException("Invalid URL property: " + e.getMessage(), key, value); 820 } 821 } 822 823 824 /** 825 * Retrieves an optional URL value. 826 * 827 * @param key The property name. 828 * @param def The default value if the property value is undefined or 829 * empty. 830 * 831 * @return The property as a URL. 832 * 833 * @throws PropertyParseException On an invalid property. 834 */ 835 public URL getOptURL(final String key, final URL def) 836 throws PropertyParseException { 837 838 String value = props.getProperty(key); 839 840 if (value == null || value.trim().isEmpty()) 841 return def; 842 843 try { 844 return new URL(value); 845 846 } catch (MalformedURLException e) { 847 848 throw new PropertyParseException("Invalid URL property: " + e.getMessage(), key, value); 849 } 850 } 851}