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