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