All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jodd.io.findfile.FindFile Maven / Gradle / Ivy

There is a newer version: 5.3.0
Show newest version
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.io.findfile;

import jodd.inex.InExRules;
import jodd.io.FileNameUtil;
import jodd.io.FileUtil;
import jodd.util.MultiComparator;
import jodd.util.StringUtil;
import jodd.util.function.Consumers;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Consumer;

/**
 * Generic iterative file finder. Searches all files on specified search path.
 * By default, it starts in whitelist mode, where everything is excluded.
 * To search, you need to explicitly set include patterns. If no pattern is
 * set, then the search starts in blacklist mode, where everything is included (search all).
 *
 * @see WildcardFindFile
 * @see RegExpFindFile
 * @see InExRules
 */
public class FindFile implements Iterable {

	public static WildcardFindFile createWildcardFF() {
		return new WildcardFindFile();
	}
	public static RegExpFindFile createRegExpFF() {
		return new RegExpFindFile();
	}
	public static FindFile create() {
		return new FindFile();
	}

	/**
	 * Match type.
	 * @see FindFile#getMatchingFilePath(java.io.File)
	 * @see FindFile#acceptFile(java.io.File)
	 */
	public enum Match {
		/**
		 * Full, absolute path.
		 */
		FULL_PATH,
		/**
		 * Relative path from current root.
		 */
		RELATIVE_PATH,
		/**
		 * Just file name.
		 */
		NAME
	}

	// ---------------------------------------------------------------- flags

	protected boolean recursive;
	protected boolean includeDirs = true;
	protected boolean includeFiles = true;
	protected boolean walking = true;
	protected Match matchType = Match.FULL_PATH;

	/**
	 * Activates recursive search.
	 */
	public FindFile recursive(final boolean recursive) {
		this.recursive = recursive;
		return this;
	}

	/**
	 * Include directories in search.
	 */
	public FindFile includeDirs(final boolean includeDirs) {
		this.includeDirs = includeDirs;
		return this;
	}

	/**
	 * Include files in search.
	 */
	public FindFile includeFiles(final boolean includeFiles) {
		this.includeFiles = includeFiles;
		return this;
	}

