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