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