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