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