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