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