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