001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util; 022 023 024 025import java.io.BufferedReader; 026import java.io.File; 027import java.io.IOException; 028import java.io.StringReader; 029import java.lang.reflect.Array; 030import java.nio.charset.StandardCharsets; 031import java.text.DecimalFormat; 032import java.text.ParseException; 033import java.text.SimpleDateFormat; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Collection; 037import java.util.Collections; 038import java.util.Date; 039import java.util.HashSet; 040import java.util.Iterator; 041import java.util.LinkedHashSet; 042import java.util.List; 043import java.util.Properties; 044import java.util.Set; 045import java.util.StringTokenizer; 046import java.util.TimeZone; 047import java.util.UUID; 048 049import com.unboundid.ldap.sdk.Attribute; 050import com.unboundid.ldap.sdk.Control; 051import com.unboundid.ldap.sdk.Version; 052 053import static com.unboundid.util.UtilityMessages.*; 054 055 056 057/** 058 * This class provides a number of static utility functions. 059 */ 060@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 061public final class StaticUtils 062{ 063 /** 064 * A pre-allocated byte array containing zero bytes. 065 */ 066 public static final byte[] NO_BYTES = new byte[0]; 067 068 069 070 /** 071 * A pre-allocated empty character array. 072 */ 073 public static final char[] NO_CHARS = new char[0]; 074 075 076 077 /** 078 * A pre-allocated empty control array. 079 */ 080 public static final Control[] NO_CONTROLS = new Control[0]; 081 082 083 084 /** 085 * A pre-allocated empty string array. 086 */ 087 public static final String[] NO_STRINGS = new String[0]; 088 089 090 091 /** 092 * The end-of-line marker for this platform. 093 */ 094 public static final String EOL = System.getProperty("line.separator"); 095 096 097 098 /** 099 * A byte array containing the end-of-line marker for this platform. 100 */ 101 public static final byte[] EOL_BYTES = getBytes(EOL); 102 103 104 105 /** 106 * Indicates whether the unit tests are currently running. 107 */ 108 private static final boolean IS_WITHIN_UNIT_TESTS = 109 Boolean.getBoolean("com.unboundid.ldap.sdk.RunningUnitTests") || 110 Boolean.getBoolean("com.unboundid.directory.server.RunningUnitTests"); 111 112 113 114 /** 115 * The width of the terminal window, in columns. 116 */ 117 public static final int TERMINAL_WIDTH_COLUMNS; 118 static 119 { 120 // Try to dynamically determine the size of the terminal window using the 121 // COLUMNS environment variable. 122 int terminalWidth = 80; 123 final String columnsEnvVar = System.getenv("COLUMNS"); 124 if (columnsEnvVar != null) 125 { 126 try 127 { 128 terminalWidth = Integer.parseInt(columnsEnvVar); 129 } 130 catch (final Exception e) 131 { 132 Debug.debugException(e); 133 } 134 } 135 136 TERMINAL_WIDTH_COLUMNS = terminalWidth; 137 } 138 139 140 141 /** 142 * The thread-local date formatter used to encode generalized time values. 143 */ 144 private static final ThreadLocal<SimpleDateFormat> DATE_FORMATTERS = 145 new ThreadLocal<>(); 146 147 148 149 /** 150 * The {@code TimeZone} object that represents the UTC (universal coordinated 151 * time) time zone. 152 */ 153 private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC"); 154 155 156 157 /** 158 * A set containing the names of attributes that will be considered sensitive 159 * by the {@code toCode} methods of various request and data structure types. 160 */ 161 private static volatile Set<String> TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = 162 setOf("userpassword", "2.5.4.35", 163 "authpassword", "1.3.6.1.4.1.4203.1.3.4"); 164 165 166 167 /** 168 * Prevent this class from being instantiated. 169 */ 170 private StaticUtils() 171 { 172 // No implementation is required. 173 } 174 175 176 177 /** 178 * Retrieves a UTF-8 byte representation of the provided string. 179 * 180 * @param s The string for which to retrieve the UTF-8 byte representation. 181 * 182 * @return The UTF-8 byte representation for the provided string. 183 */ 184 public static byte[] getBytes(final String s) 185 { 186 final int length; 187 if ((s == null) || ((length = s.length()) == 0)) 188 { 189 return NO_BYTES; 190 } 191 192 final byte[] b = new byte[length]; 193 for (int i=0; i < length; i++) 194 { 195 final char c = s.charAt(i); 196 if (c <= 0x7F) 197 { 198 b[i] = (byte) (c & 0x7F); 199 } 200 else 201 { 202 return s.getBytes(StandardCharsets.UTF_8); 203 } 204 } 205 206 return b; 207 } 208 209 210 211 /** 212 * Indicates whether the contents of the provided byte array represent an 213 * ASCII string, which is also known in LDAP terminology as an IA5 string. 214 * An ASCII string is one that contains only bytes in which the most 215 * significant bit is zero. 216 * 217 * @param b The byte array for which to make the determination. It must 218 * not be {@code null}. 219 * 220 * @return {@code true} if the contents of the provided array represent an 221 * ASCII string, or {@code false} if not. 222 */ 223 public static boolean isASCIIString(final byte[] b) 224 { 225 for (final byte by : b) 226 { 227 if ((by & 0x80) == 0x80) 228 { 229 return false; 230 } 231 } 232 233 return true; 234 } 235 236 237 238 /** 239 * Indicates whether the provided character is a printable ASCII character, as 240 * per RFC 4517 section 3.2. The only printable characters are: 241 * <UL> 242 * <LI>All uppercase and lowercase ASCII alphabetic letters</LI> 243 * <LI>All ASCII numeric digits</LI> 244 * <LI>The following additional ASCII characters: single quote, left 245 * parenthesis, right parenthesis, plus, comma, hyphen, period, equals, 246 * forward slash, colon, question mark, space.</LI> 247 * </UL> 248 * 249 * @param c The character for which to make the determination. 250 * 251 * @return {@code true} if the provided character is a printable ASCII 252 * character, or {@code false} if not. 253 */ 254 public static boolean isPrintable(final char c) 255 { 256 if (((c >= 'a') && (c <= 'z')) || 257 ((c >= 'A') && (c <= 'Z')) || 258 ((c >= '0') && (c <= '9'))) 259 { 260 return true; 261 } 262 263 switch (c) 264 { 265 case '\'': 266 case '(': 267 case ')': 268 case '+': 269 case ',': 270 case '-': 271 case '.': 272 case '=': 273 case '/': 274 case ':': 275 case '?': 276 case ' ': 277 return true; 278 default: 279 return false; 280 } 281 } 282 283 284 285 /** 286 * Indicates whether the contents of the provided byte array represent a 287 * printable LDAP string, as per RFC 4517 section 3.2. The only characters 288 * allowed in a printable string are: 289 * <UL> 290 * <LI>All uppercase and lowercase ASCII alphabetic letters</LI> 291 * <LI>All ASCII numeric digits</LI> 292 * <LI>The following additional ASCII characters: single quote, left 293 * parenthesis, right parenthesis, plus, comma, hyphen, period, equals, 294 * forward slash, colon, question mark, space.</LI> 295 * </UL> 296 * If the provided array contains anything other than the above characters 297 * (i.e., if the byte array contains any non-ASCII characters, or any ASCII 298 * control characters, or if it contains excluded ASCII characters like 299 * the exclamation point, double quote, octothorpe, dollar sign, etc.), then 300 * it will not be considered printable. 301 * 302 * @param b The byte array for which to make the determination. It must 303 * not be {@code null}. 304 * 305 * @return {@code true} if the contents of the provided byte array represent 306 * a printable LDAP string, or {@code false} if not. 307 */ 308 public static boolean isPrintableString(final byte[] b) 309 { 310 for (final byte by : b) 311 { 312 if ((by & 0x80) == 0x80) 313 { 314 return false; 315 } 316 317 if (((by >= 'a') && (by <= 'z')) || 318 ((by >= 'A') && (by <= 'Z')) || 319 ((by >= '0') && (by <= '9'))) 320 { 321 continue; 322 } 323 324 switch (by) 325 { 326 case '\'': 327 case '(': 328 case ')': 329 case '+': 330 case ',': 331 case '-': 332 case '.': 333 case '=': 334 case '/': 335 case ':': 336 case '?': 337 case ' ': 338 continue; 339 default: 340 return false; 341 } 342 } 343 344 return true; 345 } 346 347 348 349 /** 350 * Indicates whether the contents of the provided array are valid UTF-8. 351 * 352 * @param b The byte array to examine. It must not be {@code null}. 353 * 354 * @return {@code true} if the byte array can be parsed as a valid UTF-8 355 * string, or {@code false} if not. 356 */ 357 public static boolean isValidUTF8(final byte[] b) 358 { 359 int i = 0; 360 while (i < b.length) 361 { 362 final byte currentByte = b[i++]; 363 364 // If the most significant bit is not set, then this represents a valid 365 // single-byte character. 366 if ((currentByte & 0b1000_0000) == 0b0000_0000) 367 { 368 continue; 369 } 370 371 // If the first byte starts with 0b110, then it must be followed by 372 // another byte that starts with 0b10. 373 if ((currentByte & 0b1110_0000) == 0b1100_0000) 374 { 375 if (! hasExpectedSubsequentUTF8Bytes(b, i, 1)) 376 { 377 return false; 378 } 379 380 i++; 381 continue; 382 } 383 384 // If the first byte starts with 0b1110, then it must be followed by two 385 // more bytes that start with 0b10. 386 if ((currentByte & 0b1111_0000) == 0b1110_0000) 387 { 388 if (! hasExpectedSubsequentUTF8Bytes(b, i, 2)) 389 { 390 return false; 391 } 392 393 i += 2; 394 continue; 395 } 396 397 // If the first byte starts with 0b11110, then it must be followed by 398 // three more bytes that start with 0b10. 399 if ((currentByte & 0b1111_1000) == 0b1111_0000) 400 { 401 if (! hasExpectedSubsequentUTF8Bytes(b, i, 3)) 402 { 403 return false; 404 } 405 406 i += 3; 407 continue; 408 } 409 410 // If the first byte starts with 0b111110, then it must be followed by 411 // four more bytes that start with 0b10. 412 if ((currentByte & 0b1111_1100) == 0b1111_1000) 413 { 414 if (! hasExpectedSubsequentUTF8Bytes(b, i, 4)) 415 { 416 return false; 417 } 418 419 i += 4; 420 continue; 421 } 422 423 // If the first byte starts with 0b1111110, then it must be followed by 424 // five more bytes that start with 0b10. 425 if ((currentByte & 0b1111_1110) == 0b1111_1100) 426 { 427 if (! hasExpectedSubsequentUTF8Bytes(b, i, 5)) 428 { 429 return false; 430 } 431 432 i += 5; 433 continue; 434 } 435 436 // This is not a valid first byte for a UTF-8 character. 437 return false; 438 } 439 440 441 // If we've gotten here, then the provided array represents a valid UTF-8 442 // string. 443 return true; 444 } 445 446 447 448 /** 449 * Ensures that the provided array has the expected number of bytes that start 450 * with 0b10 starting at the specified position in the array. 451 * 452 * @param b The byte array to examine. 453 * @param p The position in the byte array at which to start looking. 454 * @param n The number of bytes to examine. 455 * 456 * @return {@code true} if the provided byte array has the expected number of 457 * bytes that start with 0b10, or {@code false} if not. 458 */ 459 private static boolean hasExpectedSubsequentUTF8Bytes(final byte[] b, 460 final int p, 461 final int n) 462 { 463 if (b.length < (p + n)) 464 { 465 return false; 466 } 467 468 for (int i=0; i < n; i++) 469 { 470 if ((b[p+i] & 0b1100_0000) != 0b1000_0000) 471 { 472 return false; 473 } 474 } 475 476 return true; 477 } 478 479 480 481 /** 482 * Retrieves a string generated from the provided byte array using the UTF-8 483 * encoding. 484 * 485 * @param b The byte array for which to return the associated string. 486 * 487 * @return The string generated from the provided byte array using the UTF-8 488 * encoding. 489 */ 490 public static String toUTF8String(final byte[] b) 491 { 492 try 493 { 494 return new String(b, StandardCharsets.UTF_8); 495 } 496 catch (final Exception e) 497 { 498 // This should never happen. 499 Debug.debugException(e); 500 return new String(b); 501 } 502 } 503 504 505 506 /** 507 * Retrieves a string generated from the specified portion of the provided 508 * byte array using the UTF-8 encoding. 509 * 510 * @param b The byte array for which to return the associated string. 511 * @param offset The offset in the array at which the value begins. 512 * @param length The number of bytes in the value to convert to a string. 513 * 514 * @return The string generated from the specified portion of the provided 515 * byte array using the UTF-8 encoding. 516 */ 517 public static String toUTF8String(final byte[] b, final int offset, 518 final int length) 519 { 520 try 521 { 522 return new String(b, offset, length, StandardCharsets.UTF_8); 523 } 524 catch (final Exception e) 525 { 526 // This should never happen. 527 Debug.debugException(e); 528 return new String(b, offset, length); 529 } 530 } 531 532 533 534 /** 535 * Retrieves a version of the provided string with the first character 536 * converted to lowercase but all other characters retaining their original 537 * capitalization. 538 * 539 * @param s The string to be processed. 540 * 541 * @return A version of the provided string with the first character 542 * converted to lowercase but all other characters retaining their 543 * original capitalization. 544 */ 545 public static String toInitialLowerCase(final String s) 546 { 547 if ((s == null) || s.isEmpty()) 548 { 549 return s; 550 } 551 else if (s.length() == 1) 552 { 553 return toLowerCase(s); 554 } 555 else 556 { 557 final char c = s.charAt(0); 558 if (((c >= 'A') && (c <= 'Z')) || (c < ' ') || (c > '~')) 559 { 560 final StringBuilder b = new StringBuilder(s); 561 b.setCharAt(0, Character.toLowerCase(c)); 562 return b.toString(); 563 } 564 else 565 { 566 return s; 567 } 568 } 569 } 570 571 572 573 /** 574 * Retrieves an all-lowercase version of the provided string. 575 * 576 * @param s The string for which to retrieve the lowercase version. 577 * 578 * @return An all-lowercase version of the provided string. 579 */ 580 public static String toLowerCase(final String s) 581 { 582 if (s == null) 583 { 584 return null; 585 } 586 587 final int length = s.length(); 588 final char[] charArray = s.toCharArray(); 589 for (int i=0; i < length; i++) 590 { 591 switch (charArray[i]) 592 { 593 case 'A': 594 charArray[i] = 'a'; 595 break; 596 case 'B': 597 charArray[i] = 'b'; 598 break; 599 case 'C': 600 charArray[i] = 'c'; 601 break; 602 case 'D': 603 charArray[i] = 'd'; 604 break; 605 case 'E': 606 charArray[i] = 'e'; 607 break; 608 case 'F': 609 charArray[i] = 'f'; 610 break; 611 case 'G': 612 charArray[i] = 'g'; 613 break; 614 case 'H': 615 charArray[i] = 'h'; 616 break; 617 case 'I': 618 charArray[i] = 'i'; 619 break; 620 case 'J': 621 charArray[i] = 'j'; 622 break; 623 case 'K': 624 charArray[i] = 'k'; 625 break; 626 case 'L': 627 charArray[i] = 'l'; 628 break; 629 case 'M': 630 charArray[i] = 'm'; 631 break; 632 case 'N': 633 charArray[i] = 'n'; 634 break; 635 case 'O': 636 charArray[i] = 'o'; 637 break; 638 case 'P': 639 charArray[i] = 'p'; 640 break; 641 case 'Q': 642 charArray[i] = 'q'; 643 break; 644 case 'R': 645 charArray[i] = 'r'; 646 break; 647 case 'S': 648 charArray[i] = 's'; 649 break; 650 case 'T': 651 charArray[i] = 't'; 652 break; 653 case 'U': 654 charArray[i] = 'u'; 655 break; 656 case 'V': 657 charArray[i] = 'v'; 658 break; 659 case 'W': 660 charArray[i] = 'w'; 661 break; 662 case 'X': 663 charArray[i] = 'x'; 664 break; 665 case 'Y': 666 charArray[i] = 'y'; 667 break; 668 case 'Z': 669 charArray[i] = 'z'; 670 break; 671 default: 672 if (charArray[i] > 0x7F) 673 { 674 return s.toLowerCase(); 675 } 676 break; 677 } 678 } 679 680 return new String(charArray); 681 } 682 683 684 685 /** 686 * Retrieves an all-uppercase version of the provided string. 687 * 688 * @param s The string for which to retrieve the uppercase version. 689 * 690 * @return An all-uppercase version of the provided string. 691 */ 692 public static String toUpperCase(final String s) 693 { 694 if (s == null) 695 { 696 return null; 697 } 698 699 final int length = s.length(); 700 final char[] charArray = s.toCharArray(); 701 for (int i=0; i < length; i++) 702 { 703 switch (charArray[i]) 704 { 705 case 'a': 706 charArray[i] = 'A'; 707 break; 708 case 'b': 709 charArray[i] = 'B'; 710 break; 711 case 'c': 712 charArray[i] = 'C'; 713 break; 714 case 'd': 715 charArray[i] = 'D'; 716 break; 717 case 'e': 718 charArray[i] = 'E'; 719 break; 720 case 'f': 721 charArray[i] = 'F'; 722 break; 723 case 'g': 724 charArray[i] = 'G'; 725 break; 726 case 'h': 727 charArray[i] = 'H'; 728 break; 729 case 'i': 730 charArray[i] = 'I'; 731 break; 732 case 'j': 733 charArray[i] = 'J'; 734 break; 735 case 'k': 736 charArray[i] = 'K'; 737 break; 738 case 'l': 739 charArray[i] = 'L'; 740 break; 741 case 'm': 742 charArray[i] = 'M'; 743 break; 744 case 'n': 745 charArray[i] = 'N'; 746 break; 747 case 'o': 748 charArray[i] = 'O'; 749 break; 750 case 'p': 751 charArray[i] = 'P'; 752 break; 753 case 'q': 754 charArray[i] = 'Q'; 755 break; 756 case 'r': 757 charArray[i] = 'R'; 758 break; 759 case 's': 760 charArray[i] = 'S'; 761 break; 762 case 't': 763 charArray[i] = 'T'; 764 break; 765 case 'u': 766 charArray[i] = 'U'; 767 break; 768 case 'v': 769 charArray[i] = 'V'; 770 break; 771 case 'w': 772 charArray[i] = 'W'; 773 break; 774 case 'x': 775 charArray[i] = 'X'; 776 break; 777 case 'y': 778 charArray[i] = 'Y'; 779 break; 780 case 'z': 781 charArray[i] = 'Z'; 782 break; 783 default: 784 if (charArray[i] > 0x7F) 785 { 786 return s.toUpperCase(); 787 } 788 break; 789 } 790 } 791 792 return new String(charArray); 793 } 794 795 796 797 /** 798 * Indicates whether the provided character is a valid hexadecimal digit. 799 * 800 * @param c The character for which to make the determination. 801 * 802 * @return {@code true} if the provided character does represent a valid 803 * hexadecimal digit, or {@code false} if not. 804 */ 805 public static boolean isHex(final char c) 806 { 807 switch (c) 808 { 809 case '0': 810 case '1': 811 case '2': 812 case '3': 813 case '4': 814 case '5': 815 case '6': 816 case '7': 817 case '8': 818 case '9': 819 case 'a': 820 case 'A': 821 case 'b': 822 case 'B': 823 case 'c': 824 case 'C': 825 case 'd': 826 case 'D': 827 case 'e': 828 case 'E': 829 case 'f': 830 case 'F': 831 return true; 832 833 default: 834 return false; 835 } 836 } 837 838 839 840 /** 841 * Retrieves a hexadecimal representation of the provided byte. 842 * 843 * @param b The byte to encode as hexadecimal. 844 * 845 * @return A string containing the hexadecimal representation of the provided 846 * byte. 847 */ 848 public static String toHex(final byte b) 849 { 850 final StringBuilder buffer = new StringBuilder(2); 851 toHex(b, buffer); 852 return buffer.toString(); 853 } 854 855 856 857 /** 858 * Appends a hexadecimal representation of the provided byte to the given 859 * buffer. 860 * 861 * @param b The byte to encode as hexadecimal. 862 * @param buffer The buffer to which the hexadecimal representation is to be 863 * appended. 864 */ 865 public static void toHex(final byte b, final StringBuilder buffer) 866 { 867 switch (b & 0xF0) 868 { 869 case 0x00: 870 buffer.append('0'); 871 break; 872 case 0x10: 873 buffer.append('1'); 874 break; 875 case 0x20: 876 buffer.append('2'); 877 break; 878 case 0x30: 879 buffer.append('3'); 880 break; 881 case 0x40: 882 buffer.append('4'); 883 break; 884 case 0x50: 885 buffer.append('5'); 886 break; 887 case 0x60: 888 buffer.append('6'); 889 break; 890 case 0x70: 891 buffer.append('7'); 892 break; 893 case 0x80: 894 buffer.append('8'); 895 break; 896 case 0x90: 897 buffer.append('9'); 898 break; 899 case 0xA0: 900 buffer.append('a'); 901 break; 902 case 0xB0: 903 buffer.append('b'); 904 break; 905 case 0xC0: 906 buffer.append('c'); 907 break; 908 case 0xD0: 909 buffer.append('d'); 910 break; 911 case 0xE0: 912 buffer.append('e'); 913 break; 914 case 0xF0: 915 buffer.append('f'); 916 break; 917 } 918 919 switch (b & 0x0F) 920 { 921 case 0x00: 922 buffer.append('0'); 923 break; 924 case 0x01: 925 buffer.append('1'); 926 break; 927 case 0x02: 928 buffer.append('2'); 929 break; 930 case 0x03: 931 buffer.append('3'); 932 break; 933 case 0x04: 934 buffer.append('4'); 935 break; 936 case 0x05: 937 buffer.append('5'); 938 break; 939 case 0x06: 940 buffer.append('6'); 941 break; 942 case 0x07: 943 buffer.append('7'); 944 break; 945 case 0x08: 946 buffer.append('8'); 947 break; 948 case 0x09: 949 buffer.append('9'); 950 break; 951 case 0x0A: 952 buffer.append('a'); 953 break; 954 case 0x0B: 955 buffer.append('b'); 956 break; 957 case 0x0C: 958 buffer.append('c'); 959 break; 960 case 0x0D: 961 buffer.append('d'); 962 break; 963 case 0x0E: 964 buffer.append('e'); 965 break; 966 case 0x0F: 967 buffer.append('f'); 968 break; 969 } 970 } 971 972 973 974 /** 975 * Retrieves a hexadecimal representation of the contents of the provided byte 976 * array. No delimiter character will be inserted between the hexadecimal 977 * digits for each byte. 978 * 979 * @param b The byte array to be represented as a hexadecimal string. It 980 * must not be {@code null}. 981 * 982 * @return A string containing a hexadecimal representation of the contents 983 * of the provided byte array. 984 */ 985 public static String toHex(final byte[] b) 986 { 987 Validator.ensureNotNull(b); 988 989 final StringBuilder buffer = new StringBuilder(2 * b.length); 990 toHex(b, buffer); 991 return buffer.toString(); 992 } 993 994 995 996 /** 997 * Retrieves a hexadecimal representation of the contents of the provided byte 998 * array. No delimiter character will be inserted between the hexadecimal 999 * digits for each byte. 1000 * 1001 * @param b The byte array to be represented as a hexadecimal string. 1002 * It must not be {@code null}. 1003 * @param buffer A buffer to which the hexadecimal representation of the 1004 * contents of the provided byte array should be appended. 1005 */ 1006 public static void toHex(final byte[] b, final StringBuilder buffer) 1007 { 1008 toHex(b, null, buffer); 1009 } 1010 1011 1012 1013 /** 1014 * Retrieves a hexadecimal representation of the contents of the provided byte 1015 * array. No delimiter character will be inserted between the hexadecimal 1016 * digits for each byte. 1017 * 1018 * @param b The byte array to be represented as a hexadecimal 1019 * string. It must not be {@code null}. 1020 * @param delimiter A delimiter to be inserted between bytes. It may be 1021 * {@code null} if no delimiter should be used. 1022 * @param buffer A buffer to which the hexadecimal representation of the 1023 * contents of the provided byte array should be appended. 1024 */ 1025 public static void toHex(final byte[] b, final String delimiter, 1026 final StringBuilder buffer) 1027 { 1028 boolean first = true; 1029 for (final byte bt : b) 1030 { 1031 if (first) 1032 { 1033 first = false; 1034 } 1035 else if (delimiter != null) 1036 { 1037 buffer.append(delimiter); 1038 } 1039 1040 toHex(bt, buffer); 1041 } 1042 } 1043 1044 1045 1046 /** 1047 * Retrieves a hex-encoded representation of the contents of the provided 1048 * array, along with an ASCII representation of its contents next to it. The 1049 * output will be split across multiple lines, with up to sixteen bytes per 1050 * line. For each of those sixteen bytes, the two-digit hex representation 1051 * will be appended followed by a space. Then, the ASCII representation of 1052 * those sixteen bytes will follow that, with a space used in place of any 1053 * byte that does not have an ASCII representation. 1054 * 1055 * @param array The array whose contents should be processed. 1056 * @param indent The number of spaces to insert on each line prior to the 1057 * first hex byte. 1058 * 1059 * @return A hex-encoded representation of the contents of the provided 1060 * array, along with an ASCII representation of its contents next to 1061 * it. 1062 */ 1063 public static String toHexPlusASCII(final byte[] array, final int indent) 1064 { 1065 final StringBuilder buffer = new StringBuilder(); 1066 toHexPlusASCII(array, indent, buffer); 1067 return buffer.toString(); 1068 } 1069 1070 1071 1072 /** 1073 * Appends a hex-encoded representation of the contents of the provided array 1074 * to the given buffer, along with an ASCII representation of its contents 1075 * next to it. The output will be split across multiple lines, with up to 1076 * sixteen bytes per line. For each of those sixteen bytes, the two-digit hex 1077 * representation will be appended followed by a space. Then, the ASCII 1078 * representation of those sixteen bytes will follow that, with a space used 1079 * in place of any byte that does not have an ASCII representation. 1080 * 1081 * @param array The array whose contents should be processed. 1082 * @param indent The number of spaces to insert on each line prior to the 1083 * first hex byte. 1084 * @param buffer The buffer to which the encoded data should be appended. 1085 */ 1086 public static void toHexPlusASCII(final byte[] array, final int indent, 1087 final StringBuilder buffer) 1088 { 1089 if ((array == null) || (array.length == 0)) 1090 { 1091 return; 1092 } 1093 1094 for (int i=0; i < indent; i++) 1095 { 1096 buffer.append(' '); 1097 } 1098 1099 int pos = 0; 1100 int startPos = 0; 1101 while (pos < array.length) 1102 { 1103 toHex(array[pos++], buffer); 1104 buffer.append(' '); 1105 1106 if ((pos % 16) == 0) 1107 { 1108 buffer.append(" "); 1109 for (int i=startPos; i < pos; i++) 1110 { 1111 if ((array[i] < ' ') || (array[i] > '~')) 1112 { 1113 buffer.append(' '); 1114 } 1115 else 1116 { 1117 buffer.append((char) array[i]); 1118 } 1119 } 1120 buffer.append(EOL); 1121 startPos = pos; 1122 1123 if (pos < array.length) 1124 { 1125 for (int i=0; i < indent; i++) 1126 { 1127 buffer.append(' '); 1128 } 1129 } 1130 } 1131 } 1132 1133 // If the last line isn't complete yet, then finish it off. 1134 if ((array.length % 16) != 0) 1135 { 1136 final int missingBytes = (16 - (array.length % 16)); 1137 if (missingBytes > 0) 1138 { 1139 for (int i=0; i < missingBytes; i++) 1140 { 1141 buffer.append(" "); 1142 } 1143 buffer.append(" "); 1144 for (int i=startPos; i < array.length; i++) 1145 { 1146 if ((array[i] < ' ') || (array[i] > '~')) 1147 { 1148 buffer.append(' '); 1149 } 1150 else 1151 { 1152 buffer.append((char) array[i]); 1153 } 1154 } 1155 buffer.append(EOL); 1156 } 1157 } 1158 } 1159 1160 1161 1162 /** 1163 * Retrieves the bytes that correspond to the provided hexadecimal string. 1164 * 1165 * @param hexString The hexadecimal string for which to retrieve the bytes. 1166 * It must not be {@code null}, and there must not be any 1167 * delimiter between bytes. 1168 * 1169 * @return The bytes that correspond to the provided hexadecimal string. 1170 * 1171 * @throws ParseException If the provided string does not represent valid 1172 * hexadecimal data, or if the provided string does 1173 * not contain an even number of characters. 1174 */ 1175 public static byte[] fromHex(final String hexString) 1176 throws ParseException 1177 { 1178 if ((hexString.length() % 2) != 0) 1179 { 1180 throw new ParseException( 1181 ERR_FROM_HEX_ODD_NUMBER_OF_CHARACTERS.get(hexString.length()), 1182 hexString.length()); 1183 } 1184 1185 final byte[] decodedBytes = new byte[hexString.length() / 2]; 1186 for (int i=0, j=0; i < decodedBytes.length; i++, j+= 2) 1187 { 1188 switch (hexString.charAt(j)) 1189 { 1190 case '0': 1191 // No action is required. 1192 break; 1193 case '1': 1194 decodedBytes[i] = 0x10; 1195 break; 1196 case '2': 1197 decodedBytes[i] = 0x20; 1198 break; 1199 case '3': 1200 decodedBytes[i] = 0x30; 1201 break; 1202 case '4': 1203 decodedBytes[i] = 0x40; 1204 break; 1205 case '5': 1206 decodedBytes[i] = 0x50; 1207 break; 1208 case '6': 1209 decodedBytes[i] = 0x60; 1210 break; 1211 case '7': 1212 decodedBytes[i] = 0x70; 1213 break; 1214 case '8': 1215 decodedBytes[i] = (byte) 0x80; 1216 break; 1217 case '9': 1218 decodedBytes[i] = (byte) 0x90; 1219 break; 1220 case 'a': 1221 case 'A': 1222 decodedBytes[i] = (byte) 0xA0; 1223 break; 1224 case 'b': 1225 case 'B': 1226 decodedBytes[i] = (byte) 0xB0; 1227 break; 1228 case 'c': 1229 case 'C': 1230 decodedBytes[i] = (byte) 0xC0; 1231 break; 1232 case 'd': 1233 case 'D': 1234 decodedBytes[i] = (byte) 0xD0; 1235 break; 1236 case 'e': 1237 case 'E': 1238 decodedBytes[i] = (byte) 0xE0; 1239 break; 1240 case 'f': 1241 case 'F': 1242 decodedBytes[i] = (byte) 0xF0; 1243 break; 1244 default: 1245 throw new ParseException(ERR_FROM_HEX_NON_HEX_CHARACTER.get(j), j); 1246 } 1247 1248 switch (hexString.charAt(j+1)) 1249 { 1250 case '0': 1251 // No action is required. 1252 break; 1253 case '1': 1254 decodedBytes[i] |= 0x01; 1255 break; 1256 case '2': 1257 decodedBytes[i] |= 0x02; 1258 break; 1259 case '3': 1260 decodedBytes[i] |= 0x03; 1261 break; 1262 case '4': 1263 decodedBytes[i] |= 0x04; 1264 break; 1265 case '5': 1266 decodedBytes[i] |= 0x05; 1267 break; 1268 case '6': 1269 decodedBytes[i] |= 0x06; 1270 break; 1271 case '7': 1272 decodedBytes[i] |= 0x07; 1273 break; 1274 case '8': 1275 decodedBytes[i] |= 0x08; 1276 break; 1277 case '9': 1278 decodedBytes[i] |= 0x09; 1279 break; 1280 case 'a': 1281 case 'A': 1282 decodedBytes[i] |= 0x0A; 1283 break; 1284 case 'b': 1285 case 'B': 1286 decodedBytes[i] |= 0x0B; 1287 break; 1288 case 'c': 1289 case 'C': 1290 decodedBytes[i] |= 0x0C; 1291 break; 1292 case 'd': 1293 case 'D': 1294 decodedBytes[i] |= 0x0D; 1295 break; 1296 case 'e': 1297 case 'E': 1298 decodedBytes[i] |= 0x0E; 1299 break; 1300 case 'f': 1301 case 'F': 1302 decodedBytes[i] |= 0x0F; 1303 break; 1304 default: 1305 throw new ParseException(ERR_FROM_HEX_NON_HEX_CHARACTER.get(j+1), 1306 j+1); 1307 } 1308 } 1309 1310 return decodedBytes; 1311 } 1312 1313 1314 1315 /** 1316 * Appends a hex-encoded representation of the provided character to the given 1317 * buffer. Each byte of the hex-encoded representation will be prefixed with 1318 * a backslash. 1319 * 1320 * @param c The character to be encoded. 1321 * @param buffer The buffer to which the hex-encoded representation should 1322 * be appended. 1323 */ 1324 public static void hexEncode(final char c, final StringBuilder buffer) 1325 { 1326 final byte[] charBytes; 1327 if (c <= 0x7F) 1328 { 1329 charBytes = new byte[] { (byte) (c & 0x7F) }; 1330 } 1331 else 1332 { 1333 charBytes = getBytes(String.valueOf(c)); 1334 } 1335 1336 for (final byte b : charBytes) 1337 { 1338 buffer.append('\\'); 1339 toHex(b, buffer); 1340 } 1341 } 1342 1343 1344 1345 /** 1346 * Appends a hex-encoded representation of the provided code point to the 1347 * given buffer. Each byte of the hex-encoded representation will be prefixed 1348 * with a backslash. 1349 * 1350 * @param codePoint The code point to be encoded. 1351 * @param buffer The buffer to which the hex-encoded representation 1352 * should be appended. 1353 */ 1354 public static void hexEncode(final int codePoint, final StringBuilder buffer) 1355 { 1356 final byte[] charBytes = 1357 getBytes(new String(new int[] { codePoint }, 0, 1)); 1358 1359 for (final byte b : charBytes) 1360 { 1361 buffer.append('\\'); 1362 toHex(b, buffer); 1363 } 1364 } 1365 1366 1367 1368 /** 1369 * Appends the Java code that may be used to create the provided byte 1370 * array to the given buffer. 1371 * 1372 * @param array The byte array containing the data to represent. It must 1373 * not be {@code null}. 1374 * @param buffer The buffer to which the code should be appended. 1375 */ 1376 public static void byteArrayToCode(final byte[] array, 1377 final StringBuilder buffer) 1378 { 1379 buffer.append("new byte[] {"); 1380 for (int i=0; i < array.length; i++) 1381 { 1382 if (i > 0) 1383 { 1384 buffer.append(','); 1385 } 1386 1387 buffer.append(" (byte) 0x"); 1388 toHex(array[i], buffer); 1389 } 1390 buffer.append(" }"); 1391 } 1392 1393 1394 1395 /** 1396 * Retrieves a single-line string representation of the stack trace for the 1397 * provided {@code Throwable}. It will include the unqualified name of the 1398 * {@code Throwable} class, a list of source files and line numbers (if 1399 * available) for the stack trace, and will also include the stack trace for 1400 * the cause (if present). 1401 * 1402 * @param t The {@code Throwable} for which to retrieve the stack trace. 1403 * 1404 * @return A single-line string representation of the stack trace for the 1405 * provided {@code Throwable}. 1406 */ 1407 public static String getStackTrace(final Throwable t) 1408 { 1409 final StringBuilder buffer = new StringBuilder(); 1410 getStackTrace(t, buffer); 1411 return buffer.toString(); 1412 } 1413 1414 1415 1416 /** 1417 * Appends a single-line string representation of the stack trace for the 1418 * provided {@code Throwable} to the given buffer. It will include the 1419 * unqualified name of the {@code Throwable} class, a list of source files and 1420 * line numbers (if available) for the stack trace, and will also include the 1421 * stack trace for the cause (if present). 1422 * 1423 * @param t The {@code Throwable} for which to retrieve the stack 1424 * trace. 1425 * @param buffer The buffer to which the information should be appended. 1426 */ 1427 public static void getStackTrace(final Throwable t, 1428 final StringBuilder buffer) 1429 { 1430 buffer.append(getUnqualifiedClassName(t.getClass())); 1431 buffer.append('('); 1432 1433 final String message = t.getMessage(); 1434 if (message != null) 1435 { 1436 buffer.append("message='"); 1437 buffer.append(message); 1438 buffer.append("', "); 1439 } 1440 1441 buffer.append("trace='"); 1442 getStackTrace(t.getStackTrace(), buffer); 1443 buffer.append('\''); 1444 1445 final Throwable cause = t.getCause(); 1446 if (cause != null) 1447 { 1448 buffer.append(", cause="); 1449 getStackTrace(cause, buffer); 1450 } 1451 1452 final String ldapSDKVersionString = ", ldapSDKVersion=" + 1453 Version.NUMERIC_VERSION_STRING + ", revision=" + Version.REVISION_ID; 1454 if (buffer.indexOf(ldapSDKVersionString) < 0) 1455 { 1456 buffer.append(ldapSDKVersionString); 1457 } 1458 1459 buffer.append(')'); 1460 } 1461 1462 1463 1464 /** 1465 * Returns a single-line string representation of the stack trace. It will 1466 * include a list of source files and line numbers (if available) for the 1467 * stack trace. 1468 * 1469 * @param elements The stack trace. 1470 * 1471 * @return A single-line string representation of the stack trace. 1472 */ 1473 public static String getStackTrace(final StackTraceElement[] elements) 1474 { 1475 final StringBuilder buffer = new StringBuilder(); 1476 getStackTrace(elements, buffer); 1477 return buffer.toString(); 1478 } 1479 1480 1481 1482 /** 1483 * Appends a single-line string representation of the stack trace to the given 1484 * buffer. It will include a list of source files and line numbers 1485 * (if available) for the stack trace. 1486 * 1487 * @param elements The stack trace. 1488 * @param buffer The buffer to which the information should be appended. 1489 */ 1490 public static void getStackTrace(final StackTraceElement[] elements, 1491 final StringBuilder buffer) 1492 { 1493 for (int i=0; i < elements.length; i++) 1494 { 1495 if (i > 0) 1496 { 1497 buffer.append(" / "); 1498 } 1499 1500 buffer.append(elements[i].getMethodName()); 1501 buffer.append('('); 1502 buffer.append(elements[i].getFileName()); 1503 1504 final int lineNumber = elements[i].getLineNumber(); 1505 if (lineNumber > 0) 1506 { 1507 buffer.append(':'); 1508 buffer.append(lineNumber); 1509 } 1510 else if (elements[i].isNativeMethod()) 1511 { 1512 buffer.append(":native"); 1513 } 1514 else 1515 { 1516 buffer.append(":unknown"); 1517 } 1518 buffer.append(')'); 1519 } 1520 } 1521 1522 1523 1524 /** 1525 * Retrieves a string representation of the provided {@code Throwable} object 1526 * suitable for use in a message. For runtime exceptions and errors, then a 1527 * full stack trace for the exception will be provided. For exception types 1528 * defined in the LDAP SDK, then its {@code getExceptionMessage} method will 1529 * be used to get the string representation. For all other types of 1530 * exceptions, then the standard string representation will be used. 1531 * <BR><BR> 1532 * For all types of exceptions, the message will also include the cause if one 1533 * exists. 1534 * 1535 * @param t The {@code Throwable} for which to generate the exception 1536 * message. 1537 * 1538 * @return A string representation of the provided {@code Throwable} object 1539 * suitable for use in a message. 1540 */ 1541 public static String getExceptionMessage(final Throwable t) 1542 { 1543 final boolean includeCause = 1544 Boolean.getBoolean(Debug.PROPERTY_INCLUDE_CAUSE_IN_EXCEPTION_MESSAGES); 1545 final boolean includeStackTrace = Boolean.getBoolean( 1546 Debug.PROPERTY_INCLUDE_STACK_TRACE_IN_EXCEPTION_MESSAGES); 1547 1548 return getExceptionMessage(t, includeCause, includeStackTrace); 1549 } 1550 1551 1552 1553 /** 1554 * Retrieves a string representation of the provided {@code Throwable} object 1555 * suitable for use in a message. For runtime exceptions and errors, then a 1556 * full stack trace for the exception will be provided. For exception types 1557 * defined in the LDAP SDK, then its {@code getExceptionMessage} method will 1558 * be used to get the string representation. For all other types of 1559 * exceptions, then the standard string representation will be used. 1560 * <BR><BR> 1561 * For all types of exceptions, the message will also include the cause if one 1562 * exists. 1563 * 1564 * @param t The {@code Throwable} for which to generate the 1565 * exception message. 1566 * @param includeCause Indicates whether to include information about 1567 * the cause (if any) in the exception message. 1568 * @param includeStackTrace Indicates whether to include a condensed 1569 * representation of the stack trace in the 1570 * exception message. 1571 * 1572 * @return A string representation of the provided {@code Throwable} object 1573 * suitable for use in a message. 1574 */ 1575 public static String getExceptionMessage(final Throwable t, 1576 final boolean includeCause, 1577 final boolean includeStackTrace) 1578 { 1579 if (t == null) 1580 { 1581 return ERR_NO_EXCEPTION.get(); 1582 } 1583 1584 final StringBuilder buffer = new StringBuilder(); 1585 if (t instanceof LDAPSDKException) 1586 { 1587 buffer.append(((LDAPSDKException) t).getExceptionMessage()); 1588 } 1589 else if (t instanceof LDAPSDKRuntimeException) 1590 { 1591 buffer.append(((LDAPSDKRuntimeException) t).getExceptionMessage()); 1592 } 1593 else if (t instanceof NullPointerException) 1594 { 1595 buffer.append("NullPointerException("); 1596 1597 final StackTraceElement[] stackTraceElements = t.getStackTrace(); 1598 for (int i=0; i < stackTraceElements.length; i++) 1599 { 1600 final StackTraceElement e = stackTraceElements[i]; 1601 if (i > 0) 1602 { 1603 buffer.append(" / "); 1604 } 1605 1606 buffer.append(e.getFileName()); 1607 1608 final int lineNumber = e.getLineNumber(); 1609 if (lineNumber > 0) 1610 { 1611 buffer.append(':'); 1612 buffer.append(lineNumber); 1613 } 1614 else if (e.isNativeMethod()) 1615 { 1616 buffer.append(":native"); 1617 } 1618 else 1619 { 1620 buffer.append(":unknown"); 1621 } 1622 1623 if (e.getClassName().contains("unboundid")) 1624 { 1625 if (i < (stackTraceElements.length - 1)) 1626 { 1627 buffer.append(" ..."); 1628 } 1629 1630 break; 1631 } 1632 } 1633 1634 buffer.append(')'); 1635 } 1636 else if ((t.getMessage() == null) || t.getMessage().isEmpty() || 1637 t.getMessage().equalsIgnoreCase("null")) 1638 { 1639 getStackTrace(t, buffer); 1640 } 1641 else 1642 { 1643 buffer.append(t.getClass().getSimpleName()); 1644 buffer.append('('); 1645 buffer.append(t.getMessage()); 1646 buffer.append(')'); 1647 1648 if (includeStackTrace) 1649 { 1650 buffer.append(" trace="); 1651 getStackTrace(t, buffer); 1652 } 1653 else if (includeCause) 1654 { 1655 final Throwable cause = t.getCause(); 1656 if (cause != null) 1657 { 1658 buffer.append(" caused by "); 1659 buffer.append(getExceptionMessage(cause)); 1660 } 1661 } 1662 } 1663 1664 final String ldapSDKVersionString = ", ldapSDKVersion=" + 1665 Version.NUMERIC_VERSION_STRING + ", revision=" + Version.REVISION_ID; 1666 if (buffer.indexOf(ldapSDKVersionString) < 0) 1667 { 1668 buffer.append(ldapSDKVersionString); 1669 } 1670 1671 return buffer.toString(); 1672 } 1673 1674 1675 1676 /** 1677 * Retrieves the unqualified name (i.e., the name without package information) 1678 * for the provided class. 1679 * 1680 * @param c The class for which to retrieve the unqualified name. 1681 * 1682 * @return The unqualified name for the provided class. 1683 */ 1684 public static String getUnqualifiedClassName(final Class<?> c) 1685 { 1686 final String className = c.getName(); 1687 final int lastPeriodPos = className.lastIndexOf('.'); 1688 1689 if (lastPeriodPos > 0) 1690 { 1691 return className.substring(lastPeriodPos+1); 1692 } 1693 else 1694 { 1695 return className; 1696 } 1697 } 1698 1699 1700 1701 /** 1702 * Retrieves a {@code TimeZone} object that represents the UTC (universal 1703 * coordinated time) time zone. 1704 * 1705 * @return A {@code TimeZone} object that represents the UTC time zone. 1706 */ 1707 public static TimeZone getUTCTimeZone() 1708 { 1709 return UTC_TIME_ZONE; 1710 } 1711 1712 1713 1714 /** 1715 * Encodes the provided timestamp in generalized time format. 1716 * 1717 * @param timestamp The timestamp to be encoded in generalized time format. 1718 * It should use the same format as the 1719 * {@code System.currentTimeMillis()} method (i.e., the 1720 * number of milliseconds since 12:00am UTC on January 1, 1721 * 1970). 1722 * 1723 * @return The generalized time representation of the provided date. 1724 */ 1725 public static String encodeGeneralizedTime(final long timestamp) 1726 { 1727 return encodeGeneralizedTime(new Date(timestamp)); 1728 } 1729 1730 1731 1732 /** 1733 * Encodes the provided date in generalized time format. 1734 * 1735 * @param d The date to be encoded in generalized time format. 1736 * 1737 * @return The generalized time representation of the provided date. 1738 */ 1739 public static String encodeGeneralizedTime(final Date d) 1740 { 1741 SimpleDateFormat dateFormat = DATE_FORMATTERS.get(); 1742 if (dateFormat == null) 1743 { 1744 dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'"); 1745 dateFormat.setTimeZone(UTC_TIME_ZONE); 1746 DATE_FORMATTERS.set(dateFormat); 1747 } 1748 1749 return dateFormat.format(d); 1750 } 1751 1752 1753 1754 /** 1755 * Decodes the provided string as a timestamp in generalized time format. 1756 * 1757 * @param t The timestamp to be decoded. It must not be {@code null}. 1758 * 1759 * @return The {@code Date} object decoded from the provided timestamp. 1760 * 1761 * @throws ParseException If the provided string could not be decoded as a 1762 * timestamp in generalized time format. 1763 */ 1764 public static Date decodeGeneralizedTime(final String t) 1765 throws ParseException 1766 { 1767 Validator.ensureNotNull(t); 1768 1769 // Extract the time zone information from the end of the value. 1770 int tzPos; 1771 final TimeZone tz; 1772 if (t.endsWith("Z")) 1773 { 1774 tz = TimeZone.getTimeZone("UTC"); 1775 tzPos = t.length() - 1; 1776 } 1777 else 1778 { 1779 tzPos = t.lastIndexOf('-'); 1780 if (tzPos < 0) 1781 { 1782 tzPos = t.lastIndexOf('+'); 1783 if (tzPos < 0) 1784 { 1785 throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t), 1786 0); 1787 } 1788 } 1789 1790 tz = TimeZone.getTimeZone("GMT" + t.substring(tzPos)); 1791 if (tz.getRawOffset() == 0) 1792 { 1793 // This is the default time zone that will be returned if the value 1794 // cannot be parsed. If it's valid, then it will end in "+0000" or 1795 // "-0000". Otherwise, it's invalid and GMT was just a fallback. 1796 if (! (t.endsWith("+0000") || t.endsWith("-0000"))) 1797 { 1798 throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t), 1799 tzPos); 1800 } 1801 } 1802 } 1803 1804 1805 // See if the timestamp has a sub-second portion. Note that if there is a 1806 // sub-second portion, then we may need to massage the value so that there 1807 // are exactly three sub-second characters so that it can be interpreted as 1808 // milliseconds. 1809 final String subSecFormatStr; 1810 final String trimmedTimestamp; 1811 int periodPos = t.lastIndexOf('.', tzPos); 1812 if (periodPos > 0) 1813 { 1814 final int subSecondLength = tzPos - periodPos - 1; 1815 switch (subSecondLength) 1816 { 1817 case 0: 1818 subSecFormatStr = ""; 1819 trimmedTimestamp = t.substring(0, periodPos); 1820 break; 1821 case 1: 1822 subSecFormatStr = ".SSS"; 1823 trimmedTimestamp = t.substring(0, (periodPos+2)) + "00"; 1824 break; 1825 case 2: 1826 subSecFormatStr = ".SSS"; 1827 trimmedTimestamp = t.substring(0, (periodPos+3)) + '0'; 1828 break; 1829 default: 1830 subSecFormatStr = ".SSS"; 1831 trimmedTimestamp = t.substring(0, periodPos+4); 1832 break; 1833 } 1834 } 1835 else 1836 { 1837 subSecFormatStr = ""; 1838 periodPos = tzPos; 1839 trimmedTimestamp = t.substring(0, tzPos); 1840 } 1841 1842 1843 // Look at where the period is (or would be if it existed) to see how many 1844 // characters are in the integer portion. This will give us what we need 1845 // for the rest of the format string. 1846 final String formatStr; 1847 switch (periodPos) 1848 { 1849 case 10: 1850 formatStr = "yyyyMMddHH" + subSecFormatStr; 1851 break; 1852 case 12: 1853 formatStr = "yyyyMMddHHmm" + subSecFormatStr; 1854 break; 1855 case 14: 1856 formatStr = "yyyyMMddHHmmss" + subSecFormatStr; 1857 break; 1858 default: 1859 throw new ParseException(ERR_GENTIME_CANNOT_PARSE_INVALID_LENGTH.get(t), 1860 periodPos); 1861 } 1862 1863 1864 // We should finally be able to create an appropriate date format object 1865 // to parse the trimmed version of the timestamp. 1866 final SimpleDateFormat dateFormat = new SimpleDateFormat(formatStr); 1867 dateFormat.setTimeZone(tz); 1868 dateFormat.setLenient(false); 1869 return dateFormat.parse(trimmedTimestamp); 1870 } 1871 1872 1873 1874 /** 1875 * Trims only leading spaces from the provided string, leaving any trailing 1876 * spaces intact. 1877 * 1878 * @param s The string to be processed. It must not be {@code null}. 1879 * 1880 * @return The original string if no trimming was required, or a new string 1881 * without leading spaces if the provided string had one or more. It 1882 * may be an empty string if the provided string was an empty string 1883 * or contained only spaces. 1884 */ 1885 public static String trimLeading(final String s) 1886 { 1887 Validator.ensureNotNull(s); 1888 1889 int nonSpacePos = 0; 1890 final int length = s.length(); 1891 while ((nonSpacePos < length) && (s.charAt(nonSpacePos) == ' ')) 1892 { 1893 nonSpacePos++; 1894 } 1895 1896 if (nonSpacePos == 0) 1897 { 1898 // There were no leading spaces. 1899 return s; 1900 } 1901 else if (nonSpacePos >= length) 1902 { 1903 // There were no non-space characters. 1904 return ""; 1905 } 1906 else 1907 { 1908 // There were leading spaces, so return the string without them. 1909 return s.substring(nonSpacePos, length); 1910 } 1911 } 1912 1913 1914 1915 /** 1916 * Trims only trailing spaces from the provided string, leaving any leading 1917 * spaces intact. 1918 * 1919 * @param s The string to be processed. It must not be {@code null}. 1920 * 1921 * @return The original string if no trimming was required, or a new string 1922 * without trailing spaces if the provided string had one or more. 1923 * It may be an empty string if the provided string was an empty 1924 * string or contained only spaces. 1925 */ 1926 public static String trimTrailing(final String s) 1927 { 1928 Validator.ensureNotNull(s); 1929 1930 final int lastPos = s.length() - 1; 1931 int nonSpacePos = lastPos; 1932 while ((nonSpacePos >= 0) && (s.charAt(nonSpacePos) == ' ')) 1933 { 1934 nonSpacePos--; 1935 } 1936 1937 if (nonSpacePos < 0) 1938 { 1939 // There were no non-space characters. 1940 return ""; 1941 } 1942 else if (nonSpacePos == lastPos) 1943 { 1944 // There were no trailing spaces. 1945 return s; 1946 } 1947 else 1948 { 1949 // There were trailing spaces, so return the string without them. 1950 return s.substring(0, (nonSpacePos+1)); 1951 } 1952 } 1953 1954 1955 1956 /** 1957 * Wraps the contents of the specified line using the given width. It will 1958 * attempt to wrap at spaces to preserve words, but if that is not possible 1959 * (because a single "word" is longer than the maximum width), then it will 1960 * wrap in the middle of the word at the specified maximum width. 1961 * 1962 * @param line The line to be wrapped. It must not be {@code null}. 1963 * @param maxWidth The maximum width for lines in the resulting list. A 1964 * value less than or equal to zero will cause no wrapping 1965 * to be performed. 1966 * 1967 * @return A list of the wrapped lines. It may be empty if the provided line 1968 * contained only spaces. 1969 */ 1970 public static List<String> wrapLine(final String line, final int maxWidth) 1971 { 1972 return wrapLine(line, maxWidth, maxWidth); 1973 } 1974 1975 1976 1977 /** 1978 * Wraps the contents of the specified line using the given width. It will 1979 * attempt to wrap at spaces to preserve words, but if that is not possible 1980 * (because a single "word" is longer than the maximum width), then it will 1981 * wrap in the middle of the word at the specified maximum width. 1982 * 1983 * @param line The line to be wrapped. It must not be 1984 * {@code null}. 1985 * @param maxFirstLineWidth The maximum length for the first line in 1986 * the resulting list. A value less than or 1987 * equal to zero will cause no wrapping to be 1988 * performed. 1989 * @param maxSubsequentLineWidth The maximum length for all lines except the 1990 * first line. This must be greater than zero 1991 * unless {@code maxFirstLineWidth} is less 1992 * than or equal to zero. 1993 * 1994 * @return A list of the wrapped lines. It may be empty if the provided line 1995 * contained only spaces. 1996 */ 1997 public static List<String> wrapLine(final String line, 1998 final int maxFirstLineWidth, 1999 final int maxSubsequentLineWidth) 2000 { 2001 if (maxFirstLineWidth > 0) 2002 { 2003 Validator.ensureTrue(maxSubsequentLineWidth > 0); 2004 } 2005 2006 // See if the provided string already contains line breaks. If so, then 2007 // treat it as multiple lines rather than a single line. 2008 final int breakPos = line.indexOf('\n'); 2009 if (breakPos >= 0) 2010 { 2011 final ArrayList<String> lineList = new ArrayList<>(10); 2012 final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n"); 2013 while (tokenizer.hasMoreTokens()) 2014 { 2015 lineList.addAll(wrapLine(tokenizer.nextToken(), maxFirstLineWidth, 2016 maxSubsequentLineWidth)); 2017 } 2018 2019 return lineList; 2020 } 2021 2022 final int length = line.length(); 2023 if ((maxFirstLineWidth <= 0) || (length < maxFirstLineWidth)) 2024 { 2025 return Collections.singletonList(line); 2026 } 2027 2028 2029 int wrapPos = maxFirstLineWidth; 2030 int lastWrapPos = 0; 2031 final ArrayList<String> lineList = new ArrayList<>(5); 2032 while (true) 2033 { 2034 final int spacePos = line.lastIndexOf(' ', wrapPos); 2035 if (spacePos > lastWrapPos) 2036 { 2037 // We found a space in an acceptable location, so use it after trimming 2038 // any trailing spaces. 2039 final String s = trimTrailing(line.substring(lastWrapPos, spacePos)); 2040 2041 // Don't bother adding the line if it contained only spaces. 2042 if (! s.isEmpty()) 2043 { 2044 lineList.add(s); 2045 } 2046 2047 wrapPos = spacePos; 2048 } 2049 else 2050 { 2051 // We didn't find any spaces, so we'll have to insert a hard break at 2052 // the specified wrap column. 2053 lineList.add(line.substring(lastWrapPos, wrapPos)); 2054 } 2055 2056 // Skip over any spaces before the next non-space character. 2057 while ((wrapPos < length) && (line.charAt(wrapPos) == ' ')) 2058 { 2059 wrapPos++; 2060 } 2061 2062 lastWrapPos = wrapPos; 2063 wrapPos += maxSubsequentLineWidth; 2064 if (wrapPos >= length) 2065 { 2066 // The last fragment can fit on the line, so we can handle that now and 2067 // break. 2068 if (lastWrapPos >= length) 2069 { 2070 break; 2071 } 2072 else 2073 { 2074 final String s = line.substring(lastWrapPos); 2075 if (! s.isEmpty()) 2076 { 2077 lineList.add(s); 2078 } 2079 break; 2080 } 2081 } 2082 } 2083 2084 return lineList; 2085 } 2086 2087 2088 2089 /** 2090 * This method returns a form of the provided argument that is safe to 2091 * use on the command line for the local platform. This method is provided as 2092 * a convenience wrapper around {@link ExampleCommandLineArgument}. Calling 2093 * this method is equivalent to: 2094 * 2095 * <PRE> 2096 * return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm(); 2097 * </PRE> 2098 * 2099 * For getting direct access to command line arguments that are safe to 2100 * use on other platforms, call 2101 * {@link ExampleCommandLineArgument#getCleanArgument}. 2102 * 2103 * @param s The string to be processed. It must not be {@code null}. 2104 * 2105 * @return A cleaned version of the provided string in a form that will allow 2106 * it to be displayed as the value of a command-line argument on. 2107 */ 2108 public static String cleanExampleCommandLineArgument(final String s) 2109 { 2110 return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm(); 2111 } 2112 2113 2114 2115 /** 2116 * Retrieves a single string which is a concatenation of all of the provided 2117 * strings. 2118 * 2119 * @param a The array of strings to concatenate. It must not be 2120 * {@code null}. 2121 * 2122 * @return A string containing a concatenation of all of the strings in the 2123 * provided array. 2124 */ 2125 public static String concatenateStrings(final String... a) 2126 { 2127 return concatenateStrings(null, null, " ", null, null, a); 2128 } 2129 2130 2131 2132 /** 2133 * Retrieves a single string which is a concatenation of all of the provided 2134 * strings. 2135 * 2136 * @param l The list of strings to concatenate. It must not be 2137 * {@code null}. 2138 * 2139 * @return A string containing a concatenation of all of the strings in the 2140 * provided list. 2141 */ 2142 public static String concatenateStrings(final List<String> l) 2143 { 2144 return concatenateStrings(null, null, " ", null, null, l); 2145 } 2146 2147 2148 2149 /** 2150 * Retrieves a single string which is a concatenation of all of the provided 2151 * strings. 2152 * 2153 * @param beforeList A string that should be placed at the beginning of 2154 * the list. It may be {@code null} or empty if 2155 * nothing should be placed at the beginning of the 2156 * list. 2157 * @param beforeElement A string that should be placed before each element 2158 * in the list. It may be {@code null} or empty if 2159 * nothing should be placed before each element. 2160 * @param betweenElements The separator that should be placed between 2161 * elements in the list. It may be {@code null} or 2162 * empty if no separator should be placed between 2163 * elements. 2164 * @param afterElement A string that should be placed after each element 2165 * in the list. It may be {@code null} or empty if 2166 * nothing should be placed after each element. 2167 * @param afterList A string that should be placed at the end of the 2168 * list. It may be {@code null} or empty if nothing 2169 * should be placed at the end of the list. 2170 * @param a The array of strings to concatenate. It must not 2171 * be {@code null}. 2172 * 2173 * @return A string containing a concatenation of all of the strings in the 2174 * provided list. 2175 */ 2176 public static String concatenateStrings(final String beforeList, 2177 final String beforeElement, 2178 final String betweenElements, 2179 final String afterElement, 2180 final String afterList, 2181 final String... a) 2182 { 2183 return concatenateStrings(beforeList, beforeElement, betweenElements, 2184 afterElement, afterList, Arrays.asList(a)); 2185 } 2186 2187 2188 2189 /** 2190 * Retrieves a single string which is a concatenation of all of the provided 2191 * strings. 2192 * 2193 * @param beforeList A string that should be placed at the beginning of 2194 * the list. It may be {@code null} or empty if 2195 * nothing should be placed at the beginning of the 2196 * list. 2197 * @param beforeElement A string that should be placed before each element 2198 * in the list. It may be {@code null} or empty if 2199 * nothing should be placed before each element. 2200 * @param betweenElements The separator that should be placed between 2201 * elements in the list. It may be {@code null} or 2202 * empty if no separator should be placed between 2203 * elements. 2204 * @param afterElement A string that should be placed after each element 2205 * in the list. It may be {@code null} or empty if 2206 * nothing should be placed after each element. 2207 * @param afterList A string that should be placed at the end of the 2208 * list. It may be {@code null} or empty if nothing 2209 * should be placed at the end of the list. 2210 * @param l The list of strings to concatenate. It must not 2211 * be {@code null}. 2212 * 2213 * @return A string containing a concatenation of all of the strings in the 2214 * provided list. 2215 */ 2216 public static String concatenateStrings(final String beforeList, 2217 final String beforeElement, 2218 final String betweenElements, 2219 final String afterElement, 2220 final String afterList, 2221 final List<String> l) 2222 { 2223 Validator.ensureNotNull(l); 2224 2225 final StringBuilder buffer = new StringBuilder(); 2226 2227 if (beforeList != null) 2228 { 2229 buffer.append(beforeList); 2230 } 2231 2232 final Iterator<String> iterator = l.iterator(); 2233 while (iterator.hasNext()) 2234 { 2235 if (beforeElement != null) 2236 { 2237 buffer.append(beforeElement); 2238 } 2239 2240 buffer.append(iterator.next()); 2241 2242 if (afterElement != null) 2243 { 2244 buffer.append(afterElement); 2245 } 2246 2247 if ((betweenElements != null) && iterator.hasNext()) 2248 { 2249 buffer.append(betweenElements); 2250 } 2251 } 2252 2253 if (afterList != null) 2254 { 2255 buffer.append(afterList); 2256 } 2257 2258 return buffer.toString(); 2259 } 2260 2261 2262 2263 /** 2264 * Converts a duration in seconds to a string with a human-readable duration 2265 * which may include days, hours, minutes, and seconds, to the extent that 2266 * they are needed. 2267 * 2268 * @param s The number of seconds to be represented. 2269 * 2270 * @return A string containing a human-readable representation of the 2271 * provided time. 2272 */ 2273 public static String secondsToHumanReadableDuration(final long s) 2274 { 2275 return millisToHumanReadableDuration(s * 1000L); 2276 } 2277 2278 2279 2280 /** 2281 * Converts a duration in seconds to a string with a human-readable duration 2282 * which may include days, hours, minutes, and seconds, to the extent that 2283 * they are needed. 2284 * 2285 * @param m The number of milliseconds to be represented. 2286 * 2287 * @return A string containing a human-readable representation of the 2288 * provided time. 2289 */ 2290 public static String millisToHumanReadableDuration(final long m) 2291 { 2292 final StringBuilder buffer = new StringBuilder(); 2293 long numMillis = m; 2294 2295 final long numDays = numMillis / 86_400_000L; 2296 if (numDays > 0) 2297 { 2298 numMillis -= (numDays * 86_400_000L); 2299 if (numDays == 1) 2300 { 2301 buffer.append(INFO_NUM_DAYS_SINGULAR.get(numDays)); 2302 } 2303 else 2304 { 2305 buffer.append(INFO_NUM_DAYS_PLURAL.get(numDays)); 2306 } 2307 } 2308 2309 final long numHours = numMillis / 3_600_000L; 2310 if (numHours > 0) 2311 { 2312 numMillis -= (numHours * 3_600_000L); 2313 if (buffer.length() > 0) 2314 { 2315 buffer.append(", "); 2316 } 2317 2318 if (numHours == 1) 2319 { 2320 buffer.append(INFO_NUM_HOURS_SINGULAR.get(numHours)); 2321 } 2322 else 2323 { 2324 buffer.append(INFO_NUM_HOURS_PLURAL.get(numHours)); 2325 } 2326 } 2327 2328 final long numMinutes = numMillis / 60_000L; 2329 if (numMinutes > 0) 2330 { 2331 numMillis -= (numMinutes * 60_000L); 2332 if (buffer.length() > 0) 2333 { 2334 buffer.append(", "); 2335 } 2336 2337 if (numMinutes == 1) 2338 { 2339 buffer.append(INFO_NUM_MINUTES_SINGULAR.get(numMinutes)); 2340 } 2341 else 2342 { 2343 buffer.append(INFO_NUM_MINUTES_PLURAL.get(numMinutes)); 2344 } 2345 } 2346 2347 if (numMillis == 1000) 2348 { 2349 if (buffer.length() > 0) 2350 { 2351 buffer.append(", "); 2352 } 2353 2354 buffer.append(INFO_NUM_SECONDS_SINGULAR.get(1)); 2355 } 2356 else if ((numMillis > 0) || (buffer.length() == 0)) 2357 { 2358 if (buffer.length() > 0) 2359 { 2360 buffer.append(", "); 2361 } 2362 2363 final long numSeconds = numMillis / 1000L; 2364 numMillis -= (numSeconds * 1000L); 2365 if ((numMillis % 1000L) != 0L) 2366 { 2367 final double numSecondsDouble = numSeconds + (numMillis / 1000.0); 2368 final DecimalFormat decimalFormat = new DecimalFormat("0.000"); 2369 buffer.append(INFO_NUM_SECONDS_WITH_DECIMAL.get( 2370 decimalFormat.format(numSecondsDouble))); 2371 } 2372 else 2373 { 2374 buffer.append(INFO_NUM_SECONDS_PLURAL.get(numSeconds)); 2375 } 2376 } 2377 2378 return buffer.toString(); 2379 } 2380 2381 2382 2383 /** 2384 * Converts the provided number of nanoseconds to milliseconds. 2385 * 2386 * @param nanos The number of nanoseconds to convert to milliseconds. 2387 * 2388 * @return The number of milliseconds that most closely corresponds to the 2389 * specified number of nanoseconds. 2390 */ 2391 public static long nanosToMillis(final long nanos) 2392 { 2393 return Math.max(0L, Math.round(nanos / 1_000_000.0d)); 2394 } 2395 2396 2397 2398 /** 2399 * Converts the provided number of milliseconds to nanoseconds. 2400 * 2401 * @param millis The number of milliseconds to convert to nanoseconds. 2402 * 2403 * @return The number of nanoseconds that most closely corresponds to the 2404 * specified number of milliseconds. 2405 */ 2406 public static long millisToNanos(final long millis) 2407 { 2408 return Math.max(0L, (millis * 1_000_000L)); 2409 } 2410 2411 2412 2413 /** 2414 * Indicates whether the provided string is a valid numeric OID. A numeric 2415 * OID must start and end with a digit, must have at least on period, must 2416 * contain only digits and periods, and must not have two consecutive periods. 2417 * 2418 * @param s The string to examine. It must not be {@code null}. 2419 * 2420 * @return {@code true} if the provided string is a valid numeric OID, or 2421 * {@code false} if not. 2422 */ 2423 public static boolean isNumericOID(final String s) 2424 { 2425 boolean digitRequired = true; 2426 boolean periodFound = false; 2427 for (final char c : s.toCharArray()) 2428 { 2429 switch (c) 2430 { 2431 case '0': 2432 case '1': 2433 case '2': 2434 case '3': 2435 case '4': 2436 case '5': 2437 case '6': 2438 case '7': 2439 case '8': 2440 case '9': 2441 digitRequired = false; 2442 break; 2443 2444 case '.': 2445 if (digitRequired) 2446 { 2447 return false; 2448 } 2449 else 2450 { 2451 digitRequired = true; 2452 } 2453 periodFound = true; 2454 break; 2455 2456 default: 2457 return false; 2458 } 2459 2460 } 2461 2462 return (periodFound && (! digitRequired)); 2463 } 2464 2465 2466 2467 /** 2468 * Capitalizes the provided string. The first character will be converted to 2469 * uppercase, and the rest of the string will be left unaltered. 2470 * 2471 * @param s The string to be capitalized. 2472 * 2473 * @return A capitalized version of the provided string. 2474 */ 2475 public static String capitalize(final String s) 2476 { 2477 return capitalize(s, false); 2478 } 2479 2480 2481 2482 /** 2483 * Capitalizes the provided string. The first character of the string (or 2484 * optionally the first character of each word in the string) 2485 * 2486 * @param s The string to be capitalized. 2487 * @param allWords Indicates whether to capitalize all words in the string, 2488 * or only the first word. 2489 * 2490 * @return A capitalized version of the provided string. 2491 */ 2492 public static String capitalize(final String s, final boolean allWords) 2493 { 2494 if (s == null) 2495 { 2496 return null; 2497 } 2498 2499 switch (s.length()) 2500 { 2501 case 0: 2502 return s; 2503 2504 case 1: 2505 return s.toUpperCase(); 2506 2507 default: 2508 boolean capitalize = true; 2509 final char[] chars = s.toCharArray(); 2510 final StringBuilder buffer = new StringBuilder(chars.length); 2511 for (final char c : chars) 2512 { 2513 // Whitespace and punctuation will be considered word breaks. 2514 if (Character.isWhitespace(c) || 2515 (((c >= '!') && (c <= '.')) || 2516 ((c >= ':') && (c <= '@')) || 2517 ((c >= '[') && (c <= '`')) || 2518 ((c >= '{') && (c <= '~')))) 2519 { 2520 buffer.append(c); 2521 capitalize |= allWords; 2522 } 2523 else if (capitalize) 2524 { 2525 buffer.append(Character.toUpperCase(c)); 2526 capitalize = false; 2527 } 2528 else 2529 { 2530 buffer.append(c); 2531 } 2532 } 2533 return buffer.toString(); 2534 } 2535 } 2536 2537 2538 2539 /** 2540 * Encodes the provided UUID to a byte array containing its 128-bit 2541 * representation. 2542 * 2543 * @param uuid The UUID to be encoded. It must not be {@code null}. 2544 * 2545 * @return The byte array containing the 128-bit encoded UUID. 2546 */ 2547 public static byte[] encodeUUID(final UUID uuid) 2548 { 2549 final byte[] b = new byte[16]; 2550 2551 final long mostSignificantBits = uuid.getMostSignificantBits(); 2552 b[0] = (byte) ((mostSignificantBits >> 56) & 0xFF); 2553 b[1] = (byte) ((mostSignificantBits >> 48) & 0xFF); 2554 b[2] = (byte) ((mostSignificantBits >> 40) & 0xFF); 2555 b[3] = (byte) ((mostSignificantBits >> 32) & 0xFF); 2556 b[4] = (byte) ((mostSignificantBits >> 24) & 0xFF); 2557 b[5] = (byte) ((mostSignificantBits >> 16) & 0xFF); 2558 b[6] = (byte) ((mostSignificantBits >> 8) & 0xFF); 2559 b[7] = (byte) (mostSignificantBits & 0xFF); 2560 2561 final long leastSignificantBits = uuid.getLeastSignificantBits(); 2562 b[8] = (byte) ((leastSignificantBits >> 56) & 0xFF); 2563 b[9] = (byte) ((leastSignificantBits >> 48) & 0xFF); 2564 b[10] = (byte) ((leastSignificantBits >> 40) & 0xFF); 2565 b[11] = (byte) ((leastSignificantBits >> 32) & 0xFF); 2566 b[12] = (byte) ((leastSignificantBits >> 24) & 0xFF); 2567 b[13] = (byte) ((leastSignificantBits >> 16) & 0xFF); 2568 b[14] = (byte) ((leastSignificantBits >> 8) & 0xFF); 2569 b[15] = (byte) (leastSignificantBits & 0xFF); 2570 2571 return b; 2572 } 2573 2574 2575 2576 /** 2577 * Decodes the value of the provided byte array as a Java UUID. 2578 * 2579 * @param b The byte array to be decoded as a UUID. It must not be 2580 * {@code null}. 2581 * 2582 * @return The decoded UUID. 2583 * 2584 * @throws ParseException If the provided byte array cannot be parsed as a 2585 * UUID. 2586 */ 2587 public static UUID decodeUUID(final byte[] b) 2588 throws ParseException 2589 { 2590 if (b.length != 16) 2591 { 2592 throw new ParseException(ERR_DECODE_UUID_INVALID_LENGTH.get(toHex(b)), 0); 2593 } 2594 2595 long mostSignificantBits = 0L; 2596 for (int i=0; i < 8; i++) 2597 { 2598 mostSignificantBits = (mostSignificantBits << 8) | (b[i] & 0xFF); 2599 } 2600 2601 long leastSignificantBits = 0L; 2602 for (int i=8; i < 16; i++) 2603 { 2604 leastSignificantBits = (leastSignificantBits << 8) | (b[i] & 0xFF); 2605 } 2606 2607 return new UUID(mostSignificantBits, leastSignificantBits); 2608 } 2609 2610 2611 2612 /** 2613 * Returns {@code true} if and only if the current process is running on 2614 * a Windows-based operating system. 2615 * 2616 * @return {@code true} if the current process is running on a Windows-based 2617 * operating system and {@code false} otherwise. 2618 */ 2619 public static boolean isWindows() 2620 { 2621 final String osName = toLowerCase(System.getProperty("os.name")); 2622 return ((osName != null) && osName.contains("windows")); 2623 } 2624 2625 2626 2627 /** 2628 * Attempts to parse the contents of the provided string to an argument list 2629 * (e.g., converts something like "--arg1 arg1value --arg2 --arg3 arg3value" 2630 * to a list of "--arg1", "arg1value", "--arg2", "--arg3", "arg3value"). 2631 * 2632 * @param s The string to be converted to an argument list. 2633 * 2634 * @return The parsed argument list. 2635 * 2636 * @throws ParseException If a problem is encountered while attempting to 2637 * parse the given string to an argument list. 2638 */ 2639 public static List<String> toArgumentList(final String s) 2640 throws ParseException 2641 { 2642 if ((s == null) || s.isEmpty()) 2643 { 2644 return Collections.emptyList(); 2645 } 2646 2647 int quoteStartPos = -1; 2648 boolean inEscape = false; 2649 final ArrayList<String> argList = new ArrayList<>(20); 2650 final StringBuilder currentArg = new StringBuilder(); 2651 for (int i=0; i < s.length(); i++) 2652 { 2653 final char c = s.charAt(i); 2654 if (inEscape) 2655 { 2656 currentArg.append(c); 2657 inEscape = false; 2658 continue; 2659 } 2660 2661 if (c == '\\') 2662 { 2663 inEscape = true; 2664 } 2665 else if (c == '"') 2666 { 2667 if (quoteStartPos >= 0) 2668 { 2669 quoteStartPos = -1; 2670 } 2671 else 2672 { 2673 quoteStartPos = i; 2674 } 2675 } 2676 else if (c == ' ') 2677 { 2678 if (quoteStartPos >= 0) 2679 { 2680 currentArg.append(c); 2681 } 2682 else if (currentArg.length() > 0) 2683 { 2684 argList.add(currentArg.toString()); 2685 currentArg.setLength(0); 2686 } 2687 } 2688 else 2689 { 2690 currentArg.append(c); 2691 } 2692 } 2693 2694 if (s.endsWith("\\") && (! s.endsWith("\\\\"))) 2695 { 2696 throw new ParseException(ERR_ARG_STRING_DANGLING_BACKSLASH.get(), 2697 (s.length() - 1)); 2698 } 2699 2700 if (quoteStartPos >= 0) 2701 { 2702 throw new ParseException(ERR_ARG_STRING_UNMATCHED_QUOTE.get( 2703 quoteStartPos), quoteStartPos); 2704 } 2705 2706 if (currentArg.length() > 0) 2707 { 2708 argList.add(currentArg.toString()); 2709 } 2710 2711 return Collections.unmodifiableList(argList); 2712 } 2713 2714 2715 2716 /** 2717 * Retrieves an array containing the elements of the provided collection. 2718 * 2719 * @param <T> The type of element included in the provided 2720 * collection. 2721 * @param collection The collection to convert to an array. 2722 * @param type The type of element contained in the collection. 2723 * 2724 * @return An array containing the elements of the provided list. 2725 */ 2726 public static <T> T[] toArray(final Collection<T> collection, 2727 final Class<T> type) 2728 { 2729 if (collection == null) 2730 { 2731 return null; 2732 } 2733 2734 @SuppressWarnings("unchecked") 2735 final T[] array = (T[]) Array.newInstance(type, collection.size()); 2736 2737 return collection.toArray(array); 2738 } 2739 2740 2741 2742 /** 2743 * Creates a modifiable list with all of the items of the provided array in 2744 * the same order. This method behaves much like {@code Arrays.asList}, 2745 * except that if the provided array is {@code null}, then it will return a 2746 * {@code null} list rather than throwing an exception. 2747 * 2748 * @param <T> The type of item contained in the provided array. 2749 * 2750 * @param array The array of items to include in the list. 2751 * 2752 * @return The list that was created, or {@code null} if the provided array 2753 * was {@code null}. 2754 */ 2755 public static <T> List<T> toList(final T[] array) 2756 { 2757 if (array == null) 2758 { 2759 return null; 2760 } 2761 2762 final ArrayList<T> l = new ArrayList<>(array.length); 2763 l.addAll(Arrays.asList(array)); 2764 return l; 2765 } 2766 2767 2768 2769 /** 2770 * Creates a modifiable list with all of the items of the provided array in 2771 * the same order. This method behaves much like {@code Arrays.asList}, 2772 * except that if the provided array is {@code null}, then it will return an 2773 * empty list rather than throwing an exception. 2774 * 2775 * @param <T> The type of item contained in the provided array. 2776 * 2777 * @param array The array of items to include in the list. 2778 * 2779 * @return The list that was created, or an empty list if the provided array 2780 * was {@code null}. 2781 */ 2782 public static <T> List<T> toNonNullList(final T[] array) 2783 { 2784 if (array == null) 2785 { 2786 return new ArrayList<>(0); 2787 } 2788 2789 final ArrayList<T> l = new ArrayList<>(array.length); 2790 l.addAll(Arrays.asList(array)); 2791 return l; 2792 } 2793 2794 2795 2796 /** 2797 * Indicates whether both of the provided objects are {@code null} or both 2798 * are logically equal (using the {@code equals} method). 2799 * 2800 * @param o1 The first object for which to make the determination. 2801 * @param o2 The second object for which to make the determination. 2802 * 2803 * @return {@code true} if both objects are {@code null} or both are 2804 * logically equal, or {@code false} if only one of the objects is 2805 * {@code null} or they are not logically equal. 2806 */ 2807 public static boolean bothNullOrEqual(final Object o1, final Object o2) 2808 { 2809 if (o1 == null) 2810 { 2811 return (o2 == null); 2812 } 2813 else if (o2 == null) 2814 { 2815 return false; 2816 } 2817 2818 return o1.equals(o2); 2819 } 2820 2821 2822 2823 /** 2824 * Indicates whether both of the provided strings are {@code null} or both 2825 * are logically equal ignoring differences in capitalization (using the 2826 * {@code equalsIgnoreCase} method). 2827 * 2828 * @param s1 The first string for which to make the determination. 2829 * @param s2 The second string for which to make the determination. 2830 * 2831 * @return {@code true} if both strings are {@code null} or both are 2832 * logically equal ignoring differences in capitalization, or 2833 * {@code false} if only one of the objects is {@code null} or they 2834 * are not logically equal ignoring capitalization. 2835 */ 2836 public static boolean bothNullOrEqualIgnoreCase(final String s1, 2837 final String s2) 2838 { 2839 if (s1 == null) 2840 { 2841 return (s2 == null); 2842 } 2843 else if (s2 == null) 2844 { 2845 return false; 2846 } 2847 2848 return s1.equalsIgnoreCase(s2); 2849 } 2850 2851 2852 2853 /** 2854 * Indicates whether the provided string arrays have the same elements, 2855 * ignoring the order in which they appear and differences in capitalization. 2856 * It is assumed that neither array contains {@code null} strings, and that 2857 * no string appears more than once in each array. 2858 * 2859 * @param a1 The first array for which to make the determination. 2860 * @param a2 The second array for which to make the determination. 2861 * 2862 * @return {@code true} if both arrays have the same set of strings, or 2863 * {@code false} if not. 2864 */ 2865 public static boolean stringsEqualIgnoreCaseOrderIndependent( 2866 final String[] a1, final String[] a2) 2867 { 2868 if (a1 == null) 2869 { 2870 return (a2 == null); 2871 } 2872 else if (a2 == null) 2873 { 2874 return false; 2875 } 2876 2877 if (a1.length != a2.length) 2878 { 2879 return false; 2880 } 2881 2882 if (a1.length == 1) 2883 { 2884 return (a1[0].equalsIgnoreCase(a2[0])); 2885 } 2886 2887 final HashSet<String> s1 = new HashSet<>(computeMapCapacity(a1.length)); 2888 for (final String s : a1) 2889 { 2890 s1.add(toLowerCase(s)); 2891 } 2892 2893 final HashSet<String> s2 = new HashSet<>(computeMapCapacity(a2.length)); 2894 for (final String s : a2) 2895 { 2896 s2.add(toLowerCase(s)); 2897 } 2898 2899 return s1.equals(s2); 2900 } 2901 2902 2903 2904 /** 2905 * Indicates whether the provided arrays have the same elements, ignoring the 2906 * order in which they appear. It is assumed that neither array contains 2907 * {@code null} elements, and that no element appears more than once in each 2908 * array. 2909 * 2910 * @param <T> The type of element contained in the arrays. 2911 * 2912 * @param a1 The first array for which to make the determination. 2913 * @param a2 The second array for which to make the determination. 2914 * 2915 * @return {@code true} if both arrays have the same set of elements, or 2916 * {@code false} if not. 2917 */ 2918 public static <T> boolean arraysEqualOrderIndependent(final T[] a1, 2919 final T[] a2) 2920 { 2921 if (a1 == null) 2922 { 2923 return (a2 == null); 2924 } 2925 else if (a2 == null) 2926 { 2927 return false; 2928 } 2929 2930 if (a1.length != a2.length) 2931 { 2932 return false; 2933 } 2934 2935 if (a1.length == 1) 2936 { 2937 return (a1[0].equals(a2[0])); 2938 } 2939 2940 final HashSet<T> s1 = new HashSet<>(Arrays.asList(a1)); 2941 final HashSet<T> s2 = new HashSet<>(Arrays.asList(a2)); 2942 return s1.equals(s2); 2943 } 2944 2945 2946 2947 /** 2948 * Determines the number of bytes in a UTF-8 character that starts with the 2949 * given byte. 2950 * 2951 * @param b The byte for which to make the determination. 2952 * 2953 * @return The number of bytes in a UTF-8 character that starts with the 2954 * given byte, or -1 if it does not appear to be a valid first byte 2955 * for a UTF-8 character. 2956 */ 2957 public static int numBytesInUTF8CharacterWithFirstByte(final byte b) 2958 { 2959 if ((b & 0x7F) == b) 2960 { 2961 return 1; 2962 } 2963 else if ((b & 0xE0) == 0xC0) 2964 { 2965 return 2; 2966 } 2967 else if ((b & 0xF0) == 0xE0) 2968 { 2969 return 3; 2970 } 2971 else if ((b & 0xF8) == 0xF0) 2972 { 2973 return 4; 2974 } 2975 else 2976 { 2977 return -1; 2978 } 2979 } 2980 2981 2982 2983 /** 2984 * Indicates whether the provided attribute name should be considered a 2985 * sensitive attribute for the purposes of {@code toCode} methods. If an 2986 * attribute is considered sensitive, then its values will be redacted in the 2987 * output of the {@code toCode} methods. 2988 * 2989 * @param name The name for which to make the determination. It may or may 2990 * not include attribute options. It must not be {@code null}. 2991 * 2992 * @return {@code true} if the specified attribute is one that should be 2993 * considered sensitive for the 2994 */ 2995 public static boolean isSensitiveToCodeAttribute(final String name) 2996 { 2997 final String lowerBaseName = Attribute.getBaseName(name).toLowerCase(); 2998 return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES.contains(lowerBaseName); 2999 } 3000 3001 3002 3003 /** 3004 * Retrieves a set containing the base names (in all lowercase characters) of 3005 * any attributes that should be considered sensitive for the purposes of the 3006 * {@code toCode} methods. By default, only the userPassword and 3007 * authPassword attributes and their respective OIDs will be included. 3008 * 3009 * @return A set containing the base names (in all lowercase characters) of 3010 * any attributes that should be considered sensitive for the 3011 * purposes of the {@code toCode} methods. 3012 */ 3013 public static Set<String> getSensitiveToCodeAttributeBaseNames() 3014 { 3015 return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES; 3016 } 3017 3018 3019 3020 /** 3021 * Specifies the names of any attributes that should be considered sensitive 3022 * for the purposes of the {@code toCode} methods. 3023 * 3024 * @param names The names of any attributes that should be considered 3025 * sensitive for the purposes of the {@code toCode} methods. 3026 * It may be {@code null} or empty if no attributes should be 3027 * considered sensitive. 3028 */ 3029 public static void setSensitiveToCodeAttributes(final String... names) 3030 { 3031 setSensitiveToCodeAttributes(toList(names)); 3032 } 3033 3034 3035 3036 /** 3037 * Specifies the names of any attributes that should be considered sensitive 3038 * for the purposes of the {@code toCode} methods. 3039 * 3040 * @param names The names of any attributes that should be considered 3041 * sensitive for the purposes of the {@code toCode} methods. 3042 * It may be {@code null} or empty if no attributes should be 3043 * considered sensitive. 3044 */ 3045 public static void setSensitiveToCodeAttributes( 3046 final Collection<String> names) 3047 { 3048 if ((names == null) || names.isEmpty()) 3049 { 3050 TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.emptySet(); 3051 } 3052 else 3053 { 3054 final LinkedHashSet<String> nameSet = new LinkedHashSet<>(names.size()); 3055 for (final String s : names) 3056 { 3057 nameSet.add(Attribute.getBaseName(s).toLowerCase()); 3058 } 3059 3060 TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet); 3061 } 3062 } 3063 3064 3065 3066 /** 3067 * Creates a new {@code IOException} with a cause. The constructor needed to 3068 * do this wasn't available until Java SE 6, so reflection is used to invoke 3069 * this constructor in versions of Java that provide it. In Java SE 5, the 3070 * provided message will be augmented with information about the cause. 3071 * 3072 * @param message The message to use for the exception. This may be 3073 * {@code null} if the message should be generated from the 3074 * provided cause. 3075 * @param cause The underlying cause for the exception. It may be 3076 * {@code null} if the exception should have only a message. 3077 * 3078 * @return The {@code IOException} object that was created. 3079 */ 3080 public static IOException createIOExceptionWithCause(final String message, 3081 final Throwable cause) 3082 { 3083 if (cause == null) 3084 { 3085 return new IOException(message); 3086 } 3087 else if (message == null) 3088 { 3089 return new IOException(cause); 3090 } 3091 else 3092 { 3093 return new IOException(message, cause); 3094 } 3095 } 3096 3097 3098 3099 /** 3100 * Converts the provided string (which may include line breaks) into a list 3101 * containing the lines without the line breaks. 3102 * 3103 * @param s The string to convert into a list of its representative lines. 3104 * 3105 * @return A list containing the lines that comprise the given string. 3106 */ 3107 public static List<String> stringToLines(final String s) 3108 { 3109 final ArrayList<String> l = new ArrayList<>(10); 3110 3111 if (s == null) 3112 { 3113 return l; 3114 } 3115 3116 final BufferedReader reader = new BufferedReader(new StringReader(s)); 3117 3118 try 3119 { 3120 while (true) 3121 { 3122 try 3123 { 3124 final String line = reader.readLine(); 3125 if (line == null) 3126 { 3127 return l; 3128 } 3129 else 3130 { 3131 l.add(line); 3132 } 3133 } 3134 catch (final Exception e) 3135 { 3136 Debug.debugException(e); 3137 3138 // This should never happen. If it does, just return a list 3139 // containing a single item that is the original string. 3140 l.clear(); 3141 l.add(s); 3142 return l; 3143 } 3144 } 3145 } 3146 finally 3147 { 3148 try 3149 { 3150 // This is technically not necessary in this case, but it's good form. 3151 reader.close(); 3152 } 3153 catch (final Exception e) 3154 { 3155 Debug.debugException(e); 3156 // This should never happen, and there's nothing we need to do even if 3157 // it does. 3158 } 3159 } 3160 } 3161 3162 3163 3164 /** 3165 * Constructs a {@code File} object from the provided path. 3166 * 3167 * @param baseDirectory The base directory to use as the starting point. 3168 * It must not be {@code null} and is expected to 3169 * represent a directory. 3170 * @param pathElements An array of the elements that make up the remainder 3171 * of the path to the specified file, in order from 3172 * paths closest to the root of the filesystem to 3173 * furthest away (that is, the first element should 3174 * represent a file or directory immediately below the 3175 * base directory, the second is one level below that, 3176 * and so on). It may be {@code null} or empty if the 3177 * base directory should be used. 3178 * 3179 * @return The constructed {@code File} object. 3180 */ 3181 public static File constructPath(final File baseDirectory, 3182 final String... pathElements) 3183 { 3184 Validator.ensureNotNull(baseDirectory); 3185 3186 File f = baseDirectory; 3187 if (pathElements != null) 3188 { 3189 for (final String pathElement : pathElements) 3190 { 3191 f = new File(f, pathElement); 3192 } 3193 } 3194 3195 return f; 3196 } 3197 3198 3199 3200 /** 3201 * Creates a byte array from the provided integer values. All of the integer 3202 * values must be between 0x00 and 0xFF (0 and 255), inclusive. Any bits 3203 * set outside of that range will be ignored. 3204 * 3205 * @param bytes The values to include in the byte array. 3206 * 3207 * @return A byte array with the provided set of values. 3208 */ 3209 public static byte[] byteArray(final int... bytes) 3210 { 3211 if ((bytes == null) || (bytes.length == 0)) 3212 { 3213 return NO_BYTES; 3214 } 3215 3216 final byte[] byteArray = new byte[bytes.length]; 3217 for (int i=0; i < bytes.length; i++) 3218 { 3219 byteArray[i] = (byte) (bytes[i] & 0xFF); 3220 } 3221 3222 return byteArray; 3223 } 3224 3225 3226 3227 /** 3228 * Indicates whether the unit tests are currently running in this JVM. 3229 * 3230 * @return {@code true} if the unit tests are currently running, or 3231 * {@code false} if not. 3232 */ 3233 public static boolean isWithinUnitTest() 3234 { 3235 return IS_WITHIN_UNIT_TESTS; 3236 } 3237 3238 3239 3240 /** 3241 * Throws an {@code Error} or a {@code RuntimeException} based on the provided 3242 * {@code Throwable} object. This method will always throw something, 3243 * regardless of the provided {@code Throwable} object. 3244 * 3245 * @param throwable The {@code Throwable} object to use to create the 3246 * exception to throw. 3247 * 3248 * @throws Error If the provided {@code Throwable} object is an 3249 * {@code Error} instance, then that {@code Error} instance 3250 * will be re-thrown. 3251 * 3252 * @throws RuntimeException If the provided {@code Throwable} object is a 3253 * {@code RuntimeException} instance, then that 3254 * {@code RuntimeException} instance will be 3255 * re-thrown. Otherwise, it must be a checked 3256 * exception and that checked exception will be 3257 * re-thrown as a {@code RuntimeException}. 3258 */ 3259 public static void throwErrorOrRuntimeException(final Throwable throwable) 3260 throws Error, RuntimeException 3261 { 3262 Validator.ensureNotNull(throwable); 3263 3264 if (throwable instanceof Error) 3265 { 3266 throw (Error) throwable; 3267 } 3268 else if (throwable instanceof RuntimeException) 3269 { 3270 throw (RuntimeException) throwable; 3271 } 3272 else 3273 { 3274 throw new RuntimeException(throwable); 3275 } 3276 } 3277 3278 3279 3280 /** 3281 * Re-throws the provided {@code Throwable} instance only if it is an 3282 * {@code Error} or a {@code RuntimeException} instance; otherwise, this 3283 * method will return without taking any action. 3284 * 3285 * @param throwable The {@code Throwable} object to examine and potentially 3286 * re-throw. 3287 * 3288 * @throws Error If the provided {@code Throwable} object is an 3289 * {@code Error} instance, then that {@code Error} instance 3290 * will be re-thrown. 3291 * 3292 * @throws RuntimeException If the provided {@code Throwable} object is a 3293 * {@code RuntimeException} instance, then that 3294 * {@code RuntimeException} instance will be 3295 * re-thrown. 3296 */ 3297 public static void rethrowIfErrorOrRuntimeException(final Throwable throwable) 3298 throws Error, RuntimeException 3299 { 3300 if (throwable instanceof Error) 3301 { 3302 throw (Error) throwable; 3303 } 3304 else if (throwable instanceof RuntimeException) 3305 { 3306 throw (RuntimeException) throwable; 3307 } 3308 } 3309 3310 3311 3312 /** 3313 * Re-throws the provided {@code Throwable} instance only if it is an 3314 * {@code Error}; otherwise, this method will return without taking any 3315 * action. 3316 * 3317 * @param throwable The {@code Throwable} object to examine and potentially 3318 * re-throw. 3319 * 3320 * @throws Error If the provided {@code Throwable} object is an 3321 * {@code Error} instance, then that {@code Error} instance 3322 * will be re-thrown. 3323 */ 3324 public static void rethrowIfError(final Throwable throwable) 3325 throws Error 3326 { 3327 if (throwable instanceof Error) 3328 { 3329 throw (Error) throwable; 3330 } 3331 } 3332 3333 3334 3335 /** 3336 * Computes the capacity that should be used for a map or a set with the 3337 * expected number of elements, which can help avoid the need to re-hash or 3338 * re-balance the map if too many items are added. This method bases its 3339 * computation on the default map load factor of 0.75. 3340 * 3341 * @param expectedItemCount The expected maximum number of items that will 3342 * be placed in the map or set. It must be greater 3343 * than or equal to zero. 3344 * 3345 * @return The capacity that should be used for a map or a set with the 3346 * expected number of elements 3347 */ 3348 public static int computeMapCapacity(final int expectedItemCount) 3349 { 3350 switch (expectedItemCount) 3351 { 3352 case 0: 3353 return 0; 3354 case 1: 3355 return 2; 3356 case 2: 3357 return 3; 3358 case 3: 3359 return 5; 3360 case 4: 3361 return 6; 3362 case 5: 3363 return 7; 3364 case 6: 3365 return 9; 3366 case 7: 3367 return 10; 3368 case 8: 3369 return 11; 3370 case 9: 3371 return 13; 3372 case 10: 3373 return 14; 3374 case 11: 3375 return 15; 3376 case 12: 3377 return 17; 3378 case 13: 3379 return 18; 3380 case 14: 3381 return 19; 3382 case 15: 3383 return 21; 3384 case 16: 3385 return 22; 3386 case 17: 3387 return 23; 3388 case 18: 3389 return 25; 3390 case 19: 3391 return 26; 3392 case 20: 3393 return 27; 3394 case 30: 3395 return 41; 3396 case 40: 3397 return 54; 3398 case 50: 3399 return 67; 3400 case 60: 3401 return 81; 3402 case 70: 3403 return 94; 3404 case 80: 3405 return 107; 3406 case 90: 3407 return 121; 3408 case 100: 3409 return 134; 3410 case 110: 3411 return 147; 3412 case 120: 3413 return 161; 3414 case 130: 3415 return 174; 3416 case 140: 3417 return 187; 3418 case 150: 3419 return 201; 3420 case 160: 3421 return 214; 3422 case 170: 3423 return 227; 3424 case 180: 3425 return 241; 3426 case 190: 3427 return 254; 3428 case 200: 3429 return 267; 3430 default: 3431 Validator.ensureTrue((expectedItemCount >= 0), 3432 "StaticUtils.computeMapOrSetCapacity.expectedItemCount must be " + 3433 "greater than or equal to zero."); 3434 3435 // NOTE: 536,870,911 is Integer.MAX_VALUE/4. If the value is larger 3436 // than that, then we'll fall back to using floating-point arithmetic 3437 // 3438 if (expectedItemCount > 536_870_911) 3439 { 3440 final int computedCapacity = ((int) (expectedItemCount / 0.75)) + 1; 3441 if (computedCapacity <= expectedItemCount) 3442 { 3443 // This suggests that the expected number of items is so big that 3444 // the computed capacity can't be adequately represented by an 3445 // integer. In that case, we'll just return the expected item 3446 // count and let the map or set get re-hashed/re-balanced if it 3447 // actually gets anywhere near that size. 3448 return expectedItemCount; 3449 } 3450 else 3451 { 3452 return computedCapacity; 3453 } 3454 } 3455 else 3456 { 3457 return ((expectedItemCount * 4) / 3) + 1; 3458 } 3459 } 3460 } 3461 3462 3463 3464 /** 3465 * Creates an unmodifiable set containing the provided items. The iteration 3466 * order of the provided items will be preserved. 3467 * 3468 * @param <T> The type of item to include in the set. 3469 * @param items The items to include in the set. It must not be 3470 * {@code null}, but may be empty. 3471 * 3472 * @return An unmodifiable set containing the provided items. 3473 */ 3474 public static <T> Set<T> setOf(final T... items) 3475 { 3476 return Collections.unmodifiableSet( 3477 new LinkedHashSet<>(Arrays.asList(items))); 3478 } 3479 3480 3481 3482 /** 3483 * Creates a {@code HashSet} containing the provided items. 3484 * 3485 * @param <T> The type of item to include in the set. 3486 * @param items The items to include in the set. It must not be 3487 * {@code null}, but may be empty. 3488 * 3489 * @return A {@code HashSet} containing the provided items. 3490 */ 3491 public static <T> HashSet<T> hashSetOf(final T... items) 3492 { 3493 return new HashSet<>(Arrays.asList(items)); 3494 } 3495 3496 3497 3498 /** 3499 * Creates a {@code LinkedHashSet} containing the provided items. 3500 * 3501 * @param <T> The type of item to include in the set. 3502 * @param items The items to include in the set. It must not be 3503 * {@code null}, but may be empty. 3504 * 3505 * @return A {@code LinkedHashSet} containing the provided items. 3506 */ 3507 public static <T> LinkedHashSet<T> linkedHashSetOf(final T... items) 3508 { 3509 return new LinkedHashSet<>(Arrays.asList(items)); 3510 } 3511 3512 3513 3514 /** 3515 * Retrieves the set of currently defined system properties. If possible, 3516 * this will simply return the result of a call to 3517 * {@code System.getProperties}. However, the LDAP SDK is known to be used in 3518 * environments where a security manager prevents setting system properties, 3519 * and in that case, calls to {@code System.getProperties} will be rejected 3520 * with a {@code SecurityException} because the returned structure is mutable 3521 * and could be used to alter system property values. In such cases, a new 3522 * empty {@code Properties} object will be created, and may optionally be 3523 * populated with the values of a specific set of named properties. 3524 * 3525 * @param propertyNames An optional set of property names whose values (if 3526 * defined) should be included in the 3527 * {@code Properties} object that will be returned if a 3528 * security manager prevents retrieving the full set of 3529 * system properties. This may be {@code null} or 3530 * empty if no specific properties should be retrieved. 3531 * 3532 * @return The value returned by a call to {@code System.getProperties} if 3533 * possible, or a newly-created properties map (possibly including 3534 * the values of a specified set of system properties) if it is not 3535 * possible to get a mutable set of the system properties. 3536 */ 3537 public static Properties getSystemProperties(final String... propertyNames) 3538 { 3539 try 3540 { 3541 final Properties properties = System.getProperties(); 3542 3543 final String forceThrowPropertyName = 3544 StaticUtils.class.getName() + ".forceGetSystemPropertiesToThrow"; 3545 3546 // To ensure that we can get coverage for the code below in which there is 3547 // a restrictive security manager in place, look for a system property 3548 // that will cause us to throw an exception. 3549 final Object forceThrowPropertyValue = 3550 properties.getProperty(forceThrowPropertyName); 3551 if (forceThrowPropertyValue != null) 3552 { 3553 throw new SecurityException(forceThrowPropertyName + '=' + 3554 forceThrowPropertyValue); 3555 } 3556 3557 return System.getProperties(); 3558 } 3559 catch (final SecurityException e) 3560 { 3561 Debug.debugException(e); 3562 } 3563 3564 3565 // If we have gotten here, then we can assume that a security manager 3566 // prevents us from accessing all system properties. Create a new proper 3567 final Properties properties = new Properties(); 3568 if (propertyNames != null) 3569 { 3570 for (final String propertyName : propertyNames) 3571 { 3572 final Object propertyValue = System.getProperty(propertyName); 3573 if (propertyValue != null) 3574 { 3575 properties.put(propertyName, propertyValue); 3576 } 3577 } 3578 } 3579 3580 return properties; 3581 } 3582}