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