001/*
002 * Copyright 2007-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2007-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.BufferedReader;
041import java.io.File;
042import java.io.FileOutputStream;
043import java.io.FileReader;
044import java.io.IOException;
045import java.io.PrintWriter;
046import java.io.StringReader;
047import java.lang.reflect.Array;
048import java.net.InetAddress;
049import java.net.NetworkInterface;
050import java.nio.charset.StandardCharsets;
051import java.text.DecimalFormat;
052import java.text.ParseException;
053import java.text.SimpleDateFormat;
054import java.util.ArrayList;
055import java.util.Arrays;
056import java.util.Collection;
057import java.util.Collections;
058import java.util.Date;
059import java.util.Enumeration;
060import java.util.GregorianCalendar;
061import java.util.HashSet;
062import java.util.Iterator;
063import java.util.LinkedHashMap;
064import java.util.LinkedHashSet;
065import java.util.List;
066import java.util.Map;
067import java.util.Properties;
068import java.util.Set;
069import java.util.StringTokenizer;
070import java.util.TimeZone;
071import java.util.TreeSet;
072import java.util.UUID;
073import java.util.logging.Handler;
074import java.util.logging.Level;
075import java.util.logging.Logger;
076
077import com.unboundid.ldap.sdk.Attribute;
078import com.unboundid.ldap.sdk.Control;
079import com.unboundid.ldap.sdk.LDAPConnectionOptions;
080import com.unboundid.ldap.sdk.NameResolver;
081import com.unboundid.ldap.sdk.Version;
082
083import static com.unboundid.util.UtilityMessages.*;
084
085
086
087/**
088 * This class provides a number of static utility functions.
089 */
090@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
091public final class StaticUtils
092{
093  /**
094   * A pre-allocated byte array containing zero bytes.
095   */
096  @NotNull public static final byte[] NO_BYTES = new byte[0];
097
098
099
100  /**
101   * A pre-allocated empty character array.
102   */
103  @NotNull public static final char[] NO_CHARS = new char[0];
104
105
106
107  /**
108   * A pre-allocated empty control array.
109   */
110  @NotNull public static final Control[] NO_CONTROLS = new Control[0];
111
112
113
114  /**
115   * A pre-allocated empty string array.
116   */
117  @NotNull public static final String[] NO_STRINGS = new String[0];
118
119
120
121  /**
122   * The end-of-line marker for the platform on which the LDAP SDK is
123   * currently running.
124   */
125  @NotNull public static final String EOL =
126       getSystemProperty("line.separator", "\n");
127
128
129
130  /**
131   * The end-of-line marker that consists of a carriage return character
132   * followed by a line feed character, as used on Windows systems.
133   */
134  @NotNull public static final String EOL_CR_LF = "\r\n";
135
136
137
138  /**
139   * The end-of-line marker that consists of just the line feed character, as
140   * used on UNIX-based systems.
141   */
142  @NotNull public static final String EOL_LF = "\n";
143
144
145
146  /**
147   * A byte array containing the end-of-line marker for the platform on which
148   * the LDAP SDK is currently running.
149   */
150  @NotNull public static final byte[] EOL_BYTES = getBytes(EOL);
151
152
153
154  /**
155   * A byte array containing the end-of-line marker that consists of a carriage
156   * return character followed by a line feed character, as used on Windows
157   * systems.
158   */
159  @NotNull public static final byte[] EOL_BYTES_CR_LF = getBytes(EOL_CR_LF);
160
161
162
163  /**
164   * A byte array containing the end-of-line marker that consists of just the
165   * line feed character, as used on UNIX-based systems.
166   */
167  @NotNull public static final byte[] EOL_BYTES_LF = getBytes(EOL_LF);
168
169
170
171  /**
172   * Indicates whether the unit tests are currently running.
173   */
174  private static final boolean IS_WITHIN_UNIT_TESTS =
175       Boolean.getBoolean("com.unboundid.ldap.sdk.RunningUnitTests") ||
176       Boolean.getBoolean("com.unboundid.directory.server.RunningUnitTests");
177
178
179
180  /**
181   * The thread-local date formatter used to encode generalized time values.
182   */
183  @NotNull private static final ThreadLocal<SimpleDateFormat>
184       GENERALIZED_TIME_FORMATTERS = new ThreadLocal<>();
185
186
187
188  /**
189   * The thread-local date formatter used to encode RFC 3339 time values.
190   */
191  @NotNull private static final ThreadLocal<SimpleDateFormat>
192       RFC_3339_TIME_FORMATTERS = new ThreadLocal<>();
193
194
195
196  /**
197   * The {@code TimeZone} object that represents the UTC (universal coordinated
198   * time) time zone.
199   */
200  @NotNull private static final TimeZone UTC_TIME_ZONE =
201       TimeZone.getTimeZone("UTC");
202
203
204
205  /**
206   * A set containing the names of attributes that will be considered sensitive
207   * by the {@code toCode} methods of various request and data structure types.
208   */
209  @NotNull private static volatile Set<String>
210       TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = setOf("userpassword", "2.5.4.35",
211            "authpassword", "1.3.6.1.4.1.4203.1.3.4");
212
213
214
215  /**
216   * The width of the terminal window, in columns.
217   */
218  public static final int TERMINAL_WIDTH_COLUMNS;
219  static
220  {
221    // Try to dynamically determine the size of the terminal window using the
222    // COLUMNS environment variable.
223    int terminalWidth = 80;
224    final String columnsEnvVar = getEnvironmentVariable("COLUMNS");
225    if (columnsEnvVar != null)
226    {
227      try
228      {
229        terminalWidth = Integer.parseInt(columnsEnvVar);
230      }
231      catch (final Exception e)
232      {
233        Debug.debugException(e);
234      }
235    }
236
237    TERMINAL_WIDTH_COLUMNS = terminalWidth;
238  }
239
240
241
242  /**
243   * Prevent this class from being instantiated.
244   */
245  private StaticUtils()
246  {
247    // No implementation is required.
248  }
249
250
251
252  /**
253   * Retrieves the set of currently defined system properties.  If possible,
254   * this will simply return the result of a call to
255   * {@code System.getProperties}.  However, the LDAP SDK is known to be used in
256   * environments where a security manager prevents setting system properties,
257   * and in that case, calls to {@code System.getProperties} will be rejected
258   * with a {@code SecurityException} because the returned structure is mutable
259   * and could be used to alter system property values.  In such cases, a new
260   * empty {@code Properties} object will be created, and may optionally be
261   * populated with the values of a specific set of named properties.
262   *
263   * @param  propertyNames  An optional set of property names whose values (if
264   *                        defined) should be included in the
265   *                        {@code Properties} object that will be returned if a
266   *                        security manager prevents retrieving the full set of
267   *                        system properties.  This may be {@code null} or
268   *                        empty if no specific properties should be retrieved.
269   *
270   * @return  The value returned by a call to {@code System.getProperties} if
271   *          possible, or a newly-created properties map (possibly including
272   *          the values of a specified set of system properties) if it is not
273   *          possible to get a mutable set of the system properties.
274   */
275  @NotNull()
276  public static Properties getSystemProperties(
277                                @Nullable final String... propertyNames)
278  {
279    try
280    {
281      final Properties properties = System.getProperties();
282
283      final String forceThrowPropertyName =
284           StaticUtils.class.getName() + ".forceGetSystemPropertiesToThrow";
285
286      // To ensure that we can get coverage for the code below in which there is
287      // a restrictive security manager in place, look for a system property
288      // that will cause us to throw an exception.
289      final Object forceThrowPropertyValue =
290           properties.getProperty(forceThrowPropertyName);
291      if (forceThrowPropertyValue != null)
292      {
293        throw new SecurityException(forceThrowPropertyName + '=' +
294             forceThrowPropertyValue);
295      }
296
297      return properties;
298    }
299    catch (final SecurityException e)
300    {
301      Debug.debugException(e);
302    }
303
304
305    // If we have gotten here, then we can assume that a security manager
306    // prevents us from accessing all system properties.  Create a new proper
307    final Properties properties = new Properties();
308    if (propertyNames != null)
309    {
310      for (final String propertyName : propertyNames)
311      {
312        final Object propertyValue = System.getProperty(propertyName);
313        if (propertyValue != null)
314        {
315          properties.put(propertyName, propertyValue);
316        }
317      }
318    }
319
320    return properties;
321  }
322
323
324
325  /**
326   * Retrieves the value of the specified system property.
327   *
328   * @param  name  The name of the system property for which to retrieve the
329   *               value.
330   *
331   * @return  The value of the requested system property, or {@code null} if
332   *          that variable was not set or its value could not be retrieved
333   *          (for example, because a security manager prevents it).
334   */
335  @Nullable()
336  public static String getSystemProperty(@NotNull final String name)
337  {
338    try
339    {
340      return System.getProperty(name);
341    }
342    catch (final Throwable t)
343    {
344      // It is possible that the call to System.getProperty could fail under
345      // some security managers.  In that case, simply swallow the error and
346      // act as if that system property is not set.
347      Debug.debugException(t);
348      return null;
349    }
350  }
351
352
353
354  /**
355   * Retrieves the value of the specified system property.
356   *
357   * @param  name          The name of the system property for which to retrieve
358   *                       the value.
359   * @param  defaultValue  The default value to return if the specified
360   *                       system property is not set or could not be
361   *                       retrieved.
362   *
363   * @return  The value of the requested system property, or the provided
364   *          default value if that system property was not set or its value
365   *          could not be retrieved (for example, because a security manager
366   *          prevents it).
367   */
368  @Nullable()
369  public static String getSystemProperty(@NotNull final String name,
370                                         @Nullable final String defaultValue)
371  {
372    try
373    {
374      return System.getProperty(name, defaultValue);
375    }
376    catch (final Throwable t)
377    {
378      // It is possible that the call to System.getProperty could fail under
379      // some security managers.  In that case, simply swallow the error and
380      // act as if that system property is not set.
381      Debug.debugException(t);
382      return defaultValue;
383    }
384  }
385
386
387
388  /**
389   * Attempts to set the value of the specified system property.  Note that this
390   * may not be permitted by some security managers, in which case the attempt
391   * will have no effect.
392   *
393   * @param  name   The name of the System property to set.  It must not be
394   *                {@code null}.
395   * @param  value  The value to use for the system property.  If it is
396   *                {@code null}, then the property will be cleared.
397   *
398   * @return  The former value of the system property, or {@code null} if it
399   *          did not have a value or if it could not be set (for example,
400   *          because a security manager prevents it).
401   */
402  @Nullable()
403  public static String setSystemProperty(@NotNull final String name,
404                                         @Nullable final String value)
405  {
406    try
407    {
408      if (value == null)
409      {
410        return System.clearProperty(name);
411      }
412      else
413      {
414        return System.setProperty(name, value);
415      }
416    }
417    catch (final Throwable t)
418    {
419      // It is possible that the call to System.setProperty or
420      // System.clearProperty could fail under some security managers.  In that
421      // case, simply swallow the error and act as if that system property is
422      // not set.
423      Debug.debugException(t);
424      return null;
425    }
426  }
427
428
429
430  /**
431   * Attempts to clear the value of the specified system property.  Note that
432   * this may not be permitted by some security managers, in which case the
433   * attempt will have no effect.
434   *
435   * @param  name  The name of the System property to clear.  It must not be
436   *               {@code null}.
437   *
438   * @return  The former value of the system property, or {@code null} if it
439   *          did not have a value or if it could not be set (for example,
440   *          because a security manager prevents it).
441   */
442  @Nullable()
443  public static String clearSystemProperty(@NotNull final String name)
444  {
445    try
446    {
447      return System.clearProperty(name);
448    }
449    catch (final Throwable t)
450    {
451      // It is possible that the call to System.clearProperty could fail under
452      // some security managers.  In that case, simply swallow the error and
453      // act as if that system property is not set.
454      Debug.debugException(t);
455      return null;
456    }
457  }
458
459
460
461  /**
462   * Retrieves a map of all environment variables defined in the JVM's process.
463   *
464   * @return  A map of all environment variables defined in the JVM's process,
465   *          or an empty map if no environment variables are set or the actual
466   *          set could not be retrieved (for example, because a security
467   *          manager prevents it).
468   */
469  @NotNull()
470  public static Map<String,String> getEnvironmentVariables()
471  {
472    try
473    {
474      return System.getenv();
475    }
476    catch (final Throwable t)
477    {
478      // It is possible that the call to System.getenv could fail under some
479      // security managers.  In that case, simply swallow the error and pretend
480      // that the environment variable is not set.
481      Debug.debugException(t);
482      return Collections.emptyMap();
483    }
484  }
485
486
487
488  /**
489   * Retrieves the value of the specified environment variable.
490   *
491   * @param  name  The name of the environment variable for which to retrieve
492   *               the value.
493   *
494   * @return  The value of the requested environment variable, or {@code null}
495   *          if that variable was not set or its value could not be retrieved
496   *          (for example, because a security manager prevents it).
497   */
498  @Nullable()
499  public static String getEnvironmentVariable(@NotNull final String name)
500  {
501    try
502    {
503      return System.getenv(name);
504    }
505    catch (final Throwable t)
506    {
507      // It is possible that the call to System.getenv could fail under some
508      // security managers.  In that case, simply swallow the error and pretend
509      // that the environment variable is not set.
510      Debug.debugException(t);
511      return null;
512    }
513  }
514
515
516
517  /**
518   * Retrieves the value of the specified environment variable.
519   *
520   * @param  name          The name of the environment variable for which to
521   *                       retrieve the value.
522   * @param  defaultValue  The default value to use if the specified environment
523   *                       variable is not set.  It may be {@code null} if no
524   *                       default should be used.
525   *
526   * @return  The value of the requested environment variable, or {@code null}
527   *          if that variable was not set or its value could not be retrieved
528   *          (for example, because a security manager prevents it) and there
529   *          is no default value.
530   */
531  @Nullable()
532  public static String getEnvironmentVariable(@NotNull final String name,
533                            @Nullable final String defaultValue)
534  {
535    final String value = getEnvironmentVariable(name);
536    if (value == null)
537    {
538      return defaultValue;
539    }
540    else
541    {
542      return value;
543    }
544  }
545
546
547
548  /**
549   * Attempts to set the desired log level for the specified logger.  Note that
550   * this may not be permitted by some security managers, in which case the
551   * attempt will have no effect.
552   *
553   * @param  logger    The logger whose level should be updated.
554   * @param  logLevel  The log level to set for the logger.
555   */
556  public static void setLoggerLevel(@NotNull final Logger logger,
557                                    @NotNull final Level logLevel)
558  {
559    try
560    {
561      logger.setLevel(logLevel);
562    }
563    catch (final Throwable t)
564    {
565      Debug.debugException(t);
566    }
567  }
568
569
570
571  /**
572   * Attempts to set the desired log level for the specified log handler.  Note
573   * that this may not be permitted by some security managers, in which case the
574   * attempt will have no effect.
575   *
576   * @param  logHandler  The log handler whose level should be updated.
577   * @param  logLevel    The log level to set for the log handler.
578   */
579  public static void setLogHandlerLevel(@NotNull final Handler logHandler,
580                                        @NotNull final Level logLevel)
581  {
582    try
583    {
584      logHandler.setLevel(logLevel);
585    }
586    catch (final Throwable t)
587    {
588      Debug.debugException(t);
589    }
590  }
591
592
593
594  /**
595   * Retrieves a UTF-8 byte representation of the provided string.
596   *
597   * @param  s  The string for which to retrieve the UTF-8 byte representation.
598   *
599   * @return  The UTF-8 byte representation for the provided string.
600   */
601  @NotNull()
602  public static byte[] getBytes(@Nullable final String s)
603  {
604    final int length;
605    if ((s == null) || ((length = s.length()) == 0))
606    {
607      return NO_BYTES;
608    }
609
610    final byte[] b = new byte[length];
611    for (int i=0; i < length; i++)
612    {
613      final char c = s.charAt(i);
614      if (c <= 0x7F)
615      {
616        b[i] = (byte) (c & 0x7F);
617      }
618      else
619      {
620        return s.getBytes(StandardCharsets.UTF_8);
621      }
622    }
623
624    return b;
625  }
626
627
628
629  /**
630   * Indicates whether the contents of the provided byte array represent an
631   * ASCII string, which is also known in LDAP terminology as an IA5 string.
632   * An ASCII string is one that contains only bytes in which the most
633   * significant bit is zero.
634   *
635   * @param  b  The byte array for which to make the determination.  It must
636   *            not be {@code null}.
637   *
638   * @return  {@code true} if the contents of the provided array represent an
639   *          ASCII string, or {@code false} if not.
640   */
641  public static boolean isASCIIString(@NotNull final byte[] b)
642  {
643    for (final byte by : b)
644    {
645      if ((by & 0x80) == 0x80)
646      {
647        return false;
648      }
649    }
650
651    return true;
652  }
653
654
655
656  /**
657   * Indicates whether the contents of the provided string represent an ASCII
658   * string, which is also known in LDAP terminology as an IA5 string.  An ASCII
659   * string is one that contains only bytes in which the most significant bit is
660   * zero.
661   *
662   * @param  s  The string for which to make the determination.  It must not be
663   *            {@code null}.
664   *
665   * @return  {@code true} if the contents of the provided string represent an
666   *          ASCII string, or {@code false} if not.
667   */
668  public static boolean isASCIIString(@NotNull final String s)
669  {
670    return isASCIIString(getBytes(s));
671  }
672
673
674
675  /**
676   * Indicates whether the provided character is a printable ASCII character, as
677   * per RFC 4517 section 3.2.  The only printable characters are:
678   * <UL>
679   *   <LI>All uppercase and lowercase ASCII alphabetic letters</LI>
680   *   <LI>All ASCII numeric digits</LI>
681   *   <LI>The following additional ASCII characters:  single quote, left
682   *       parenthesis, right parenthesis, plus, comma, hyphen, period, equals,
683   *       forward slash, colon, question mark, space.</LI>
684   * </UL>
685   *
686   * @param  c  The character for which to make the determination.
687   *
688   * @return  {@code true} if the provided character is a printable ASCII
689   *          character, or {@code false} if not.
690   */
691  public static boolean isPrintable(final char c)
692  {
693    if (((c >= 'a') && (c <= 'z')) ||
694        ((c >= 'A') && (c <= 'Z')) ||
695        ((c >= '0') && (c <= '9')))
696    {
697      return true;
698    }
699
700    switch (c)
701    {
702      case '\'':
703      case '(':
704      case ')':
705      case '+':
706      case ',':
707      case '-':
708      case '.':
709      case '=':
710      case '/':
711      case ':':
712      case '?':
713      case ' ':
714        return true;
715      default:
716        return false;
717    }
718  }
719
720
721
722  /**
723   * Indicates whether the contents of the provided byte array represent a
724   * printable LDAP string, as per RFC 4517 section 3.2.  The only characters
725   * allowed in a printable string are:
726   * <UL>
727   *   <LI>All uppercase and lowercase ASCII alphabetic letters</LI>
728   *   <LI>All ASCII numeric digits</LI>
729   *   <LI>The following additional ASCII characters:  single quote, left
730   *       parenthesis, right parenthesis, plus, comma, hyphen, period, equals,
731   *       forward slash, colon, question mark, space.</LI>
732   * </UL>
733   * If the provided array contains anything other than the above characters
734   * (i.e., if the byte array contains any non-ASCII characters, or any ASCII
735   * control characters, or if it contains excluded ASCII characters like
736   * the exclamation point, double quote, octothorpe, dollar sign, etc.), then
737   * it will not be considered printable.
738   *
739   * @param  b  The byte array for which to make the determination.  It must
740   *            not be {@code null}.
741   *
742   * @return  {@code true} if the contents of the provided byte array represent
743   *          a printable LDAP string, or {@code false} if not.
744   */
745  public static boolean isPrintableString(@NotNull final byte[] b)
746  {
747    for (final byte by : b)
748    {
749      if ((by & 0x80) == 0x80)
750      {
751        return false;
752      }
753
754      if (((by >= 'a') && (by <= 'z')) ||
755          ((by >= 'A') && (by <= 'Z')) ||
756          ((by >= '0') && (by <= '9')))
757      {
758        continue;
759      }
760
761      switch (by)
762      {
763        case '\'':
764        case '(':
765        case ')':
766        case '+':
767        case ',':
768        case '-':
769        case '.':
770        case '=':
771        case '/':
772        case ':':
773        case '?':
774        case ' ':
775          continue;
776        default:
777          return false;
778      }
779    }
780
781    return true;
782  }
783
784
785
786  /**
787   * Indicates whether the provided string represents a printable LDAP string,
788   * as per RFC 4517 section 3.2.  The only characters allowed in a printable
789   * string are:
790   * <UL>
791   *   <LI>All uppercase and lowercase ASCII alphabetic letters</LI>
792   *   <LI>All ASCII numeric digits</LI>
793   *   <LI>The following additional ASCII characters:  single quote, left
794   *       parenthesis, right parenthesis, plus, comma, hyphen, period, equals,
795   *       forward slash, colon, question mark, space.</LI>
796   * </UL>
797   * If the provided array contains anything other than the above characters
798   * (i.e., if the byte array contains any non-ASCII characters, or any ASCII
799   * control characters, or if it contains excluded ASCII characters like
800   * the exclamation point, double quote, octothorpe, dollar sign, etc.), then
801   * it will not be considered printable.
802   *
803   * @param  s  The string for which to make the determination.  It must not be
804   *            {@code null}.
805   *
806   * @return  {@code true} if the provided string represents a printable LDAP
807   *          string, or {@code false} if not.
808   */
809  public static boolean isPrintableString(@NotNull final String s)
810  {
811    final int length = s.length();
812    for (int i=0; i < length; i++)
813    {
814      final char c = s.charAt(i);
815      if ((c & 0x80) == 0x80)
816      {
817        return false;
818      }
819
820      if (((c >= 'a') && (c <= 'z')) ||
821          ((c >= 'A') && (c <= 'Z')) ||
822          ((c >= '0') && (c <= '9')))
823      {
824        continue;
825      }
826
827      switch (c)
828      {
829        case '\'':
830        case '(':
831        case ')':
832        case '+':
833        case ',':
834        case '-':
835        case '.':
836        case '=':
837        case '/':
838        case ':':
839        case '?':
840        case ' ':
841          continue;
842        default:
843          return false;
844      }
845    }
846
847    return true;
848  }
849
850
851
852  /**
853   * Indicates whether the contents of the provided array are valid UTF-8.
854   *
855   * @param  b  The byte array to examine.  It must not be {@code null}.
856   *
857   * @return  {@code true} if the byte array can be parsed as a valid UTF-8
858   *          string, or {@code false} if not.
859   */
860  public static boolean isValidUTF8(@NotNull final byte[] b)
861  {
862    int i = 0;
863    while (i < b.length)
864    {
865      final byte currentByte = b[i++];
866
867      // If the most significant bit is not set, then this represents a valid
868      // single-byte character.
869      if ((currentByte & 0b1000_0000) == 0b0000_0000)
870      {
871        continue;
872      }
873
874      // If the first byte starts with 0b110, then it must be followed by
875      // another byte that starts with 0b10.
876      if ((currentByte & 0b1110_0000) == 0b1100_0000)
877      {
878        if (! hasExpectedSubsequentUTF8Bytes(b, i, 1))
879        {
880          return false;
881        }
882
883        i++;
884        continue;
885      }
886
887      // If the first byte starts with 0b1110, then it must be followed by two
888      // more bytes that start with 0b10.
889      if ((currentByte & 0b1111_0000) == 0b1110_0000)
890      {
891        if (! hasExpectedSubsequentUTF8Bytes(b, i, 2))
892        {
893          return false;
894        }
895
896        i += 2;
897        continue;
898      }
899
900      // If the first byte starts with 0b11110, then it must be followed by
901      // three more bytes that start with 0b10.
902      if ((currentByte & 0b1111_1000) == 0b1111_0000)
903      {
904        if (! hasExpectedSubsequentUTF8Bytes(b, i, 3))
905        {
906          return false;
907        }
908
909        i += 3;
910        continue;
911      }
912
913      // If the first byte starts with 0b111110, then it must be followed by
914      // four more bytes that start with 0b10.
915      if ((currentByte & 0b1111_1100) == 0b1111_1000)
916      {
917        if (! hasExpectedSubsequentUTF8Bytes(b, i, 4))
918        {
919          return false;
920        }
921
922        i += 4;
923        continue;
924      }
925
926      // If the first byte starts with 0b1111110, then it must be followed by
927      // five more bytes that start with 0b10.
928      if ((currentByte & 0b1111_1110) == 0b1111_1100)
929      {
930        if (! hasExpectedSubsequentUTF8Bytes(b, i, 5))
931        {
932          return false;
933        }
934
935        i += 5;
936        continue;
937      }
938
939      // This is not a valid first byte for a UTF-8 character.
940      return false;
941    }
942
943
944    // If we've gotten here, then the provided array represents a valid UTF-8
945    // string.
946    return true;
947  }
948
949
950
951  /**
952   * Ensures that the provided array has the expected number of bytes that start
953   * with 0b10 starting at the specified position in the array.
954   *
955   * @param  b  The byte array to examine.
956   * @param  p  The position in the byte array at which to start looking.
957   * @param  n  The number of bytes to examine.
958   *
959   * @return  {@code true} if the provided byte array has the expected number of
960   *          bytes that start with 0b10, or {@code false} if not.
961   */
962  private static boolean hasExpectedSubsequentUTF8Bytes(@NotNull final byte[] b,
963                                                        final int p,
964                                                        final int n)
965  {
966    if (b.length < (p + n))
967    {
968      return false;
969    }
970
971    for (int i=0; i < n; i++)
972    {
973      if ((b[p+i] & 0b1100_0000) != 0b1000_0000)
974      {
975        return false;
976      }
977    }
978
979    return true;
980  }
981
982
983
984  /**
985   * Retrieves a string generated from the provided byte array using the UTF-8
986   * encoding.
987   *
988   * @param  b  The byte array for which to return the associated string.
989   *
990   * @return  The string generated from the provided byte array using the UTF-8
991   *          encoding.
992   */
993  @NotNull()
994  public static String toUTF8String(@NotNull final byte[] b)
995  {
996    try
997    {
998      return new String(b, StandardCharsets.UTF_8);
999    }
1000    catch (final Exception e)
1001    {
1002      // This should never happen.
1003      Debug.debugException(e);
1004      return new String(b);
1005    }
1006  }
1007
1008
1009
1010  /**
1011   * Retrieves a string generated from the specified portion of the provided
1012   * byte array using the UTF-8 encoding.
1013   *
1014   * @param  b       The byte array for which to return the associated string.
1015   * @param  offset  The offset in the array at which the value begins.
1016   * @param  length  The number of bytes in the value to convert to a string.
1017   *
1018   * @return  The string generated from the specified portion of the provided
1019   *          byte array using the UTF-8 encoding.
1020   */
1021  @NotNull()
1022  public static String toUTF8String(@NotNull final byte[] b, final int offset,
1023                                    final int length)
1024  {
1025    try
1026    {
1027      return new String(b, offset, length, StandardCharsets.UTF_8);
1028    }
1029    catch (final Exception e)
1030    {
1031      // This should never happen.
1032      Debug.debugException(e);
1033      return new String(b, offset, length);
1034    }
1035  }
1036
1037
1038
1039  /**
1040   * Retrieves a version of the provided string with the first character
1041   * converted to lowercase but all other characters retaining their original
1042   * capitalization.
1043   *
1044   * @param  s  The string to be processed.
1045   *
1046   * @return  A version of the provided string with the first character
1047   *          converted to lowercase but all other characters retaining their
1048   *          original capitalization.  It may be {@code null} if the provided
1049   *          string is {@code null}.
1050   */
1051  @Nullable()
1052  public static String toInitialLowerCase(@Nullable final String s)
1053  {
1054    if ((s == null) || s.isEmpty())
1055    {
1056      return s;
1057    }
1058    else if (s.length() == 1)
1059    {
1060      return toLowerCase(s);
1061    }
1062    else
1063    {
1064      final char c = s.charAt(0);
1065      if (((c >= 'A') && (c <= 'Z')) || (c < ' ') || (c > '~'))
1066      {
1067        final StringBuilder b = new StringBuilder(s);
1068        b.setCharAt(0, Character.toLowerCase(c));
1069        return b.toString();
1070      }
1071      else
1072      {
1073        return s;
1074      }
1075    }
1076  }
1077
1078
1079
1080  /**
1081   * Retrieves an all-lowercase version of the provided string.
1082   *
1083   * @param  s  The string for which to retrieve the lowercase version.
1084   *
1085   * @return  An all-lowercase version of the provided string, or {@code null}
1086   *          if the provided string was {@code null}.
1087   */
1088  @Nullable()
1089  public static String toLowerCase(@Nullable final String s)
1090  {
1091    if (s == null)
1092    {
1093      return null;
1094    }
1095
1096    final int length = s.length();
1097    final char[] charArray = s.toCharArray();
1098    for (int i=0; i < length; i++)
1099    {
1100      switch (charArray[i])
1101      {
1102        case 'A':
1103          charArray[i] = 'a';
1104          break;
1105        case 'B':
1106          charArray[i] = 'b';
1107          break;
1108        case 'C':
1109          charArray[i] = 'c';
1110          break;
1111        case 'D':
1112          charArray[i] = 'd';
1113          break;
1114        case 'E':
1115          charArray[i] = 'e';
1116          break;
1117        case 'F':
1118          charArray[i] = 'f';
1119          break;
1120        case 'G':
1121          charArray[i] = 'g';
1122          break;
1123        case 'H':
1124          charArray[i] = 'h';
1125          break;
1126        case 'I':
1127          charArray[i] = 'i';
1128          break;
1129        case 'J':
1130          charArray[i] = 'j';
1131          break;
1132        case 'K':
1133          charArray[i] = 'k';
1134          break;
1135        case 'L':
1136          charArray[i] = 'l';
1137          break;
1138        case 'M':
1139          charArray[i] = 'm';
1140          break;
1141        case 'N':
1142          charArray[i] = 'n';
1143          break;
1144        case 'O':
1145          charArray[i] = 'o';
1146          break;
1147        case 'P':
1148          charArray[i] = 'p';
1149          break;
1150        case 'Q':
1151          charArray[i] = 'q';
1152          break;
1153        case 'R':
1154          charArray[i] = 'r';
1155          break;
1156        case 'S':
1157          charArray[i] = 's';
1158          break;
1159        case 'T':
1160          charArray[i] = 't';
1161          break;
1162        case 'U':
1163          charArray[i] = 'u';
1164          break;
1165        case 'V':
1166          charArray[i] = 'v';
1167          break;
1168        case 'W':
1169          charArray[i] = 'w';
1170          break;
1171        case 'X':
1172          charArray[i] = 'x';
1173          break;
1174        case 'Y':
1175          charArray[i] = 'y';
1176          break;
1177        case 'Z':
1178          charArray[i] = 'z';
1179          break;
1180        default:
1181          if (charArray[i] > 0x7F)
1182          {
1183            return s.toLowerCase();
1184          }
1185          break;
1186      }
1187    }
1188
1189    return new String(charArray);
1190  }
1191
1192
1193
1194  /**
1195   * Retrieves an all-uppercase version of the provided string.
1196   *
1197   * @param  s  The string for which to retrieve the uppercase version.
1198   *
1199   * @return  An all-uppercase version of the provided string, or {@code null}
1200   *          if the provided string was {@code null}.
1201   */
1202  @Nullable()
1203  public static String toUpperCase(@Nullable final String s)
1204  {
1205    if (s == null)
1206    {
1207      return null;
1208    }
1209
1210    final int length = s.length();
1211    final char[] charArray = s.toCharArray();
1212    for (int i=0; i < length; i++)
1213    {
1214      switch (charArray[i])
1215      {
1216        case 'a':
1217          charArray[i] = 'A';
1218          break;
1219        case 'b':
1220          charArray[i] = 'B';
1221          break;
1222        case 'c':
1223          charArray[i] = 'C';
1224          break;
1225        case 'd':
1226          charArray[i] = 'D';
1227          break;
1228        case 'e':
1229          charArray[i] = 'E';
1230          break;
1231        case 'f':
1232          charArray[i] = 'F';
1233          break;
1234        case 'g':
1235          charArray[i] = 'G';
1236          break;
1237        case 'h':
1238          charArray[i] = 'H';
1239          break;
1240        case 'i':
1241          charArray[i] = 'I';
1242          break;
1243        case 'j':
1244          charArray[i] = 'J';
1245          break;
1246        case 'k':
1247          charArray[i] = 'K';
1248          break;
1249        case 'l':
1250          charArray[i] = 'L';
1251          break;
1252        case 'm':
1253          charArray[i] = 'M';
1254          break;
1255        case 'n':
1256          charArray[i] = 'N';
1257          break;
1258        case 'o':
1259          charArray[i] = 'O';
1260          break;
1261        case 'p':
1262          charArray[i] = 'P';
1263          break;
1264        case 'q':
1265          charArray[i] = 'Q';
1266          break;
1267        case 'r':
1268          charArray[i] = 'R';
1269          break;
1270        case 's':
1271          charArray[i] = 'S';
1272          break;
1273        case 't':
1274          charArray[i] = 'T';
1275          break;
1276        case 'u':
1277          charArray[i] = 'U';
1278          break;
1279        case 'v':
1280          charArray[i] = 'V';
1281          break;
1282        case 'w':
1283          charArray[i] = 'W';
1284          break;
1285        case 'x':
1286          charArray[i] = 'X';
1287          break;
1288        case 'y':
1289          charArray[i] = 'Y';
1290          break;
1291        case 'z':
1292          charArray[i] = 'Z';
1293          break;
1294        default:
1295          if (charArray[i] > 0x7F)
1296          {
1297            return s.toUpperCase();
1298          }
1299          break;
1300      }
1301    }
1302
1303    return new String(charArray);
1304  }
1305
1306
1307
1308  /**
1309   * Indicates whether the provided character is a valid hexadecimal digit.
1310   *
1311   * @param  c  The character for which to make the determination.
1312   *
1313   * @return  {@code true} if the provided character does represent a valid
1314   *          hexadecimal digit, or {@code false} if not.
1315   */
1316  public static boolean isHex(final char c)
1317  {
1318    switch (c)
1319    {
1320      case '0':
1321      case '1':
1322      case '2':
1323      case '3':
1324      case '4':
1325      case '5':
1326      case '6':
1327      case '7':
1328      case '8':
1329      case '9':
1330      case 'a':
1331      case 'A':
1332      case 'b':
1333      case 'B':
1334      case 'c':
1335      case 'C':
1336      case 'd':
1337      case 'D':
1338      case 'e':
1339      case 'E':
1340      case 'f':
1341      case 'F':
1342        return true;
1343
1344      default:
1345        return false;
1346    }
1347  }
1348
1349
1350
1351  /**
1352   * Retrieves a hexadecimal representation of the provided byte.
1353   *
1354   * @param  b  The byte to encode as hexadecimal.
1355   *
1356   * @return  A string containing the hexadecimal representation of the provided
1357   *          byte.
1358   */
1359  @NotNull()
1360  public static String toHex(final byte b)
1361  {
1362    final StringBuilder buffer = new StringBuilder(2);
1363    toHex(b, buffer);
1364    return buffer.toString();
1365  }
1366
1367
1368
1369  /**
1370   * Appends a hexadecimal representation of the provided byte to the given
1371   * buffer.
1372   *
1373   * @param  b       The byte to encode as hexadecimal.
1374   * @param  buffer  The buffer to which the hexadecimal representation is to be
1375   *                 appended.
1376   */
1377  public static void toHex(final byte b, @NotNull final StringBuilder buffer)
1378  {
1379    switch (b & 0xF0)
1380    {
1381      case 0x00:
1382        buffer.append('0');
1383        break;
1384      case 0x10:
1385        buffer.append('1');
1386        break;
1387      case 0x20:
1388        buffer.append('2');
1389        break;
1390      case 0x30:
1391        buffer.append('3');
1392        break;
1393      case 0x40:
1394        buffer.append('4');
1395        break;
1396      case 0x50:
1397        buffer.append('5');
1398        break;
1399      case 0x60:
1400        buffer.append('6');
1401        break;
1402      case 0x70:
1403        buffer.append('7');
1404        break;
1405      case 0x80:
1406        buffer.append('8');
1407        break;
1408      case 0x90:
1409        buffer.append('9');
1410        break;
1411      case 0xA0:
1412        buffer.append('a');
1413        break;
1414      case 0xB0:
1415        buffer.append('b');
1416        break;
1417      case 0xC0:
1418        buffer.append('c');
1419        break;
1420      case 0xD0:
1421        buffer.append('d');
1422        break;
1423      case 0xE0:
1424        buffer.append('e');
1425        break;
1426      case 0xF0:
1427        buffer.append('f');
1428        break;
1429    }
1430
1431    switch (b & 0x0F)
1432    {
1433      case 0x00:
1434        buffer.append('0');
1435        break;
1436      case 0x01:
1437        buffer.append('1');
1438        break;
1439      case 0x02:
1440        buffer.append('2');
1441        break;
1442      case 0x03:
1443        buffer.append('3');
1444        break;
1445      case 0x04:
1446        buffer.append('4');
1447        break;
1448      case 0x05:
1449        buffer.append('5');
1450        break;
1451      case 0x06:
1452        buffer.append('6');
1453        break;
1454      case 0x07:
1455        buffer.append('7');
1456        break;
1457      case 0x08:
1458        buffer.append('8');
1459        break;
1460      case 0x09:
1461        buffer.append('9');
1462        break;
1463      case 0x0A:
1464        buffer.append('a');
1465        break;
1466      case 0x0B:
1467        buffer.append('b');
1468        break;
1469      case 0x0C:
1470        buffer.append('c');
1471        break;
1472      case 0x0D:
1473        buffer.append('d');
1474        break;
1475      case 0x0E:
1476        buffer.append('e');
1477        break;
1478      case 0x0F:
1479        buffer.append('f');
1480        break;
1481    }
1482  }
1483
1484
1485
1486  /**
1487   * Retrieves a hexadecimal representation of the contents of the provided byte
1488   * array.  No delimiter character will be inserted between the hexadecimal
1489   * digits for each byte.
1490   *
1491   * @param  b  The byte array to be represented as a hexadecimal string.  It
1492   *            must not be {@code null}.
1493   *
1494   * @return  A string containing a hexadecimal representation of the contents
1495   *          of the provided byte array.
1496   */
1497  @NotNull()
1498  public static String toHex(@NotNull final byte[] b)
1499  {
1500    Validator.ensureNotNull(b);
1501
1502    final StringBuilder buffer = new StringBuilder(2 * b.length);
1503    toHex(b, buffer);
1504    return buffer.toString();
1505  }
1506
1507
1508
1509  /**
1510   * Retrieves a hexadecimal representation of the contents of the provided byte
1511   * array.  No delimiter character will be inserted between the hexadecimal
1512   * digits for each byte.
1513   *
1514   * @param  b       The byte array to be represented as a hexadecimal string.
1515   *                 It must not be {@code null}.
1516   * @param  buffer  A buffer to which the hexadecimal representation of the
1517   *                 contents of the provided byte array should be appended.
1518   */
1519  public static void toHex(@NotNull final byte[] b,
1520                           @NotNull final StringBuilder buffer)
1521  {
1522    toHex(b, null, buffer);
1523  }
1524
1525
1526
1527  /**
1528   * Retrieves a hexadecimal representation of the contents of the provided byte
1529   * array.  No delimiter character will be inserted between the hexadecimal
1530   * digits for each byte.
1531   *
1532   * @param  b          The byte array to be represented as a hexadecimal
1533   *                    string.  It must not be {@code null}.
1534   * @param  delimiter  A delimiter to be inserted between bytes.  It may be
1535   *                    {@code null} if no delimiter should be used.
1536   * @param  buffer     A buffer to which the hexadecimal representation of the
1537   *                    contents of the provided byte array should be appended.
1538   */
1539  public static void toHex(@NotNull final byte[] b,
1540                           @Nullable final String delimiter,
1541                           @NotNull final StringBuilder buffer)
1542  {
1543    boolean first = true;
1544    for (final byte bt : b)
1545    {
1546      if (first)
1547      {
1548        first = false;
1549      }
1550      else if (delimiter != null)
1551      {
1552        buffer.append(delimiter);
1553      }
1554
1555      toHex(bt, buffer);
1556    }
1557  }
1558
1559
1560
1561  /**
1562   * Retrieves a hex-encoded representation of the contents of the provided
1563   * array, along with an ASCII representation of its contents next to it.  The
1564   * output will be split across multiple lines, with up to sixteen bytes per
1565   * line.  For each of those sixteen bytes, the two-digit hex representation
1566   * will be appended followed by a space.  Then, the ASCII representation of
1567   * those sixteen bytes will follow that, with a space used in place of any
1568   * byte that does not have an ASCII representation.
1569   *
1570   * @param  array   The array whose contents should be processed.
1571   * @param  indent  The number of spaces to insert on each line prior to the
1572   *                 first hex byte.
1573   *
1574   * @return  A hex-encoded representation of the contents of the provided
1575   *          array, along with an ASCII representation of its contents next to
1576   *          it.
1577   */
1578  @NotNull()
1579  public static String toHexPlusASCII(@NotNull final byte[] array,
1580                                      final int indent)
1581  {
1582    final StringBuilder buffer = new StringBuilder();
1583    toHexPlusASCII(array, indent, buffer);
1584    return buffer.toString();
1585  }
1586
1587
1588
1589  /**
1590   * Appends a hex-encoded representation of the contents of the provided array
1591   * to the given buffer, along with an ASCII representation of its contents
1592   * next to it.  The output will be split across multiple lines, with up to
1593   * sixteen bytes per line.  For each of those sixteen bytes, the two-digit hex
1594   * representation will be appended followed by a space.  Then, the ASCII
1595   * representation of those sixteen bytes will follow that, with a space used
1596   * in place of any byte that does not have an ASCII representation.
1597   *
1598   * @param  array   The array whose contents should be processed.
1599   * @param  indent  The number of spaces to insert on each line prior to the
1600   *                 first hex byte.
1601   * @param  buffer  The buffer to which the encoded data should be appended.
1602   */
1603  public static void toHexPlusASCII(@Nullable final byte[] array,
1604                                    final int indent,
1605                                    @NotNull final StringBuilder buffer)
1606  {
1607    if ((array == null) || (array.length == 0))
1608    {
1609      return;
1610    }
1611
1612    for (int i=0; i < indent; i++)
1613    {
1614      buffer.append(' ');
1615    }
1616
1617    int pos = 0;
1618    int startPos = 0;
1619    while (pos < array.length)
1620    {
1621      toHex(array[pos++], buffer);
1622      buffer.append(' ');
1623
1624      if ((pos % 16) == 0)
1625      {
1626        buffer.append("  ");
1627        for (int i=startPos; i < pos; i++)
1628        {
1629          if ((array[i] < ' ') || (array[i] > '~'))
1630          {
1631            buffer.append(' ');
1632          }
1633          else
1634          {
1635            buffer.append((char) array[i]);
1636          }
1637        }
1638        buffer.append(EOL);
1639        startPos = pos;
1640
1641        if (pos < array.length)
1642        {
1643          for (int i=0; i < indent; i++)
1644          {
1645            buffer.append(' ');
1646          }
1647        }
1648      }
1649    }
1650
1651    // If the last line isn't complete yet, then finish it off.
1652    if ((array.length % 16) != 0)
1653    {
1654      final int missingBytes = (16 - (array.length % 16));
1655      for (int i=0; i < missingBytes; i++)
1656      {
1657        buffer.append("   ");
1658      }
1659      buffer.append("  ");
1660      for (int i=startPos; i < array.length; i++)
1661      {
1662        if ((array[i] < ' ') || (array[i] > '~'))
1663        {
1664          buffer.append(' ');
1665        }
1666        else
1667        {
1668          buffer.append((char) array[i]);
1669        }
1670      }
1671      buffer.append(EOL);
1672    }
1673  }
1674
1675
1676
1677  /**
1678   * Retrieves the bytes that correspond to the provided hexadecimal string.
1679   *
1680   * @param  hexString  The hexadecimal string for which to retrieve the bytes.
1681   *                    It must not be {@code null}, and there must not be any
1682   *                    delimiter between bytes.
1683   *
1684   * @return  The bytes that correspond to the provided hexadecimal string.
1685   *
1686   * @throws  ParseException  If the provided string does not represent valid
1687   *                          hexadecimal data, or if the provided string does
1688   *                          not contain an even number of characters.
1689   */
1690  @NotNull()
1691  public static byte[] fromHex(@NotNull final String hexString)
1692         throws ParseException
1693  {
1694    if ((hexString.length() % 2) != 0)
1695    {
1696      throw new ParseException(
1697           ERR_FROM_HEX_ODD_NUMBER_OF_CHARACTERS.get(hexString.length()),
1698           hexString.length());
1699    }
1700
1701    final byte[] decodedBytes = new byte[hexString.length() / 2];
1702    for (int i=0, j=0; i < decodedBytes.length; i++, j+= 2)
1703    {
1704      switch (hexString.charAt(j))
1705      {
1706        case '0':
1707          // No action is required.
1708          break;
1709        case '1':
1710          decodedBytes[i] = 0x10;
1711          break;
1712        case '2':
1713          decodedBytes[i] = 0x20;
1714          break;
1715        case '3':
1716          decodedBytes[i] = 0x30;
1717          break;
1718        case '4':
1719          decodedBytes[i] = 0x40;
1720          break;
1721        case '5':
1722          decodedBytes[i] = 0x50;
1723          break;
1724        case '6':
1725          decodedBytes[i] = 0x60;
1726          break;
1727        case '7':
1728          decodedBytes[i] = 0x70;
1729          break;
1730        case '8':
1731          decodedBytes[i] = (byte) 0x80;
1732          break;
1733        case '9':
1734          decodedBytes[i] = (byte) 0x90;
1735          break;
1736        case 'a':
1737        case 'A':
1738          decodedBytes[i] = (byte) 0xA0;
1739          break;
1740        case 'b':
1741        case 'B':
1742          decodedBytes[i] = (byte) 0xB0;
1743          break;
1744        case 'c':
1745        case 'C':
1746          decodedBytes[i] = (byte) 0xC0;
1747          break;
1748        case 'd':
1749        case 'D':
1750          decodedBytes[i] = (byte) 0xD0;
1751          break;
1752        case 'e':
1753        case 'E':
1754          decodedBytes[i] = (byte) 0xE0;
1755          break;
1756        case 'f':
1757        case 'F':
1758          decodedBytes[i] = (byte) 0xF0;
1759          break;
1760        default:
1761          throw new ParseException(ERR_FROM_HEX_NON_HEX_CHARACTER.get(j), j);
1762      }
1763
1764      switch (hexString.charAt(j+1))
1765      {
1766        case '0':
1767          // No action is required.
1768          break;
1769        case '1':
1770          decodedBytes[i] |= 0x01;
1771          break;
1772        case '2':
1773          decodedBytes[i] |= 0x02;
1774          break;
1775        case '3':
1776          decodedBytes[i] |= 0x03;
1777          break;
1778        case '4':
1779          decodedBytes[i] |= 0x04;
1780          break;
1781        case '5':
1782          decodedBytes[i] |= 0x05;
1783          break;
1784        case '6':
1785          decodedBytes[i] |= 0x06;
1786          break;
1787        case '7':
1788          decodedBytes[i] |= 0x07;
1789          break;
1790        case '8':
1791          decodedBytes[i] |= 0x08;
1792          break;
1793        case '9':
1794          decodedBytes[i] |= 0x09;
1795          break;
1796        case 'a':
1797        case 'A':
1798          decodedBytes[i] |= 0x0A;
1799          break;
1800        case 'b':
1801        case 'B':
1802          decodedBytes[i] |= 0x0B;
1803          break;
1804        case 'c':
1805        case 'C':
1806          decodedBytes[i] |= 0x0C;
1807          break;
1808        case 'd':
1809        case 'D':
1810          decodedBytes[i] |= 0x0D;
1811          break;
1812        case 'e':
1813        case 'E':
1814          decodedBytes[i] |= 0x0E;
1815          break;
1816        case 'f':
1817        case 'F':
1818          decodedBytes[i] |= 0x0F;
1819          break;
1820        default:
1821          throw new ParseException(ERR_FROM_HEX_NON_HEX_CHARACTER.get(j+1),
1822               j+1);
1823      }
1824    }
1825
1826    return decodedBytes;
1827  }
1828
1829
1830
1831  /**
1832   * Appends a hex-encoded representation of the provided character to the given
1833   * buffer.  Each byte of the hex-encoded representation will be prefixed with
1834   * a backslash.
1835   *
1836   * @param  c       The character to be encoded.
1837   * @param  buffer  The buffer to which the hex-encoded representation should
1838   *                 be appended.
1839   */
1840  public static void hexEncode(final char c,
1841                               @NotNull final StringBuilder buffer)
1842  {
1843    final byte[] charBytes;
1844    if (c <= 0x7F)
1845    {
1846      charBytes = new byte[] { (byte) (c & 0x7F) };
1847    }
1848    else
1849    {
1850      charBytes = getBytes(String.valueOf(c));
1851    }
1852
1853    for (final byte b : charBytes)
1854    {
1855      buffer.append('\\');
1856      toHex(b, buffer);
1857    }
1858  }
1859
1860
1861
1862  /**
1863   * Appends a hex-encoded representation of the provided code point to the
1864   * given buffer.  Each byte of the hex-encoded representation will be prefixed
1865   * with a backslash.
1866   *
1867   * @param  codePoint  The code point to be encoded.
1868   * @param  buffer     The buffer to which the hex-encoded representation
1869   *                    should be appended.
1870   */
1871  public static void hexEncode(final int codePoint,
1872                               @NotNull final StringBuilder buffer)
1873  {
1874    final byte[] charBytes =
1875         getBytes(new String(new int[] { codePoint }, 0, 1));
1876
1877    for (final byte b : charBytes)
1878    {
1879      buffer.append('\\');
1880      toHex(b, buffer);
1881    }
1882  }
1883
1884
1885
1886  /**
1887   * Appends the Java code that may be used to create the provided byte
1888   * array to the given buffer.
1889   *
1890   * @param  array   The byte array containing the data to represent.  It must
1891   *                 not be {@code null}.
1892   * @param  buffer  The buffer to which the code should be appended.
1893   */
1894  public static void byteArrayToCode(@NotNull final byte[] array,
1895                                     @NotNull final StringBuilder buffer)
1896  {
1897    buffer.append("new byte[] {");
1898    for (int i=0; i < array.length; i++)
1899    {
1900      if (i > 0)
1901      {
1902        buffer.append(',');
1903      }
1904
1905      buffer.append(" (byte) 0x");
1906      toHex(array[i], buffer);
1907    }
1908    buffer.append(" }");
1909  }
1910
1911
1912
1913  /**
1914   * Retrieves a single-line string representation of the stack trace for the
1915   * provided {@code Throwable}.  It will include the unqualified name of the
1916   * {@code Throwable} class, a list of source files and line numbers (if
1917   * available) for the stack trace, and will also include the stack trace for
1918   * the cause (if present).
1919   *
1920   * @param  t  The {@code Throwable} for which to retrieve the stack trace.
1921   *
1922   * @return  A single-line string representation of the stack trace for the
1923   *          provided {@code Throwable}.
1924   */
1925  @NotNull()
1926  public static String getStackTrace(@NotNull final Throwable t)
1927  {
1928    final StringBuilder buffer = new StringBuilder();
1929    getStackTrace(t, buffer);
1930    return buffer.toString();
1931  }
1932
1933
1934
1935  /**
1936   * Appends a single-line string representation of the stack trace for the
1937   * provided {@code Throwable} to the given buffer.  It will include the
1938   * unqualified name of the {@code Throwable} class, a list of source files and
1939   * line numbers (if available) for the stack trace, and will also include the
1940   * stack trace for the cause (if present).
1941   *
1942   * @param  t       The {@code Throwable} for which to retrieve the stack
1943   *                 trace.
1944   * @param  buffer  The buffer to which the information should be appended.
1945   */
1946  public static void getStackTrace(@NotNull final Throwable t,
1947                                   @NotNull final StringBuilder buffer)
1948  {
1949    buffer.append(getUnqualifiedClassName(t.getClass()));
1950    buffer.append('(');
1951
1952    final String message = t.getMessage();
1953    if (message != null)
1954    {
1955      buffer.append("message='");
1956      buffer.append(message);
1957      buffer.append("', ");
1958    }
1959
1960    buffer.append("trace='");
1961    getStackTrace(t.getStackTrace(), buffer);
1962    buffer.append('\'');
1963
1964    final Throwable cause = t.getCause();
1965    if (cause != null)
1966    {
1967      buffer.append(", cause=");
1968      getStackTrace(cause, buffer);
1969    }
1970
1971    final String ldapSDKVersionString = ", ldapSDKVersion=" +
1972         Version.NUMERIC_VERSION_STRING + ", revision=" + Version.REVISION_ID;
1973    if (buffer.indexOf(ldapSDKVersionString) < 0)
1974    {
1975      buffer.append(ldapSDKVersionString);
1976    }
1977
1978    buffer.append(')');
1979  }
1980
1981
1982
1983  /**
1984   * Returns a single-line string representation of the stack trace.  It will
1985   * include a list of source files and line numbers (if available) for the
1986   * stack trace.
1987   *
1988   * @param  elements  The stack trace.
1989   *
1990   * @return  A single-line string representation of the stack trace.
1991   */
1992  @NotNull()
1993  public static String getStackTrace(
1994                            @NotNull final StackTraceElement[] elements)
1995  {
1996    final StringBuilder buffer = new StringBuilder();
1997    getStackTrace(elements, buffer);
1998    return buffer.toString();
1999  }
2000
2001
2002
2003  /**
2004   * Appends a single-line string representation of the stack trace to the given
2005   * buffer.  It will include a list of source files and line numbers
2006   * (if available) for the stack trace.
2007   *
2008   * @param  elements  The stack trace.
2009   * @param  buffer    The buffer to which the information should be appended.
2010   */
2011  public static void getStackTrace(@NotNull final StackTraceElement[] elements,
2012                                   @NotNull final StringBuilder buffer)
2013  {
2014    getStackTrace(elements, buffer, -1);
2015  }
2016
2017
2018
2019  /**
2020   * Appends a single-line string representation of the stack trace to the given
2021   * buffer.  It will include a list of source files and line numbers
2022   * (if available) for the stack trace.
2023   *
2024   * @param  elements         The stack trace.
2025   * @param  buffer           The buffer to which the information should be
2026   *                          appended.
2027   * @param  maxPreSDKFrames  The maximum number of stack trace frames to
2028   *                          include from code invoked before calling into the
2029   *                          LDAP SDK.  A value of zero indicates that only
2030   *                          stack trace frames from the LDAP SDK itself (or
2031   *                          things that it calls) will be included.  A
2032   *                          negative value indicates that
2033   */
2034  public static void getStackTrace(@NotNull final StackTraceElement[] elements,
2035                                   @NotNull final StringBuilder buffer,
2036                                   final int maxPreSDKFrames)
2037  {
2038    boolean sdkElementFound = false;
2039    int numPreSDKElementsFound = 0;
2040    for (int i=0; i < elements.length; i++)
2041    {
2042      if (i > 0)
2043      {
2044        buffer.append(" / ");
2045      }
2046
2047      if (elements[i].getClassName().startsWith("com.unboundid."))
2048      {
2049        sdkElementFound = true;
2050      }
2051      else if (sdkElementFound)
2052      {
2053        if ((maxPreSDKFrames >= 0) &&
2054             (numPreSDKElementsFound >= maxPreSDKFrames))
2055        {
2056          buffer.append("...");
2057          return;
2058        }
2059
2060        numPreSDKElementsFound++;
2061      }
2062
2063      buffer.append(elements[i].getMethodName());
2064      buffer.append('(');
2065      buffer.append(elements[i].getFileName());
2066
2067      final int lineNumber = elements[i].getLineNumber();
2068      if (lineNumber > 0)
2069      {
2070        buffer.append(':');
2071        buffer.append(lineNumber);
2072      }
2073      else if (elements[i].isNativeMethod())
2074      {
2075        buffer.append(":native");
2076      }
2077      else
2078      {
2079        buffer.append(":unknown");
2080      }
2081      buffer.append(')');
2082    }
2083  }
2084
2085
2086
2087  /**
2088   * Retrieves a string representation of the provided {@code Throwable} object
2089   * suitable for use in a message.  For runtime exceptions and errors, then a
2090   * full stack trace for the exception will be provided.  For exception types
2091   * defined in the LDAP SDK, then its {@code getExceptionMessage} method will
2092   * be used to get the string representation.  For all other types of
2093   * exceptions, then the standard string representation will be used.
2094   * <BR><BR>
2095   * For all types of exceptions, the message will also include the cause if one
2096   * exists.
2097   *
2098   * @param  t  The {@code Throwable} for which to generate the exception
2099   *            message.
2100   *
2101   * @return  A string representation of the provided {@code Throwable} object
2102   *          suitable for use in a message.
2103   */
2104  @NotNull()
2105  public static String getExceptionMessage(@NotNull final Throwable t)
2106  {
2107    final boolean includeCause =
2108         Boolean.getBoolean(Debug.PROPERTY_INCLUDE_CAUSE_IN_EXCEPTION_MESSAGES);
2109    final boolean includeStackTrace = Boolean.getBoolean(
2110         Debug.PROPERTY_INCLUDE_STACK_TRACE_IN_EXCEPTION_MESSAGES);
2111
2112    return getExceptionMessage(t, includeCause, includeStackTrace);
2113  }
2114
2115
2116
2117  /**
2118   * Retrieves a string representation of the provided {@code Throwable} object
2119   * suitable for use in a message.  For runtime exceptions and errors, then a
2120   * full stack trace for the exception will be provided.  For exception types
2121   * defined in the LDAP SDK, then its {@code getExceptionMessage} method will
2122   * be used to get the string representation.  For all other types of
2123   * exceptions, then the standard string representation will be used.
2124   * <BR><BR>
2125   * For all types of exceptions, the message will also include the cause if one
2126   * exists.
2127   *
2128   * @param  t                  The {@code Throwable} for which to generate the
2129   *                            exception message.
2130   * @param  includeCause       Indicates whether to include information about
2131   *                            the cause (if any) in the exception message.
2132   * @param  includeStackTrace  Indicates whether to include a condensed
2133   *                            representation of the stack trace in the
2134   *                            exception message.
2135   *
2136   * @return  A string representation of the provided {@code Throwable} object
2137   *          suitable for use in a message.
2138   */
2139  @NotNull()
2140  public static String getExceptionMessage(@Nullable final Throwable t,
2141                                           final boolean includeCause,
2142                                           final boolean includeStackTrace)
2143  {
2144    if (t == null)
2145    {
2146      return ERR_NO_EXCEPTION.get();
2147    }
2148
2149    final StringBuilder buffer = new StringBuilder();
2150    if (t instanceof LDAPSDKException)
2151    {
2152      buffer.append(((LDAPSDKException) t).getExceptionMessage());
2153    }
2154    else if (t instanceof LDAPSDKRuntimeException)
2155    {
2156      buffer.append(((LDAPSDKRuntimeException) t).getExceptionMessage());
2157    }
2158    else if (t instanceof NullPointerException)
2159    {
2160      // For NullPointerExceptions, we'll always print at least a portion of
2161      // the stack trace that includes all of the LDAP SDK code, and up to
2162      // three frames of whatever called into the SDK.
2163      buffer.append("NullPointerException(");
2164      getStackTrace(t.getStackTrace(), buffer, 3);
2165      buffer.append(')');
2166    }
2167    else if ((t.getMessage() == null) || t.getMessage().isEmpty() ||
2168         t.getMessage().equalsIgnoreCase("null"))
2169    {
2170      getStackTrace(t, buffer);
2171    }
2172    else
2173    {
2174      buffer.append(t.getClass().getSimpleName());
2175      buffer.append('(');
2176      buffer.append(t.getMessage());
2177      buffer.append(')');
2178
2179      if (includeStackTrace)
2180      {
2181        buffer.append(" trace=");
2182        getStackTrace(t, buffer);
2183      }
2184      else if (includeCause)
2185      {
2186        final Throwable cause = t.getCause();
2187        if (cause != null)
2188        {
2189          buffer.append(" caused by ");
2190          buffer.append(getExceptionMessage(cause));
2191        }
2192      }
2193    }
2194
2195    final String ldapSDKVersionString = ", ldapSDKVersion=" +
2196         Version.NUMERIC_VERSION_STRING + ", revision=" + Version.REVISION_ID;
2197    if (buffer.indexOf(ldapSDKVersionString) < 0)
2198    {
2199      buffer.append(ldapSDKVersionString);
2200    }
2201
2202    return buffer.toString();
2203  }
2204
2205
2206
2207  /**
2208   * Retrieves the unqualified name (i.e., the name without package information)
2209   * for the provided class.
2210   *
2211   * @param  c  The class for which to retrieve the unqualified name.
2212   *
2213   * @return  The unqualified name for the provided class.
2214   */
2215  @NotNull()
2216  public static String getUnqualifiedClassName(@NotNull final Class<?> c)
2217  {
2218    final String className     = c.getName();
2219    final int    lastPeriodPos = className.lastIndexOf('.');
2220
2221    if (lastPeriodPos > 0)
2222    {
2223      return className.substring(lastPeriodPos+1);
2224    }
2225    else
2226    {
2227      return className;
2228    }
2229  }
2230
2231
2232
2233  /**
2234   * Retrieves a {@code TimeZone} object that represents the UTC (universal
2235   * coordinated time) time zone.
2236   *
2237   * @return  A {@code TimeZone} object that represents the UTC time zone.
2238   */
2239  @NotNull()
2240  public static TimeZone getUTCTimeZone()
2241  {
2242    return UTC_TIME_ZONE;
2243  }
2244
2245
2246
2247  /**
2248   * Encodes the provided timestamp in generalized time format.
2249   *
2250   * @param  timestamp  The timestamp to be encoded in generalized time format.
2251   *                    It should use the same format as the
2252   *                    {@code System.currentTimeMillis()} method (i.e., the
2253   *                    number of milliseconds since 12:00am UTC on January 1,
2254   *                    1970).
2255   *
2256   * @return  The generalized time representation of the provided date.
2257   */
2258  @NotNull()
2259  public static String encodeGeneralizedTime(final long timestamp)
2260  {
2261    return encodeGeneralizedTime(new Date(timestamp));
2262  }
2263
2264
2265
2266  /**
2267   * Encodes the provided date in generalized time format.
2268   *
2269   * @param  d  The date to be encoded in generalized time format.
2270   *
2271   * @return  The generalized time representation of the provided date.
2272   */
2273  @NotNull()
2274  public static String encodeGeneralizedTime(@NotNull final Date d)
2275  {
2276    SimpleDateFormat dateFormat = GENERALIZED_TIME_FORMATTERS.get();
2277    if (dateFormat == null)
2278    {
2279      dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'");
2280      dateFormat.setTimeZone(UTC_TIME_ZONE);
2281      GENERALIZED_TIME_FORMATTERS.set(dateFormat);
2282    }
2283
2284    return dateFormat.format(d);
2285  }
2286
2287
2288
2289  /**
2290   * Decodes the provided string as a timestamp in generalized time format.
2291   *
2292   * @param  t  The timestamp to be decoded.  It must not be {@code null}.
2293   *
2294   * @return  The {@code Date} object decoded from the provided timestamp.
2295   *
2296   * @throws  ParseException  If the provided string could not be decoded as a
2297   *                          timestamp in generalized time format.
2298   */
2299  @NotNull()
2300  public static Date decodeGeneralizedTime(@NotNull final String t)
2301         throws ParseException
2302  {
2303    Validator.ensureNotNull(t);
2304
2305    // Extract the time zone information from the end of the value.
2306    int tzPos;
2307    final TimeZone tz;
2308    if (t.endsWith("Z"))
2309    {
2310      tz = TimeZone.getTimeZone("UTC");
2311      tzPos = t.length() - 1;
2312    }
2313    else
2314    {
2315      tzPos = t.lastIndexOf('-');
2316      if (tzPos < 0)
2317      {
2318        tzPos = t.lastIndexOf('+');
2319        if (tzPos < 0)
2320        {
2321          throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
2322                                   0);
2323        }
2324      }
2325
2326      tz = TimeZone.getTimeZone("GMT" + t.substring(tzPos));
2327      if (tz.getRawOffset() == 0)
2328      {
2329        // This is the default time zone that will be returned if the value
2330        // cannot be parsed.  If it's valid, then it will end in "+0000" or
2331        // "-0000".  Otherwise, it's invalid and GMT was just a fallback.
2332        if (! (t.endsWith("+0000") || t.endsWith("-0000")))
2333        {
2334          throw new ParseException(ERR_GENTIME_DECODE_CANNOT_PARSE_TZ.get(t),
2335                                   tzPos);
2336        }
2337      }
2338    }
2339
2340
2341    // See if the timestamp has a sub-second portion.  Note that if there is a
2342    // sub-second portion, then we may need to massage the value so that there
2343    // are exactly three sub-second characters so that it can be interpreted as
2344    // milliseconds.
2345    final String subSecFormatStr;
2346    final String trimmedTimestamp;
2347    int periodPos = t.lastIndexOf('.', tzPos);
2348    if (periodPos > 0)
2349    {
2350      final int subSecondLength = tzPos - periodPos - 1;
2351      switch (subSecondLength)
2352      {
2353        case 0:
2354          subSecFormatStr  = "";
2355          trimmedTimestamp = t.substring(0, periodPos);
2356          break;
2357        case 1:
2358          subSecFormatStr  = ".SSS";
2359          trimmedTimestamp = t.substring(0, (periodPos+2)) + "00";
2360          break;
2361        case 2:
2362          subSecFormatStr  = ".SSS";
2363          trimmedTimestamp = t.substring(0, (periodPos+3)) + '0';
2364          break;
2365        default:
2366          subSecFormatStr  = ".SSS";
2367          trimmedTimestamp = t.substring(0, periodPos+4);
2368          break;
2369      }
2370    }
2371    else
2372    {
2373      subSecFormatStr  = "";
2374      periodPos        = tzPos;
2375      trimmedTimestamp = t.substring(0, tzPos);
2376    }
2377
2378
2379    // Look at where the period is (or would be if it existed) to see how many
2380    // characters are in the integer portion.  This will give us what we need
2381    // for the rest of the format string.
2382    final String formatStr;
2383    switch (periodPos)
2384    {
2385      case 10:
2386        formatStr = "yyyyMMddHH" + subSecFormatStr;
2387        break;
2388      case 12:
2389        formatStr = "yyyyMMddHHmm" + subSecFormatStr;
2390        break;
2391      case 14:
2392        formatStr = "yyyyMMddHHmmss" + subSecFormatStr;
2393        break;
2394      default:
2395        throw new ParseException(ERR_GENTIME_CANNOT_PARSE_INVALID_LENGTH.get(t),
2396                                 periodPos);
2397    }
2398
2399
2400    // We should finally be able to create an appropriate date format object
2401    // to parse the trimmed version of the timestamp.
2402    final SimpleDateFormat dateFormat = new SimpleDateFormat(formatStr);
2403    dateFormat.setTimeZone(tz);
2404    dateFormat.setLenient(false);
2405    return dateFormat.parse(trimmedTimestamp);
2406  }
2407
2408
2409
2410  /**
2411   * Encodes the provided timestamp to the ISO 8601 format described in RFC
2412   * 3339.
2413   *
2414   * @param  timestamp  The timestamp to be encoded in the RFC 3339 format.
2415   *                    It should use the same format as the
2416   *                    {@code System.currentTimeMillis()} method (i.e., the
2417   *                    number of milliseconds since 12:00am UTC on January 1,
2418   *                    1970).
2419   *
2420   * @return  The RFC 3339 representation of the provided date.
2421   */
2422  @NotNull()
2423  public static String encodeRFC3339Time(final long timestamp)
2424  {
2425    return encodeRFC3339Time(new Date(timestamp));
2426  }
2427
2428
2429
2430  /**
2431   * Encodes the provided timestamp to the ISO 8601 format described in RFC
2432   * 3339.
2433   *
2434   * @param  d  The date to be encoded in the RFC 3339 format.
2435   *
2436   * @return  The RFC 3339 representation of the provided date.
2437   */
2438  @NotNull()
2439  public static String encodeRFC3339Time(@NotNull final Date d)
2440  {
2441    SimpleDateFormat dateFormat = RFC_3339_TIME_FORMATTERS.get();
2442    if (dateFormat == null)
2443    {
2444      dateFormat = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'");
2445      dateFormat.setTimeZone(UTC_TIME_ZONE);
2446      RFC_3339_TIME_FORMATTERS.set(dateFormat);
2447    }
2448
2449    return dateFormat.format(d);
2450  }
2451
2452
2453
2454  /**
2455   * Decodes the provided string as a timestamp encoded in the ISO 8601 format
2456   * described in RFC 3339.
2457   *
2458   * @param  timestamp  The timestamp to be decoded in the RFC 3339 format.
2459   *
2460   * @return  The {@code Date} object decoded from the provided timestamp.
2461   *
2462   * @throws  ParseException  If the provided string could not be decoded as a
2463   *                          timestamp in the RFC 3339 time format.
2464   */
2465  @NotNull()
2466  public static Date decodeRFC3339Time(@NotNull final String timestamp)
2467         throws ParseException
2468  {
2469    // Make sure that the string representation has the minimum acceptable
2470    // length.
2471    if (timestamp.length() < 20)
2472    {
2473      throw new ParseException(ERR_RFC_3339_TIME_TOO_SHORT.get(timestamp), 0);
2474    }
2475
2476
2477    // Parse the year, month, day, hour, minute, and second components from the
2478    // timestamp, and make sure the appropriate separator characters are between
2479    // those components.
2480    final int year = parseRFC3339Number(timestamp, 0, 4);
2481    validateRFC3339TimestampSeparatorCharacter(timestamp, 4, '-');
2482    final int month = parseRFC3339Number(timestamp, 5, 2);
2483    validateRFC3339TimestampSeparatorCharacter(timestamp, 7, '-');
2484    final int day = parseRFC3339Number(timestamp, 8, 2);
2485    validateRFC3339TimestampSeparatorCharacter(timestamp, 10, 'T');
2486    final int hour = parseRFC3339Number(timestamp, 11, 2);
2487    validateRFC3339TimestampSeparatorCharacter(timestamp, 13, ':');
2488    final int minute = parseRFC3339Number(timestamp, 14, 2);
2489    validateRFC3339TimestampSeparatorCharacter(timestamp, 16, ':');
2490    final int second = parseRFC3339Number(timestamp, 17, 2);
2491
2492
2493    // Make sure that the month and day values are acceptable.
2494    switch (month)
2495    {
2496      case 1:
2497      case 3:
2498      case 5:
2499      case 7:
2500      case 8:
2501      case 10:
2502      case 12:
2503        // January, March, May, July, August, October, and December all have 31
2504        // days.
2505        if ((day < 1) || (day > 31))
2506        {
2507          throw new ParseException(
2508               ERR_RFC_3339_TIME_INVALID_DAY_FOR_MONTH.get(timestamp, day,
2509                    month),
2510               8);
2511        }
2512        break;
2513
2514      case 4:
2515      case 6:
2516      case 9:
2517      case 11:
2518        // April, June, September, and November all have 30 days.
2519        if ((day < 1) || (day > 30))
2520        {
2521          throw new ParseException(
2522               ERR_RFC_3339_TIME_INVALID_DAY_FOR_MONTH.get(timestamp, day,
2523                    month),
2524               8);
2525        }
2526        break;
2527
2528      case 2:
2529        // February can have 28 or 29 days, depending on whether it's a leap
2530        // year.  Although we could determine whether the provided year is a
2531        // leap year, we'll just always accept up to 29 days for February.
2532        if ((day < 1) || (day > 29))
2533        {
2534          throw new ParseException(
2535               ERR_RFC_3339_TIME_INVALID_DAY_FOR_MONTH.get(timestamp, day,
2536                    month),
2537               8);
2538        }
2539        break;
2540
2541      default:
2542        throw new ParseException(
2543             ERR_RFC_3339_TIME_INVALID_MONTH.get(timestamp, month), 5);
2544    }
2545
2546
2547    // Make sure that the hour, minute, and second values are acceptable.  Note
2548    // that while ISO 8601 permits a value of 24 for the hour, RFC 3339 only
2549    // permits hour values between 0 and 23.  Also note that some minutes can
2550    // have up to 61 seconds for leap seconds, so we'll always account for that.
2551    if ((hour < 0) || (hour > 23))
2552    {
2553      throw new ParseException(
2554           ERR_RFC_3339_TIME_INVALID_HOUR.get(timestamp, hour), 11);
2555    }
2556
2557    if ((minute < 0) || (minute > 59))
2558    {
2559      throw new ParseException(
2560           ERR_RFC_3339_TIME_INVALID_MINUTE.get(timestamp, minute), 14);
2561    }
2562
2563    if ((second < 0) || (second > 60))
2564    {
2565      throw new ParseException(
2566           ERR_RFC_3339_TIME_INVALID_SECOND.get(timestamp, second), 17);
2567    }
2568
2569
2570    // See if there is a sub-second portion.  If so, then there will be a
2571    // period at position 19 followed by at least one digit.  This
2572    // implementation will only support timestamps with no more than three
2573    // sub-second digits.
2574    int milliseconds = 0;
2575    int timeZoneStartPos = -1;
2576    if (timestamp.charAt(19) == '.')
2577    {
2578      int numDigits = 0;
2579      final StringBuilder subSecondString = new StringBuilder(3);
2580      for (int pos=20; pos < timestamp.length(); pos++)
2581      {
2582        final char c = timestamp.charAt(pos);
2583        switch (c)
2584        {
2585          case '0':
2586            numDigits++;
2587            if (subSecondString.length() > 0)
2588            {
2589              // Only add a zero if it's not the first digit.
2590              subSecondString.append(c);
2591            }
2592            break;
2593          case '1':
2594          case '2':
2595          case '3':
2596          case '4':
2597          case '5':
2598          case '6':
2599          case '7':
2600          case '8':
2601          case '9':
2602            numDigits++;
2603            subSecondString.append(c);
2604            break;
2605          case 'Z':
2606          case '+':
2607          case '-':
2608            timeZoneStartPos = pos;
2609            break;
2610          default:
2611            throw new ParseException(
2612                 ERR_RFC_3339_TIME_INVALID_SUB_SECOND_CHAR.get(timestamp, c,
2613                      pos),
2614                 pos);
2615        }
2616
2617        if (timeZoneStartPos > 0)
2618        {
2619          break;
2620        }
2621
2622        if (numDigits > 3)
2623        {
2624          throw new ParseException(
2625               ERR_RFC_3339_TIME_TOO_MANY_SUB_SECOND_DIGITS.get(timestamp),
2626               20);
2627        }
2628      }
2629
2630      if (timeZoneStartPos < 0)
2631      {
2632        throw new ParseException(
2633             ERR_RFC_3339_TIME_MISSING_TIME_ZONE_AFTER_SUB_SECOND.get(
2634                  timestamp),
2635             (timestamp.length() - 1));
2636      }
2637
2638      if (numDigits == 0)
2639      {
2640        throw new ParseException(
2641             ERR_RFC_3339_TIME_NO_SUB_SECOND_DIGITS.get(timestamp), 19);
2642      }
2643
2644      if (subSecondString.length() == 0)
2645      {
2646        // This is possible if the sub-second portion is all zeroes.
2647        subSecondString.append('0');
2648      }
2649
2650      milliseconds = Integer.parseInt(subSecondString.toString());
2651      if (numDigits == 1)
2652      {
2653        milliseconds *= 100;
2654      }
2655      else if (numDigits == 2)
2656      {
2657        milliseconds *= 10;
2658      }
2659    }
2660    else
2661    {
2662      timeZoneStartPos = 19;
2663    }
2664
2665
2666    // The remainder of the timestamp should be the time zone.
2667    final TimeZone timeZone;
2668    if (timestamp.substring(timeZoneStartPos).equals("Z"))
2669    {
2670      // This is shorthand for the UTC time zone.
2671      timeZone = UTC_TIME_ZONE;
2672    }
2673    else
2674    {
2675      // This is an offset from UTC, which should be in the form "+HH:MM" or
2676      // "-HH:MM".  Make sure it has the expected length.
2677      if ((timestamp.length() - timeZoneStartPos) != 6)
2678      {
2679        throw new ParseException(
2680             ERR_RFC_3339_TIME_INVALID_TZ.get(timestamp), timeZoneStartPos);
2681      }
2682
2683      // Make sure it starts with "+" or "-".
2684      final int firstChar = timestamp.charAt(timeZoneStartPos);
2685      if ((firstChar != '+') && (firstChar != '-'))
2686      {
2687        throw new ParseException(
2688             ERR_RFC_3339_TIME_INVALID_TZ.get(timestamp), timeZoneStartPos);
2689      }
2690
2691
2692      // Make sure the hour offset is valid.
2693      final int timeZoneHourOffset =
2694           parseRFC3339Number(timestamp, (timeZoneStartPos+1), 2);
2695      if ((timeZoneHourOffset < 0) || (timeZoneHourOffset > 23))
2696      {
2697        throw new ParseException(
2698             ERR_RFC_3339_TIME_INVALID_TZ.get(timestamp), timeZoneStartPos);
2699      }
2700
2701
2702      // Make sure there is a colon between the hour and the minute portions of
2703      // the offset.
2704      if (timestamp.charAt(timeZoneStartPos+3) != ':')
2705      {
2706        throw new ParseException(
2707             ERR_RFC_3339_TIME_INVALID_TZ.get(timestamp), timeZoneStartPos);
2708      }
2709
2710      final int timeZoneMinuteOffset =
2711           parseRFC3339Number(timestamp, (timeZoneStartPos+4), 2);
2712      if ((timeZoneMinuteOffset < 0) || (timeZoneMinuteOffset > 59))
2713      {
2714        throw new ParseException(
2715             ERR_RFC_3339_TIME_INVALID_TZ.get(timestamp), timeZoneStartPos);
2716      }
2717
2718      timeZone = TimeZone.getTimeZone(
2719           "GMT" + timestamp.substring(timeZoneStartPos));
2720    }
2721
2722
2723    // Put everything together to construct the appropriate date.
2724    final GregorianCalendar calendar =
2725         new GregorianCalendar(year,
2726              (month-1), // NOTE:  Calendar stupidly uses zero-indexed months.
2727              day, hour, minute, second);
2728    calendar.set(GregorianCalendar.MILLISECOND, milliseconds);
2729    calendar.setTimeZone(timeZone);
2730    return calendar.getTime();
2731  }
2732
2733
2734
2735  /**
2736   * Ensures that the provided timestamp string has the expected character at
2737   * the specified position.
2738   *
2739   * @param  timestamp     The timestamp to examine.
2740   *                       It must not be {@code null}.
2741   * @param  pos           The position of the character to examine.
2742   * @param  expectedChar  The character expected at the specified position.
2743   *
2744   * @throws  ParseException  If the provided timestamp does not have the
2745   * expected
2746   */
2747  private static void validateRFC3339TimestampSeparatorCharacter(
2748                           @NotNull final String timestamp, final int pos,
2749                           final char expectedChar)
2750          throws ParseException
2751  {
2752    if (timestamp.charAt(pos) != expectedChar)
2753    {
2754      throw new ParseException(
2755           ERR_RFC_3339_INVALID_SEPARATOR.get(timestamp, timestamp.charAt(pos),
2756                pos, expectedChar),
2757           pos);
2758    }
2759  }
2760
2761
2762
2763  /**
2764   * Parses the number at the specified location in the timestamp.
2765   *
2766   * @param  timestamp  The timestamp to examine.  It must not be {@code null}.
2767   * @param  pos        The position at which to begin parsing the number.
2768   * @param  numDigits  The number of digits in the number.
2769   *
2770   * @return  The number parsed from the provided timestamp.
2771   *
2772   * @throws  ParseException  If a problem is encountered while trying to parse
2773   *                          the number from the timestamp.
2774   */
2775  private static int parseRFC3339Number(@NotNull final String timestamp,
2776                                        final int pos, final int numDigits)
2777          throws ParseException
2778  {
2779    int value = 0;
2780    for (int i=0; i < numDigits; i++)
2781    {
2782      value *= 10;
2783      switch (timestamp.charAt(pos+i))
2784      {
2785        case '0':
2786          break;
2787        case '1':
2788          value += 1;
2789          break;
2790        case '2':
2791          value += 2;
2792          break;
2793        case '3':
2794          value += 3;
2795          break;
2796        case '4':
2797          value += 4;
2798          break;
2799        case '5':
2800          value += 5;
2801          break;
2802        case '6':
2803          value += 6;
2804          break;
2805        case '7':
2806          value += 7;
2807          break;
2808        case '8':
2809          value += 8;
2810          break;
2811        case '9':
2812          value += 9;
2813          break;
2814        default:
2815          throw new ParseException(
2816               ERR_RFC_3339_INVALID_DIGIT.get(timestamp,
2817                    timestamp.charAt(pos+i), (pos+i)),
2818               (pos+i));
2819      }
2820    }
2821
2822    return value;
2823  }
2824
2825
2826
2827  /**
2828   * Trims only leading spaces from the provided string, leaving any trailing
2829   * spaces intact.
2830   *
2831   * @param  s  The string to be processed.  It must not be {@code null}.
2832   *
2833   * @return  The original string if no trimming was required, or a new string
2834   *          without leading spaces if the provided string had one or more.  It
2835   *          may be an empty string if the provided string was an empty string
2836   *          or contained only spaces.
2837   */
2838  @NotNull()
2839  public static String trimLeading(@NotNull final String s)
2840  {
2841    Validator.ensureNotNull(s);
2842
2843    int nonSpacePos = 0;
2844    final int length = s.length();
2845    while ((nonSpacePos < length) && (s.charAt(nonSpacePos) == ' '))
2846    {
2847      nonSpacePos++;
2848    }
2849
2850    if (nonSpacePos == 0)
2851    {
2852      // There were no leading spaces.
2853      return s;
2854    }
2855    else if (nonSpacePos >= length)
2856    {
2857      // There were no non-space characters.
2858      return "";
2859    }
2860    else
2861    {
2862      // There were leading spaces, so return the string without them.
2863      return s.substring(nonSpacePos, length);
2864    }
2865  }
2866
2867
2868
2869  /**
2870   * Trims only trailing spaces from the provided string, leaving any leading
2871   * spaces intact.
2872   *
2873   * @param  s  The string to be processed.  It must not be {@code null}.
2874   *
2875   * @return  The original string if no trimming was required, or a new string
2876   *          without trailing spaces if the provided string had one or more.
2877   *          It may be an empty string if the provided string was an empty
2878   *          string or contained only spaces.
2879   */
2880  @NotNull()
2881  public static String trimTrailing(@NotNull final String s)
2882  {
2883    Validator.ensureNotNull(s);
2884
2885    final int lastPos = s.length() - 1;
2886    int nonSpacePos = lastPos;
2887    while ((nonSpacePos >= 0) && (s.charAt(nonSpacePos) == ' '))
2888    {
2889      nonSpacePos--;
2890    }
2891
2892    if (nonSpacePos < 0)
2893    {
2894      // There were no non-space characters.
2895      return "";
2896    }
2897    else if (nonSpacePos == lastPos)
2898    {
2899      // There were no trailing spaces.
2900      return s;
2901    }
2902    else
2903    {
2904      // There were trailing spaces, so return the string without them.
2905      return s.substring(0, (nonSpacePos+1));
2906    }
2907  }
2908
2909
2910
2911  /**
2912   * Wraps the contents of the specified line using the given width.  It will
2913   * attempt to wrap at spaces to preserve words, but if that is not possible
2914   * (because a single "word" is longer than the maximum width), then it will
2915   * wrap in the middle of the word at the specified maximum width.
2916   *
2917   * @param  line      The line to be wrapped.  It must not be {@code null}.
2918   * @param  maxWidth  The maximum width for lines in the resulting list.  A
2919   *                   value less than or equal to zero will cause no wrapping
2920   *                   to be performed.
2921   *
2922   * @return  A list of the wrapped lines.  It may be empty if the provided line
2923   *          contained only spaces.
2924   */
2925  @NotNull()
2926  public static List<String> wrapLine(@NotNull final String line,
2927                                      final int maxWidth)
2928  {
2929    return wrapLine(line, maxWidth, maxWidth);
2930  }
2931
2932
2933
2934  /**
2935   * Wraps the contents of the specified line using the given width.  It will
2936   * attempt to wrap at spaces to preserve words, but if that is not possible
2937   * (because a single "word" is longer than the maximum width), then it will
2938   * wrap in the middle of the word at the specified maximum width.
2939   *
2940   * @param  line                    The line to be wrapped.  It must not be
2941   *                                 {@code null}.
2942   * @param  maxFirstLineWidth       The maximum length for the first line in
2943   *                                 the resulting list.  A value less than or
2944   *                                 equal to zero will cause no wrapping to be
2945   *                                 performed.
2946   * @param  maxSubsequentLineWidth  The maximum length for all lines except the
2947   *                                 first line.  This must be greater than zero
2948   *                                 unless {@code maxFirstLineWidth} is less
2949   *                                 than or equal to zero.
2950   *
2951   * @return  A list of the wrapped lines.  It may be empty if the provided line
2952   *          contained only spaces.
2953   */
2954  @NotNull()
2955  public static List<String> wrapLine(@NotNull final String line,
2956                                      final int maxFirstLineWidth,
2957                                      final int maxSubsequentLineWidth)
2958  {
2959    if (maxFirstLineWidth > 0)
2960    {
2961      Validator.ensureTrue(maxSubsequentLineWidth > 0);
2962    }
2963
2964    // See if the provided string already contains line breaks.  If so, then
2965    // treat it as multiple lines rather than a single line.
2966    final int breakPos = line.indexOf('\n');
2967    if (breakPos >= 0)
2968    {
2969      final ArrayList<String> lineList = new ArrayList<>(10);
2970      final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
2971      while (tokenizer.hasMoreTokens())
2972      {
2973        lineList.addAll(wrapLine(tokenizer.nextToken(), maxFirstLineWidth,
2974             maxSubsequentLineWidth));
2975      }
2976
2977      return lineList;
2978    }
2979
2980    final int length = line.length();
2981    if ((maxFirstLineWidth <= 0) || (length < maxFirstLineWidth))
2982    {
2983      return Collections.singletonList(line);
2984    }
2985
2986
2987    int wrapPos = maxFirstLineWidth;
2988    int lastWrapPos = 0;
2989    final ArrayList<String> lineList = new ArrayList<>(5);
2990    while (true)
2991    {
2992      final int spacePos = line.lastIndexOf(' ', wrapPos);
2993      if (spacePos > lastWrapPos)
2994      {
2995        // We found a space in an acceptable location, so use it after trimming
2996        // any trailing spaces.
2997        final String s = trimTrailing(line.substring(lastWrapPos, spacePos));
2998
2999        // Don't bother adding the line if it contained only spaces.
3000        if (! s.isEmpty())
3001        {
3002          lineList.add(s);
3003        }
3004
3005        wrapPos = spacePos;
3006      }
3007      else
3008      {
3009        // We didn't find any spaces, so we'll have to insert a hard break at
3010        // the specified wrap column.
3011        lineList.add(line.substring(lastWrapPos, wrapPos));
3012      }
3013
3014      // Skip over any spaces before the next non-space character.
3015      while ((wrapPos < length) && (line.charAt(wrapPos) == ' '))
3016      {
3017        wrapPos++;
3018      }
3019
3020      lastWrapPos = wrapPos;
3021      wrapPos += maxSubsequentLineWidth;
3022      if (wrapPos >= length)
3023      {
3024        // The last fragment can fit on the line, so we can handle that now and
3025        // break.
3026        if (lastWrapPos >= length)
3027        {
3028          break;
3029        }
3030        else
3031        {
3032          final String s = line.substring(lastWrapPos);
3033          lineList.add(s);
3034          break;
3035        }
3036      }
3037    }
3038
3039    return lineList;
3040  }
3041
3042
3043
3044  /**
3045   * This method returns a form of the provided argument that is safe to
3046   * use on the command line for the local platform. This method is provided as
3047   * a convenience wrapper around {@link ExampleCommandLineArgument}.  Calling
3048   * this method is equivalent to:
3049   *
3050   * <PRE>
3051   *  return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
3052   * </PRE>
3053   *
3054   * For getting direct access to command line arguments that are safe to
3055   * use on other platforms, call
3056   * {@link ExampleCommandLineArgument#getCleanArgument}.
3057   *
3058   * @param  s  The string to be processed.  It must not be {@code null}.
3059   *
3060   * @return  A cleaned version of the provided string in a form that will allow
3061   *          it to be displayed as the value of a command-line argument on.
3062   */
3063  @NotNull()
3064  public static String cleanExampleCommandLineArgument(@NotNull final String s)
3065  {
3066    return ExampleCommandLineArgument.getCleanArgument(s).getLocalForm();
3067  }
3068
3069
3070
3071  /**
3072   * Retrieves a single string which is a concatenation of all of the provided
3073   * strings.
3074   *
3075   * @param  a  The array of strings to concatenate.  It must not be
3076   *            {@code null} but may be empty.
3077   *
3078   * @return  A string containing a concatenation of all of the strings in the
3079   *          provided array.
3080   */
3081  @NotNull()
3082  public static String concatenateStrings(@NotNull final String... a)
3083  {
3084    return concatenateStrings(null, null, "  ", null, null, a);
3085  }
3086
3087
3088
3089  /**
3090   * Retrieves a single string which is a concatenation of all of the provided
3091   * strings.
3092   *
3093   * @param  l  The list of strings to concatenate.  It must not be
3094   *            {@code null} but may be empty.
3095   *
3096   * @return  A string containing a concatenation of all of the strings in the
3097   *          provided list.
3098   */
3099  @NotNull()
3100  public static String concatenateStrings(@NotNull final List<String> l)
3101  {
3102    return concatenateStrings(null, null, "  ", null, null, l);
3103  }
3104
3105
3106
3107  /**
3108   * Retrieves a single string which is a concatenation of all of the provided
3109   * strings.
3110   *
3111   * @param  beforeList       A string that should be placed at the beginning of
3112   *                          the list.  It may be {@code null} or empty if
3113   *                          nothing should be placed at the beginning of the
3114   *                          list.
3115   * @param  beforeElement    A string that should be placed before each element
3116   *                          in the list.  It may be {@code null} or empty if
3117   *                          nothing should be placed before each element.
3118   * @param  betweenElements  The separator that should be placed between
3119   *                          elements in the list.  It may be {@code null} or
3120   *                          empty if no separator should be placed between
3121   *                          elements.
3122   * @param  afterElement     A string that should be placed after each element
3123   *                          in the list.  It may be {@code null} or empty if
3124   *                          nothing should be placed after each element.
3125   * @param  afterList        A string that should be placed at the end of the
3126   *                          list.  It may be {@code null} or empty if nothing
3127   *                          should be placed at the end of the list.
3128   * @param  a                The array of strings to concatenate.  It must not
3129   *                          be {@code null} but may be empty.
3130   *
3131   * @return  A string containing a concatenation of all of the strings in the
3132   *          provided list.
3133   */
3134  @NotNull()
3135  public static String concatenateStrings(@Nullable final String beforeList,
3136                            @Nullable final String beforeElement,
3137                            @Nullable final String betweenElements,
3138                            @Nullable final String afterElement,
3139                            @Nullable final String afterList,
3140                            @NotNull final String... a)
3141  {
3142    return concatenateStrings(beforeList, beforeElement, betweenElements,
3143         afterElement, afterList, Arrays.asList(a));
3144  }
3145
3146
3147
3148  /**
3149   * Retrieves a single string which is a concatenation of all of the provided
3150   * strings.
3151   *
3152   * @param  beforeList       A string that should be placed at the beginning of
3153   *                          the list.  It may be {@code null} or empty if
3154   *                          nothing should be placed at the beginning of the
3155   *                          list.
3156   * @param  beforeElement    A string that should be placed before each element
3157   *                          in the list.  It may be {@code null} or empty if
3158   *                          nothing should be placed before each element.
3159   * @param  betweenElements  The separator that should be placed between
3160   *                          elements in the list.  It may be {@code null} or
3161   *                          empty if no separator should be placed between
3162   *                          elements.
3163   * @param  afterElement     A string that should be placed after each element
3164   *                          in the list.  It may be {@code null} or empty if
3165   *                          nothing should be placed after each element.
3166   * @param  afterList        A string that should be placed at the end of the
3167   *                          list.  It may be {@code null} or empty if nothing
3168   *                          should be placed at the end of the list.
3169   * @param  l                The list of strings to concatenate.  It must not
3170   *                          be {@code null} but may be empty.
3171   *
3172   * @return  A string containing a concatenation of all of the strings in the
3173   *          provided list.
3174   */
3175  @NotNull()
3176  public static String concatenateStrings(@Nullable final String beforeList,
3177                            @Nullable final String beforeElement,
3178                            @Nullable final String betweenElements,
3179                            @Nullable final String afterElement,
3180                            @Nullable final String afterList,
3181                            @NotNull final List<String> l)
3182  {
3183    Validator.ensureNotNull(l);
3184
3185    final StringBuilder buffer = new StringBuilder();
3186
3187    if (beforeList != null)
3188    {
3189      buffer.append(beforeList);
3190    }
3191
3192    final Iterator<String> iterator = l.iterator();
3193    while (iterator.hasNext())
3194    {
3195      if (beforeElement != null)
3196      {
3197        buffer.append(beforeElement);
3198      }
3199
3200      buffer.append(iterator.next());
3201
3202      if (afterElement != null)
3203      {
3204        buffer.append(afterElement);
3205      }
3206
3207      if ((betweenElements != null) && iterator.hasNext())
3208      {
3209        buffer.append(betweenElements);
3210      }
3211    }
3212
3213    if (afterList != null)
3214    {
3215      buffer.append(afterList);
3216    }
3217
3218    return buffer.toString();
3219  }
3220
3221
3222
3223  /**
3224   * Converts a duration in seconds to a string with a human-readable duration
3225   * which may include days, hours, minutes, and seconds, to the extent that
3226   * they are needed.
3227   *
3228   * @param  s  The number of seconds to be represented.
3229   *
3230   * @return  A string containing a human-readable representation of the
3231   *          provided time.
3232   */
3233  @NotNull()
3234  public static String secondsToHumanReadableDuration(final long s)
3235  {
3236    return millisToHumanReadableDuration(s * 1000L);
3237  }
3238
3239
3240
3241  /**
3242   * Converts a duration in seconds to a string with a human-readable duration
3243   * which may include days, hours, minutes, and seconds, to the extent that
3244   * they are needed.
3245   *
3246   * @param  m  The number of milliseconds to be represented.
3247   *
3248   * @return  A string containing a human-readable representation of the
3249   *          provided time.
3250   */
3251  @NotNull()
3252  public static String millisToHumanReadableDuration(final long m)
3253  {
3254    final StringBuilder buffer = new StringBuilder();
3255    long numMillis = m;
3256
3257    final long numDays = numMillis / 86_400_000L;
3258    if (numDays > 0)
3259    {
3260      numMillis -= (numDays * 86_400_000L);
3261      if (numDays == 1)
3262      {
3263        buffer.append(INFO_NUM_DAYS_SINGULAR.get(numDays));
3264      }
3265      else
3266      {
3267        buffer.append(INFO_NUM_DAYS_PLURAL.get(numDays));
3268      }
3269    }
3270
3271    final long numHours = numMillis / 3_600_000L;
3272    if (numHours > 0)
3273    {
3274      numMillis -= (numHours * 3_600_000L);
3275      if (buffer.length() > 0)
3276      {
3277        buffer.append(", ");
3278      }
3279
3280      if (numHours == 1)
3281      {
3282        buffer.append(INFO_NUM_HOURS_SINGULAR.get(numHours));
3283      }
3284      else
3285      {
3286        buffer.append(INFO_NUM_HOURS_PLURAL.get(numHours));
3287      }
3288    }
3289
3290    final long numMinutes = numMillis / 60_000L;
3291    if (numMinutes > 0)
3292    {
3293      numMillis -= (numMinutes * 60_000L);
3294      if (buffer.length() > 0)
3295      {
3296        buffer.append(", ");
3297      }
3298
3299      if (numMinutes == 1)
3300      {
3301        buffer.append(INFO_NUM_MINUTES_SINGULAR.get(numMinutes));
3302      }
3303      else
3304      {
3305        buffer.append(INFO_NUM_MINUTES_PLURAL.get(numMinutes));
3306      }
3307    }
3308
3309    if (numMillis == 1000)
3310    {
3311      if (buffer.length() > 0)
3312      {
3313        buffer.append(", ");
3314      }
3315
3316      buffer.append(INFO_NUM_SECONDS_SINGULAR.get(1));
3317    }
3318    else if ((numMillis > 0) || (buffer.length() == 0))
3319    {
3320      if (buffer.length() > 0)
3321      {
3322        buffer.append(", ");
3323      }
3324
3325      final long numSeconds = numMillis / 1000L;
3326      numMillis -= (numSeconds * 1000L);
3327      if ((numMillis % 1000L) != 0L)
3328      {
3329        final double numSecondsDouble = numSeconds + (numMillis / 1000.0);
3330        final DecimalFormat decimalFormat = new DecimalFormat("0.000");
3331        buffer.append(INFO_NUM_SECONDS_WITH_DECIMAL.get(
3332             decimalFormat.format(numSecondsDouble)));
3333      }
3334      else
3335      {
3336        buffer.append(INFO_NUM_SECONDS_PLURAL.get(numSeconds));
3337      }
3338    }
3339
3340    return buffer.toString();
3341  }
3342
3343
3344
3345  /**
3346   * Converts the provided number of nanoseconds to milliseconds.
3347   *
3348   * @param  nanos  The number of nanoseconds to convert to milliseconds.
3349   *
3350   * @return  The number of milliseconds that most closely corresponds to the
3351   *          specified number of nanoseconds.
3352   */
3353  public static long nanosToMillis(final long nanos)
3354  {
3355    return Math.max(0L, Math.round(nanos / 1_000_000.0d));
3356  }
3357
3358
3359
3360  /**
3361   * Converts the provided number of milliseconds to nanoseconds.
3362   *
3363   * @param  millis  The number of milliseconds to convert to nanoseconds.
3364   *
3365   * @return  The number of nanoseconds that most closely corresponds to the
3366   *          specified number of milliseconds.
3367   */
3368  public static long millisToNanos(final long millis)
3369  {
3370    return Math.max(0L, (millis * 1_000_000L));
3371  }
3372
3373
3374
3375  /**
3376   * Indicates whether the provided string is a valid numeric OID.  A numeric
3377   * OID must start and end with a digit, must have at least on period, must
3378   * contain only digits and periods, and must not have two consecutive periods.
3379   *
3380   * @param  s  The string to examine.  It must not be {@code null}.
3381   *
3382   * @return  {@code true} if the provided string is a valid numeric OID, or
3383   *          {@code false} if not.
3384   */
3385  public static boolean isNumericOID(@NotNull final String s)
3386  {
3387    boolean digitRequired = true;
3388    boolean periodFound   = false;
3389    for (final char c : s.toCharArray())
3390    {
3391      switch (c)
3392      {
3393        case '0':
3394        case '1':
3395        case '2':
3396        case '3':
3397        case '4':
3398        case '5':
3399        case '6':
3400        case '7':
3401        case '8':
3402        case '9':
3403          digitRequired = false;
3404          break;
3405
3406        case '.':
3407          if (digitRequired)
3408          {
3409            return false;
3410          }
3411          else
3412          {
3413            digitRequired = true;
3414          }
3415          periodFound = true;
3416          break;
3417
3418        default:
3419          return false;
3420      }
3421
3422    }
3423
3424    return (periodFound && (! digitRequired));
3425  }
3426
3427
3428
3429  /**
3430   * Capitalizes the provided string.  The first character will be converted to
3431   * uppercase, and the rest of the string will be left unaltered.
3432   *
3433   * @param  s  The string to be capitalized.
3434   *
3435   * @return  A capitalized version of the provided string, or {@code null} if
3436   *          the provided string was {@code null}.
3437   */
3438  @Nullable()
3439  public static String capitalize(@Nullable final String s)
3440  {
3441    return capitalize(s, false);
3442  }
3443
3444
3445
3446  /**
3447   * Capitalizes the provided string.  The first character of the string (or
3448   * optionally the first character of each word in the string)
3449   *
3450   * @param  s         The string to be capitalized.
3451   * @param  allWords  Indicates whether to capitalize all words in the string,
3452   *                   or only the first word.
3453   *
3454   * @return  A capitalized version of the provided string, or {@code null} if
3455   *          the provided string was {@code null}.
3456   */
3457  @Nullable()
3458  public static String capitalize(@Nullable final String s,
3459                                  final boolean allWords)
3460  {
3461    if (s == null)
3462    {
3463      return null;
3464    }
3465
3466    switch (s.length())
3467    {
3468      case 0:
3469        return s;
3470
3471      case 1:
3472        return s.toUpperCase();
3473
3474      default:
3475        boolean capitalize = true;
3476        final char[] chars = s.toCharArray();
3477        final StringBuilder buffer = new StringBuilder(chars.length);
3478        for (final char c : chars)
3479        {
3480          // Whitespace and punctuation will be considered word breaks.
3481          if (Character.isWhitespace(c) ||
3482              (((c >= '!') && (c <= '.')) ||
3483               ((c >= ':') && (c <= '@')) ||
3484               ((c >= '[') && (c <= '`')) ||
3485               ((c >= '{') && (c <= '~'))))
3486          {
3487            buffer.append(c);
3488            capitalize |= allWords;
3489          }
3490          else if (capitalize)
3491          {
3492            buffer.append(Character.toUpperCase(c));
3493            capitalize = false;
3494          }
3495          else
3496          {
3497            buffer.append(c);
3498          }
3499        }
3500        return buffer.toString();
3501    }
3502  }
3503
3504
3505
3506  /**
3507   * Encodes the provided UUID to a byte array containing its 128-bit
3508   * representation.
3509   *
3510   * @param  uuid  The UUID to be encoded.  It must not be {@code null}.
3511   *
3512   * @return  The byte array containing the 128-bit encoded UUID.
3513   */
3514  @NotNull()
3515  public static byte[] encodeUUID(@NotNull final UUID uuid)
3516  {
3517    final byte[] b = new byte[16];
3518
3519    final long mostSignificantBits  = uuid.getMostSignificantBits();
3520    b[0]  = (byte) ((mostSignificantBits >> 56) & 0xFF);
3521    b[1]  = (byte) ((mostSignificantBits >> 48) & 0xFF);
3522    b[2]  = (byte) ((mostSignificantBits >> 40) & 0xFF);
3523    b[3]  = (byte) ((mostSignificantBits >> 32) & 0xFF);
3524    b[4]  = (byte) ((mostSignificantBits >> 24) & 0xFF);
3525    b[5]  = (byte) ((mostSignificantBits >> 16) & 0xFF);
3526    b[6]  = (byte) ((mostSignificantBits >> 8) & 0xFF);
3527    b[7]  = (byte) (mostSignificantBits & 0xFF);
3528
3529    final long leastSignificantBits = uuid.getLeastSignificantBits();
3530    b[8]  = (byte) ((leastSignificantBits >> 56) & 0xFF);
3531    b[9]  = (byte) ((leastSignificantBits >> 48) & 0xFF);
3532    b[10] = (byte) ((leastSignificantBits >> 40) & 0xFF);
3533    b[11] = (byte) ((leastSignificantBits >> 32) & 0xFF);
3534    b[12] = (byte) ((leastSignificantBits >> 24) & 0xFF);
3535    b[13] = (byte) ((leastSignificantBits >> 16) & 0xFF);
3536    b[14] = (byte) ((leastSignificantBits >> 8) & 0xFF);
3537    b[15] = (byte) (leastSignificantBits & 0xFF);
3538
3539    return b;
3540  }
3541
3542
3543
3544  /**
3545   * Decodes the value of the provided byte array as a Java UUID.
3546   *
3547   * @param  b  The byte array to be decoded as a UUID.  It must not be
3548   *            {@code null}.
3549   *
3550   * @return  The decoded UUID.
3551   *
3552   * @throws  ParseException  If the provided byte array cannot be parsed as a
3553   *                         UUID.
3554   */
3555  @NotNull()
3556  public static UUID decodeUUID(@NotNull final byte[] b)
3557         throws ParseException
3558  {
3559    if (b.length != 16)
3560    {
3561      throw new ParseException(ERR_DECODE_UUID_INVALID_LENGTH.get(toHex(b)), 0);
3562    }
3563
3564    long mostSignificantBits = 0L;
3565    for (int i=0; i < 8; i++)
3566    {
3567      mostSignificantBits = (mostSignificantBits << 8) | (b[i] & 0xFF);
3568    }
3569
3570    long leastSignificantBits = 0L;
3571    for (int i=8; i < 16; i++)
3572    {
3573      leastSignificantBits = (leastSignificantBits << 8) | (b[i] & 0xFF);
3574    }
3575
3576    return new UUID(mostSignificantBits, leastSignificantBits);
3577  }
3578
3579
3580
3581  /**
3582   * Returns {@code true} if and only if the current process is running on
3583   * a Windows-based operating system.
3584   *
3585   * @return  {@code true} if the current process is running on a Windows-based
3586   *          operating system and {@code false} otherwise.
3587   */
3588  public static boolean isWindows()
3589  {
3590    final String osName = toLowerCase(getSystemProperty("os.name"));
3591    return ((osName != null) && osName.contains("windows"));
3592  }
3593
3594
3595
3596  /**
3597   * Retrieves the string that should be appended to the end of all but the last
3598   * line of a multi-line command to indicate that the command continues onto
3599   * the next line.
3600   * <BR><BR>
3601   * This will be the caret (also called a circumflex accent) character on
3602   * Windows systems, and a backslash (also called a reverse solidus) character
3603   * on Linux and UNIX-based systems.
3604   * <BR><BR>
3605   * The string value that is returned will not include a space, but it should
3606   * generally be preceded by one or more space to separate it from the previous
3607   * component on the command line.
3608   *
3609   * @return  The string that should be appended (generally after one or more
3610   *          spaces to separate it from the previous component) to the end of
3611   *          all but the last line of a multi-line command to indicate that the
3612   *          command continues onto the next line.
3613   */
3614  @NotNull()
3615  public static String getCommandLineContinuationString()
3616  {
3617    if (isWindows())
3618    {
3619      return "^";
3620    }
3621    else
3622    {
3623      return "\\";
3624    }
3625  }
3626
3627
3628
3629  /**
3630   * Attempts to parse the contents of the provided string to an argument list
3631   * (e.g., converts something like "--arg1 arg1value --arg2 --arg3 arg3value"
3632   * to a list of "--arg1", "arg1value", "--arg2", "--arg3", "arg3value").
3633   *
3634   * @param  s  The string to be converted to an argument list.
3635   *
3636   * @return  The parsed argument list.
3637   *
3638   * @throws  ParseException  If a problem is encountered while attempting to
3639   *                          parse the given string to an argument list.
3640   */
3641  @NotNull()
3642  public static List<String> toArgumentList(@Nullable final String s)
3643         throws ParseException
3644  {
3645    if ((s == null) || s.isEmpty())
3646    {
3647      return Collections.emptyList();
3648    }
3649
3650    int quoteStartPos = -1;
3651    boolean inEscape = false;
3652    final ArrayList<String> argList = new ArrayList<>(20);
3653    final StringBuilder currentArg = new StringBuilder();
3654    for (int i=0; i < s.length(); i++)
3655    {
3656      final char c = s.charAt(i);
3657      if (inEscape)
3658      {
3659        currentArg.append(c);
3660        inEscape = false;
3661        continue;
3662      }
3663
3664      if (c == '\\')
3665      {
3666        inEscape = true;
3667      }
3668      else if (c == '"')
3669      {
3670        if (quoteStartPos >= 0)
3671        {
3672          quoteStartPos = -1;
3673        }
3674        else
3675        {
3676          quoteStartPos = i;
3677        }
3678      }
3679      else if (c == ' ')
3680      {
3681        if (quoteStartPos >= 0)
3682        {
3683          currentArg.append(c);
3684        }
3685        else if (currentArg.length() > 0)
3686        {
3687          argList.add(currentArg.toString());
3688          currentArg.setLength(0);
3689        }
3690      }
3691      else
3692      {
3693        currentArg.append(c);
3694      }
3695    }
3696
3697    if (s.endsWith("\\") && (! s.endsWith("\\\\")))
3698    {
3699      throw new ParseException(ERR_ARG_STRING_DANGLING_BACKSLASH.get(),
3700           (s.length() - 1));
3701    }
3702
3703    if (quoteStartPos >= 0)
3704    {
3705      throw new ParseException(ERR_ARG_STRING_UNMATCHED_QUOTE.get(
3706           quoteStartPos), quoteStartPos);
3707    }
3708
3709    if (currentArg.length() > 0)
3710    {
3711      argList.add(currentArg.toString());
3712    }
3713
3714    return Collections.unmodifiableList(argList);
3715  }
3716
3717
3718
3719  /**
3720   * Retrieves an array containing the elements of the provided collection.
3721   *
3722   * @param  <T>         The type of element included in the provided
3723   *                     collection.
3724   * @param  collection  The collection to convert to an array.
3725   * @param  type        The type of element contained in the collection.
3726   *
3727   * @return  An array containing the elements of the provided list, or
3728   *          {@code null} if the provided list is {@code null}.
3729   */
3730  @Nullable()
3731  public static <T> T[] toArray(@Nullable final Collection<T> collection,
3732                                @NotNull final Class<T> type)
3733  {
3734    if (collection == null)
3735    {
3736      return null;
3737    }
3738
3739    @SuppressWarnings("unchecked")
3740    final T[] array = (T[]) Array.newInstance(type, collection.size());
3741
3742    return collection.toArray(array);
3743  }
3744
3745
3746
3747  /**
3748   * Creates a modifiable list with all of the items of the provided array in
3749   * the same order.  This method behaves much like {@code Arrays.asList},
3750   * except that if the provided array is {@code null}, then it will return a
3751   * {@code null} list rather than throwing an exception.
3752   *
3753   * @param  <T>  The type of item contained in the provided array.
3754   *
3755   * @param  array  The array of items to include in the list.
3756   *
3757   * @return  The list that was created, or {@code null} if the provided array
3758   *          was {@code null}.
3759   */
3760  @Nullable()
3761  public static <T> List<T> toList(@Nullable final T[] array)
3762  {
3763    if (array == null)
3764    {
3765      return null;
3766    }
3767
3768    final ArrayList<T> l = new ArrayList<>(array.length);
3769    l.addAll(Arrays.asList(array));
3770    return l;
3771  }
3772
3773
3774
3775  /**
3776   * Creates a modifiable list with all of the items of the provided array in
3777   * the same order.  This method behaves much like {@code Arrays.asList},
3778   * except that if the provided array is {@code null}, then it will return an
3779   * empty list rather than throwing an exception.
3780   *
3781   * @param  <T>  The type of item contained in the provided array.
3782   *
3783   * @param  array  The array of items to include in the list.
3784   *
3785   * @return  The list that was created, or an empty list if the provided array
3786   *          was {@code null}.
3787   */
3788  @NotNull()
3789  public static <T> List<T> toNonNullList(@Nullable final T[] array)
3790  {
3791    if (array == null)
3792    {
3793      return new ArrayList<>(0);
3794    }
3795
3796    final ArrayList<T> l = new ArrayList<>(array.length);
3797    l.addAll(Arrays.asList(array));
3798    return l;
3799  }
3800
3801
3802
3803  /**
3804   * Indicates whether both of the provided objects are {@code null} or both
3805   * are logically equal (using the {@code equals} method).
3806   *
3807   * @param  o1  The first object for which to make the determination.
3808   * @param  o2  The second object for which to make the determination.
3809   *
3810   * @return  {@code true} if both objects are {@code null} or both are
3811   *          logically equal, or {@code false} if only one of the objects is
3812   *          {@code null} or they are not logically equal.
3813   */
3814  public static boolean bothNullOrEqual(@Nullable final Object o1,
3815                                        @Nullable final Object o2)
3816  {
3817    if (o1 == null)
3818    {
3819      return (o2 == null);
3820    }
3821    else if (o2 == null)
3822    {
3823      return false;
3824    }
3825
3826    return o1.equals(o2);
3827  }
3828
3829
3830
3831  /**
3832   * Indicates whether both of the provided strings are {@code null} or both
3833   * are logically equal ignoring differences in capitalization (using the
3834   * {@code equalsIgnoreCase} method).
3835   *
3836   * @param  s1  The first string for which to make the determination.
3837   * @param  s2  The second string for which to make the determination.
3838   *
3839   * @return  {@code true} if both strings are {@code null} or both are
3840   *          logically equal ignoring differences in capitalization, or
3841   *          {@code false} if only one of the objects is {@code null} or they
3842   *          are not logically equal ignoring capitalization.
3843   */
3844  public static boolean bothNullOrEqualIgnoreCase(@Nullable final String s1,
3845                                                  @Nullable final String s2)
3846  {
3847    if (s1 == null)
3848    {
3849      return (s2 == null);
3850    }
3851    else if (s2 == null)
3852    {
3853      return false;
3854    }
3855
3856    return s1.equalsIgnoreCase(s2);
3857  }
3858
3859
3860
3861  /**
3862   * Indicates whether the provided string arrays have the same elements,
3863   * ignoring the order in which they appear and differences in capitalization.
3864   * It is assumed that neither array contains {@code null} strings, and that
3865   * no string appears more than once in each array.
3866   *
3867   * @param  a1  The first array for which to make the determination.
3868   * @param  a2  The second array for which to make the determination.
3869   *
3870   * @return  {@code true} if both arrays have the same set of strings, or
3871   *          {@code false} if not.
3872   */
3873  public static boolean stringsEqualIgnoreCaseOrderIndependent(
3874                             @Nullable final String[] a1,
3875                             @Nullable final String[] a2)
3876  {
3877    if (a1 == null)
3878    {
3879      return (a2 == null);
3880    }
3881    else if (a2 == null)
3882    {
3883      return false;
3884    }
3885
3886    if (a1.length != a2.length)
3887    {
3888      return false;
3889    }
3890
3891    if (a1.length == 1)
3892    {
3893      return (a1[0].equalsIgnoreCase(a2[0]));
3894    }
3895
3896    final HashSet<String> s1 = new HashSet<>(computeMapCapacity(a1.length));
3897    for (final String s : a1)
3898    {
3899      s1.add(toLowerCase(s));
3900    }
3901
3902    final HashSet<String> s2 = new HashSet<>(computeMapCapacity(a2.length));
3903    for (final String s : a2)
3904    {
3905      s2.add(toLowerCase(s));
3906    }
3907
3908    return s1.equals(s2);
3909  }
3910
3911
3912
3913  /**
3914   * Indicates whether the provided arrays have the same elements, ignoring the
3915   * order in which they appear.  It is assumed that neither array contains
3916   * {@code null} elements, and that no element appears more than once in each
3917   * array.
3918   *
3919   * @param  <T>  The type of element contained in the arrays.
3920   *
3921   * @param  a1  The first array for which to make the determination.
3922   * @param  a2  The second array for which to make the determination.
3923   *
3924   * @return  {@code true} if both arrays have the same set of elements, or
3925   *          {@code false} if not.
3926   */
3927  public static <T> boolean arraysEqualOrderIndependent(@Nullable final T[] a1,
3928                                                        @Nullable final T[] a2)
3929  {
3930    if (a1 == null)
3931    {
3932      return (a2 == null);
3933    }
3934    else if (a2 == null)
3935    {
3936      return false;
3937    }
3938
3939    if (a1.length != a2.length)
3940    {
3941      return false;
3942    }
3943
3944    if (a1.length == 1)
3945    {
3946      return (a1[0].equals(a2[0]));
3947    }
3948
3949    final HashSet<T> s1 = new HashSet<>(Arrays.asList(a1));
3950    final HashSet<T> s2 = new HashSet<>(Arrays.asList(a2));
3951    return s1.equals(s2);
3952  }
3953
3954
3955
3956  /**
3957   * Determines the number of bytes in a UTF-8 character that starts with the
3958   * given byte.
3959   *
3960   * @param  b  The byte for which to make the determination.
3961   *
3962   * @return  The number of bytes in a UTF-8 character that starts with the
3963   *          given byte, or -1 if it does not appear to be a valid first byte
3964   *          for a UTF-8 character.
3965   */
3966  public static int numBytesInUTF8CharacterWithFirstByte(final byte b)
3967  {
3968    if ((b & 0x7F) == b)
3969    {
3970      return 1;
3971    }
3972    else if ((b & 0xE0) == 0xC0)
3973    {
3974      return 2;
3975    }
3976    else if ((b & 0xF0) == 0xE0)
3977    {
3978      return 3;
3979    }
3980    else if ((b & 0xF8) == 0xF0)
3981    {
3982      return 4;
3983    }
3984    else
3985    {
3986      return -1;
3987    }
3988  }
3989
3990
3991
3992  /**
3993   * Indicates whether the provided attribute name should be considered a
3994   * sensitive attribute for the purposes of {@code toCode} methods.  If an
3995   * attribute is considered sensitive, then its values will be redacted in the
3996   * output of the {@code toCode} methods.
3997   *
3998   * @param  name  The name for which to make the determination.  It may or may
3999   *               not include attribute options.  It must not be {@code null}.
4000   *
4001   * @return  {@code true} if the specified attribute is one that should be
4002   *          considered sensitive for the
4003   */
4004  public static boolean isSensitiveToCodeAttribute(@NotNull final String name)
4005  {
4006    final String lowerBaseName = Attribute.getBaseName(name).toLowerCase();
4007    return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES.contains(lowerBaseName);
4008  }
4009
4010
4011
4012  /**
4013   * Retrieves a set containing the base names (in all lowercase characters) of
4014   * any attributes that should be considered sensitive for the purposes of the
4015   * {@code toCode} methods.  By default, only the userPassword and
4016   * authPassword attributes and their respective OIDs will be included.
4017   *
4018   * @return  A set containing the base names (in all lowercase characters) of
4019   *          any attributes that should be considered sensitive for the
4020   *          purposes of the {@code toCode} methods.
4021   */
4022  @NotNull()
4023  public static Set<String> getSensitiveToCodeAttributeBaseNames()
4024  {
4025    return TO_CODE_SENSITIVE_ATTRIBUTE_NAMES;
4026  }
4027
4028
4029
4030  /**
4031   * Specifies the names of any attributes that should be considered sensitive
4032   * for the purposes of the {@code toCode} methods.
4033   *
4034   * @param  names  The names of any attributes that should be considered
4035   *                sensitive for the purposes of the {@code toCode} methods.
4036   *                It may be {@code null} or empty if no attributes should be
4037   *                considered sensitive.
4038   */
4039  public static void setSensitiveToCodeAttributes(
4040                          @Nullable final String... names)
4041  {
4042    setSensitiveToCodeAttributes(toList(names));
4043  }
4044
4045
4046
4047  /**
4048   * Specifies the names of any attributes that should be considered sensitive
4049   * for the purposes of the {@code toCode} methods.
4050   *
4051   * @param  names  The names of any attributes that should be considered
4052   *                sensitive for the purposes of the {@code toCode} methods.
4053   *                It may be {@code null} or empty if no attributes should be
4054   *                considered sensitive.
4055   */
4056  public static void setSensitiveToCodeAttributes(
4057                          @Nullable final Collection<String> names)
4058  {
4059    if ((names == null) || names.isEmpty())
4060    {
4061      TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.emptySet();
4062    }
4063    else
4064    {
4065      final LinkedHashSet<String> nameSet = new LinkedHashSet<>(names.size());
4066      for (final String s : names)
4067      {
4068        nameSet.add(Attribute.getBaseName(s).toLowerCase());
4069      }
4070
4071      TO_CODE_SENSITIVE_ATTRIBUTE_NAMES = Collections.unmodifiableSet(nameSet);
4072    }
4073  }
4074
4075
4076
4077  /**
4078   * Creates a new {@code IOException} with a cause.  The constructor needed to
4079   * do this wasn't available until Java SE 6, so reflection is used to invoke
4080   * this constructor in versions of Java that provide it.  In Java SE 5, the
4081   * provided message will be augmented with information about the cause.
4082   *
4083   * @param  message  The message to use for the exception.  This may be
4084   *                  {@code null} if the message should be generated from the
4085   *                  provided cause.
4086   * @param  cause    The underlying cause for the exception.  It may be
4087   *                  {@code null} if the exception should have only a message.
4088   *
4089   * @return  The {@code IOException} object that was created.
4090   */
4091  @NotNull()
4092  public static IOException createIOExceptionWithCause(
4093                                 @Nullable final String message,
4094                                 @Nullable final Throwable cause)
4095  {
4096    if (cause == null)
4097    {
4098      return new IOException(message);
4099    }
4100    else if (message == null)
4101    {
4102      return new IOException(cause);
4103    }
4104    else
4105    {
4106      return new IOException(message, cause);
4107    }
4108  }
4109
4110
4111
4112  /**
4113   * Converts the provided string (which may include line breaks) into a list
4114   * containing the lines without the line breaks.
4115   *
4116   * @param  s  The string to convert into a list of its representative lines.
4117   *
4118   * @return  A list containing the lines that comprise the given string.
4119   */
4120  @NotNull()
4121  public static List<String> stringToLines(@Nullable final String s)
4122  {
4123    final ArrayList<String> l = new ArrayList<>(10);
4124
4125    if (s == null)
4126    {
4127      return l;
4128    }
4129
4130    final BufferedReader reader = new BufferedReader(new StringReader(s));
4131
4132    try
4133    {
4134      while (true)
4135      {
4136        try
4137        {
4138          final String line = reader.readLine();
4139          if (line == null)
4140          {
4141            return l;
4142          }
4143          else
4144          {
4145            l.add(line);
4146          }
4147        }
4148        catch (final Exception e)
4149        {
4150          Debug.debugException(e);
4151
4152          // This should never happen.  If it does, just return a list
4153          // containing a single item that is the original string.
4154          l.clear();
4155          l.add(s);
4156          return l;
4157        }
4158      }
4159    }
4160    finally
4161    {
4162      try
4163      {
4164        // This is technically not necessary in this case, but it's good form.
4165        reader.close();
4166      }
4167      catch (final Exception e)
4168      {
4169        Debug.debugException(e);
4170        // This should never happen, and there's nothing we need to do even if
4171        // it does.
4172      }
4173    }
4174  }
4175
4176
4177
4178  /**
4179   * Creates a string that is a concatenation of all of the provided lines, with
4180   * a line break (using the end-of-line sequence appropriate for the underlying
4181   * platform) after each line (including the last line).
4182   *
4183   * @param  lines  The lines to include in the string.
4184   *
4185   * @return  The string resulting from concatenating the provided lines with
4186   *          line breaks.
4187   */
4188  @NotNull()
4189  public static String linesToString(@Nullable final CharSequence... lines)
4190  {
4191    if (lines == null)
4192    {
4193      return "";
4194    }
4195
4196    return linesToString(Arrays.asList(lines));
4197  }
4198
4199
4200
4201  /**
4202   * Creates a string that is a concatenation of all of the provided lines, with
4203   * a line break (using the end-of-line sequence appropriate for the underlying
4204   * platform) after each line (including the last line).
4205   *
4206   * @param  lines  The lines to include in the string.
4207   *
4208   * @return  The string resulting from concatenating the provided lines with
4209   *          line breaks.
4210   */
4211  @NotNull()
4212  public static String linesToString(
4213                            @Nullable final List<? extends CharSequence> lines)
4214  {
4215    if (lines == null)
4216    {
4217      return "";
4218    }
4219
4220    final StringBuilder buffer = new StringBuilder();
4221    for (final CharSequence line : lines)
4222    {
4223      buffer.append(line);
4224      buffer.append(EOL);
4225    }
4226
4227    return buffer.toString();
4228  }
4229
4230
4231
4232  /**
4233   * Constructs a {@code File} object from the provided path.
4234   *
4235   * @param  baseDirectory  The base directory to use as the starting point.
4236   *                        It must not be {@code null} and is expected to
4237   *                        represent a directory.
4238   * @param  pathElements   An array of the elements that make up the remainder
4239   *                        of the path to the specified file, in order from
4240   *                        paths closest to the root of the filesystem to
4241   *                        furthest away (that is, the first element should
4242   *                        represent a file or directory immediately below the
4243   *                        base directory, the second is one level below that,
4244   *                        and so on).  It may be {@code null} or empty if the
4245   *                        base directory should be used.
4246   *
4247   * @return  The constructed {@code File} object.
4248   */
4249  @NotNull()
4250  public static File constructPath(@NotNull final File baseDirectory,
4251                                   @Nullable final String... pathElements)
4252  {
4253    Validator.ensureNotNull(baseDirectory);
4254
4255    File f = baseDirectory;
4256    if (pathElements != null)
4257    {
4258      for (final String pathElement : pathElements)
4259      {
4260        f = new File(f, pathElement);
4261      }
4262    }
4263
4264    return f;
4265  }
4266
4267
4268
4269  /**
4270   * Creates a byte array from the provided integer values.  All of the integer
4271   * values must be between 0x00 and 0xFF (0 and 255), inclusive.  Any bits
4272   * set outside of that range will be ignored.
4273   *
4274   * @param  bytes  The values to include in the byte array.
4275   *
4276   * @return  A byte array with the provided set of values.
4277   */
4278  @NotNull()
4279  public static byte[] byteArray(@Nullable final int... bytes)
4280  {
4281    if ((bytes == null) || (bytes.length == 0))
4282    {
4283      return NO_BYTES;
4284    }
4285
4286    final byte[] byteArray = new byte[bytes.length];
4287    for (int i=0; i < bytes.length; i++)
4288    {
4289      byteArray[i] = (byte) (bytes[i] & 0xFF);
4290    }
4291
4292    return byteArray;
4293  }
4294
4295
4296
4297  /**
4298   * Indicates whether the unit tests are currently running in this JVM.
4299   *
4300   * @return  {@code true} if the unit tests are currently running, or
4301   *          {@code false} if not.
4302   */
4303  public static boolean isWithinUnitTest()
4304  {
4305    return IS_WITHIN_UNIT_TESTS;
4306  }
4307
4308
4309
4310  /**
4311   * Throws an {@code Error} or a {@code RuntimeException} based on the provided
4312   * {@code Throwable} object.  This method will always throw something,
4313   * regardless of the provided {@code Throwable} object.
4314   *
4315   * @param  throwable  The {@code Throwable} object to use to create the
4316   *                    exception to throw.
4317   *
4318   * @throws  Error  If the provided {@code Throwable} object is an
4319   *                 {@code Error} instance, then that {@code Error} instance
4320   *                 will be re-thrown.
4321   *
4322   * @throws  RuntimeException  If the provided {@code Throwable} object is a
4323   *                            {@code RuntimeException} instance, then that
4324   *                            {@code RuntimeException} instance will be
4325   *                            re-thrown.  Otherwise, it must be a checked
4326   *                            exception and that checked exception will be
4327   *                            re-thrown as a {@code RuntimeException}.
4328   */
4329  public static void throwErrorOrRuntimeException(
4330                          @NotNull final Throwable throwable)
4331         throws Error, RuntimeException
4332  {
4333    Validator.ensureNotNull(throwable);
4334
4335    if (throwable instanceof Error)
4336    {
4337      throw (Error) throwable;
4338    }
4339    else if (throwable instanceof RuntimeException)
4340    {
4341      throw (RuntimeException) throwable;
4342    }
4343    else
4344    {
4345      throw new RuntimeException(throwable);
4346    }
4347  }
4348
4349
4350
4351  /**
4352   * Re-throws the provided {@code Throwable} instance only if it is an
4353   * {@code Error} or a {@code RuntimeException} instance; otherwise, this
4354   * method will return without taking any action.
4355   *
4356   * @param  throwable  The {@code Throwable} object to examine and potentially
4357   *                    re-throw.
4358   *
4359   * @throws  Error  If the provided {@code Throwable} object is an
4360   *                 {@code Error} instance, then that {@code Error} instance
4361   *                 will be re-thrown.
4362   *
4363   * @throws  RuntimeException  If the provided {@code Throwable} object is a
4364   *                            {@code RuntimeException} instance, then that
4365   *                            {@code RuntimeException} instance will be
4366   *                            re-thrown.
4367   */
4368  public static void rethrowIfErrorOrRuntimeException(
4369                          @NotNull final Throwable throwable)
4370         throws Error, RuntimeException
4371  {
4372    if (throwable instanceof Error)
4373    {
4374      throw (Error) throwable;
4375    }
4376    else if (throwable instanceof RuntimeException)
4377    {
4378      throw (RuntimeException) throwable;
4379    }
4380  }
4381
4382
4383
4384  /**
4385   * Re-throws the provided {@code Throwable} instance only if it is an
4386   * {@code Error}; otherwise, this method will return without taking any
4387   * action.
4388   *
4389   * @param  throwable  The {@code Throwable} object to examine and potentially
4390   *                    re-throw.
4391   *
4392   * @throws  Error  If the provided {@code Throwable} object is an
4393   *                 {@code Error} instance, then that {@code Error} instance
4394   *                 will be re-thrown.
4395   */
4396  public static void rethrowIfError(@NotNull final Throwable throwable)
4397         throws Error
4398  {
4399    if (throwable instanceof Error)
4400    {
4401      throw (Error) throwable;
4402    }
4403  }
4404
4405
4406
4407  /**
4408   * Computes the capacity that should be used for a map or a set with the
4409   * expected number of elements, which can help avoid the need to re-hash or
4410   * re-balance the map if too many items are added.  This method bases its
4411   * computation on the default map load factor of 0.75.
4412   *
4413   * @param  expectedItemCount  The expected maximum number of items that will
4414   *                            be placed in the map or set.  It must be greater
4415   *                            than or equal to zero.
4416   *
4417   * @return  The capacity that should be used for a map or a set with the
4418   *          expected number of elements
4419   */
4420  public static int computeMapCapacity(final int expectedItemCount)
4421  {
4422    switch (expectedItemCount)
4423    {
4424      case 0:
4425        return 0;
4426      case 1:
4427        return 2;
4428      case 2:
4429        return 3;
4430      case 3:
4431        return 5;
4432      case 4:
4433        return 6;
4434      case 5:
4435        return 7;
4436      case 6:
4437        return 9;
4438      case 7:
4439        return 10;
4440      case 8:
4441        return 11;
4442      case 9:
4443        return 13;
4444      case 10:
4445        return 14;
4446      case 11:
4447        return 15;
4448      case 12:
4449        return 17;
4450      case 13:
4451        return 18;
4452      case 14:
4453        return 19;
4454      case 15:
4455        return 21;
4456      case 16:
4457        return 22;
4458      case 17:
4459        return 23;
4460      case 18:
4461        return 25;
4462      case 19:
4463        return 26;
4464      case 20:
4465        return 27;
4466      case 30:
4467        return 41;
4468      case 40:
4469        return 54;
4470      case 50:
4471        return 67;
4472      case 60:
4473        return 81;
4474      case 70:
4475        return 94;
4476      case 80:
4477        return 107;
4478      case 90:
4479        return 121;
4480      case 100:
4481        return 134;
4482      case 110:
4483        return 147;
4484      case 120:
4485        return 161;
4486      case 130:
4487        return 174;
4488      case 140:
4489        return 187;
4490      case 150:
4491        return 201;
4492      case 160:
4493        return 214;
4494      case 170:
4495        return 227;
4496      case 180:
4497        return 241;
4498      case 190:
4499        return 254;
4500      case 200:
4501        return 267;
4502      default:
4503        Validator.ensureTrue((expectedItemCount >= 0),
4504             "StaticUtils.computeMapOrSetCapacity.expectedItemCount must be " +
4505                  "greater than or equal to zero.");
4506
4507        // NOTE:  536,870,911 is Integer.MAX_VALUE/4.  If the value is larger
4508        // than that, then we'll fall back to using floating-point arithmetic
4509        //
4510        if (expectedItemCount > 536_870_911)
4511        {
4512          final int computedCapacity = ((int) (expectedItemCount / 0.75)) + 1;
4513          if (computedCapacity <= expectedItemCount)
4514          {
4515            // This suggests that the expected number of items is so big that
4516            // the computed capacity can't be adequately represented by an
4517            // integer.  In that case, we'll just return the expected item
4518            // count and let the map or set get re-hashed/re-balanced if it
4519            // actually gets anywhere near that size.
4520            return expectedItemCount;
4521          }
4522          else
4523          {
4524            return computedCapacity;
4525          }
4526        }
4527        else
4528        {
4529          return ((expectedItemCount * 4) / 3) + 1;
4530        }
4531    }
4532  }
4533
4534
4535
4536  /**
4537   * Creates an unmodifiable set containing the provided items.  The iteration
4538   * order of the provided items will be preserved.
4539   *
4540   * @param  <T>    The type of item to include in the set.
4541   * @param  items  The items to include in the set.  It must not be
4542   *                {@code null}, but may be empty.
4543   *
4544   * @return  An unmodifiable set containing the provided items.
4545   */
4546  @SafeVarargs()
4547  @SuppressWarnings("varargs")
4548  @NotNull()
4549  public static <T> Set<T> setOf(@NotNull final T... items)
4550  {
4551    return Collections.unmodifiableSet(
4552         new LinkedHashSet<>(Arrays.asList(items)));
4553  }
4554
4555
4556
4557  /**
4558   * Creates a {@code HashSet} containing the provided items.
4559   *
4560   * @param  <T>    The type of item to include in the set.
4561   * @param  items  The items to include in the set.  It must not be
4562   *                {@code null}, but may be empty.
4563   *
4564   * @return  A {@code HashSet} containing the provided items.
4565   */
4566  @SafeVarargs()
4567  @SuppressWarnings("varargs")
4568  @NotNull()
4569  public static <T> HashSet<T> hashSetOf(@NotNull final T... items)
4570  {
4571    return new HashSet<>(Arrays.asList(items));
4572  }
4573
4574
4575
4576  /**
4577   * Creates a {@code LinkedHashSet} containing the provided items.
4578   *
4579   * @param  <T>    The type of item to include in the set.
4580   * @param  items  The items to include in the set.  It must not be
4581   *                {@code null}, but may be empty.
4582   *
4583   * @return  A {@code LinkedHashSet} containing the provided items.
4584   */
4585  @SafeVarargs()
4586  @SuppressWarnings("varargs")
4587  @NotNull()
4588  public static <T> LinkedHashSet<T> linkedHashSetOf(@NotNull final T... items)
4589  {
4590    return new LinkedHashSet<>(Arrays.asList(items));
4591  }
4592
4593
4594
4595  /**
4596   * Creates a {@code TreeSet} containing the provided items.
4597   *
4598   * @param  <T>    The type of item to include in the set.
4599   * @param  items  The items to include in the set.  It must not be
4600   *                {@code null}, but may be empty.
4601   *
4602   * @return  A {@code LinkedHashSet} containing the provided items.
4603   */
4604  @SafeVarargs()
4605  @SuppressWarnings("varargs")
4606  @NotNull()
4607  public static <T> TreeSet<T> treeSetOf(@NotNull final T... items)
4608  {
4609    return new TreeSet<>(Arrays.asList(items));
4610  }
4611
4612
4613
4614  /**
4615   * Creates an unmodifiable map containing the provided items.
4616   *
4617   * @param  <K>    The type for the map keys.
4618   * @param  <V>    The type for the map values.
4619   * @param  key    The only key to include in the map.
4620   * @param  value  The only value to include in the map.
4621   *
4622   * @return  The unmodifiable map that was created.
4623   */
4624  @NotNull()
4625  public static <K,V> Map<K,V> mapOf(@NotNull final K key,
4626                                     @NotNull final V value)
4627  {
4628    return Collections.singletonMap(key, value);
4629  }
4630
4631
4632
4633  /**
4634   * Creates an unmodifiable map containing the provided items.
4635   *
4636   * @param  <K>     The type for the map keys.
4637   * @param  <V>     The type for the map values.
4638   * @param  key1    The first key to include in the map.
4639   * @param  value1  The first value to include in the map.
4640   * @param  key2    The second key to include in the map.
4641   * @param  value2  The second value to include in the map.
4642   *
4643   * @return  The unmodifiable map that was created.
4644   */
4645  @NotNull()
4646  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4647                                     @NotNull final V value1,
4648                                     @NotNull final K key2,
4649                                     @NotNull final V value2)
4650  {
4651    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(2));
4652
4653    map.put(key1, value1);
4654    map.put(key2, value2);
4655
4656    return Collections.unmodifiableMap(map);
4657  }
4658
4659
4660
4661  /**
4662   * Creates an unmodifiable map containing the provided items.
4663   *
4664   * @param  <K>     The type for the map keys.
4665   * @param  <V>     The type for the map values.
4666   * @param  key1    The first key to include in the map.
4667   * @param  value1  The first value to include in the map.
4668   * @param  key2    The second key to include in the map.
4669   * @param  value2  The second value to include in the map.
4670   * @param  key3    The third key to include in the map.
4671   * @param  value3  The third value to include in the map.
4672   *
4673   * @return  The unmodifiable map that was created.
4674   */
4675  @NotNull()
4676  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4677                                     @NotNull final V value1,
4678                                     @NotNull final K key2,
4679                                     @NotNull final V value2,
4680                                     @NotNull final K key3,
4681                                     @NotNull final V value3)
4682  {
4683    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(3));
4684
4685    map.put(key1, value1);
4686    map.put(key2, value2);
4687    map.put(key3, value3);
4688
4689    return Collections.unmodifiableMap(map);
4690  }
4691
4692
4693
4694  /**
4695   * Creates an unmodifiable map containing the provided items.
4696   *
4697   * @param  <K>     The type for the map keys.
4698   * @param  <V>     The type for the map values.
4699   * @param  key1    The first key to include in the map.
4700   * @param  value1  The first value to include in the map.
4701   * @param  key2    The second key to include in the map.
4702   * @param  value2  The second value to include in the map.
4703   * @param  key3    The third key to include in the map.
4704   * @param  value3  The third value to include in the map.
4705   * @param  key4    The fourth key to include in the map.
4706   * @param  value4  The fourth value to include in the map.
4707   *
4708   * @return  The unmodifiable map that was created.
4709   */
4710  @NotNull()
4711  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4712                                     @NotNull final V value1,
4713                                     @NotNull final K key2,
4714                                     @NotNull final V value2,
4715                                     @NotNull final K key3,
4716                                     @NotNull final V value3,
4717                                     @NotNull final K key4,
4718                                     @NotNull final V value4)
4719  {
4720    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(4));
4721
4722    map.put(key1, value1);
4723    map.put(key2, value2);
4724    map.put(key3, value3);
4725    map.put(key4, value4);
4726
4727    return Collections.unmodifiableMap(map);
4728  }
4729
4730
4731
4732  /**
4733   * Creates an unmodifiable map containing the provided items.
4734   *
4735   * @param  <K>     The type for the map keys.
4736   * @param  <V>     The type for the map values.
4737   * @param  key1    The first key to include in the map.
4738   * @param  value1  The first value to include in the map.
4739   * @param  key2    The second key to include in the map.
4740   * @param  value2  The second value to include in the map.
4741   * @param  key3    The third key to include in the map.
4742   * @param  value3  The third value to include in the map.
4743   * @param  key4    The fourth key to include in the map.
4744   * @param  value4  The fourth value to include in the map.
4745   * @param  key5    The fifth key to include in the map.
4746   * @param  value5  The fifth value to include in the map.
4747   *
4748   * @return  The unmodifiable map that was created.
4749   */
4750  @NotNull()
4751  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4752                                     @NotNull final V value1,
4753                                     @NotNull final K key2,
4754                                     @NotNull final V value2,
4755                                     @NotNull final K key3,
4756                                     @NotNull final V value3,
4757                                     @NotNull final K key4,
4758                                     @NotNull final V value4,
4759                                     @NotNull final K key5,
4760                                     @NotNull final V value5)
4761  {
4762    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(5));
4763
4764    map.put(key1, value1);
4765    map.put(key2, value2);
4766    map.put(key3, value3);
4767    map.put(key4, value4);
4768    map.put(key5, value5);
4769
4770    return Collections.unmodifiableMap(map);
4771  }
4772
4773
4774
4775  /**
4776   * Creates an unmodifiable map containing the provided items.
4777   *
4778   * @param  <K>     The type for the map keys.
4779   * @param  <V>     The type for the map values.
4780   * @param  key1    The first key to include in the map.
4781   * @param  value1  The first value to include in the map.
4782   * @param  key2    The second key to include in the map.
4783   * @param  value2  The second value to include in the map.
4784   * @param  key3    The third key to include in the map.
4785   * @param  value3  The third value to include in the map.
4786   * @param  key4    The fourth key to include in the map.
4787   * @param  value4  The fourth value to include in the map.
4788   * @param  key5    The fifth key to include in the map.
4789   * @param  value5  The fifth value to include in the map.
4790   * @param  key6    The sixth key to include in the map.
4791   * @param  value6  The sixth value to include in the map.
4792   *
4793   * @return  The unmodifiable map that was created.
4794   */
4795  @NotNull()
4796  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4797                                     @NotNull final V value1,
4798                                     @NotNull final K key2,
4799                                     @NotNull final V value2,
4800                                     @NotNull final K key3,
4801                                     @NotNull final V value3,
4802                                     @NotNull final K key4,
4803                                     @NotNull final V value4,
4804                                     @NotNull final K key5,
4805                                     @NotNull final V value5,
4806                                     @NotNull final K key6,
4807                                     @NotNull final V value6)
4808  {
4809    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(6));
4810
4811    map.put(key1, value1);
4812    map.put(key2, value2);
4813    map.put(key3, value3);
4814    map.put(key4, value4);
4815    map.put(key5, value5);
4816    map.put(key6, value6);
4817
4818    return Collections.unmodifiableMap(map);
4819  }
4820
4821
4822
4823  /**
4824   * Creates an unmodifiable map containing the provided items.
4825   *
4826   * @param  <K>     The type for the map keys.
4827   * @param  <V>     The type for the map values.
4828   * @param  key1    The first key to include in the map.
4829   * @param  value1  The first value to include in the map.
4830   * @param  key2    The second key to include in the map.
4831   * @param  value2  The second value to include in the map.
4832   * @param  key3    The third key to include in the map.
4833   * @param  value3  The third value to include in the map.
4834   * @param  key4    The fourth key to include in the map.
4835   * @param  value4  The fourth value to include in the map.
4836   * @param  key5    The fifth key to include in the map.
4837   * @param  value5  The fifth value to include in the map.
4838   * @param  key6    The sixth key to include in the map.
4839   * @param  value6  The sixth value to include in the map.
4840   * @param  key7    The seventh key to include in the map.
4841   * @param  value7  The seventh value to include in the map.
4842   *
4843   * @return  The unmodifiable map that was created.
4844   */
4845  @NotNull()
4846  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4847                                     @NotNull final V value1,
4848                                     @NotNull final K key2,
4849                                     @NotNull final V value2,
4850                                     @NotNull final K key3,
4851                                     @NotNull final V value3,
4852                                     @NotNull final K key4,
4853                                     @NotNull final V value4,
4854                                     @NotNull final K key5,
4855                                     @NotNull final V value5,
4856                                     @NotNull final K key6,
4857                                     @NotNull final V value6,
4858                                     @NotNull final K key7,
4859                                     @NotNull final V value7)
4860  {
4861    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(7));
4862
4863    map.put(key1, value1);
4864    map.put(key2, value2);
4865    map.put(key3, value3);
4866    map.put(key4, value4);
4867    map.put(key5, value5);
4868    map.put(key6, value6);
4869    map.put(key7, value7);
4870
4871    return Collections.unmodifiableMap(map);
4872  }
4873
4874
4875
4876  /**
4877   * Creates an unmodifiable map containing the provided items.
4878   *
4879   * @param  <K>     The type for the map keys.
4880   * @param  <V>     The type for the map values.
4881   * @param  key1    The first key to include in the map.
4882   * @param  value1  The first value to include in the map.
4883   * @param  key2    The second key to include in the map.
4884   * @param  value2  The second value to include in the map.
4885   * @param  key3    The third key to include in the map.
4886   * @param  value3  The third value to include in the map.
4887   * @param  key4    The fourth key to include in the map.
4888   * @param  value4  The fourth value to include in the map.
4889   * @param  key5    The fifth key to include in the map.
4890   * @param  value5  The fifth value to include in the map.
4891   * @param  key6    The sixth key to include in the map.
4892   * @param  value6  The sixth value to include in the map.
4893   * @param  key7    The seventh key to include in the map.
4894   * @param  value7  The seventh value to include in the map.
4895   * @param  key8    The eighth key to include in the map.
4896   * @param  value8  The eighth value to include in the map.
4897   *
4898   * @return  The unmodifiable map that was created.
4899   */
4900  @NotNull()
4901  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4902                                     @NotNull final V value1,
4903                                     @NotNull final K key2,
4904                                     @NotNull final V value2,
4905                                     @NotNull final K key3,
4906                                     @NotNull final V value3,
4907                                     @NotNull final K key4,
4908                                     @NotNull final V value4,
4909                                     @NotNull final K key5,
4910                                     @NotNull final V value5,
4911                                     @NotNull final K key6,
4912                                     @NotNull final V value6,
4913                                     @NotNull final K key7,
4914                                     @NotNull final V value7,
4915                                     @NotNull final K key8,
4916                                     @NotNull final V value8)
4917  {
4918    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(8));
4919
4920    map.put(key1, value1);
4921    map.put(key2, value2);
4922    map.put(key3, value3);
4923    map.put(key4, value4);
4924    map.put(key5, value5);
4925    map.put(key6, value6);
4926    map.put(key7, value7);
4927    map.put(key8, value8);
4928
4929    return Collections.unmodifiableMap(map);
4930  }
4931
4932
4933
4934  /**
4935   * Creates an unmodifiable map containing the provided items.
4936   *
4937   * @param  <K>     The type for the map keys.
4938   * @param  <V>     The type for the map values.
4939   * @param  key1    The first key to include in the map.
4940   * @param  value1  The first value to include in the map.
4941   * @param  key2    The second key to include in the map.
4942   * @param  value2  The second value to include in the map.
4943   * @param  key3    The third key to include in the map.
4944   * @param  value3  The third value to include in the map.
4945   * @param  key4    The fourth key to include in the map.
4946   * @param  value4  The fourth value to include in the map.
4947   * @param  key5    The fifth key to include in the map.
4948   * @param  value5  The fifth value to include in the map.
4949   * @param  key6    The sixth key to include in the map.
4950   * @param  value6  The sixth value to include in the map.
4951   * @param  key7    The seventh key to include in the map.
4952   * @param  value7  The seventh value to include in the map.
4953   * @param  key8    The eighth key to include in the map.
4954   * @param  value8  The eighth value to include in the map.
4955   * @param  key9    The ninth key to include in the map.
4956   * @param  value9  The ninth value to include in the map.
4957   *
4958   * @return  The unmodifiable map that was created.
4959   */
4960  @NotNull()
4961  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
4962                                     @NotNull final V value1,
4963                                     @NotNull final K key2,
4964                                     @NotNull final V value2,
4965                                     @NotNull final K key3,
4966                                     @NotNull final V value3,
4967                                     @NotNull final K key4,
4968                                     @NotNull final V value4,
4969                                     @NotNull final K key5,
4970                                     @NotNull final V value5,
4971                                     @NotNull final K key6,
4972                                     @NotNull final V value6,
4973                                     @NotNull final K key7,
4974                                     @NotNull final V value7,
4975                                     @NotNull final K key8,
4976                                     @NotNull final V value8,
4977                                     @NotNull final K key9,
4978                                     @NotNull final V value9)
4979  {
4980    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(9));
4981
4982    map.put(key1, value1);
4983    map.put(key2, value2);
4984    map.put(key3, value3);
4985    map.put(key4, value4);
4986    map.put(key5, value5);
4987    map.put(key6, value6);
4988    map.put(key7, value7);
4989    map.put(key8, value8);
4990    map.put(key9, value9);
4991
4992    return Collections.unmodifiableMap(map);
4993  }
4994
4995
4996
4997  /**
4998   * Creates an unmodifiable map containing the provided items.
4999   *
5000   * @param  <K>      The type for the map keys.
5001   * @param  <V>      The type for the map values.
5002   * @param  key1     The first key to include in the map.
5003   * @param  value1   The first value to include in the map.
5004   * @param  key2     The second key to include in the map.
5005   * @param  value2   The second value to include in the map.
5006   * @param  key3     The third key to include in the map.
5007   * @param  value3   The third value to include in the map.
5008   * @param  key4     The fourth key to include in the map.
5009   * @param  value4   The fourth value to include in the map.
5010   * @param  key5     The fifth key to include in the map.
5011   * @param  value5   The fifth value to include in the map.
5012   * @param  key6     The sixth key to include in the map.
5013   * @param  value6   The sixth value to include in the map.
5014   * @param  key7     The seventh key to include in the map.
5015   * @param  value7   The seventh value to include in the map.
5016   * @param  key8     The eighth key to include in the map.
5017   * @param  value8   The eighth value to include in the map.
5018   * @param  key9     The ninth key to include in the map.
5019   * @param  value9   The ninth value to include in the map.
5020   * @param  key10    The tenth key to include in the map.
5021   * @param  value10  The tenth value to include in the map.
5022   *
5023   * @return  The unmodifiable map that was created.
5024   */
5025  @NotNull()
5026  public static <K,V> Map<K,V> mapOf(@NotNull final K key1,
5027                                     @NotNull final V value1,
5028                                     @NotNull final K key2,
5029                                     @NotNull final V value2,
5030                                     @NotNull final K key3,
5031                                     @NotNull final V value3,
5032                                     @NotNull final K key4,
5033                                     @NotNull final V value4,
5034                                     @NotNull final K key5,
5035                                     @NotNull final V value5,
5036                                     @NotNull final K key6,
5037                                     @NotNull final V value6,
5038                                     @NotNull final K key7,
5039                                     @NotNull final V value7,
5040                                     @NotNull final K key8,
5041                                     @NotNull final V value8,
5042                                     @NotNull final K key9,
5043                                     @NotNull final V value9,
5044                                     @NotNull final K key10,
5045                                     @NotNull final V value10)
5046  {
5047    final LinkedHashMap<K,V> map = new LinkedHashMap<>(computeMapCapacity(10));
5048
5049    map.put(key1, value1);
5050    map.put(key2, value2);
5051    map.put(key3, value3);
5052    map.put(key4, value4);
5053    map.put(key5, value5);
5054    map.put(key6, value6);
5055    map.put(key7, value7);
5056    map.put(key8, value8);
5057    map.put(key9, value9);
5058    map.put(key10, value10);
5059
5060    return Collections.unmodifiableMap(map);
5061  }
5062
5063
5064
5065  /**
5066   * Creates an unmodifiable map containing the provided items.  The map entries
5067   * must have the same data type for keys and values.
5068   *
5069   * @param  <T>    The type for the map keys and values.
5070   * @param  items  The items to include in the map.  If it is null or empty,
5071   *                the map will be empty.  If it is non-empty, then the number
5072   *                of elements in the array must be a multiple of two.
5073   *                Elements in even-numbered indexes will be the keys for the
5074   *                map entries, while elements in odd-numbered indexes will be
5075   *                the map values.
5076   *
5077   * @return  The unmodifiable map that was created.
5078   */
5079  @SafeVarargs()
5080  @NotNull()
5081  public static <T> Map<T,T> mapOf(@Nullable final T... items)
5082  {
5083    if ((items == null) || (items.length == 0))
5084    {
5085      return Collections.emptyMap();
5086    }
5087
5088    Validator.ensureTrue(((items.length % 2) == 0),
5089         "StaticUtils.mapOf.items must have an even number of elements");
5090
5091    final int numEntries = items.length / 2;
5092    final LinkedHashMap<T,T> map =
5093         new LinkedHashMap<>(computeMapCapacity(numEntries));
5094    for (int i=0; i < items.length; )
5095    {
5096      map.put(items[i++], items[i++]);
5097    }
5098
5099    return Collections.unmodifiableMap(map);
5100  }
5101
5102
5103
5104  /**
5105   * Creates an unmodifiable map containing the provided items.
5106   *
5107   * @param  <K>    The type for the map keys.
5108   * @param  <V>    The type for the map values.
5109   * @param  items  The items to include in the map.
5110   *
5111   * @return  The unmodifiable map that was created.
5112   */
5113  @SafeVarargs()
5114  @NotNull()
5115  public static <K,V> Map<K,V> mapOfObjectPairs(
5116                                    @Nullable final ObjectPair<K,V>... items)
5117  {
5118    if ((items == null) || (items.length == 0))
5119    {
5120      return Collections.emptyMap();
5121    }
5122
5123    final LinkedHashMap<K,V> map = new LinkedHashMap<>(
5124         computeMapCapacity(items.length));
5125    for (final ObjectPair<K,V> item : items)
5126    {
5127      map.put(item.getFirst(), item.getSecond());
5128    }
5129
5130    return Collections.unmodifiableMap(map);
5131  }
5132
5133
5134
5135  /**
5136   * Attempts to determine all addresses associated with the local system.
5137   *
5138   * @param  nameResolver  The name resolver to use to determine the local
5139   *                       host and loopback addresses.  If this is
5140   *                       {@code null}, then the LDAP SDK's default name
5141   *                       resolver will be used.
5142   *
5143   * @return  A set of the local addresses that were identified.
5144   */
5145  @NotNull()
5146  public static Set<InetAddress> getAllLocalAddresses(
5147                                      @Nullable final NameResolver nameResolver)
5148  {
5149    final NameResolver resolver;
5150    if (nameResolver == null)
5151    {
5152      resolver = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER;
5153    }
5154    else
5155    {
5156      resolver = nameResolver;
5157    }
5158
5159    final LinkedHashSet<InetAddress> localAddresses =
5160         new LinkedHashSet<>(computeMapCapacity(10));
5161
5162    try
5163    {
5164      localAddresses.add(resolver.getLocalHost());
5165    }
5166    catch (final Exception e)
5167    {
5168      Debug.debugException(e);
5169    }
5170
5171    try
5172    {
5173      final Enumeration<NetworkInterface> networkInterfaces =
5174           NetworkInterface.getNetworkInterfaces();
5175      while (networkInterfaces.hasMoreElements())
5176      {
5177        final NetworkInterface networkInterface =
5178             networkInterfaces.nextElement();
5179        final Enumeration<InetAddress> interfaceAddresses =
5180             networkInterface.getInetAddresses();
5181        while (interfaceAddresses.hasMoreElements())
5182        {
5183          localAddresses.add(interfaceAddresses.nextElement());
5184        }
5185      }
5186    }
5187    catch (final Exception e)
5188    {
5189      Debug.debugException(e);
5190    }
5191
5192    try
5193    {
5194      localAddresses.add(resolver.getLoopbackAddress());
5195    }
5196    catch (final Exception e)
5197    {
5198      Debug.debugException(e);
5199    }
5200
5201    return Collections.unmodifiableSet(localAddresses);
5202  }
5203
5204
5205
5206  /**
5207   * Retrieves the canonical host name for the provided address, if it can be
5208   * resolved to a name.
5209   *
5210   * @param  nameResolver  The name resolver to use to obtain the canonical
5211   *                       host name.  If this is {@code null}, then the LDAP
5212   *                       SDK's default name resolver will be used.
5213   * @param  address       The {@code InetAddress} for which to attempt to
5214   *                       obtain the canonical host name.
5215   *
5216   * @return  The canonical host name for the provided address, or {@code null}
5217   *          if it cannot be obtained (either because the attempt returns
5218   *          {@code null}, which shouldn't happen, or because it matches the
5219   *          IP address).
5220   */
5221  @Nullable()
5222  public static String getCanonicalHostNameIfAvailable(
5223                            @Nullable final NameResolver nameResolver,
5224                            @NotNull final InetAddress address)
5225  {
5226    final NameResolver resolver;
5227    if (nameResolver == null)
5228    {
5229      resolver = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER;
5230    }
5231    else
5232    {
5233      resolver = nameResolver;
5234    }
5235
5236    final String hostAddress = address.getHostAddress();
5237    final String trimmedHostAddress =
5238         trimInterfaceNameFromHostAddress(hostAddress);
5239
5240    final String canonicalHostName = resolver.getCanonicalHostName(address);
5241    if ((canonicalHostName == null) ||
5242         canonicalHostName.equalsIgnoreCase(hostAddress) ||
5243         canonicalHostName.equalsIgnoreCase(trimmedHostAddress))
5244    {
5245      return null;
5246    }
5247
5248    return canonicalHostName;
5249  }
5250
5251
5252
5253  /**
5254   * Retrieves the canonical host names for the provided set of
5255   * {@code InetAddress} objects.  If any of the provided addresses cannot be
5256   * resolved to a canonical host name (in which case the attempt to get the
5257   * canonical host name will return its IP address), it will be excluded from
5258   * the returned set.
5259   *
5260   * @param  nameResolver  The name resolver to use to obtain the canonical
5261   *                       host names.  If this is {@code null}, then the LDAP
5262   *                       SDK's default name resolver will be used.
5263   * @param  addresses     The set of addresses for which to obtain the
5264   *                       canonical host names.
5265   *
5266   * @return  A set of the canonical host names that could be obtained from the
5267   *          provided addresses.
5268   */
5269  @NotNull()
5270  public static Set<String> getAvailableCanonicalHostNames(
5271                     @Nullable final NameResolver nameResolver,
5272                     @NotNull final Collection<InetAddress> addresses)
5273  {
5274    final NameResolver resolver;
5275    if (nameResolver == null)
5276    {
5277      resolver = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER;
5278    }
5279    else
5280    {
5281      resolver = nameResolver;
5282    }
5283
5284    final Set<String> canonicalHostNames =
5285         new LinkedHashSet<>(computeMapCapacity(addresses.size()));
5286    for (final InetAddress address : addresses)
5287    {
5288      final String canonicalHostName =
5289           getCanonicalHostNameIfAvailable(resolver, address);
5290      if (canonicalHostName != null)
5291      {
5292        canonicalHostNames.add(canonicalHostName);
5293      }
5294    }
5295
5296    return Collections.unmodifiableSet(canonicalHostNames);
5297  }
5298
5299
5300
5301  /**
5302   * Retrieves a version of the provided host address with the interface name
5303   * stripped off.  Java sometimes follows an IP address with a percent sign and
5304   * the interface name.  If that interface name is present in the provided
5305   * host address, then this method will trim it off, leaving just the IP
5306   * address.  If the provided host address does not include the interface name,
5307   * then the provided address will be returned as-is.
5308   *
5309   * @param  hostAddress  The host address to be trimmed.
5310   *
5311   * @return  The provided host address without the interface name.
5312   */
5313  @NotNull()
5314  public static String trimInterfaceNameFromHostAddress(
5315                            @NotNull final String hostAddress)
5316  {
5317    final int percentPos = hostAddress.indexOf('%');
5318    if (percentPos > 0)
5319    {
5320      return hostAddress.substring(0, percentPos);
5321    }
5322    else
5323    {
5324      return hostAddress;
5325    }
5326  }
5327
5328
5329
5330  /**
5331   * Reads the bytes that comprise the specified file.
5332   *
5333   * @param  path  The path to the file to be read.
5334   *
5335   * @return  The bytes that comprise the specified file.
5336   *
5337   * @throws  IOException  If a problem occurs while trying to read the file.
5338   */
5339  @NotNull()
5340  public static byte[] readFileBytes(@NotNull final String path)
5341         throws IOException
5342  {
5343    return readFileBytes(new File(path));
5344  }
5345
5346
5347
5348  /**
5349   * Reads the bytes that comprise the specified file.
5350   *
5351   * @param  file  The file to be read.
5352   *
5353   * @return  The bytes that comprise the specified file.
5354   *
5355   * @throws  IOException  If a problem occurs while trying to read the file.
5356   */
5357  @NotNull()
5358  public static byte[] readFileBytes(@NotNull final File file)
5359         throws IOException
5360  {
5361    final ByteStringBuffer buffer = new ByteStringBuffer((int) file.length());
5362    buffer.readFrom(file);
5363    return buffer.toByteArray();
5364  }
5365
5366
5367
5368  /**
5369   * Reads the contents of the specified file as a string.  All line breaks in
5370   * the file will be preserved, with the possible exception of the one on the
5371   * last line.
5372   *
5373   * @param  path                   The path to the file to be read.
5374   * @param  includeFinalLineBreak  Indicates whether the final line break (if
5375   *                                there is one) should be preserved.
5376   *
5377   * @return  The contents of the specified file as a string.
5378   *
5379   * @throws  IOException  If a problem occurs while trying to read the file.
5380   */
5381  @NotNull()
5382  public static String readFileAsString(@NotNull final String path,
5383                                        final boolean includeFinalLineBreak)
5384         throws IOException
5385  {
5386    return readFileAsString(new File(path), includeFinalLineBreak);
5387  }
5388
5389
5390
5391  /**
5392   * Reads the contents of the specified file as a string.  All line breaks in
5393   * the file will be preserved, with the possible exception of the one on the
5394   * last line.
5395   *
5396   * @param  file                   The file to be read.
5397   * @param  includeFinalLineBreak  Indicates whether the final line break (if
5398   *                                there is one) should be preserved.
5399   *
5400   * @return  The contents of the specified file as a string.
5401   *
5402   * @throws  IOException  If a problem occurs while trying to read the file.
5403   */
5404  @NotNull()
5405  public static String readFileAsString(@NotNull final File file,
5406                                        final boolean includeFinalLineBreak)
5407         throws IOException
5408  {
5409    final ByteStringBuffer buffer = new ByteStringBuffer((int) file.length());
5410    buffer.readFrom(file);
5411
5412    if (! includeFinalLineBreak)
5413    {
5414      if (buffer.endsWith(EOL_BYTES_CR_LF))
5415      {
5416        buffer.setLength(buffer.length() - EOL_BYTES_CR_LF.length);
5417      }
5418      else if (buffer.endsWith(EOL_BYTES_LF))
5419      {
5420        buffer.setLength(buffer.length() - EOL_BYTES_LF.length);
5421      }
5422    }
5423
5424    return buffer.toString();
5425  }
5426
5427
5428
5429  /**
5430   * Reads the lines that comprise the specified file.
5431   *
5432   * @param  path  The path to the file to be read.
5433   *
5434   * @return  The lines that comprise the specified file.
5435   *
5436   * @throws  IOException  If a problem occurs while trying to read the file.
5437   */
5438  @NotNull()
5439  public static List<String> readFileLines(@NotNull final String path)
5440         throws IOException
5441  {
5442    return readFileLines(new File(path));
5443  }
5444
5445
5446
5447  /**
5448   * Reads the lines that comprise the specified file.
5449   *
5450   * @param  file  The file to be read.
5451   *
5452   * @return  The lines that comprise the specified file.
5453   *
5454   * @throws  IOException  If a problem occurs while trying to read the file.
5455   */
5456  @NotNull()
5457  public static List<String> readFileLines(@NotNull final File file)
5458         throws IOException
5459  {
5460    try (FileReader fileReader = new FileReader(file);
5461         BufferedReader bufferedReader = new BufferedReader(fileReader))
5462    {
5463      final List<String> lines = new ArrayList<>();
5464      while (true)
5465      {
5466        final String line = bufferedReader.readLine();
5467        if (line == null)
5468        {
5469          return Collections.unmodifiableList(lines);
5470        }
5471
5472        lines.add(line);
5473      }
5474    }
5475  }
5476
5477
5478
5479  /**
5480   * Writes the provided bytes to the specified file.  If the file already
5481   * exists, it will be overwritten.
5482   *
5483   * @param  path   The path to the file to be written.
5484   * @param  bytes  The bytes to be written to the specified file.
5485   *
5486   * @throws  IOException  If a problem is encountered while writing the file.
5487   */
5488  public static void writeFile(@NotNull final String path,
5489                               @NotNull final byte[] bytes)
5490         throws IOException
5491  {
5492    writeFile(new File(path), bytes);
5493  }
5494
5495
5496
5497  /**
5498   * Writes the provided bytes to the specified file.  If the file already
5499   * exists, it will be overwritten.
5500   *
5501   * @param  file   The file to be written.
5502   * @param  bytes  The bytes to be written to the specified file.
5503   *
5504   * @throws  IOException  If a problem is encountered while writing the file.
5505   */
5506  public static void writeFile(@NotNull final File file,
5507                               @NotNull final byte[] bytes)
5508         throws IOException
5509  {
5510    try (FileOutputStream outputStream = new FileOutputStream(file))
5511    {
5512      outputStream.write(bytes);
5513    }
5514  }
5515
5516
5517
5518  /**
5519   * Writes the provided lines to the specified file, with each followed by an
5520   * appropriate end-of-line marker for the current platform.  If the file
5521   * already exists, it will be overwritten.
5522   *
5523   * @param  path   The path to the file to be written.
5524   * @param  lines  The lines to be written to the specified file.
5525   *
5526   * @throws  IOException  If a problem is encountered while writing the file.
5527   */
5528  public static void writeFile(@NotNull final String path,
5529                               @NotNull final CharSequence... lines)
5530         throws IOException
5531  {
5532    writeFile(new File(path), lines);
5533  }
5534
5535
5536
5537  /**
5538   * Writes the provided lines to the specified file, with each followed by an
5539   * appropriate end-of-line marker for the current platform.  If the file
5540   * already exists, it will be overwritten.
5541   *
5542   * @param  file   The file to be written.
5543   * @param  lines  The lines to be written to the specified file.
5544   *
5545   * @throws  IOException  If a problem is encountered while writing the file.
5546   */
5547  public static void writeFile(@NotNull final File file,
5548                               @NotNull final CharSequence... lines)
5549         throws IOException
5550  {
5551    writeFile(file, toList(lines));
5552  }
5553
5554
5555
5556  /**
5557   * Writes the provided lines to the specified file, with each followed by an
5558   * appropriate end-of-line marker for the current platform.  If the file
5559   * already exists, it will be overwritten.
5560   *
5561   * @param  path   The path to the file to be written.
5562   * @param  lines  The lines to be written to the specified file.
5563   *
5564   * @throws  IOException  If a problem is encountered while writing the file.
5565   */
5566  public static void writeFile(@NotNull final String path,
5567                          @Nullable final List<? extends CharSequence> lines)
5568         throws IOException
5569  {
5570    writeFile(new File(path), lines);
5571  }
5572
5573
5574
5575  /**
5576   * Writes the provided lines to the specified file, with each followed by an
5577   * appropriate end-of-line marker for the current platform.  If the file
5578   * already exists, it will be overwritten.
5579   *
5580   * @param  file   The file to be written.
5581   * @param  lines  The lines to be written to the specified file.
5582   *
5583   * @throws  IOException  If a problem is encountered while writing the file.
5584   */
5585  public static void writeFile(@NotNull final File file,
5586                          @Nullable final List<? extends CharSequence> lines)
5587         throws IOException
5588  {
5589    try (PrintWriter writer = new PrintWriter(file))
5590    {
5591      if (lines != null)
5592      {
5593        for (final CharSequence line : lines)
5594        {
5595          writer.println(line);
5596        }
5597      }
5598    }
5599  }
5600}