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