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