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);
   }
}