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