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