content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
  package org.apache.causeway.commons.internal.os;
  
  import java.io.File;
  import java.io.FileWriter;
  import java.net.NetworkInterface;
  import java.nio.charset.StandardCharsets;
  import java.util.Collections;
  import java.util.OptionalLong;
  
  import org.jspecify.annotations.Nullable;
  
  import org.apache.causeway.commons.internal.base._NullSafe;
  import org.apache.causeway.commons.internal.base._Strings;
  import org.apache.causeway.commons.internal.exceptions._Exceptions;
  import org.apache.causeway.commons.io.TextUtils;
  
  import lombok.SneakyThrows;
  import lombok.experimental.UtilityClass;
  
  /**
   * <h1>- internal use only -</h1>
   * <p>
   * Provides some OS related utilities.
   * </p>
   * <p>
   * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
   * These may be changed or removed without notice!
   * </p>
   *
   * @since 2.0
   */
  @UtilityClass
  public class _OsUtil {
  
      public static enum OS {
          WINDOWS,
          LINUX,
          MAC_OS,
          OTHER;
          public boolean isWindows() { return this==WINDOWS; }
          public boolean isLinux() { return this==LINUX; }
          public boolean isMacOs() { return this==MAC_OS; }
          public static OS current() {
              var osName = System.getProperty("os.name").toLowerCase();
              if (osName.startsWith("windows")) {
                  return OS.WINDOWS;
              }
              if (osName.contains("linux")) {
                  return OS.LINUX;
              }
              if (osName.contains("mac")) {
                  return OS.MAC_OS;
              }
              return OS.OTHER;
          }
      }
  
      /**
       * Uses given {@code pidFile} as a way to communicate between processes,
       * whether there is already a running one.
       * And if so, terminates the already running one.
       * <p>
       * This is useful for web-app development from within an IDE, such that,
       * when placed within the app's {@code main} method, a previous run can be
       * auto-terminated.
       * <p>Example<pre>
       * void main(String[] args) {
       *     _OsUtil.thereCanBeOnlyOne(new File("pid.log"));
       *     ...
       * }
       * </pre>
       */
      @SneakyThrows
      public void thereCanBeOnlyOne(final File pidFile) {
  
          if(pidFile.exists()) {
              TextUtils.readLinesFromFile(pidFile, StandardCharsets.UTF_8)
              .filter(_Strings::isNotEmpty)
              .getFirst()
              .ifPresent(pid->terminateProcessByPid(pid));
          }
  
          final long newPid = ProcessHandle.current().pid();
          try(var fw = new FileWriter(pidFile, StandardCharsets.UTF_8)) {
              fw.write("" + newPid);
          }
      }
  
      /**
       * Terminates an instance of this application,
       * if there is an already running one found.
       * <p>
       * Use in conjunction with system environment variable
       * {@code THERE_CAN_BE_ONLY_ONE=true}, in order to switch this feature on.
       * @see #thereCanBeOnlyOne(File)
       */
      public void thereCanBeOnlyOne() {
          if("true".equalsIgnoreCase(System.getenv("THERE_CAN_BE_ONLY_ONE"))) {
              _OsUtil.thereCanBeOnlyOne(new File("pid.log"));
          }
      }
  
      @SneakyThrows
      public void terminateProcessByPid(final @Nullable String pid) {
          var pidTrimmed = _Strings.blankToNullOrTrim(pid);
          if(pidTrimmed==null) {
              return; // do nothing
          }
          var rt = Runtime.getRuntime();
          var os = OS.current();
          final String[] cmd;
          switch(os) {
          case WINDOWS:
              cmd = new String[] {"taskkill.exe", "/F", "/PID", pidTrimmed, "/T"};
              break;
          case LINUX:
              //XXX implement eventually
          case MAC_OS:
              //XXX implement eventually
          default:
              throw _Exceptions.unsupportedOperation("OS " + os + " not (yet) supported");
          }
          rt.exec(cmd);
      }
  
      /**
       * Optionally returns a machine specific unique number, based on whether
       * the algorithm was able to generate one.
       * <p>
       * Should at least be stable until machine reboot.
       */
      public OptionalLong machineId() {
          try {
  
              long hash = 5381L;
              boolean valid = false;
  
              for (NetworkInterface netint : Collections.list(NetworkInterface.getNetworkInterfaces())) {
                  var hwAddr = netint.getHardwareAddress();
                  if(_NullSafe.size(hwAddr)<6) {
                      continue;
                  }
                  for(byte b:hwAddr) {
                      hash = hash*33L + b;
                  }
                  valid = true;
              }
  
              if(valid) {
                  return OptionalLong.of(hash);
              }
  
              // fallback to empty
  
          } catch (Throwable e) {
              // fallback to empty
          }
          return OptionalLong.empty();
      }
  
  }
  