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