package alma.acs.util; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import alma.acs.makesupport.AcsFileFinder; /** * Finds a specific file in the directory structure * made up of logically overlaid directories * with roots at $INTROOT, $INTLIST, $ACSROOT, * as specified in the system property <code>acs.system.path</code>. * Optionally also looks under $ACSDATA. * <p> * The first occurrence of a file is taken, so that "less permanent" root directories * such as $INTROOT are preferred over more permanent dirs such as $ACSROOT. * <p> * Implementation note: We do not use {@link AcsFileFinder} * because it would cache all files under the root directories, * which is too much for our purpose. * * @author hsommer * @since ACS 10.2, see http://jira.alma.cl/browse/COMP-5690 */ public class AcsFileFinderForDirectories { public static final String SEARCHPATH_PROPERTYNAME = "acs.system.path"; public static final String ACSDATA_PATH_PROPERTYNAME = "ACS.data"; private final Logger logger; private final List<File> rootDirs; /** * Creates an AcsFileFinderForDirectories that can be used for multiple file searches. * * @param logger * @param includeAcsdataDir If true then $ACSDATA is included in the list of root dirs, * as the last chance for finding a file. */ public AcsFileFinderForDirectories(Logger logger, boolean includeAcsdataDir) { this.logger = logger; String searchpath = System.getProperty(SEARCHPATH_PROPERTYNAME); List<String> rootDirNames = new ArrayList<String>(Arrays.asList(searchpath.split(File.pathSeparator + "+"))); if (includeAcsdataDir) { String acsdataDirName = System.getProperty(ACSDATA_PATH_PROPERTYNAME); rootDirNames.add(acsdataDirName); } // check and store directories rootDirs = new ArrayList<File>(); for (String rootDirName : rootDirNames) { if (!rootDirName.trim().isEmpty()) { File rootDir = new File(rootDirName); if (rootDir.exists() && rootDir.isDirectory() && rootDir.canRead()) { rootDirs.add(rootDir); } else { logger.fine("Dropping bad root dir '" + rootDirName + "'."); } } } } /** * This method is meant only for unit testing. */ List<File> getRootDirs() { return new ArrayList<File>(rootDirs); } /** * Searches for a single file with a known name. * * @param relativePath * The path underneath $INTROOT, $ACSROOT, for example "config" or "lib/oracle/hiddenJars" or "". * @param fileName * The file name (without path), e.g. "myConfigFile.txt". * @return The file found, or <code>null</code> if it wasn't found. * @throws NullPointerException * If <code>fileName</code> is <code>null</code> */ public File findFile(String relativePath, String fileName) { if (relativePath == null) { relativePath = ""; } for (File rootDir : rootDirs) { File dir = new File(rootDir, relativePath); File file = new File(dir, fileName); if (file.exists()) { return file; } } return null; } /** * More flexible file search, that allows the user to check the filename * in the supplied <code>filter</code> callback, for example to return * all "*.txt" files under a given relative directory "config/myBelovedTxtFiles". * <p> * For performance reasons (and to get simpler code) the method {@link #findFile(String, String)} * should be used instead whenever the single file name is known. * * @param relativePath The path underneath $INTROOT, $ACSROOT, for example "config" or "lib/oracle/hiddenJars". * @param filter Must decide which files match. * The <code>dir</code> callback argument will be consistent with the <code>relativePath</code> * and should not be considered in the FilenameFilter implementation that should focus * only on the <code>name</code> parameter. * @return The list of all files that matched the directory and filter, possibly empty. * If a certain file appeared under more than one root directory * (e.g. under both "$INTROOT/config/bla" and "$ACSROOT/config/bla") * then the first occurrence (from the less permanent location) is taken. */ public List<File> findFiles(String relativePath, FilenameFilter filter) { if (relativePath == null) { relativePath = ""; } Map<String, File> foundFiles = new LinkedHashMap<String, File>(); for (File rootDir : rootDirs) { File dir = new File(rootDir, relativePath); if (dir.exists()) { File[] matchedFiles = dir.listFiles(filter); for (File matchedFile : matchedFiles) { // Do not 'overwrite' a file with same relative path and name that was found under a previous rooDir String relativePathName = relativePath + File.separator + matchedFile.getName(); if (!foundFiles.containsKey(relativePathName)) { foundFiles.put(relativePathName, matchedFile); } } } } return new ArrayList<File>(foundFiles.values()); } }