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