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