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