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