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