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