	/**
	 * Sets the walking recursive mode. When walking mode is on (by default),
	 * folders are walked immediately. Although natural, for large
	 * set of files, this is not memory-optimal approach, since many
	 * files are held in memory, when going deeper.
	 * 

* When walking mode is turned off, folders are processed once * all files are processed, one after the other. The order is * not natural, but memory consumption is optimal. * @see #recursive(boolean) */ public FindFile walking(final boolean walking) { this.walking = walking; return this; } /** * Set {@link Match matching type}. */ public FindFile matchType(final Match match) { this.matchType = match; return this; } public FindFile matchOnlyFileName() { this.matchType = Match.NAME; return this; } public FindFile matchFullPath() { this.matchType = Match.FULL_PATH; return this; } public FindFile matchRelativePath() { this.matchType = Match.RELATIVE_PATH; return this; } // ---------------------------------------------------------------- consumer private Consumers consumers; /** * Registers file consumer */ public FindFile onFile(final Consumer fileConsumer) { if (consumers == null) { consumers = Consumers.of(fileConsumer); } else { consumers.add(fileConsumer); } return this; } // ---------------------------------------------------------------- search path /** * Specifies single search path. */ public FindFile searchPath(final File searchPath) { addPath(searchPath); return this; } /** * Specifies a set of search paths. */ public FindFile searchPath(final File... searchPath) { for (File file : searchPath) { addPath(file); } return this; } /** * Specifies the search path. If provided path contains * {@link File#pathSeparator} than string will be tokenized * and each part will be added separately as a search path. */ public FindFile searchPath(final String searchPath) { if (searchPath.indexOf(File.pathSeparatorChar) != -1) { String[] paths = StringUtil.split(searchPath, File.pathSeparator); for (String path : paths) { addPath(new File(path)); } } else { addPath(new File(searchPath)); } return this; } /** * Specifies search paths. * @see #searchPath(String) */ public FindFile searchPaths(final String... searchPaths) { for (String searchPath : searchPaths) { searchPath(searchPath); } return this; } /** * Specifies the search path. Throws an exception if URI is invalid. */ public FindFile searchPath(final URI searchPath) { File file; try { file = new File(searchPath); } catch (Exception ex) { throw new FindFileException("URI error: " + searchPath, ex); } addPath(file); return this; } /** * Specifies the search path. */ public FindFile searchPaths(final URI... searchPath) { for (URI uri : searchPath) { searchPath(uri); } return this; } /** * Specifies the search path. Throws an exception if URL is invalid. */ public FindFile searchPath(final URL searchPath) { File file = FileUtil.toContainerFile(searchPath); if (file == null) { throw new FindFileException("URL error: " + searchPath); } addPath(file); return this; } /** * Specifies the search path. */ public FindFile searchPaths(final URL... searchPath) { for (URL url : searchPath) { searchPath(url); } return this; } // ---------------------------------------------------------------- files iterator /** * Files iterator simply walks over files array. * Ignores null items. Consumed files are immediately * removed from the array. */ protected class FilesIterator { protected final File folder; protected final String[] fileNames; protected final File[] files; public FilesIterator(final File folder) { this.folder = folder; if (sortComparators != null) { this.files = folder.listFiles(); if (this.files != null) { Arrays.sort(this.files, new MultiComparator<>(sortComparators)); } this.fileNames = null; } else { this.files = null; this.fileNames = folder.list(); } } public FilesIterator(final String[] fileNames) { this.folder = null; if (sortComparators != null) { int fileNamesLength = fileNames.length; this.files = new File[fileNamesLength]; for (int i = 0; i < fileNamesLength; i++) { String fileName = fileNames[i]; if (fileName != null) { this.files[i] = new File(fileName); } } this.fileNames = null; } else { this.files = null; this.fileNames = fileNames; } } protected int index; /** * Returns next file or null * when no next file is available. */ public File next() { if (files != null) { return nextFile(); } else if (fileNames != null) { return nextFileName(); } else { return null; } } protected File nextFileName() { while (index < fileNames.length) { String fileName = fileNames[index]; if (fileName == null) { index++; continue; } fileNames[index] = null; index++; File file; if (folder == null) { file = new File(fileName); } else { file = new File(folder, fileName); } if (file.isFile()) { if (!includeFiles) { continue; } if (!acceptFile(file)) { continue; } } return file; } return null; } protected File nextFile() { while (index < files.length) { File file = files[index]; if (file == null) { index++; continue; } files[index] = null; index++; if (file.isFile()) { if (!includeFiles) { continue; } if (!acceptFile(file)) { continue; } } return file; } return null; } } // ---------------------------------------------------------------- matching protected final InExRules rules = createRulesEngine(); /** * Creates rule engine. */ protected InExRules createRulesEngine() { return new InExRules<>(); } /** * Defines include pattern. */ public FindFile include(final String pattern) { rules.include(pattern); return this; } /** * Defines include patterns. */ public FindFile include(final String... patterns) { for (String pattern : patterns) { rules.include(pattern); } return this; } /** * Enables whitelist mode. */ public FindFile excludeAll() { rules.whitelist(); return this; } /** * Enables blacklist mode. */ public FindFile includeAll() { rules.blacklist(); return this; } /** * Defines exclude pattern. */ public FindFile exclude(final String pattern) { rules.exclude(pattern); return this; } /** * Defines exclude patterns. */ public FindFile exclude(final String... patterns) { for (String pattern : patterns) { rules.exclude(pattern); } return this; } /** * Determine if file is accepted, based on include and exclude * rules. Called on each file entry (file or directory) and * returns true if file passes search criteria. * File is matched using {@link #getMatchingFilePath(java.io.File) matching file path}. * @see InExRules */ protected boolean acceptFile(final File file) { String matchingFilePath = getMatchingFilePath(file); if (rules.match(matchingFilePath)) { if (consumers != null) { consumers.accept(file); } return true; } return false; } /** * Resolves file path depending on {@link Match matching type} * Returned path is formatted in unix style. */ protected String getMatchingFilePath(final File file) { String path = null; switch (matchType) { case FULL_PATH: path = file.getAbsolutePath(); break; case RELATIVE_PATH: path = file.getAbsolutePath(); path = path.substring(rootPath.length()); break; case NAME: path = file.getName(); } path = FileNameUtil.separatorsToUnix(path); return path; } // ---------------------------------------------------------------- next file protected LinkedList pathList; protected LinkedList pathListOriginal; protected LinkedList todoFolders; protected LinkedList todoFiles; protected File lastFile; protected File rootFile; protected String rootPath; /** * Returns last founded file. * Returns null at the very beginning. */ public File lastFile() { return lastFile; } /** * Adds existing search path to the file list. * Non existing files are ignored. * If path is a folder, it will be scanned for all files. */ protected void addPath(final File path) { if (!path.exists()) { return; } if (pathList == null) { pathList = new LinkedList<>(); } pathList.add(path); } /** * Reset the search so it can be run again with very * same parameters (and sorting options). */ public void reset() { pathList = pathListOriginal; pathListOriginal = null; todoFiles = null; lastFile = null; rules.reset(); } /** * Finds the next file. Returns founded file that matches search configuration * or null if no more files can be found. */ public File nextFile() { if (todoFiles == null) { init(); } while (true) { // iterate files if (!todoFiles.isEmpty()) { FilesIterator filesIterator = todoFiles.getLast(); File nextFile = filesIterator.next(); if (nextFile == null) { todoFiles.removeLast(); continue; } if (nextFile.isDirectory()) { if (!walking) { todoFolders.add(nextFile); continue; } // walking if (recursive) { todoFiles.add(new FilesIterator(nextFile)); } if (includeDirs) { if (acceptFile(nextFile)) { lastFile = nextFile; return nextFile; } } continue; } lastFile = nextFile; return nextFile; } // process folders File folder; boolean initialDir = false; if (todoFolders.isEmpty()) { if (pathList.isEmpty()) { // the end return null; } folder = pathList.removeFirst(); rootFile = folder; rootPath = rootFile.getAbsolutePath(); initialDir = true; } else { folder = todoFolders.removeFirst(); } if ((initialDir) || (recursive)) { todoFiles.add(new FilesIterator(folder)); } if ((!initialDir) && (includeDirs)) { if (acceptFile(folder)) { lastFile = folder; return folder; } } } } /** * Finds all files and returns list of founded files. */ public List findAll() { List allFiles = new ArrayList<>(); File file; while ((file = nextFile()) != null) { allFiles.add(file); } return allFiles; } /** * Initializes file walking. * Separates input files and folders. */ protected void init() { rules.detectMode(); todoFiles = new LinkedList<>(); todoFolders = new LinkedList<>(); if (pathList == null) { pathList = new LinkedList<>(); return; } if (pathListOriginal == null) { pathListOriginal = (LinkedList) pathList.clone(); } String[] files = new String[pathList.size()]; int index = 0; Iterator iterator = pathList.iterator(); while (iterator.hasNext()) { File file = iterator.next(); if (file.isFile()) { files[index++] = file.getAbsolutePath(); iterator.remove(); } } if (index != 0) { FilesIterator filesIterator = new FilesIterator(files); todoFiles.add(filesIterator); } } /** * Returns file walking iterator. */ @Override public Iterator iterator() { return new Iterator() { private File nextFile; @Override public boolean hasNext() { nextFile = nextFile(); return nextFile != null; } @Override public File next() { if (nextFile == null) { throw new NoSuchElementException(); } return nextFile; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } // ---------------------------------------------------------------- sort protected List> sortComparators; protected void addComparator(final Comparator comparator) { if (sortComparators == null) { sortComparators = new ArrayList<>(4); } sortComparators.add(comparator); } /** * Removes ALL sorting options. */ public FindFile sortNone() { sortComparators = null; return this; } /** * Adds generic sorting. */ public FindFile sortWith(final Comparator fileComparator) { addComparator(fileComparator); return this; } /** * Puts folders before files. */ public FindFile sortFoldersFirst() { addComparator(new FolderFirstComparator(true)); return this; } /** * Puts files before folders. */ public FindFile sortFoldersLast() { addComparator(new FolderFirstComparator(false)); return this; } /** * Sorts files by file name, using natural sort. */ public FindFile sortByName() { addComparator(new FileNameComparator(true)); return this; } /** * Sorts files by file names descending, using natural sort. */ public FindFile sortByNameDesc() { addComparator(new FileNameComparator(false)); return this; } /** * Sorts files by file extension. */ public FindFile sortByExtension() { addComparator(new FileExtensionComparator(true)); return this; } /** * Sorts files by file extension descending. */ public FindFile sortByExtensionDesc() { addComparator(new FileExtensionComparator(false)); return this; } /** * Sorts files by last modified time. */ public FindFile sortByTime() { addComparator(new FileLastModifiedTimeComparator(true)); return this; } /** * Sorts files by last modified time descending. */ public FindFile sortByTimeDesc() { addComparator(new FileLastModifiedTimeComparator(false)); return this; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy