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