001/* 002 * Copyright 2007-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util; 022 023 024 025import java.io.BufferedReader; 026import java.io.IOException; 027import java.io.StringReader; 028import java.text.DecimalFormat; 029import java.text.ParseException; 030import java.text.SimpleDateFormat; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.Collection; 034import java.util.Collections; 035import java.util.Date; 036import java.util.HashSet; 037import java.util.Iterator; 038import java.util.LinkedHashSet; 039import java.util.List; 040import java.util.Set; 041import java.util.StringTokenizer; 042import java.util.TimeZone; 043import java.util.UUID; 044 045import com.unboundid.ldap.sdk.Attribute; 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.Version; 048 049import static com.unboundid.util.Debug.*; 050import static com.unboundid.util.UtilityMessages.*; 051import static com.unboundid.util.Validator.*; 052 053 054 055/** 056 * This class provides a number of static utility functions. 057 */ 058@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 059public 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 (final 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 (final 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 (final 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(", ldapSDKVersion="); 1027 buffer.append(Version.NUMERIC_VERSION_STRING); 1028 buffer.append(", revision="); 1029 buffer.append(Version.REVISION_ID); 1030 buffer.append(')'); 1031 } 1032 1033 1034 1035 /** 1036 * Returns a single-line string representation of the stack trace. It will 1037 * include a list of source files and line numbers (if available) for the 1038 * stack trace. 1039 * 1040 * @param elements The stack trace. 1041 * 1042 * @return A single-line string representation of the stack trace. 1043 */ 1044 public static String getStackTrace(final StackTraceElement[] elements) 1045 { 1046 final StringBuilder buffer = new StringBuilder(); 1047 getStackTrace(elements, buffer); 1048 return buffer.toString(); 1049 } 1050 1051 1052 1053 /** 1054 * Appends a single-line string representation of the stack trace to the given 1055 * buffer. It will include a list of source files and line numbers 1056 * (if available) for the stack trace. 1057 * 1058 * @param elements The stack trace. 1059 * @param buffer The buffer to which the information should be appended. 1060 */ 1061 public static void getStackTrace(final StackTraceElement[] elements, 1062 final StringBuilder buffer) 1063 { 1064 for (int i=0; i < elements.length; i++) 1065 { 1066 if (i > 0) 1067 { 1068 buffer.append(" / "); 1069 } 1070 1071 buffer.append(elements[i].getMethodName()); 1072 buffer.append('('); 1073 buffer.append(elements[i].getFileName()); 1074 1075 final int lineNumber = elements[i].getLineNumber(); 1076 if (lineNumber > 0) 1077 { 1078 buffer.append(':'); 1079 buffer.append(lineNumber); 1080 } 1081 else if (elements[i].isNativeMethod()) 1082 { 1083 buffer.append(":native"); 1084 } 1085 else 1086 { 1087 buffer.append(":unknown"); 1088 } 1089 buffer.append(')'); 1090 } 1091 } 1092 1093 1094 1095 /** 1096 * Retrieves a string representation of the provided {@code Throwable} object 1097 * suitable for use in a message. For runtime exceptions and errors, then a 1098 * full stack trace for the exception will be provided. For exception types 1099 * defined in the LDAP SDK, then its {@code getExceptionMessage} method will 1100 * be used to get the string representation. For all other types of 1101 * exceptions, then the standard string representation will be used. 1102 * <BR><BR> 1103 * For all types of exceptions, the message will also include the cause if one 1104 * exists. 1105 * 1106 * @param t The {@code Throwable} for which to generate the exception 1107 * message. 1108 * 1109 * @return A string representation of the provided {@code Throwable} object 1110 * suitable for use in a message. 1111 */ 1112 public static String getExceptionMessage(final Throwable t) 1113 { 1114 if (t == null) 1115 { 1116 return ERR_NO_EXCEPTION.get(); 1117 } 1118 1119 final StringBuilder buffer = new StringBuilder(); 1120 if (t instanceof LDAPSDKException) 1121 { 1122 buffer.append(((LDAPSDKException) t).getExceptionMessage()); 1123 } 1124 else if (t instanceof LDAPSDKRuntimeException) 1125 { 1126 buffer.append(((LDAPSDKRuntimeException) t).getExceptionMessage()); 1127 } 1128 else 1129 { 1130 return getStackTrace(t); 1131 } 1132 1133 final Throwable cause = t.getCause(); 1134 if (cause != null) 1135 { 1136 buffer.append(" caused by "); 1137 buffer.append(getExceptionMessage(cause)); 1138 } 1139 1140 return buffer.toString(); 1141 } 1142 1143 1144 1145 /** 1146 * Retrieves the unqualified name (i.e., the name without package information) 1147 * for the provided class. 1148 * 1149 * @param c The class for which to retrieve the unqualified name. 1150 * 1151 * @return The unqualified name for the provided class. 1152 */ 1153 public static String getUnqualifiedClassName(final Class<?> c) 1154 { 1155 final String className = c.getName(); 1156 final int lastPeriodPos = className.lastIndexOf('.'); 1157 1158 if (lastPeriodPos > 0) 1159 { 1160 return className.substring(lastPeriodPos+1); 1161 } 1162 else 1163 { 1164 return className; 1165 } 1166 } 1167 1168 1169 1170 /** 1171 * Encodes the provided timestamp in generalized time format. 1172 * 1173 * @param timestamp The timestamp to be encoded in generalized time format. 1174 * It should use the same format as the 1175 * {@code System.currentTimeMillis()} method (i.e., the 1176 * number of milliseconds since 12:00am UTC on January 1, 1177 * 1970). 1178 * 1179 * @return The generalized time representation of the provided date. 1180 */ 1181 public static String encodeGeneralizedTime(final long timestamp) 1182 { 1183 return encodeGeneralizedTime(new Date(timestamp)); 1184 } 1185 1186 1187 1188 /** 1189 * Encodes the provided date in generalized time format. 1190 * 1191 * @param d The date to be encoded in generalized time format. 1192 * 1193 * @return The generalized time representation of the provided date. 1194 */ 1195 public static String encodeGeneralizedTime(final Date d) 1196 { 1197 SimpleDateFormat dateFormat = DATE_FORMATTERS.get(); 1198 if (dateFormat == null) 1199 { 1200 dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'"); 1201 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 1202 DATE_FORMATTERS.set(dateFormat); 1203 } 1204 1205 return dateFormat.format(d); 1206 } 1207 1208 1209 1210 /** 1211 * Decodes the provided string as a timestamp in generalized time format. 1212 * 1213 * @param t The timestamp to be decoded. It must not be {@code null}. 1214 * 1215 * @return The {@code Date} object decoded from the provided timestamp. 1216 * 1217 * @throws ParseException If the provided string could not be decoded as a 1218 * timestamp in generalized time format. 1219 */ 1220 public static Date decodeGeneralizedTime(final String t) 1221 throws ParseException 1222 { 1223 ensureNotNull(t); 1224 1225 // Extract the time zone information from the end of the value. 1226 int tzPos; 1227 final TimeZone tz; 1228 if (t.endsWith("Z")) 1229 { 1230 tz = TimeZone.getTimeZone("UTC"); 1231 tzPos = t.length() - 1; 1232 } 1233 else 1234 { 1235 tzPos = t.lastIndexOf('-'); 1236 if (tzPos < 0) 1237 { 1238 tzPos = t.lastIndexOf('+'); 1239 if (tzPos < 0) 1240 { 1241 throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t), 1242 0); 1243 } 1244 } 1245 1246 tz = TimeZone.getTimeZone("GMT" + t.substring(tzPos)); 1247 if (tz.getRawOffset() == 0) 1248 { 1249 // This is the default time zone that will be returned if the value 1250 // cannot be parsed. If it's valid, then it will end in "+0000" or 1251 // "-0000". Otherwise, it's invalid and GMT was just a fallback. 1252 if (! (t.endsWith("+0000") || t.endsWith("-0000"))) 1253 { 1254 throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t), 1255 tzPos); 1256 } 1257 } 1258 } 1259 1260 1261 // See if the timestamp has a sub-second portion. Note that if there is a 1262 // sub-second portion, then we may need to massage the value so that there 1263 // are exactly three sub-second characters so that it can be interpreted as 1264 // milliseconds. 1265 final String subSecFormatStr; 1266 final String trimmedTimestamp; 1267 int periodPos = t.lastIndexOf('.', tzPos); 1268 if (periodPos > 0) 1269 { 1270 final int subSecondLength = tzPos - periodPos - 1; 1271 switch (subSecondLength) 1272 { 1273 case 0: 1274 subSecFormatStr = ""; 1275 trimmedTimestamp = t.substring(0, periodPos); 1276 break; 1277 case 1: 1278 subSecFormatStr = ".SSS"; 1279 trimmedTimestamp = t.substring(0, (periodPos+2)) + "00"; 1280 break; 1281 case 2: 1282 subSecFormatStr = ".SSS"; 1283 trimmedTimestamp = t.substring(0, (periodPos+3)) + '0'; 1284 break; 1285 default: 1286 subSecFormatStr = ".SSS"; 1287 trimmedTimestamp = t.substring(0, periodPos+4); 1288 break; 1289 } 1290 } 1291 else 1292 { 1293 subSecFormatStr = ""; 1294 periodPos = tzPos; 1295 trimmedTimestamp = t.substring(0, tzPos); 1296 } 1297 1298 1299 // Look at where the period is (or would be if it existed) to see how many 1300 // characters are in the integer portion. This will give us what we need 1301 // for the rest of the format string. 1302 final String formatStr; 1303 switch (periodPos) 1304 { 1305 case 10: 1306 formatStr = "yyyyMMddHH" + subSecFormatStr; 1307 break; 1308 case 12: 1309 formatStr = "yyyyMMddHHmm" + subSecFormatStr; 1310 break; 1311 case 14: 1312 formatStr = "yyyyMMddHHmmss" + subSecFormatStr; 1313 break; 1314 default: 1315 throw new ParseException(ERR_GENTIME_CANNOT_PARSE_INVALID_LENGTH.get(t), 1316 periodPos); 1317 } 1318 1319 1320 // We should finally be able to create an appropriate date format object 1321 // to parse the trimmed version of the timestamp. 1322 final SimpleDateFormat dateFormat = new SimpleDateFormat(formatStr); 1323 dateFormat.setTimeZone(tz); 1324 dateFormat.setLenient(false); 1325 return dateFormat.parse(trimmedTimestamp); 1326 } 1327 1328 1329 1330 /** 1331 * Trims only leading spaces from the provided string, leaving any trailing 1332 * spaces intact. 1333 * 1334 * @param s The string to be processed. It must not be {@code null}. 1335 * 1336 * @return The original string if no trimming was required, or a new string 1337 * without leading spaces if the provided string had one or more. It 1338 * may be an empty string if the provided string was an empty string 1339 * or contained only spaces. 1340 */ 1341 public static String trimLeading(final String s) 1342 { 1343 ensureNotNull(s); 1344 1345 int nonSpacePos = 0; 1346 final int length = s.length(); 1347 while ((nonSpacePos < length) && (s.charAt(nonSpacePos) == ' ')) 1348 { 1349 nonSpacePos++; 1350 } 1351 1352 if (nonSpacePos == 0) 1353 { 1354 // There were no leading spaces. 1355 return s; 1356 } 1357 else if (nonSpacePos >= length) 1358 { 1359 // There were no non-space characters. 1360 return ""; 1361 } 1362 else 1363 { 1364 // There were leading spaces, so return the string without them. 1365 return s.substring(nonSpacePos, length); 1366 } 1367 } 1368 1369 1370 1371 /** 1372 * Trims only trailing spaces from the provided string, leaving any leading 1373 * spaces intact. 1374 * 1375 * @param s The string to be processed. It must not be {@code null}. 1376 * 1377 * @return The original string if no trimming was required, or a new string 1378 * without trailing spaces if the provided string had one or more. 1379 * It may be an empty string if the provided string was an empty 1380 * string or contained only spaces. 1381 */ 1382 public static String trimTrailing(final String s) 1383 { 1384 ensureNotNull(s); 1385 1386 final int lastPos = s.length() - 1; 1387 int nonSpacePos = lastPos; 1388 while ((nonSpacePos >= 0) && (s.charAt(nonSpacePos) == ' ')) 1389 { 1390 nonSpacePos--; 1391 } 1392 1393 if (nonSpacePos < 0) 1394 { 1395 // There were no non-space characters. 1396 return ""; 1397 } 1398 else if (nonSpacePos == lastPos) 1399 { 1400 // There were no trailing spaces. 1401 return s; 1402 } 1403 else 1404 { 1405 // There were trailing spaces, so return the string without them. 1406 return s.substring(0, (nonSpacePos+1)); 1407 } 1408 } 1409 1410 1411 1412 /** 1413 * Wraps the contents of the specified line using the given width. It will 1414 * attempt to wrap at spaces to preserve words, but if that is not possible 1415 * (because a single "word" is longer than the maximum width), then it will 1416 * wrap in the middle of the word at the specified maximum width. 1417 * 1418 * @param line The line to be wrapped. It must not be {@code null}. 1419 * @param maxWidth The maximum width for lines in the resulting list. A 1420 * value less than or equal to zero will cause no wrapping 1421 * to be performed. 1422 * 1423 * @return A list of the wrapped lines. It may be empty if the provided line 1424 * contained only spaces. 1425 */ 1426 public static List<String> wrapLine(final String line, final int maxWidth) 1427 { 1428 return wrapLine(line, maxWidth, maxWidth); 1429 } 1430 1431 1432 1433 /** 1434 * Wraps the contents of the specified line using the given width. It will 1435 * attempt to wrap at spaces to preserve words, but if that is not possible 1436 * (because a single "word" is longer than the maximum width), then it will 1437 * wrap in the middle of the word at the specified maximum width. 1438 * 1439 * @param line The line to be wrapped. It must not be 1440 * {@code null}. 1441 * @param maxFirstLineWidth The maximum length for the first line in 1442 * the resulting list. A value less than or 1443 * equal to zero will cause no wrapping to be 1444 * performed. 1445 * @param maxSubsequentLineWidth The maximum length for all lines except the 1446 * first line. This must be greater than zero 1447 * unless {@code maxFirstLineWidth} is less 1448 * than or equal to zero. 1449 * 1450 * @return A list of the wrapped lines. It may be empty if the provided line 1451 * contained only spaces. 1452 */ 1453 public static List<String> wrapLine(final String line, 1454 final int maxFirstLineWidth, 1455 final int maxSubsequentLineWidth) 1456 { 1457 if (maxFirstLineWidth > 0) 1458 { 1459 Validator.ensureTrue(maxSubsequentLineWidth > 0); 1460 } 1461 1462 // See if the provided string already contains line breaks. If so, then 1463 // treat it as multiple lines rather than a single line. 1464 final int breakPos = line.indexOf('\n'); 1465 if (breakPos >= 0) 1466 { 1467 final ArrayList<String> lineList = new ArrayList<String>(10); 1468 final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n"); 1469 while (tokenizer.hasMoreTokens()) 1470 { 1471 lineList.addAll(wrapLine(tokenizer.nextToken(), maxFirstLineWidth, 1472 maxSubsequentLineWidth)); 1473 } 1474 1475 return lineList; 1476 } 1477 1478 final int length = line.length(); 1479 if ((maxFirstLineWidth <= 0) || (length < maxFirstLineWidth)) 1480 { 1481 return Arrays.asList(line); 1482 } 1483 1484 1485 int wrapPos = maxFirstLineWidth; 1486 int lastWrapPos = 0; 1487 final ArrayList<String> lineList = new ArrayList<String>(5); 1488 while (true) 1489 { 1490 final int spacePos = line.lastIndexOf(' ', wrapPos); 1491 if (spacePos > lastWrapPos) 1492 { 1493 // We found a space in an acceptable location, so use it after trimming 1494 // any trailing spaces. 1495 final String s = trimTrailing(line.substring(lastWrapPos, spacePos)); 1496 1497 // Don't bother adding the line if it contained only spaces. 1498 if (s.length() > 0) 1499 { 1500 lineList.add(s); 1501 } 1502 1503 wrapPos = spacePos; 1504 } 1505 else 1506 { 1507 // We didn't find any spaces, so we'll have to insert a hard break at 1508 // the specified wrap column. 1509 lineList.add(line.substring(lastWrapPos, wrapPos)); 1510 } 1511 1512 // Skip over any spaces before the next non-space character. 1513 while ((wrapPos < length) && (line.charAt(wrapPos) == ' ')) 1514 { 1515 wrapPos++; 1516 } 1517 1518 lastWrapPos = wrapPos; 1519 wrapPos += maxSubsequentLineWidth; 1520 if (wrapPos >= length) 1521 { 1522 // The last fragment can fit on the line, so we can handle that now and 1523 // break. 1524 if (lastWrapPos >= length) 1525 { 1526 break; 1527 } 1528 else 1529 { 1530 final String s = line.substring(lastWrapPos); 1531 if (s.length() > 0) 1532 { 1533 lineList.add(s); 1534 } 1535 break; 1536 } 1537 } 1538 } 1539 1540 return lineList; 1541 } 1542 1543 1544 1545 /** 1546 * This method returns a form of the provided argument that is safe to 1547 * use on the command line for the local platform. This method is provided as 1548 * a convenience wrapper around {@link ExampleCommandLineArgument}. Calling 1549 * this method is equivalent to: 1550 * 1551 * <PRE> 1552 * return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm(); 1553 * </PRE> 1554 * 1555 * For getting direct access to command line arguments that are safe to 1556 * use on other platforms, call 1557 * {@link ExampleCommandLineArgument#getCleanArgument}. 1558 * 1559 * @param s The string to be processed. It must not be {@code null}. 1560 * 1561 * @return A cleaned version of the provided string in a form that will allow 1562 * it to be displayed as the value of a command-line argument on. 1563 */ 1564 public static String cleanExampleCommandLineArgument(final String s) 1565 { 1566 return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm(); 1567 } 1568 1569 1570 1571 /** 1572 * Retrieves a single string which is a concatenation of all of the provided 1573 * strings. 1574 * 1575 * @param a The array of strings to concatenate. It must not be 1576 * {@code null}. 1577 * 1578 * @return A string containing a concatenation of all of the strings in the 1579 * provided array. 1580 */ 1581 public static String concatenateStrings(final String... a) 1582 { 1583 return concatenateStrings(null, null, " ", null, null, a); 1584 } 1585 1586 1587 1588 /** 1589 * Retrieves a single string which is a concatenation of all of the provided 1590 * strings. 1591 * 1592 * @param l The list of strings to concatenate. It must not be 1593 * {@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 List<String> l) 1599 { 1600 return concatenateStrings(null, null, " ", null, null, l); 1601 } 1602 1603 1604 1605 /** 1606 * Retrieves a single string which is a concatenation of all of the provided 1607 * strings. 1608 * 1609 * @param beforeList A string that should be placed at the beginning of 1610 * the list. It may be {@code null} or empty if 1611 * nothing should be placed at the beginning of the 1612 * list. 1613 * @param beforeElement A string that should be placed before each element 1614 * in the list. It may be {@code null} or empty if 1615 * nothing should be placed before each element. 1616 * @param betweenElements The separator that should be placed between 1617 * elements in the list. It may be {@code null} or 1618 * empty if no separator should be placed between 1619 * elements. 1620 * @param afterElement A string that should be placed after each element 1621 * in the list. It may be {@code null} or empty if 1622 * nothing should be placed after each element. 1623 * @param afterList A string that should be placed at the end of the 1624 * list. It may be {@code null} or empty if nothing 1625 * should be placed at the end of the list. 1626 * @param a The array of strings to concatenate. It must not 1627 * be {@code null}. 1628 * 1629 * @return A string containing a concatenation of all of the strings in the 1630 * provided list. 1631 */ 1632 public static String concatenateStrings(final String beforeList, 1633 final String beforeElement, 1634 final String betweenElements, 1635 final String afterElement, 1636 final String afterList, 1637 final String... a) 1638 { 1639 return concatenateStrings(beforeList, beforeElement, betweenElements, 1640 afterElement, afterList, Arrays.asList(a)); 1641 } 1642 1643 1644 1645 /** 1646 * Retrieves a single string which is a concatenation of all of the provided 1647 * strings. 1648 * 1649 * @param beforeList A string that should be placed at the beginning of 1650 * the list. It may be {@code null} or empty if 1651 * nothing should be placed at the beginning of the 1652 * list. 1653 * @param beforeElement A string that should be placed before each element 1654 * in the list. It may be {@code null} or empty if 1655 * nothing should be placed before each element. 1656 * @param betweenElements The separator that should be placed between 1657 * elements in the list. It may be {@code null} or 1658 * empty if no separator should be placed between 1659 * elements. 1660 * @param afterElement A string that should be placed after each element 1661 * in the list. It may be {@code null} or empty if 1662 * nothing should be placed after each element. 1663 * @param afterList A string that should be placed at the end of the 1664 * list. It may be {@code null} or empty if nothing 1665 * should be placed at the end of the list. 1666 * @param l The list of strings to concatenate. It must not 1667 * be {@code null}. 1668 * 1669 * @return A string containing a concatenation of all of the strings in the 1670 * provided list. 1671 */ 1672 public static String concatenateStrings(final String beforeList, 1673 final String beforeElement, 1674 final String betweenElements, 1675 final String afterElement, 1676 final String afterList, 1677 final List<String> l) 1678 { 1679 ensureNotNull(l); 1680 1681 final StringBuilder buffer = new StringBuilder(); 1682 1683 if (beforeList != null) 1684 { 1685 buffer.append(beforeList); 1686 } 1687 1688 final Iterator<String> iterator = l.iterator(); 1689 while (iterator.hasNext()) 1690 { 1691 if (beforeElement != null) 1692 { 1693 buffer.append(beforeElement); 1694 } 1695 1696 buffer.append(iterator.next()); 1697 1698 if (afterElement != null) 1699 { 1700 buffer.append(afterElement); 1701 } 1702 1703 if ((betweenElements != null) && iterator.hasNext()) 1704 { 1705 buffer.append(betweenElements); 1706 } 1707 } 1708 1709 if (afterList != null) 1710 { 1711 buffer.append(afterList); 1712 } 1713 1714 return buffer.toString(); 1715 } 1716 1717 1718 1719 /** 1720 * Converts a duration in seconds to a string with a human-readable duration 1721 * which may include days, hours, minutes, and seconds, to the extent that 1722 * they are needed. 1723 * 1724 * @param s The number of seconds to be represented. 1725 * 1726 * @return A string containing a human-readable representation of the 1727 * provided time. 1728 */ 1729 public static String secondsToHumanReadableDuration(final long s) 1730 { 1731 return millisToHumanReadableDuration(s * 1000L); 1732 } 1733 1734 1735 1736 /** 1737 * Converts a duration in seconds to a string with a human-readable duration 1738 * which may include days, hours, minutes, and seconds, to the extent that 1739 * they are needed. 1740 * 1741 * @param m The number of milliseconds to be represented. 1742 * 1743 * @return A string containing a human-readable representation of the 1744 * provided time. 1745 */ 1746 public static String millisToHumanReadableDuration(final long m) 1747 { 1748 final StringBuilder buffer = new StringBuilder(); 1749 long numMillis = m; 1750 1751 final long numDays = numMillis / 86400000L; 1752 if (numDays > 0) 1753 { 1754 numMillis -= (numDays * 86400000L); 1755 if (numDays == 1) 1756 { 1757 buffer.append(INFO_NUM_DAYS_SINGULAR.get(numDays)); 1758 } 1759 else 1760 { 1761 buffer.append(INFO_NUM_DAYS_PLURAL.get(numDays)); 1762 } 1763 } 1764 1765 final long numHours = numMillis / 3600000L; 1766 if (numHours > 0) 1767 { 1768 numMillis -= (numHours * 3600000L); 1769 if (buffer.length() > 0) 1770 { 1771 buffer.append(", "); 1772 } 1773 1774 if (numHours == 1) 1775 { 1776 buffer.append(INFO_NUM_HOURS_SINGULAR.get(numHours)); 1777 } 1778 else 1779 { 1780 buffer.append(INFO_NUM_HOURS_PLURAL.get(numHours)); 1781 } 1782 } 1783 1784 final long numMinutes = numMillis / 60000L; 1785 if (numMinutes > 0) 1786 { 1787 numMillis -= (numMinutes * 60000L); 1788 if (buffer.length() > 0) 1789 { 1790 buffer.append(", "); 1791 } 1792 1793 if (numMinutes == 1) 1794 { 1795 buffer.append(INFO_NUM_MINUTES_SINGULAR.get(numMinutes)); 1796 } 1797 else 1798 { 1799 buffer.append(INFO_NUM_MINUTES_PLURAL.get(numMinutes)); 1800 } 1801 } 1802 1803 if (numMillis == 1000) 1804 { 1805 if (buffer.length() > 0) 1806 { 1807 buffer.append(", "); 1808 } 1809 1810 buffer.append(INFO_NUM_SECONDS_SINGULAR.get(1)); 1811 } 1812 else if ((numMillis > 0) || (buffer.length() == 0)) 1813 { 1814 if (buffer.length() > 0) 1815 { 1816 buffer.append(", "); 1817 } 1818 1819 final long numSeconds = numMillis / 1000L; 1820 numMillis -= (numSeconds * 1000L); 1821 if ((numMillis % 1000L) != 0L) 1822 { 1823 final double numSecondsDouble = numSeconds + (numMillis / 1000.0); 1824 final DecimalFormat decimalFormat = new DecimalFormat("0.000"); 1825 buffer.append(INFO_NUM_SECONDS_WITH_DECIMAL.get( 1826 decimalFormat.format(numSecondsDouble))); 1827 } 1828 else 1829 { 1830 buffer.append(INFO_NUM_SECONDS_PLURAL.get(numSeconds)); 1831 } 1832 } 1833 1834 return buffer.toString(); 1835 } 1836 1837 1838 1839 /** 1840 * Converts the provided number of nanoseconds to milliseconds. 1841 * 1842 * @param nanos The number of nanoseconds to convert to milliseconds. 1843 * 1844 * @return The number of milliseconds that most closely corresponds to the 1845 * specified number of nanoseconds. 1846 */ 1847 public static long nanosToMillis(final long nanos) 1848 { 1849 return Math.max(0L, Math.round(nanos / 1000000.0d)); 1850 } 1851 1852 1853 1854 /** 1855 * Converts the provided number of milliseconds to nanoseconds. 1856 * 1857 * @param millis The number of milliseconds to convert to nanoseconds. 1858 * 1859 * @return The number of nanoseconds that most closely corresponds to the 1860 * specified number of milliseconds. 1861 */ 1862 public static long millisToNanos(final long millis) 1863 { 1864 return Math.max(0L, (millis * 1000000L)); 1865 } 1866 1867 1868 1869 /** 1870 * Indicates whether the provided string is a valid numeric OID. A numeric 1871 * OID must start and end with a digit, must have at least on period, must 1872 * contain only digits and periods, and must not have two consecutive periods. 1873 * 1874 * @param s The string to examine. It must not be {@code null}. 1875 * 1876 * @return {@code true} if the provided string is a valid numeric OID, or 1877 * {@code false} if not. 1878 */ 1879 public static boolean isNumericOID(final String s) 1880 { 1881 boolean digitRequired = true; 1882 boolean periodFound = false; 1883 for (final char c : s.toCharArray()) 1884 { 1885 switch (c) 1886 { 1887 case '0': 1888 case '1': 1889 case '2': 1890 case '3': 1891 case '4': 1892 case '5': 1893 case '6': 1894 case '7': 1895 case '8': 1896 case '9': 1897 digitRequired = false; 1898 break; 1899 1900 case '.': 1901 if (digitRequired) 1902 { 1903 return false; 1904 } 1905 else 1906 { 1907 digitRequired = true; 1908 } 1909 periodFound = true; 1910 break; 1911 1912 default: 1913 return false; 1914 } 1915 1916 } 1917 1918 return (periodFound && (! digitRequired)); 1919 } 1920 1921 1922 1923 /** 1924 * Capitalizes the provided string. The first character will be converted to 1925 * uppercase, and the rest of the string will be left unaltered. 1926 * 1927 * @param s The string to be capitalized. 1928 * 1929 * @return A capitalized version of the provided string. 1930 */ 1931 public static String capitalize(final String s) 1932 { 1933 return capitalize(s, false); 1934 } 1935 1936 1937 1938 /** 1939 * Capitalizes the provided string. The first character of the string (or 1940 * optionally the first character of each word in the string) 1941 * 1942 * @param s The string to be capitalized. 1943 * @param allWords Indicates whether to capitalize all words in the string, 1944 * or only the first word. 1945 * 1946 * @return A capitalized version of the provided string. 1947 */ 1948 public static String capitalize(final String s, final boolean allWords) 1949 { 1950 if (s == null) 1951 { 1952 return null; 1953 } 1954 1955 switch (s.length()) 1956 { 1957 case 0: 1958 return s; 1959 1960 case 1: 1961 return s.toUpperCase(); 1962 1963 default: 1964 boolean capitalize = true; 1965 final char[] chars = s.toCharArray(); 1966 final StringBuilder buffer = new StringBuilder(chars.length); 1967 for (final char c : chars) 1968 { 1969 // Whitespace and punctuation will be considered word breaks. 1970 if (Character.isWhitespace(c) || 1971 (((c >= '!') && (c <= '.')) || 1972 ((c >= ':') && (c <= '@')) || 1973 ((c >= '[') && (c <= '`')) || 1974 ((c >= '{') && (c <= '~')))) 1975 { 1976 buffer.append(c); 1977 capitalize |= allWords; 1978 } 1979 else if (capitalize) 1980 { 1981 buffer.append(Character.toUpperCase(c)); 1982 capitalize = false; 1983 } 1984 else 1985 { 1986 buffer.append(c); 1987 } 1988 } 1989 return buffer.toString(); 1990 } 1991 } 1992 1993 1994 1995 /** 1996 * Encodes the provided UUID to a byte array containing its 128-bit 1997 * representation. 1998 * 1999 * @param uuid The UUID to be encoded. It must not be {@code null}. 2000 * 2001 * @return The byte array containing the 128-bit encoded UUID. 2002 */ 2003 public static byte[] encodeUUID(final UUID uuid) 2004 { 2005 final byte[] b = new byte[16]; 2006 2007 final long mostSignificantBits = uuid.getMostSignificantBits(); 2008 b[0] = (byte) ((mostSignificantBits >> 56) & 0xFF); 2009 b[1] = (byte) ((mostSignificantBits >> 48) & 0xFF); 2010 b[2] = (byte) ((mostSignificantBits >> 40) & 0xFF); 2011 b[3] = (byte) ((mostSignificantBits >> 32) & 0xFF); 2012 b[4] = (byte) ((mostSignificantBits >> 24) & 0xFF); 2013 b[5] = (byte) ((mostSignificantBits >> 16) & 0xFF); 2014 b[6] = (byte) ((mostSignificantBits >> 8) & 0xFF); 2015 b[7] = (byte) (mostSignificantBits & 0xFF); 2016 2017 final long leastSignificantBits = uuid.getLeastSignificantBits(); 2018 b[8] = (byte) ((leastSignificantBits >> 56) & 0xFF); 2019 b[9] = (byte) ((leastSignificantBits >> 48) & 0xFF); 2020 b[10] = (byte) ((leastSignificantBits >> 40) & 0xFF); 2021 b[11] = (byte) ((leastSignificantBits >> 32) & 0xFF); 2022 b[12] = (byte) ((leastSignificantBits >> 24) & 0xFF); 2023 b[13] = (byte) ((leastSignificantBits >> 16) & 0xFF); 2024 b[14] = (byte) ((leastSignificantBits >> 8) & 0xFF); 2025 b[15] = (byte) (leastSignificantBits & 0xFF); 2026 2027 return b; 2028 } 2029 2030 2031 2032 /** 2033 * Decodes the value of the provided byte array as a Java UUID. 2034 * 2035 * @param b The byte array to be decoded as a UUID. It must not be 2036 * {@code null}. 2037 * 2038 * @return The decoded UUID. 2039 * 2040 * @throws ParseException If the provided byte array cannot be parsed as a 2041 * UUID. 2042 */ 2043 public static UUID decodeUUID(final byte[] b) 2044 throws ParseException 2045 { 2046 if (b.length != 16) 2047 { 2048 throw new ParseException(ERR_DECODE_UUID_INVALID_LENGTH.get(toHex(b)), 0); 2049 } 2050 2051 long mostSignificantBits = 0L; 2052 for (int i=0; i < 8; i++) 2053 { 2054 mostSignificantBits = (mostSignificantBits << 8) | (b[i] & 0xFF); 2055 } 2056 2057 long leastSignificantBits = 0L; 2058 for (int i=8; i < 16; i++) 2059 { 2060 leastSignificantBits = (leastSignificantBits << 8) | (b[i] & 0xFF); 2061 } 2062 2063 return new UUID(mostSignificantBits, leastSignificantBits); 2064 } 2065 2066 2067 2068 /** 2069 * Returns {@code true} if and only if the current process is running on 2070 * a Windows-based operating system. 2071 * 2072 * @return {@code true} if the current process is running on a Windows-based 2073 * operating system and {@code false} otherwise. 2074 */ 2075 public static boolean isWindows() 2076 { 2077 final String osName = toLowerCase(System.getProperty("os.name")); 2078 return ((osName != null) && osName.contains("windows")); 2079 } 2080 2081 2082 2083 /** 2084 * Attempts to parse the contents of the provided string to an argument list 2085 * (e.g., converts something like "--arg1 arg1value --arg2 --arg3 arg3value" 2086 * to a list of "--arg1", "arg1value", "--arg2", "--arg3", "arg3value"). 2087 * 2088 * @param s The string to be converted to an argument list. 2089 * 2090 * @return The parsed argument list. 2091 * 2092 * @throws ParseException If a problem is encountered while attempting to 2093 * parse the given string to an argument list. 2094 */ 2095 public static List<String> toArgumentList(final String s) 2096 throws ParseException 2097 { 2098 if ((s == null) || (s.length() == 0)) 2099 { 2100 return Collections.emptyList(); 2101 } 2102 2103 int quoteStartPos = -1; 2104 boolean inEscape = false; 2105 final ArrayList<String> argList = new ArrayList<String>(); 2106 final StringBuilder currentArg = new StringBuilder(); 2107 for (int i=0; i < s.length(); i++) 2108 { 2109 final char c = s.charAt(i); 2110 if (inEscape) 2111 { 2112 currentArg.append(c); 2113 inEscape = false; 2114 continue; 2115 } 2116 2117 if (c == '\\') 2118 { 2119 inEscape = true; 2120 } 2121 else if (c == '"') 2122 { 2123 if (quoteStartPos >= 0) 2124 { 2125 quoteStartPos = -1; 2126 } 2127 else 2128 { 2129 quoteStartPos = i; 2130 } 2131 } 2132 else if (c == ' ') 2133 { 2134 if (quoteStartPos >= 0) 2135 { 2136 currentArg.append(c); 2137 } 2138 else if (currentArg.length() > 0) 2139 { 2140 argList.add(currentArg.toString()); 2141 currentArg.setLength(0); 2142 } 2143 } 2144 else 2145 { 2146 currentArg.append(c); 2147 } 2148 } 2149 2150 if (s.endsWith("\\") && (! s.endsWith("\\\\"))) 2151 { 2152 throw new ParseException(ERR_ARG_STRING_DANGLING_BACKSLASH.get(), 2153 (s.length() - 1)); 2154 } 2155 2156 if (quoteStartPos >= 0) 2157 { 2158 throw new ParseException(ERR_ARG_STRING_UNMATCHED_QUOTE.get( 2159 quoteStartPos), quoteStartPos); 2160 } 2161 2162 if (currentArg.length() > 0) 2163 { 2164 argList.add(currentArg.toString()); 2165 } 2166 2167 return Collections.unmodifiableList(argList); 2168 } 2169 2170 2171 2172 /** 2173 * Creates a modifiable list with all of the items of the provided array in 2174 * the same order. This method behaves much like {@code Arrays.asList}, 2175 * except that if the provided array is {@code null}, then it will return a 2176 * {@code null} list rather than throwing an exception. 2177 * 2178 * @param <T> The type of item contained in the provided array. 2179 * 2180 * @param array The array of items to include in the list. 2181 * 2182 * @return The list that was created, or {@code null} if the provided array 2183 * was {@code null}. 2184 */ 2185 public static <T> List<T> toList(final T[] array) 2186 { 2187 if (array == null) 2188 { 2189 return null; 2190 } 2191 2192 final ArrayList<T> l = new ArrayList<T>(array.length); 2193 l.addAll(Arrays.asList(array)); 2194 return l; 2195 } 2196 2197 2198 2199 /** 2200 * Creates a modifiable list with all of the items of the provided array in 2201 * the same order. This method behaves much like {@code Arrays.asList}, 2202 * except that if the provided array is {@code null}, then it will return an 2203 * empty list rather than throwing an exception. 2204 * 2205 * @param <T> The type of item contained in the provided array. 2206 * 2207 * @param array The array of items to include in the list. 2208 * 2209 * @return The list that was created, or an empty list if the provided array 2210 * was {@code null}. 2211 */ 2212 public static <T> List<T> toNonNullList(final T[] array) 2213 { 2214 if (array == null) 2215 { 2216 return new ArrayList<T>(0); 2217 } 2218 2219 final ArrayList<T> l = new ArrayList<T>(array.length); 2220 l.addAll(Arrays.asList(array)); 2221 return l; 2222 } 2223 2224 2225 2226 /** 2227 * Indicates whether both of the provided objects are {@code null} or both 2228 * are logically equal (using the {@code equals} method). 2229 * 2230 * @param o1 The first object for which to make the determination. 2231 * @param o2 The second object for which to make the determination. 2232 * 2233 * @return {@code true} if both objects are {@code null} or both are 2234 * logically equal, or {@code false} if only one of the objects is 2235 * {@code null} or they are not logically equal. 2236 */ 2237 public static boolean bothNullOrEqual(final Object o1, final Object o2) 2238 { 2239 if (o1 == null) 2240 { 2241 return (o2 == null); 2242 } 2243 else if (o2 == null) 2244 { 2245 return false; 2246 } 2247 2248 return o1.equals(o2); 2249 } 2250 2251 2252 2253 /** 2254 * Indicates whether both of the provided strings are {@code null} or both 2255 * are logically equal ignoring differences in capitalization (using the 2256 * {@code equalsIgnoreCase} method). 2257 * 2258 * @param s1 The first string for which to make the determination. 2259 * @param s2 The second string for which to make the determination. 2260 * 2261 * @return {@code true} if both strings are {@code null} or both are 2262 * logically equal ignoring differences in capitalization, or 2263 * {@code false} if only one of the objects is {@code null} or they 2264 * are not logically equal ignoring capitalization. 2265 */ 2266 public static boolean bothNullOrEqualIgnoreCase(final String s1, 2267 final String s2) 2268 { 2269 if (s1 == null) 2270 { 2271 return (s2 == null); 2272 } 2273 else if (s2 == null) 2274 { 2275 return false; 2276 } 2277 2278 return s1.equalsIgnoreCase(s2); 2279 } 2280 2281 2282 2283 /** 2284 * Indicates whether the provided string arrays have the same elements, 2285 * ignoring the order in which they appear and differences in capitalization. 2286 * It is assumed that neither array contains {@code null} strings, and that 2287 * no string appears more than once in each array. 2288 * 2289 * @param a1 The first array for which to make the determination. 2290 * @param a2 The second array for which to make the determination. 2291 * 2292 * @return {@code true} if both arrays have the same set of strings, or 2293 * {@code false} if not. 2294 */ 2295 public static boolean stringsEqualIgnoreCaseOrderIndependent( 2296 final String[] a1, final String[] a2) 2297 { 2298 if (a1 == null) 2299 { 2300 return (a2 == null); 2301 } 2302 else if (a2 == null) 2303 { 2304 return false; 2305 } 2306 2307 if (a1.length != a2.length) 2308 { 2309 return false; 2310 } 2311 2312 if (a1.length == 1) 2313 { 2314 return (a1[0].equalsIgnoreCase(a2[0])); 2315 } 2316 2317 final HashSet<String> s1 = new HashSet<String>(a1.length); 2318 for (final String s : a1) 2319 { 2320 s1.add(toLowerCase(s)); 2321 } 2322 2323 final HashSet<String> s2 = new HashSet<String>(a2.length); 2324 for (final String s : a2) 2325 { 2326 s2.add(toLowerCase(s)); 2327 } 2328 2329 return s1.equals(s2); 2330 } 2331 2332 2333 2334 /** 2335 * Indicates whether the provided arrays have the same elements, ignoring the 2336 * order in which they appear. It is assumed that neither array contains 2337 * {@code null} elements, and that no element appears more than once in each 2338 * array. 2339 * 2340 * @param <T> The type of element contained in the arrays. 2341 * 2342 * @param a1 The first array for which to make the determination. 2343 * @param a2 The second array for which to make the determination. 2344 * 2345 * @return {@code true} if both arrays have the same set of elements, or 2346 * {@code false} if not. 2347 */ 2348 public static <T> boolean arraysEqualOrderIndependent(final T[] a1, 2349 final T[] a2) 2350 { 2351 if (a1 == null) 2352 { 2353 return (a2 == null); 2354 } 2355 else if (a2 == null) 2356 { 2357 return false; 2358 } 2359 2360 if (a1.length != a2.length) 2361 { 2362 return false; 2363 } 2364 2365 if (a1.length == 1) 2366 { 2367 return (a1[0].equals(a2[0])); 2368 } 2369 2370 final HashSet<T> s1 = new HashSet<T>(Arrays.asList(a1)); 2371 final HashSet<T> s2 = new HashSet<T>(Arrays.asList(a2)); 2372 return s1.equals(s2); 2373 } 2374 2375 2376 2377 /** 2378 * Determines the number of bytes in a UTF-8 character that starts with the 2379 * given byte. 2380 * 2381 * @param b The byte for which to make the determination. 2382 * 2383 * @return The number of bytes in a UTF-8 character that starts with the 2384 * given byte, or -1 if it does not appear to be a valid first byte 2385 * for a UTF-8 character. 2386 */ 2387 public static int numBytesInUTF8CharacterWithFirstByte(final byte b) 2388 { 2389 if ((b & 0x7F) == b) 2390 { 2391 return 1; 2392 } 2393 else if ((b & 0xE0) == 0xC0) 2394 { 2395 return 2; 2396 } 2397 else if ((b & 0xF0) == 0xE0) 2398 { 2399 return 3; 2400 } 2401 else if ((b & 0xF8) == 0xF0) 2402 { 2403 return 4; 2404 } 2405 else 2406 { 2407 return -1; 2408 } 2409 } 2410 2411 2412 2413 /** 2414 * Indicates whether the provided attribute name should be considered a 2415 * sensitive attribute for the purposes of {@code toCode} methods. If an 2416 * attribute is considered sensitive, then its values will be redacted in the 2417 * output of the {@code toCode} methods. 2418 * 2419 * @param name The name for which to make the determination. It may or may 2420 * not include attribute options. It must not be {@code null}. 2421 * 2422 * @return {@code true} if the specified attribute is one that should be 2423 * considered sensitive for the 2424 */ 2425 public static boolean isSensitiveToCodeAttribute(final String name) 2426 { 2427 final String lowerBaseName = Attribute.getBaseName(name).toLowerCase(); 2428 return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES.contains(lowerBaseName); 2429 } 2430 2431 2432 2433 /** 2434 * Retrieves a set containing the base names (in all lowercase characters) of 2435 * any attributes that should be considered sensitive for the purposes of the 2436 * {@code toCode} methods. By default, only the userPassword and 2437 * authPassword attributes and their respective OIDs will be included. 2438 * 2439 * @return A set containing the base names (in all lowercase characters) of 2440 * any attributes that should be considered sensitive for the 2441 * purposes of the {@code toCode} methods. 2442 */ 2443 public static Set<String> getSensitiveToCodeAttributeBaseNames() 2444 { 2445 return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES; 2446 } 2447 2448 2449 2450 /** 2451 * Specifies the names of any attributes that should be considered sensitive 2452 * for the purposes of the {@code toCode} methods. 2453 * 2454 * @param names The names of any attributes that should be considered 2455 * sensitive for the purposes of the {@code toCode} methods. 2456 * It may be {@code null} or empty if no attributes should be 2457 * considered sensitive. 2458 */ 2459 public static void setSensitiveToCodeAttributes(final String... names) 2460 { 2461 setSensitiveToCodeAttributes(toList(names)); 2462 } 2463 2464 2465 2466 /** 2467 * Specifies the names of any attributes that should be considered sensitive 2468 * for the purposes of the {@code toCode} methods. 2469 * 2470 * @param names The names of any attributes that should be considered 2471 * sensitive for the purposes of the {@code toCode} methods. 2472 * It may be {@code null} or empty if no attributes should be 2473 * considered sensitive. 2474 */ 2475 public static void setSensitiveToCodeAttributes( 2476 final Collection<String> names) 2477 { 2478 if ((names == null) || names.isEmpty()) 2479 { 2480 TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.emptySet(); 2481 } 2482 else 2483 { 2484 final LinkedHashSet<String> nameSet = 2485 new LinkedHashSet<String>(names.size()); 2486 for (final String s : names) 2487 { 2488 nameSet.add(Attribute.getBaseName(s).toLowerCase()); 2489 } 2490 2491 TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet); 2492 } 2493 } 2494 2495 2496 2497 /** 2498 * Creates a new {@code IOException} with a cause. The constructor needed to 2499 * do this wasn't available until Java SE 6, so reflection is used to invoke 2500 * this constructor in versions of Java that provide it. In Java SE 5, the 2501 * provided message will be augmented with information about the cause. 2502 * 2503 * @param message The message to use for the exception. This may be 2504 * {@code null} if the message should be generated from the 2505 * provided cause. 2506 * @param cause The underlying cause for the exception. It may be 2507 * {@code null} if the exception should have only a message. 2508 * 2509 * @return The {@code IOException} object that was created. 2510 */ 2511 public static IOException createIOExceptionWithCause(final String message, 2512 final Throwable cause) 2513 { 2514 if (cause == null) 2515 { 2516 return new IOException(message); 2517 } 2518 else if (message == null) 2519 { 2520 return new IOException(cause); 2521 } 2522 else 2523 { 2524 return new IOException(message, cause); 2525 } 2526 } 2527 2528 2529 2530 /** 2531 * Converts the provided string (which may include line breaks) into a list 2532 * containing the lines without the line breaks. 2533 * 2534 * @param s The string to convert into a list of its representative lines. 2535 * 2536 * @return A list containing the lines that comprise the given string. 2537 */ 2538 public static List<String> stringToLines(final String s) 2539 { 2540 final ArrayList<String> l = new ArrayList<String>(10); 2541 2542 if (s == null) 2543 { 2544 return l; 2545 } 2546 2547 final BufferedReader reader = new BufferedReader(new StringReader(s)); 2548 2549 try 2550 { 2551 while (true) 2552 { 2553 try 2554 { 2555 final String line = reader.readLine(); 2556 if (line == null) 2557 { 2558 return l; 2559 } 2560 else 2561 { 2562 l.add(line); 2563 } 2564 } 2565 catch (final Exception e) 2566 { 2567 debugException(e); 2568 2569 // This should never happen. If it does, just return a list 2570 // containing a single item that is the original string. 2571 l.clear(); 2572 l.add(s); 2573 return l; 2574 } 2575 } 2576 } 2577 finally 2578 { 2579 try 2580 { 2581 // This is technically not necessary in this case, but it's good form. 2582 reader.close(); 2583 } 2584 catch (final Exception e) 2585 { 2586 debugException(e); 2587 // This should never happen, and there's nothing we need to do even if 2588 // it does. 2589 } 2590 } 2591 } 2592}