code: package zemberek.core.io;
  
  import static com.google.common.base.Preconditions.checkArgument;
  import static com.google.common.base.Preconditions.checkNotNull;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileFilter;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.text.Collator;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  import java.util.Locale;
  import java.util.regex.Pattern;
  
  /**
   * Helper methods for File operations. File operations throws RuntimeException instead of
   * IOException.
   */
  public class Files {
  
    public static final Comparator<File> FILE_MODIFICATION_TIME_COMPARATOR_ASC = new FileModificationTimeComparatorAsc();
    public static final Comparator<File> FILE_MODIFICATION_TIME_COMPARATOR_DESC = new FileModificationTimeComparatorDesc();
  
    private Files() {
    }
  
    public static Comparator<File> getNameSortingComparator(final Locale locale) {
      return (file, file1) -> {
        Collator coll = Collator.getInstance(locale);
        return coll.compare(file.getName(), file1.getName());
      };
    }
  
    public static Comparator<File> getAbsolutePathSortingComparator(final Locale locale) {
      return (file, file1) -> {
        Collator coll = Collator.getInstance(locale);
        return coll.compare(file.getAbsolutePath(), file1.getAbsolutePath());
      };
    }
  
    public static Comparator<File> getNameSortingComparator() {
      return (file, file1) -> file.getName().compareToIgnoreCase(file1.getName());
    }
  
    public static Comparator<File> getAbsolutePathSortingComparator() {
      return (file, file1) -> file.getAbsolutePath().compareToIgnoreCase(file1.getAbsolutePath());
    }
  
    public static FileFilter extensionFilter(String... extensions) {
      if (extensions.length == 0) {
        return new AcceptAllFilter();
      }
      return new ExtensionFilter(extensions);
    }
  
    /**
     * This deletes files in a directory. it does not go into sub directories, and it does not delete
     * directories.
     *
     * @param files : zero or more files.
     */
    public static void deleteFiles(File... files) {
      for (File s : files) {
        if (s.exists() && !s.isDirectory()) {
          Files.deleteFiles(s);
        }
      }
    }
  
    /**
     * This deletes files and directories and all child directories and files.
     *
     * @param files : zero or more file or dorectory.
     */
    public static void deleteFilesAndDirs(File... files) {
      for (File file : files) {
        if (file.exists()) {
          if (file.isDirectory()) {
            deleteFilesAndDirs(file.listFiles());
          } else {
            file.delete();
          }
        }
      }
    }
  
    /**
     * Crawls into a directory and retrieves all the files in it and its sub directories.
     *
     * @param dir a File representing a directory
     * @return all the files in the
     */
    public static List<File> crawlDirectory(File dir) {
      return crawlDirectory(dir, new AcceptAllFilter());
    }
  
    /**
     * Crawls into a directory and retrieves all the files in it and its sub directories.
     *
     * @param dir a File representing a directory
     * @param comparator comparator to apply.
     * @return all the files in the
     */
    public static List<File> getFilesSorted(File dir, Comparator<File> comparator) {
      checkExistingDirectory(dir);
      List<File> files = Arrays.asList(dir.listFiles());
      files.sort(comparator);
      return files;
    }
  
    /**
     * Crawls into a directory and retrieves all the files in it and its sub directories.
     *
     * @param dir a File representing a directory
     * @param comparator comparator to apply.
     * @param filters filters to apply.
     * @return all the files in the
     */
    public static List<File> getFilesSorted(File dir, Comparator<File> comparator,
        FileFilter... filters) {
      checkExistingDirectory(dir);
      List<File> files = new ArrayList<>();
      for (File file : dir.listFiles()) {
        if (filters.length == 0) {
          files.add(file);
        } else {
          for (FileFilter filter : filters) {
            if (filter.accept(file)) {
              files.add(file);
              break;
            }
          }
        }
      }
      files.sort(comparator);
      return files;
    }
  
    /**
     * Crawls into a directory and retrieves all the files in it and its sub directories. Only the
     * files matching to the filter will be included.
     *
     * @param dir a File representing a directory
     * @param filters filter
     * @return all the files in the
     */
    public static List<File> crawlDirectory(File dir, FileFilter... filters) {
      return crawlDirectory(dir, true, filters);
    }
  
    /**
     * Crawls into a directory and retrieves all the files in it and its sub directories. Only the
     * files matching to the filter will be included.
     *
     * @param dir a File representing a directory
     * @param recurseSubDirs determines if it will recurse to the sub directories.
     * @param filters filter
     * @return all the files in the
     */
    public static List<File> crawlDirectory(File dir, boolean recurseSubDirs, FileFilter... filters) {
      checkNotNull(dir, "File is null!");
      checkExistingDirectory(dir);
      List<File> files = new ArrayList<>();
      for (File file : dir.listFiles()) {
        if (file.isDirectory() && recurseSubDirs) {
          files.addAll(crawlDirectory(file, true, filters));
        } else if (!file.isDirectory()) {
          if (filters.length == 0) {
            files.add(file);
          } else {
            for (FileFilter filter : filters) {
              if (filter.accept(file)) {
                files.add(file);
                break;
              }
            }
          }
        }
      }
      return files;
    }
  
    private static void checkExistingDirectory(File dir) {
      checkNotNull(dir, "Dir is null!");
      checkArgument(dir.exists(), "Directory does not exist! : " + dir);
      checkArgument(dir.isDirectory(), "i was expecting a directory : " + dir);
    }
  
    /**
     * get all directories under root dir.
     *
     * @param rootDir root dir to scan
     * @param recurseSubDirs if true, sub directories are also scanned.
     * @return List of directories.
     */
    public static List<File> getDirectories(File rootDir, boolean recurseSubDirs) {
      checkNotNull(rootDir, "File is null!");
      checkArgument(rootDir.isDirectory(), "i was expecting a directory..");
      checkArgument(rootDir.exists(), "Directory does not exist!.");
      List<File> dirs = new ArrayList<>();
      for (File dir : rootDir.listFiles()) {
        if (dir.isDirectory()) {
          if (recurseSubDirs) {
            dirs.addAll(getDirectories(dir, true));
          }
          dirs.add(dir);
        }
      }
      return dirs;
    }
  
    /**
     * calculates MD5 of a file.
     *
     * @param file file name
     * @return MD5 result as a byte array.
     * @throws java.io.IOException if an IO error occurs.
     */
    public static byte[] calculateMD5(File file) throws IOException {
      checkIfFileExist(file);
      return IOs.calculateMD5(new FileInputStream(file));
    }
  
    private static void checkIfFileExist(File file) {
      checkNotNull(file, "file is null!");
      checkArgument(file.exists(), "File does not exist");
      checkArgument(!file.isDirectory(), "This is a directory, not a file..");
    }
  
    /**
     * Copies all files under srcDir to dstDir. If dstDir does not exist, it will be created.
     *
     * @param srcDir Source directory
     * @param dstDir destination directory
     * @throws java.io.IOException if an exception occurs during copy operation.
     */
    public static void copyDirectory(File srcDir, File dstDir) throws IOException {
      if (srcDir.isDirectory()) {
        java.nio.file.Files.createDirectories(dstDir.toPath());
        String[] children = srcDir.list();
        for (String aChildren : children) {
          copyDirectory(
              new File(srcDir, aChildren),
              new File(dstDir, aChildren));
        }
      } else {
        copy(srcDir, dstDir);
      }
    }
  
    /**
     * copies one file to another.
     *
     * @param src source file
     * @param dst destination file
     * @throws java.io.IOException if there is an error occurs during the read or write of the files
     */
    public static void copy(File src, File dst) throws IOException {
      IOs.copy(new FileInputStream(src), new FileOutputStream(dst));
    }
  
    /**
     * compares two input file contents.
     *
     * @param file1 first file name
     * @param file2 second file name
     * @return true if contents of two file contents are equal.
     * @throws NullPointerException if one of the filename is null
     * @throws java.io.IOException if an IO exception occurs while reading streams.
     */
    public static boolean contentEquals(File file1, File file2) throws IOException {
      checkNotNull(file1, "file1 cannot be null.");
      checkNotNull(file2, "file2 cannot be null.");
      checkIfFileExist(file1);
      checkIfFileExist(file2);
      return file1.length() == file2.length() &&
          IOs.contentEquals(new FileInputStream(file1), new FileInputStream(file2));
    }
  
    /**
     * checks if input file contains UTF-8 BOM information. note that it is not required that UTF-8
     * files carries this information.
     *
     * @param file file to be checked.
     * @return true if file contains UTF-8 bom data
     * @throws java.io.IOException if there is an error during file access.
     */
    public static boolean containsUTF8Bom(File file) throws IOException {
      return IOs.containsUTF8Bom(new FileInputStream(file));
    }
  
    /**
     * merges files into a single file. if the target file already exist, it appends it. if target
     * does not exist, it creates it.
     *
     * @param target target file to be appended.
     * @param filesToAppend filesToMerge
     * @throws java.io.IOException if there is an error during appending files.
     */
    public static void appendFiles(File target, File... filesToAppend) throws IOException {
      BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target, true));
      try {
        for (File file : filesToAppend) {
          IOs.copy(new BufferedInputStream(new FileInputStream(file)), bos, true);
        }
      } finally {
        IOs.closeSilently(bos);
      }
    }
  
    /**
     * Appends files to an output stream. Output stream is not closed after operation is finished.
     *
     * @param os output stream to be appended.
     * @param filesToAppend filesToMerge
     * @throws java.io.IOException if there is an error during appending files.
     */
    public static void appendFiles(OutputStream os, File... filesToAppend) throws IOException {
      for (File file : filesToAppend) {
        IOs.copy(new BufferedInputStream(new FileInputStream(file)), os, true);
      }
    }
  
    /**
     * This method dumps the contents of a file in hex format to console.
     *
     * @param f file to hex dump.
     * @param amount amount of bytes to write. if this value is -1, then it dupms all the content.
     * @throws java.io.IOException if there is a problem accessing the file.
     */
    public static void hexDump(File f, long amount) throws IOException {
      Dumper.hexDump(new FileInputStream(f), System.out, 20, amount);
    }
  
    /**
     * This method dumps the contents of a file in hex format to another file.
     *
     * @param f file to hex dump.
     * @param out the output file hex values will be written.
     * @param amount amount of bytes to write. if this value is -1, then it dupms all the content.
     * @throws java.io.IOException if there is a problem accessing the file.
     */
    public static void hexDump(File f, File out, long amount) throws IOException {
      Dumper.hexDump(new FileInputStream(f), new FileOutputStream(out), 20, amount);
    }
  
    private static class FileModificationTimeComparatorDesc implements Comparator<File> {
  
      public int compare(File f1, File f2) {
        if (f1.lastModified() > f2.lastModified()) {
          return 1;
        } else {
          return f1.lastModified() == f2.lastModified() ? 0 : -1;
        }
      }
    }
  
    private static class FileModificationTimeComparatorAsc implements Comparator<File> {
  
      public int compare(File f1, File f2) {
        if (f1.lastModified() < f2.lastModified()) {
          return 1;
        } else {
          return f1.lastModified() == f2.lastModified() ? 0 : -1;
        }
      }
    }
  
    /**
     * This is a file filter using regular expressions. if file path/name matches with regexp it will
     * accept.
     */
    public static class RegexpFilter implements FileFilter {
  
      final Pattern regexp;
  
      public RegexpFilter(String regExp) {
        checkNotNull(regExp, "regexp String cannot be null.");
        checkArgument(!Strings.isNullOrEmpty(regExp), "regexp String cannot be empty");
        this.regexp = Pattern.compile(regExp);
      }
  
      public RegexpFilter(Pattern regExp) {
        checkNotNull(regExp, "regexp Pattern cannot be null.");
        this.regexp = regExp;
      }
  
      public boolean accept(File pathname) {
        return regexp.matcher(pathname.getPath()).find();
      }
    }
  
    public static class ExtensionFilter implements FileFilter {
  
      private final String[] extensions;
  
      public ExtensionFilter(String... extensions) {
        this.extensions = extensions;
      }
  
      public boolean accept(File pathname) {
        if (extensions == null || extensions.length == 0) {
          return true;
        }
        for (String extension : extensions) {
          if (pathname.getName().endsWith(extension)) {
            return true;
          }
        }
        return false;
      }
    }
  
    public static class AcceptAllFilter implements FileFilter {
  
      public boolean accept(File pathname) {
        return true;
      }
    }
  }
  
  