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