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