content: package com.termux.shared.file.filesystem;
  
  import android.system.Os;
  
  import androidx.annotation.NonNull;
  
  import com.termux.shared.logger.Logger;
  
  import java.io.File;
  
  public class FileTypes {
  
      /** Flags to represent regular, directory and symlink file types defined by {@link FileType} */
      public static final int FILE_TYPE_NORMAL_FLAGS = FileType.REGULAR.getValue() | FileType.DIRECTORY.getValue() | FileType.SYMLINK.getValue();
  
      /** Flags to represent any file type defined by {@link FileType} */
      public static final int FILE_TYPE_ANY_FLAGS = Integer.MAX_VALUE; // 1111111111111111111111111111111 (31 1's)
  
      public static String convertFileTypeFlagsToNamesString(int fileTypeFlags) {
          StringBuilder fileTypeFlagsStringBuilder = new StringBuilder();
  
          FileType[] fileTypes = {FileType.REGULAR, FileType.DIRECTORY, FileType.SYMLINK, FileType.CHARACTER, FileType.FIFO, FileType.BLOCK, FileType.UNKNOWN};
          for (FileType fileType : fileTypes) {
              if ((fileTypeFlags & fileType.getValue()) > 0)
                  fileTypeFlagsStringBuilder.append(fileType.getName()).append(",");
          }
  
          String fileTypeFlagsString = fileTypeFlagsStringBuilder.toString();
  
          if (fileTypeFlagsString.endsWith(","))
              fileTypeFlagsString = fileTypeFlagsString.substring(0, fileTypeFlagsString.lastIndexOf(","));
  
          return fileTypeFlagsString;
      }
  
      /**
       * Checks the type of file that exists at {@code filePath}.
       *
       * Returns:
       * - {@link FileType#NO_EXIST} if {@code filePath} is {@code null}, empty, an exception is raised
       *      or no file exists at {@code filePath}.
       * - {@link FileType#REGULAR} if file at {@code filePath} is a regular file.
       * - {@link FileType#DIRECTORY} if file at {@code filePath} is a directory file.
       * - {@link FileType#SYMLINK} if file at {@code filePath} is a symlink file and {@code followLinks} is {@code false}.
       * - {@link FileType#CHARACTER} if file at {@code filePath} is a character special file.
       * - {@link FileType#FIFO} if file at {@code filePath} is a fifo special file.
       * - {@link FileType#BLOCK} if file at {@code filePath} is a block special file.
       * - {@link FileType#UNKNOWN} if file at {@code filePath} is of unknown type.
       *
       * The {@link File#isFile()} and {@link File#isDirectory()} uses {@link Os#stat(String)} system
       * call (not {@link Os#lstat(String)}) to check file type and does follow symlinks.
       *
       * The {@link File#exists()} uses {@link Os#access(String, int)} system call to check if file is
       * accessible and does not follow symlinks. However, it returns {@code false} for dangling symlinks,
       * on android at least. Check https://stackoverflow.com/a/57747064/14686958
       *
       * Basically {@link File} API is not reliable to check for symlinks.
       *
       * So we get the file type directly with {@link Os#lstat(String)} if {@code followLinks} is
       * {@code false} and {@link Os#stat(String)} if {@code followLinks} is {@code true}. All exceptions
       * are assumed as non-existence.
       *
       * The {@link org.apache.commons.io.FileUtils#isSymlink(File)} can also be used for checking
       * symlinks but {@link FileAttributes} will provide access to more attributes if necessary,
       * including getting other special file types considering that {@link File#exists()} can't be
       * used to reliably check for non-existence and exclude the other 3 file types. commons.io is
       * also not compatible with android < 8 for many things.
       *
       * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/java/io/File.java;l=793
       * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/java/io/UnixFileSystem.java;l=248
       * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/native/UnixFileSystem_md.c;l=121
       * https://cs.android.com/android/_/android/platform/libcore/+/001ac51d61ad7443ba518bf2cf7e086efe698c6d
       * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/luni/src/main/java/libcore/io/Os.java;l=51
       * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/luni/src/main/java/libcore/io/Libcore.java;l=45
       * https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7530
       *
       * @param filePath The {@code path} for file to check.
       * @param followLinks The {@code boolean} that decides if symlinks will be followed while
       *                       finding type. If set to {@code true}, then type of symlink target will
       *                       be returned if file at {@code filePath} is a symlink. If set to
       *                       {@code false}, then type of file at {@code filePath} itself will be
       *                       returned.
       * @return Returns the {@link FileType} of file.
       */
      @NonNull
      public static FileType getFileType(final String filePath, final boolean followLinks) {
          if (filePath == null || filePath.isEmpty()) return FileType.NO_EXIST;
  
          try {
              FileAttributes fileAttributes = FileAttributes.get(filePath, followLinks);
              return getFileType(fileAttributes);
          } catch (Exception e) {
              // If not a ENOENT (No such file or directory) exception
              if (e.getMessage() != null && !e.getMessage().contains("ENOENT"))
                  Logger.logError("Failed to get file type for file at path \"" + filePath + "\": " + e.getMessage());
              return FileType.NO_EXIST;
          }
      }
  
      public static FileType getFileType(@NonNull final FileAttributes fileAttributes) {
          if (fileAttributes.isRegularFile())
              return FileType.REGULAR;
          else if (fileAttributes.isDirectory())
              return FileType.DIRECTORY;
          else if (fileAttributes.isSymbolicLink())
              return FileType.SYMLINK;
          else if (fileAttributes.isSocket())
              return FileType.SOCKET;
          else if (fileAttributes.isCharacter())
              return FileType.CHARACTER;
          else if (fileAttributes.isFifo())
              return FileType.FIFO;
          else if (fileAttributes.isBlock())
              return FileType.BLOCK;
          else
              return FileType.UNKNOWN;
      }
  
  }
  