# FileList.java

    /*
    ** Module   : FileList.java
    ** Abstract : base class for creating file lists
    **
    ** Copyright (c) 2005-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
    ** 001 GES 20050125   @19403 Created initial version supporting wildcards, searches of the current dir or a
    **                           user-defined directory and optional recursion.
    ** 002 GES 20050224   @19950 Added lexicographic sorting of the resulting file list.
    ** 003 ECF 20050315   @20341 Added methods to access the properties of the file list object:
    **                           getStartingDirectory, getFileSpecification, and isRecursive.
    ** 004 GES 20050316   @20400 Save off original (unmodified) file spec for use in reporting or debug purposes.
    ** 005 GES 20051005   @22973 Changed listImpl() to protected instead of private in order to allow subclasses
    **                           to override the list algorithm. Also made a protected default constructor.
    ** 006 GES 20060129   @24127 First version based on code moved from the FileSpecList class.
    ** 007 ECF 20190218          Implemented generics.
    ** 008 CA  20190703          Allow the files to be sorted by sub-classes.
    ** 009 GES 20210707          Added a helper to return the list with a given suffix.
    **     CA  20220412          Added file-set support (either -Z command-line option or p2j.cfg.xml
    **                           configuration).
    ** 010 ES  20250328          Added addAdditionalFiles and removeFiles methods to add/remove specific files.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import java.io.*;
    import java.util.*;
    import java.util.function.*;

    /**
     * Creates a sorted list of files that can be accessed as either an array
     * of filenames (strings) or as an array of <code>File</code> objects. No
     * directories will be returned in this list.  The list is sorted
     * lexicographically with controllable case-sensitivity (defaults to
     * case-insensitive).
     * <p>
     * Once {@link #list}, {@link #listFilenames} or {@link #size} is called
     * once, the results are cached for fast subsequent access.  Please note that
     * this means that any file system changes (e.g. directories or files
     * deleted/added) after that point will not be reflected in this instance's
     * data.
     *
     * @author    GES
     */
    public abstract class FileList
    {
       /** Determines if sorting should be case-sensitive. */
       private boolean caseSens = false;

       /** Stores cached results so subsequent access is fast. */
       private File[] files = null;

       /** Provides a comparator for sorting filenames. */
       protected static Comparator<File> compare = null;

       /**
        * Base constructor.
        *
        * @param    caseSensitive
        *           Sets the case-sensitivity of the sorting algorithm and legacy operating system.
        */
       public FileList(boolean caseSensitive)
       {
          caseSens = caseSensitive;

          if (compare == null)
          {
             // create a single instance of an anonymous inner class and save it
             // in the static member for future use
             compare = new Comparator<File>()
             {
                public int compare(File f1, File f2)
                {
                   String s1 = f1.getPath();

                   return caseSens ? s1.compareTo(f2.getPath())
                                   : s1.compareToIgnoreCase(f2.getPath());
                }
             };
          }
       }

       /**
        * Default constructor (provides a case-insensitive sort).
        */
       protected FileList()
       {
          this(false);
       }

       /**
        * Gets the case-sensitivity of the sorting algorithm and legacy operating system for this instance.
        *
        * @return   <code>true</code> if the legacy operating system is case-sensitive.
        */
       public boolean isCaseSensitive()
       {
          return caseSens;
       }

       /**
        * Sets the case-sensitivity of the sorting algorithm for this instance.
        *
        * @param    caseSens
        *           <code>true</code> if the sorting should be done on a case-
        *           sensitive basis.
        */
       public void setCaseSensitive(boolean caseSens)
       {
          this.caseSens = caseSens;
       }

       /**
        * Create and return the list of files based on user input specified
        * during construction.  No directories will be returned in the list
        * and only existing files will be returned.  The resulting list will be
        * sorted lexicographically.
        *
        * @return    An array of all files.
        */
       public File[] list()
       {
          if (files == null)
          {
             ArrayList<File> results = new ArrayList<>();

             listImpl(results);

             sortFiles(results);

             files = (File[]) results.toArray(new File[0]);
          }

          return files;
       }

       /**
        * Adds all the files from a set to files array.
        *
        * @param    newFiles.
        *           Set of files to be added to the list.
        */
       public File[] addAdditionalFiles(Set<File> newFiles)
       {
          files = list();

          ArrayList<File> results = new ArrayList<>(Arrays.asList(files));

          for (File file: newFiles)
          {
             results.add(file);
          }

          sortFiles(results);
          files = (File[]) results.toArray(new File[0]);

          return files;
       }

        /**
        * Removes a file from the files array.
        *
        * @param    removeFiles.
        *           Set of files to be removed from the list.
        */
       public File[] removeFiles(Set<File> removeFiles)
       throws IOException
       {
          files = list();

          Set<String> removeFileNames = new HashSet<>();
          List<File>  results         = new ArrayList<>(Arrays.asList(files));

          for (File file: removeFiles)
          {
             removeFileNames.add(file.getCanonicalPath());
          }

          for (int i = 0; i < files.length; i ++)
          {
             File file = files[i];

             String fileCanonicalName = file.getCanonicalPath();
             if (!removeFileNames.contains(fileCanonicalName))
             {
                results.add(file);
             }
          }

          sortFiles(results);
          files = (File[]) results.toArray(new File[0]);

          return files;
       }

       /**
        * List all files which match the specified filter.
        *
        * @param    filter
        *           The filter to apply.
        *
        * @return   The list of files matching the specified filter.
        */
       public String[] listFilenames(Function<String, Boolean> filter)
       {
          List<String> res = new ArrayList<>();

          for (String file : listFilenames())
          {
             if (filter.apply(file))
             {
                res.add(file);
             }
          }

          return res.toArray(new String[0]);
       }

       /**
        * Create and return the list of files based on user input specified during construction.  No directories
        * will be returned in the list. The resulting list will be sorted lexicographically.
        *
        * @return   The array of filenames.
        */
       public String[] listFilenames()
       {
          return withSuffix(null);
       }

       /**
        * Create and return the list of files based on user input specified during construction and the given
        * option suffix.  No directories will be returned in the list. The resulting list will be sorted
        * lexicographically.
        *
        * @param    suffix
        *           {@code null} for no suffix otherwise the suffix to append to the end of each filename.
        *
        * @return   The array of filenames.
        */
       public File[] listWithSuffix(String suffix)
       {
          // use the same core method which will handle sorting and filtering for us
          File[] files = list();

          if (suffix != null && suffix.length() > 0)
          {
             for (int i = 0; i < files.length; i++)
             {
                files[i] = new File(files[i].toString() + suffix);
             }
          }

          return files;
       }

       /**
        * Create and return the list of files based on user input specified during construction and the given
        * option suffix.  No directories will be returned in the list. The resulting list will be sorted
        * lexicographically.
        *
        * @param    suffix
        *           {@code null} for no suffix otherwise the suffix to append to the end of each filename.
        *
        * @return   The array of filenames.
        */
       public String[] withSuffix(String suffix)
       {
          // use the same core method which will handle sorting and filtering for us
          File[] files = list();

          String[] results = new String[files.length];

          // convert to a string array
          for (int i = 0; i < files.length; i++)
          {
             results[i] = files[i].toString();

             if (suffix != null && suffix.length() > 0)
             {
                results[i] += suffix;
             }
          }

          return results;
       }

       /**
        * Gets the size of the list of files.
        *
        * @return   The number of files in the list.
        */
       public int size()
       {
          if (files == null)
          {
             list();
          }

          return files.length;
       }

       /**
        * Core implementation of the listing algorithm which must make a filtered
        * list of just those files that exist (excluding any directories). All
        * results are added to the results list that is passed as a parameter.
        *
        * @param    results
        *           The list to which all matching files must be appended.
        */
       protected abstract void listImpl(List<File> results);

       /**
        * Sort the files using a comparator which is aware of the {@link #caseSens case sensitivity}.
        *
        * @param     files
        *            The file list to sort.
        */
       protected void sortFiles(List<File> files)
       {
          // sort the list so the output is not determined by the J2SE internal
          // hashing algorithms
          Collections.sort(files, compare);
       }
    }