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