001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.util; 019 020import org.apiguardian.api.API; 021import org.tquadrat.foundation.annotation.ClassVersion; 022import org.tquadrat.foundation.annotation.UtilityClass; 023import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError; 024import org.tquadrat.foundation.exception.ValidationException; 025import org.tquadrat.foundation.lang.CommonConstants; 026import org.tquadrat.foundation.lang.Lazy; 027 028import java.io.File; 029import java.math.BigInteger; 030import java.net.Inet4Address; 031import java.net.InetAddress; 032import java.net.NetworkInterface; 033import java.net.SocketException; 034import java.net.UnknownHostException; 035import java.time.Duration; 036import java.time.Instant; 037import java.time.ZoneId; 038import java.util.Collection; 039import java.util.HashMap; 040import java.util.HexFormat; 041import java.util.IllformedLocaleException; 042import java.util.List; 043import java.util.Locale; 044import java.util.Map; 045import java.util.Optional; 046import java.util.Random; 047import java.util.regex.Pattern; 048import java.util.regex.PatternSyntaxException; 049 050import static java.lang.Math.max; 051import static java.lang.String.format; 052import static java.lang.System.arraycopy; 053import static java.lang.System.currentTimeMillis; 054import static java.lang.System.getProperties; 055import static java.lang.System.getProperty; 056import static java.lang.System.nanoTime; 057import static java.lang.Thread.currentThread; 058import static java.net.InetAddress.getByName; 059import static java.util.Arrays.stream; 060import static java.util.Collections.list; 061import static java.util.Locale.ROOT; 062import static org.apiguardian.api.API.Status.DEPRECATED; 063import static org.apiguardian.api.API.Status.EXPERIMENTAL; 064import static org.apiguardian.api.API.Status.INTERNAL; 065import static org.apiguardian.api.API.Status.STABLE; 066import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_CPUARCHITECTURE; 067import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_OSNAME; 068import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_OSVERSION; 069import static org.tquadrat.foundation.lang.CommonConstants.PROPERTY_USER_NAME; 070import static org.tquadrat.foundation.lang.CommonConstants.TIME_DELTA_BEGINGREGORIAN2BEGINEPOCH; 071import static org.tquadrat.foundation.lang.Objects.isNull; 072import static org.tquadrat.foundation.lang.Objects.nonNull; 073import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 074import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 075import static org.tquadrat.foundation.util.StringUtils.splitString; 076 077/** 078 * This class provides some system related helper and convenience 079 * methods. 080 * 081 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 082 * @version $Id: SystemUtils.java 1182 2026-04-03 15:38:16Z tquadrat $ 083 * @since 0.0.5 084 * 085 * @UMLGraph.link 086 */ 087@SuppressWarnings( {"ClassWithTooManyMethods", "OverlyComplexClass"} ) 088@ClassVersion( sourceVersion = "$Id: SystemUtils.java 1182 2026-04-03 15:38:16Z tquadrat $" ) 089@API( status = STABLE, since = "0.0.5" ) 090@UtilityClass 091public final class SystemUtils 092{ 093 /*---------------*\ 094 ====** Inner Classes **==================================================== 095 \*---------------*/ 096 /** 097 * The operating system families that are supported (or not) by Java.<br> 098 * <br>Currently, we can distinguish only between Microsoft Windows, 099 * UNIX/Linux and macOS/OS-X. 100 * 101 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 102 * @version $Id: SystemUtils.java 1182 2026-04-03 15:38:16Z tquadrat $ 103 * @since 0.0.6 104 * 105 * @UMLGraph.link 106 * 107 * @see SystemUtils#determineOperatingSystem() 108 */ 109 @ClassVersion( sourceVersion = "$Id: SystemUtils.java 1182 2026-04-03 15:38:16Z tquadrat $" ) 110 @API( status = STABLE, since = "0.0.6" ) 111 public static enum OperatingSystem 112 { 113 /*------------------*\ 114 ====** Enum Declaration **============================================= 115 \*------------------*/ 116 /** 117 * The operating system is OS-X 118 */ 119 OSX, 120 121 /** 122 * The operating system is UNIX/Linux. 123 */ 124 UNIX, 125 126 /** 127 * The operating system is Microsoft Windows. 128 */ 129 WINDOWS, 130 131 /** 132 * The current operating system is unknown to this library. 133 */ 134 UNKNOWN; 135 136 /*---------*\ 137 ====** Methods **====================================================== 138 \*---------*/ 139 /** 140 * Returns the operating system architecture. 141 * 142 * @return The architecture. 143 */ 144 @SuppressWarnings( "static-method" ) 145 public final String getArchitecture() {return getProperty( PROPERTY_CPUARCHITECTURE ); } 146 147 /** 148 * Returns the name of the operating system. 149 * 150 * @return The operating system name. 151 */ 152 @SuppressWarnings( "static-method" ) 153 public final String getName() { return getProperty( PROPERTY_OSNAME ); } 154 155 /** 156 * Returns the name of the operating system. 157 * 158 * @return The operating system name. 159 */ 160 public final String getNameVersion() { return format( "%s %s (%s)", getName(), getVersion(), getArchitecture() ); } 161 162 /** 163 * Returns the version of the operating system. 164 * 165 * @return The version of the operating system. 166 */ 167 @SuppressWarnings( "static-method" ) 168 public final String getVersion() { return getProperty( PROPERTY_OSVERSION ); } 169 } 170 // enum OperatingSystem 171 172 /*-----------*\ 173 ====** Constants **======================================================== 174 \*-----------*/ 175 /** 176 * The valid bits for a node id: {@value}. 177 * 178 * @see #m_Node 179 */ 180 @API( status = INTERNAL, since = "0.1.0" ) 181 private static final long m_NodeIdBits = 0x0000FFFFFFFFFFFFL; 182 183 /** 184 * The sign bit for a node id: {@value}. 185 * 186 * @see #m_Node 187 */ 188 @API( status = INTERNAL, since = "0.1.0" ) 189 private static final long m_NodeIdSign = 0x0000010000000000L; 190 191 /** 192 * The length of a String containing a MAC address: {@value}. 193 */ 194 @API( status = STABLE, since = "0.0.5" ) 195 public static final int MAC_ADDRESS_Size = 17; 196 197 /** 198 * Factor for the conversion of milliseconds to nanoseconds. 199 */ 200 private static final BigInteger ONE_MILLION = BigInteger.valueOf( 1_000_000 ); 201 202 /** 203 * Factor for the conversion of seconds to milliseconds. 204 */ 205 private static final BigInteger ONE_THOUSAND = BigInteger.valueOf( 1_000 ); 206 207 /** 208 * The regular expression for a valid IPv4 address: {@value}. 209 */ 210 @API( status = STABLE, since = "0.0.5" ) 211 public static final String PATTERN_IPv4_ADDRESS = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; 212 213 /** 214 * <p>{@summary The name of the system property for the node id: 215 * {@value}.}</p> 216 * <p>The value has to be a positive number. It will be parsed by 217 * {@link Long#decode(String)}; 218 * this means, it could be a decimal, a hex or an octal number – so be 219 * careful with leading zeroes!</p> 220 * <p>This system property is not necessarily configured; if missing, the 221 * node id will be determined by the hardware address of the NIC.</p> 222 * 223 * @see #getNodeId() 224 */ 225 @API( status = STABLE, since = "0.1.0" ) 226 public static final String PROPERTY_NODE_ID = "org.tquadrat.foundation.util.SystemUtils.NodeId"; 227 228 /*------------*\ 229 ====** Attributes **======================================================= 230 \*------------*/ 231 /** 232 * The location or node id. 233 */ 234 private static Long m_Node = null; 235 236 /*------------------------*\ 237 ====** Static Initialisations **=========================================== 238 \*------------------------*/ 239 /** 240 * <p>{@summary The adjustment value that is used to align the time 241 * returned from 242 * {@link System#nanoTime()} 243 * with the system time.}</p> 244 * 245 * @see #currentTimeNanos() 246 */ 247 private static final BigInteger m_NanoAdjust; 248 249 /** 250 * The operating system. 251 */ 252 private static final Lazy<OperatingSystem> m_OperatingSystem; 253 254 /** 255 * The pattern to check a string for an IPv4 address. 256 */ 257 private static final Lazy<Pattern> m_PatternIPv4Address; 258 /** 259 * The random number generator. 260 */ 261 private static final Lazy<Random> m_Random; 262 263 static 264 { 265 //---* The node id *--------------------------------------------------- 266 final var nodeId = Long.getLong( PROPERTY_NODE_ID ); 267 if( nonNull( nodeId ) ) 268 { 269 m_Node = Long.valueOf( (nodeId.longValue() & m_NodeIdBits) | m_NodeIdSign ); 270 } 271 272 //---* The operating system *------------------------------------------ 273 m_OperatingSystem = Lazy.use( SystemUtils::determineOperatingSystem ); 274 275 //---* The pattern for the IP addresses *------------------------------ 276 m_PatternIPv4Address = Lazy.use( () -> 277 { 278 try 279 { 280 return Pattern.compile( PATTERN_IPv4_ADDRESS ); 281 } 282 catch( final PatternSyntaxException e ) 283 { 284 throw new ExceptionInInitializerError( e ); 285 } 286 } ); 287 288 //---* The random number generator *----------------------------------- 289 m_Random = Lazy.use( Random::new ); 290 291 //---* The time adjustment *------------------------------------------- 292 final var timeDelta = BigInteger.valueOf( TIME_DELTA_BEGINGREGORIAN2BEGINEPOCH ).multiply( ONE_THOUSAND ).multiply( ONE_MILLION ); 293 final var nanoNow = BigInteger.valueOf( nanoTime() ); 294 final var milliNow = BigInteger.valueOf( currentTimeMillis() ).multiply( ONE_MILLION ); 295 m_NanoAdjust = milliNow.subtract( nanoNow ).add( timeDelta ); 296 } 297 298 /*--------------*\ 299 ====** Constructors **===================================================== 300 \*--------------*/ 301 /** 302 * No instance allowed for this class. 303 */ 304 private SystemUtils() { throw new PrivateConstructorForStaticClassCalledError( SystemUtils.class ); } 305 306 /*---------*\ 307 ====** Methods **========================================================== 308 \*---------*/ 309 /** 310 * <p>{@summary Converts an IP address that is given as a string into an 311 * {@link InetAddress} 312 * object.}</p> 313 * 314 * TODO Currently this method do not process IPv6 addresses properly. 315 * 316 * @param ipAddress The string with the IP address. 317 * @return The {@code InetAddress} object. 318 * @throws IllegalArgumentException The given parameter is not a valid 319 * IP address. 320 */ 321 @API( status = EXPERIMENTAL, since = "0.0.5" ) 322 public static final InetAddress convertIPAddress( final CharSequence ipAddress ) 323 { 324 final var message = "IP address is invalid: %1$s"; 325 InetAddress retValue = null; 326 327 final var arg = requireNotEmptyArgument( ipAddress, "ipAddress" ).toString(); 328 if( m_PatternIPv4Address.get().matcher( arg ).matches() || arg.contains( ":" ) ) 329 { 330 try 331 { 332 retValue = getByName( arg ); 333 } 334 catch( final UnknownHostException e ) 335 { 336 throw new IllegalArgumentException( format( message, arg ), e ); 337 } 338 } 339 else 340 { 341 throw new IllegalArgumentException( format( message, arg ) ); 342 } 343 344 //---* Done *---------------------------------------------------------- 345 return retValue; 346 } // convertIPAddress() 347 348 /** 349 * <p>{@summary Returns a pseudo node id; this is useful in cases a 350 * machine does not have a network card (NIC) so that a MAC address could 351 * not be obtained, or if the real node id should not be used.}</p> 352 * <p>Each call to this method will return a new value.</p> 353 * 354 * @return The node id. 355 */ 356 @API( status = STABLE, since = "0.0.5" ) 357 public static final long createPseudoNodeId() 358 { 359 final var retValue = (getRandom().nextLong() & m_NodeIdBits) | m_NodeIdSign; 360 361 //---* Done *---------------------------------------------------------- 362 return retValue; 363 } // createPseudoNodeId() 364 365 /** 366 * Creates the alias map for the old (deprecated) zone ids that are used 367 * for the call to 368 * {@link ZoneId#of(String, java.util.Map)} 369 * to retrieve a 370 * {@link ZoneId} 371 * instance for the given zone id. 372 * 373 * @return The alias map. 374 * 375 * @since 0.0.6 376 * 377 * @deprecated Use 378 * {@link DateTimeUtils#createZoneIdAliasMap()} 379 * instead. 380 */ 381 @API( status = DEPRECATED, since = "0.0.6" ) 382 @Deprecated( since = "0.4.0", forRemoval = true ) 383 public static final Map<String,String> createZoneIdAliasMap() 384 { 385 return DateTimeUtils.createZoneIdAliasMap(); 386 } // createZoneIdAliasMap() 387 388 /** 389 * Returns the current time as nanoseconds since the beginning of the 390 * Gregorian calendar (1582-10-15T00:00). 391 * 392 * @return The current time in nanoseconds. 393 */ 394 @API( status = STABLE, since = "0.0.5" ) 395 public static final BigInteger currentTimeNanos() 396 { 397 final var retValue = m_NanoAdjust.add( BigInteger.valueOf( nanoTime() ) ); 398 399 //---* Done *---------------------------------------------------------- 400 return retValue; 401 } // currentTimeNanos() 402 403 /** 404 * Determines an IP address of the machine this program is running on. 405 * Usually this will be one of the addresses that is visible to the 406 * outside. In addition, the method has a clear precedence for IPv4 407 * addresses over IPv6 ones. 408 * 409 * @return An instance of 410 * {@link Optional} 411 * that holds the IP address; it will be empty if really no network 412 * adapter is active on this machine. 413 * @throws SocketException Problems to retrieve the internet address. 414 */ 415 @SuppressWarnings( "OverlyNestedMethod" ) 416 @API( status = STABLE, since = "0.0.5" ) 417 public static final Optional<InetAddress> determineIPAddress() throws SocketException 418 { 419 Optional<InetAddress> retValue = Optional.empty(); 420 421 List<InetAddress> addresses; 422 ScanLoop: for( final var networkInterface : getNetworkInterfaces() ) 423 { 424 /* 425 * Due to the nature of the scan logic, the returned address will 426 * always be that of the last NIC in sequence that is not the 427 * {@code loopback} interface. 428 */ 429 if( networkInterface.isUp() ) 430 { 431 addresses = list( networkInterface.getInetAddresses() ); 432 AddressLoop: for( final var address : addresses ) 433 { 434 /* 435 * The AddressLoop terminates early, when the first IPv4 436 * address for the NIC was found. 437 */ 438 if( networkInterface.isLoopback() ) 439 { 440 /* 441 * We take loopback if we do not get something 442 * better ... 443 */ 444 if( retValue.isEmpty() ) 445 { 446 retValue = Optional.of( address ); 447 if( address instanceof Inet4Address ) break AddressLoop; 448 } 449 else if( !(retValue.get() instanceof Inet4Address) ) 450 { 451 retValue = Optional.of( address ); 452 break AddressLoop; 453 } 454 } 455 else 456 { 457 retValue = Optional.of( address ); 458 if( address instanceof Inet4Address ) break AddressLoop; 459 } 460 } 461 } 462 } // ScanLoop: 463 464 //---* Done *---------------------------------------------------------- 465 return retValue; 466 } // determineIPAddress() 467 468 /** 469 * Determines all the IP addresses of the machine this program is running 470 * on. 471 * 472 * @return The IP addresses. 473 */ 474 @API( status = STABLE, since = "0.0.5" ) 475 public static final InetAddress [] determineIPAddresses() 476 { 477 final var retValue = getNetworkInterfaces().stream() 478 .flatMap( networkInterface -> list( networkInterface.getInetAddresses() ).stream() ) 479 .toArray( InetAddress []::new ); 480 481 //---* Done *---------------------------------------------------------- 482 return retValue; 483 } // determineIPAddresses() 484 485 /** 486 * Determines the operating system. 487 * 488 * @return The operating system. 489 */ 490 private static final OperatingSystem determineOperatingSystem() 491 { 492 /* 493 * TODO Add more criteria for each family. And perhaps it should be 494 * distinguished between Linux, BSD and Unix (and there between HP-UX 495 * and AIX) ... 496 */ 497 var retValue = OperatingSystem.UNKNOWN; 498 499 /* 500 * All currently known MS-DOS and Windows versions and their file 501 * systems use the backslash as the separator for folders. 502 * They also use the semicolon as the PATH separator. 503 */ 504 final var fileSeparator = File.separatorChar; 505 final var pathSeparator = File.pathSeparatorChar; 506 if( (fileSeparator == '\\') && (pathSeparator == ';') ) 507 { 508 retValue = OperatingSystem.WINDOWS; 509 } 510 else if( (fileSeparator == '/') && (pathSeparator == ':') ) 511 { 512 if( getProperty( PROPERTY_OSNAME ).toLowerCase( ROOT ).contains( "osx" ) ) 513 { 514 retValue = OperatingSystem.OSX; 515 } 516 else 517 { 518 retValue = OperatingSystem.UNIX; 519 } 520 } 521 522 //---* Done *---------------------------------------------------------- 523 return retValue; 524 } // determineOperatingSystem() 525 526 /** 527 * Determines those IP addresses of the machine this program is running 528 * on that are used to communicate with the outside world. This means that 529 * only those <i>active</i> network interfaces are considered that are 530 * <i>not</i> a {@code loopback} interface. 531 * 532 * @return The IP addresses. 533 */ 534 @API( status = STABLE, since = "0.0.5" ) 535 public static final InetAddress [] determineOutboundIPAddresses() 536 { 537 final var retValue = getNetworkInterfaces().stream() 538 .filter( networkInterface -> 539 { 540 try 541 { 542 return networkInterface.isUp() && !networkInterface.isLoopback(); 543 } 544 catch( final SocketException ignored ) 545 { 546 return false; 547 } 548 } ) 549 .flatMap( networkInterface -> list( networkInterface.getInetAddresses() ).stream() ) 550 .toArray( InetAddress []::new ); 551 552 //---* Done *---------------------------------------------------------- 553 return retValue; 554 } // determineOutboundIPAddresses() 555 556 /** 557 * Formats the given node id as a MAC address string. 558 * 559 * @param nodeId The node id. 560 * @return The MAC address string. 561 */ 562 @API( status = STABLE, since = "0.0.5" ) 563 public static final String formatNodeIdAsMAC( final long nodeId ) 564 { 565 if( nodeId != (nodeId & m_NodeIdBits) ) 566 { 567 throw new ValidationException( "Node id is invalid: %1$d".formatted( nodeId ) ); 568 } 569 570 final var hexFormat = HexFormat.of(); 571 final var bytes = hexFormat.parseHex( hexFormat.toHexDigits( nodeId, 12 ) ); 572 final var retValue = HexFormat.ofDelimiter( "-" ).withUpperCase().formatHex( bytes ); 573 574 //---* Done *---------------------------------------------------------- 575 return retValue; 576 } // formatNodeIdAsMAC() 577 578 /** 579 * Returns the MAC address for the current computer. In case the machine 580 * will have more than one network card (NIC), the returned MAC is 581 * selected in the same as for 582 * {@link #getNodeId()}. 583 * 584 * @return The MAC address. 585 */ 586 @API( status = STABLE, since = "0.0.5" ) 587 public static final String getMACAddress() { return formatNodeIdAsMAC( getNodeId() ); } 588 589 /** 590 * Returns a 591 * {@link Collection} 592 * of all the network interfaces on this machine. Instead of throwing an 593 * exception, the method will return an empty collection in case the 594 * machine do not have network configured. Otherwise, the collection 595 * contains at least one element, possibly representing a {@code loopback} 596 * interface that only supports communication between entities on this 597 * machine. 598 * 599 * @return The 600 * {@link NetworkInterface}s found on this machine. 601 * 602 * @see java.net.NetworkInterface#getNetworkInterfaces() 603 */ 604 @API( status = STABLE, since = "0.0.5" ) 605 public static final Collection<NetworkInterface> getNetworkInterfaces() 606 { 607 Collection<NetworkInterface> retValue = List.of(); 608 try 609 { 610 retValue = List.copyOf( list( java.net.NetworkInterface.getNetworkInterfaces() ) ); 611 } 612 catch( final SocketException ignored ) 613 { 614 /* 615 * There are no NICs on this machine, so we return the empty list. 616 */ 617 } 618 619 //---* Done *---------------------------------------------------------- 620 return retValue; 621 } // getNetworkInterfaces() 622 623 /** 624 * Returns the unique id of this node. 625 * 626 * @return The node id. 627 * 628 * @see #PROPERTY_NODE_ID 629 */ 630 @SuppressWarnings( "OverlyComplexMethod" ) 631 @API( status = STABLE, since = "0.0.5" ) 632 public static final long getNodeId() 633 { 634 if( isNull( m_Node ) ) 635 { 636 final var interfaces = getNetworkInterfaces(); 637 var randomGenerationRequired = interfaces.isEmpty(); 638 if( !randomGenerationRequired ) 639 { 640 var count = interfaces.size(); 641 ScanLoop: for( final var nic : interfaces ) 642 { 643 try 644 { 645 if( (--count > 0) && (nic.isLoopback() || nic.isVirtual()) ) 646 { 647 /* 648 * We prefer a real hardware NIC if we can get one. 649 */ 650 continue ScanLoop; 651 } 652 653 final var hardwareAddress = nic.getHardwareAddress(); 654 if( nonNull( hardwareAddress ) && (hardwareAddress.length > 0) ) 655 { 656 m_Node = Long.valueOf( new BigInteger( hardwareAddress ).longValue() & m_NodeIdBits ); 657 break ScanLoop; 658 } 659 } 660 catch( final SocketException ignored ) 661 { 662 /* 663 * If a SocketException is caught here, this indicates 664 * that we have problems to determine the NICs. In 665 * this case, we have to create a random node id. 666 */ 667 break ScanLoop; 668 } 669 } // ScanLoop: 670 671 /* 672 * If we still do not have a valid node id, it seems that none 673 * of the NICs have a hardware address; may be that they are 674 * NICs for a virtual machine or virtual NICs of some kind. 675 * But is it possible to have a NIC without a MAC? 676 */ 677 randomGenerationRequired = isNull( m_Node ); 678 } 679 680 if( randomGenerationRequired ) 681 { 682 //---* We have to create a random location *------------------- 683 m_Node = Long.valueOf( createPseudoNodeId() ); 684 } 685 } 686 687 final var retValue = m_Node.longValue(); 688 689 //---* Done *---------------------------------------------------------- 690 return retValue; 691 } // getNodeId() 692 693 /** 694 * Returns the current operating system. 695 * 696 * @return The operating system. 697 */ 698 @API( status = STABLE, since = "0.0.6" ) 699 public static final OperatingSystem getOperatingSystem() { return m_OperatingSystem.get(); } 700 701 /** 702 * Returns the PID, the process id, for the VM this (the current) program 703 * runs in. 704 * 705 * @return The PID. 706 * @throws UnsupportedOperationException The implementation of 707 * {@link ProcessHandle} 708 * that is currently in use does not support the operation 709 * {@link ProcessHandle#pid()}. 710 * 711 * @see ProcessHandle#pid() 712 */ 713 @API( status = STABLE, since = "0.0.5" ) 714 public static final long getPID() throws UnsupportedOperationException 715 { 716 final var retValue = ProcessHandle.current().pid(); 717 718 //---* Done *---------------------------------------------------------- 719 return retValue; 720 } // getPID() 721 722 /** 723 * Returns the system random number generator. 724 * 725 * @return The random number generator. 726 */ 727 @API( status = STABLE, since = "0.0.5" ) 728 public static final Random getRandom() { return m_Random.get(); } 729 730 /** 731 * Returns the current username from the system property 732 * {@value CommonConstants#PROPERTY_USER_NAME}. 733 * 734 * @return The username. 735 * 736 * @since 0.4.8 737 */ 738 @API( status = STABLE, since = "0.4.8" ) 739 public static final String getUserName() { return getProperty( PROPERTY_USER_NAME ); } 740 741 /** 742 * Returns the alias map; if not yet created, the alias map will be 743 * created by a call to 744 * {@link #createZoneIdAliasMap()} 745 * and the result to that call will be cached for future calls. 746 * 747 * @return The alias map. 748 * 749 * @see #createZoneIdAliasMap() 750 * 751 * @since 0.0.6 752 * 753 * @deprecated Use 754 * {@link DateTimeUtils#getZoneIdAliasMap()} 755 * instead. 756 */ 757 @API( status = DEPRECATED, since = "0.0.5" ) 758 @Deprecated( since = "0.4.0", forRemoval = true ) 759 public static final Map<String,String> getZoneIdAliasMap() { return DateTimeUtils.getZoneIdAliasMap(); } 760 761 /** 762 * Checks whether the current system has a network interface installed. 763 * 764 * @return {@code true} if the current system has a network interface 765 * installed, {@code false} otherwise. 766 */ 767 @API( status = STABLE, since = "0.0.5" ) 768 public static final boolean hasNetworkInterface() 769 { 770 final var retValue = !getNetworkInterfaces().isEmpty(); 771 772 //---* Done *---------------------------------------------------------- 773 return retValue; 774 } // hasNetworkInterface() 775 776 /** 777 * An implementation of 778 * {@link Thread#sleep(long)} 779 * that does not throw an exception in case it will be interrupted. 780 * 781 * @note While sleeping, the thread does not lose ownership of any 782 * monitors. 783 * 784 * @param millis The time to sleep in milliseconds. 785 * @return {@code true} if the sleep was interrupted, {@code false} if it 786 * terminated as planned. 787 */ 788 @SuppressWarnings( {"BooleanMethodNameMustStartWithQuestion", "BusyWait"} ) 789 @API( status = STABLE, since = "0.0.7" ) 790 public static final boolean repose( final long millis ) 791 { 792 var retValue = true; 793 final var endTimeMillis = currentTimeMillis() + millis; 794 SleepLoop: while( currentTimeMillis() < endTimeMillis ) 795 { 796 try 797 { 798 Thread.sleep( max( 0, endTimeMillis - currentTimeMillis() ) ); 799 retValue = false; 800 } 801 catch( final InterruptedException ignored ) 802 { 803 if( currentThread().isInterrupted() ) break SleepLoop; 804 } 805 } // SleepLoop: 806 807 //---* Done *---------------------------------------------------------- 808 return retValue; 809 } // repose() 810 811 /** 812 * An implementation of 813 * {@link Thread#sleep(Duration)} 814 * that does not throw an exception in case it will be interrupted. 815 * 816 * @note While sleeping, the thread does not lose ownership of any 817 * monitors. 818 * 819 * @param duration The time to sleep. 820 * @return {@code true} if the sleep was interrupted, {@code false} if it 821 * terminated as planned. 822 */ 823 @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" ) 824 @API( status = STABLE, since = "0.0.7" ) 825 public static final boolean repose( final Duration duration ) 826 { 827 var retValue = true; 828 var remainingDuration = requireNonNullArgument( duration, "duration" ); 829 var now = Instant.now(); 830 final var endTime = now.plus( duration ); 831 SleepLoop: while( retValue && now.isBefore( endTime ) ) 832 { 833 try 834 { 835 Thread.sleep( remainingDuration ); 836 retValue = false; 837 } 838 catch( final InterruptedException ignored ) 839 { 840 if( currentThread().isInterrupted() ) break SleepLoop; 841 now = Instant.now(); 842 remainingDuration = Duration.between( now, endTime ); 843 } 844 } // SleepLoop: 845 846 //---* Done *---------------------------------------------------------- 847 return retValue; 848 } // repose() 849 850 /** 851 * An implementation of 852 * {@link #sleepUntil(Instant)} 853 * that does not throw an exception when interrupted. 854 * 855 * @note While sleeping, the thread does not lose ownership of any 856 * monitors. 857 * 858 * @param until The end time for the sleep period. 859 * @return {@code true} if the sleep was interrupted, {@code false} if it 860 * terminated as planned. 861 */ 862 @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" ) 863 @API( status = STABLE, since = "0.0.7" ) 864 public static final boolean reposeUntil( final Instant until ) 865 { 866 var retValue = true; 867 SleepLoop: while( Instant.now().isBefore( requireNonNullArgument( until, "until" ) ) ) 868 { 869 /* 870 * It is intended that Instant::now will be called twice, returning 871 * different results. This will increase accuracy (OK, just 872 * marginally), without causing much harm – we want to waste time 873 * anyway. 874 */ 875 try 876 { 877 Thread.sleep( Duration.between( Instant.now(), until ) ); 878 retValue = false; 879 } 880 catch( final InterruptedException ignored ) 881 { 882 if( currentThread().isInterrupted() ) break SleepLoop; 883 } 884 } 885 886 //---* Done *---------------------------------------------------------- 887 return retValue; 888 } // reposeUntil() 889 /** 890 * Retrieves an instance of 891 * {@link Locale} 892 * for the given locale name. This method will look up the requested 893 * locale first in the locales returned by 894 * {@link Locale#getAvailableLocales()} 895 * before it will create a new instance of {@code Locale} using 896 * {@link java.util.Locale.Builder Locale.Builder}.<br> 897 * <br>The method will return 898 * {@link Optional#empty()} 899 * when the format of the given {@code localeName} is invalid.<br> 900 * <br>If none of the already existing instances of {@code Locale} matches 901 * the given name, the language part, the country part, the variant part 902 * and the script part (if provided) are used to create a new instance. If 903 * extensions are given, they will be ignored.<br> 904 * <br>If the given locale name is empty or contains only whitespace, the 905 * method will return 906 * {@link Locale#ROOT}. 907 * 908 * @param localeName The name of the locale. 909 * @return An instance of 910 * {@link Optional} 911 * that holds the instance of {@code Locale} for the given name. 912 */ 913 @SuppressWarnings( {"AssignmentToNull", "OverlyNestedMethod", "OverlyComplexMethod"} ) 914 @API( status = STABLE, since = "0.0.6" ) 915 public static final Optional<Locale> retrieveLocale( final CharSequence localeName ) 916 { 917 final var name = requireNonNullArgument( localeName, "localeName" ).toString(); 918 @SuppressWarnings( "OverlyLongLambda" ) 919 final var locale = name.isBlank() 920 ? ROOT 921 : stream( Locale.getAvailableLocales() ) 922 .filter( currentLocale -> currentLocale.toString().equals( name ) ) 923 .findFirst() 924 .orElseGet( () -> 925 { 926 Locale result = null; 927 final var parts = splitString( name, '_' ); 928 try 929 { 930 final var builder = new Locale.Builder(); 931 ComposeSwitch: result = switch( parts.length ) 932 { 933 case 1 -> builder.setLanguage( parts[0] ).build(); 934 case 2 -> builder.setLanguage( parts[0] ).setRegion( parts[1] ).build(); 935 case 3 -> { 936 builder.setLanguage( parts[0] ).setRegion( parts[1] ); 937 if( parts[2].startsWith( "#" ) ) 938 { 939 if( parts[2].length() == 4 ) 940 builder.setScript( parts[2].substring( 1 ) ); 941 } 942 else 943 { 944 builder.setVariant( parts[2] ); 945 } 946 yield builder.build(); 947 } 948 case 4 -> { 949 builder.setLanguage( parts[0] ).setRegion( parts[1] ); 950 if( parts[2].startsWith( "#" ) ) 951 { 952 builder.setScript( parts[2].substring( 1 ) ); 953 } 954 else 955 { 956 builder.setVariant( parts[2] ); 957 if( parts[3].startsWith( "#" ) ) 958 { 959 if( parts[3].length() == 4 ) 960 builder.setScript( parts[3].substring( 1 ) ); 961 } 962 } 963 yield builder.build(); 964 } 965 case 5 -> builder.setLanguage( parts[0] ).setRegion( parts[1] ).setVariant( parts[2] ).setScript( parts[3].substring( 1 ) ).build(); 966 default -> null; // Invalid input ... 967 }; // ComposeSwitch: 968 } 969 catch( final IllformedLocaleException ignored ) 970 { 971 result = null; 972 } 973 return result; 974 } ); 975 976 final var retValue = Optional.ofNullable( locale ); 977 978 //---* Done *---------------------------------------------------------- 979 return retValue; 980 } // retrieveLocale() 981 982 /** 983 * An implementation of 984 * {@link Thread#sleep} 985 * that takes an instance of 986 * {@link Duration} 987 * to determine the time to sleep. 988 * 989 * @note While sleeping, the thread does not lose ownership of any 990 * monitors. 991 * 992 * @param duration The time to sleep. 993 * @throws InterruptedException Another thread has interrupted the 994 * current thread (that one executing {@code sleep()}). The 995 * <i>interrupted status</i> of the current thread is cleared when 996 * this exception is thrown. 997 * 998 * @deprecated Use 999 * {@link Thread#sleep(Duration)} 1000 * instead. 1001 */ 1002 @API( status = DEPRECATED, since = "0.0.7" ) 1003 @Deprecated( since = "0.4.8", forRemoval = true ) 1004 public static final void sleep( final Duration duration ) throws InterruptedException 1005 { 1006 final var milliseconds = requireNonNullArgument( duration, "duration" ).toMillis(); 1007 Thread.sleep( milliseconds ); 1008 } // sleep() 1009 1010 /** 1011 * Causes the currently executing thread to sleep (temporarily cease 1012 * execution) until the specified time, subject to the precision and 1013 * accuracy of system timers and schedulers. The thread does not lose 1014 * ownership of any monitors. 1015 * 1016 * @note While sleeping, the thread does not lose ownership of any 1017 * monitors. 1018 * 1019 * @param until The end time for the sleep. 1020 * @throws InterruptedException Another thread has interrupted the 1021 * current thread (that one executing {@code sleep()}). The 1022 * <i>interrupted status</i> of the current thread is cleared when 1023 * this exception is thrown. 1024 * 1025 * @see Thread#sleep(long) 1026 * 1027 * @since 0.0.7 1028 */ 1029 @API( status = STABLE, since = "0.0.7" ) 1030 public static final void sleepUntil( final Instant until ) throws InterruptedException 1031 { 1032 final var milliseconds = max( 0, requireNonNullArgument( until, "until" ).toEpochMilli() - currentTimeMillis() ); 1033 Thread.sleep( milliseconds ); 1034 } // sleepUntil() 1035 1036 /** 1037 * <p>{@summary Returns the 1038 * {@linkplain System#getProperties() system properties}} 1039 * as a 1040 * {@link Map Map<String,String>}.</p> 1041 * <p>{@link System#getProperties()} 1042 * returns an instance of 1043 * {@link java.util.Properties}, and that is implementing 1044 * {@code Map<String,Object>}. Although it is unlikely that a value is 1045 * <i>not</i> a String (if not impossible …), this method allows 1046 * to enforce it.</p> 1047 * 1048 * @note A mere cast to {@code Map<String,String>} does not work 1049 * … 1050 * 1051 * @return An unmodifiable map with the system properties as Strings. 1052 */ 1053 public static final Map<String,String> systemPropertiesAsStringMap() 1054 { 1055 final Map<String,String> map = new HashMap<>(); 1056 final var properties = getProperties(); 1057 for( final var key : properties.stringPropertyNames() ) 1058 { 1059 map.put( key, properties.getProperty( key ) ); 1060 } 1061 final var retValue = Map.copyOf( map ); 1062 1063 //---* Done *---------------------------------------------------------- 1064 return retValue; 1065 } // systemPropertiesAsStringMap() 1066 1067 /** 1068 * <p>{@summary Translates a given MAC address into a numerical node 1069 * id.}</p> 1070 * <p>A MAC address is a string consisting of 12 hex digits, grouped by 1071 * two, each group separated by a hyphen: 1072 * <code><i>xx</i>-<i>xx</i>-<i>xx</i>-<i>xx</i>-<i>xx</i>-<i>xx</i></code></p> 1073 * 1074 * @param macAddress The MAC address to translate. 1075 * @return The node id. 1076 */ 1077 @API( status = STABLE, since = "0.0.5" ) 1078 public static final long translateMACToNodeId( final String macAddress ) 1079 { 1080 final var bytes = HexFormat.ofDelimiter( "-" ).withUpperCase().parseHex( requireNotEmptyArgument( macAddress, "macAddress" ) ); 1081 if( bytes.length != 6 ) 1082 { 1083 throw new ValidationException( "MAC address is invalid: %1$s".formatted( macAddress ) ); 1084 } 1085 final var bytes2 = new byte [7]; 1086 arraycopy( bytes, 0, bytes2, 1, 6 ); 1087 final var retValue = new BigInteger( bytes2 ).longValue(); 1088 1089 //---* Done *---------------------------------------------------------- 1090 return retValue; 1091 } // translateMACToNodeId() 1092} 1093// class SystemUtils 1094 1095/* 1096 * End of File 1097 */