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