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