tikhomirov@229: /*
tikhomirov@443:  * Copyright (c) 2011-2012 TMate Software Ltd
tikhomirov@229:  *  
tikhomirov@229:  * This program is free software; you can redistribute it and/or modify
tikhomirov@229:  * it under the terms of the GNU General Public License as published by
tikhomirov@229:  * the Free Software Foundation; version 2 of the License.
tikhomirov@229:  *
tikhomirov@229:  * This program is distributed in the hope that it will be useful,
tikhomirov@229:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
tikhomirov@229:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
tikhomirov@229:  * GNU General Public License for more details.
tikhomirov@229:  *
tikhomirov@229:  * For information on how to redistribute this software under
tikhomirov@229:  * the terms of a license other than GNU General Public License
tikhomirov@229:  * contact TMate Software at support@hg4j.com
tikhomirov@229:  */
tikhomirov@229: package org.tmatesoft.hg.internal;
tikhomirov@229: 
tikhomirov@443: import static org.tmatesoft.hg.util.Path.CompareResult.*;
tikhomirov@443: 
tikhomirov@229: import java.util.ArrayList;
tikhomirov@229: 
tikhomirov@443: import org.tmatesoft.hg.util.FileIterator;
tikhomirov@229: import org.tmatesoft.hg.util.Path;
tikhomirov@443: import org.tmatesoft.hg.util.Path.CompareResult;
tikhomirov@229: 
tikhomirov@229: /**
tikhomirov@443:  * 
tikhomirov@443:  * -  Specify folder to get all files in there included, but no subdirs
tikhomirov@443:  * 
 -  Specify folder to get all files and files in subdirectories included
tikhomirov@443:  * 
 -  Specify exact set files (with option to accept or not paths leading to them) 
tikhomirov@443:  * 
 
tikhomirov@229:  * @author Artem Tikhomirov
tikhomirov@229:  * @author TMate Software Ltd.
tikhomirov@229:  */
tikhomirov@229: public class PathScope implements Path.Matcher {
tikhomirov@229: 	private final Path[] files;
tikhomirov@229: 	private final Path[] dirs;
tikhomirov@443: 	private final boolean includeNestedDirs;
tikhomirov@443: 	private final boolean includeParentDirs;
tikhomirov@443: 	private final boolean includeDirContent;
tikhomirov@443: 	
tikhomirov@443: 	/**
tikhomirov@443: 	 * See {@link PathScope#PathScope(boolean, boolean, Path...)} 
tikhomirov@443: 	 */
tikhomirov@443: 	public PathScope(boolean recursiveDirs, Path... paths) {
tikhomirov@443: 		this(true, recursiveDirs, true, paths);
tikhomirov@443: 	}
tikhomirov@229: 
tikhomirov@443: 	/**
tikhomirov@443: 	 * With matchParentDirs, recursiveDirs and matchDirContent set to false, 
tikhomirov@443: 	 * this scope matches only exact paths specified.
tikhomirov@443: 	 *  
tikhomirov@443: 	 * With matchParentDirs set to true, parent directories for files and folders listed in 
tikhomirov@443: 	 * the paths would get accepted as well (handy for {@link FileIterator FileIterators}). 
tikhomirov@443: 	 * Note, if supplied path lists a file, parent directory for the file is not matched unless matchParentDirs
tikhomirov@443: 	 * is true. To match file's immediate parent without matching all other parents up to the root, just add file parent
tikhomirov@443: 	 * along with the file to paths.
tikhomirov@443: 	 * 
 
tikhomirov@443: 	 * With recursiveDirs set to true, subdirectories (with files) of directories listed in paths would 
tikhomirov@443: 	 * be matched as well. Similar to `a/b/**`
tikhomirov@443: 	 * 
tikhomirov@443: 	 * With matchDirContent set to true, files right under any directory listed in path would be matched.
tikhomirov@443: 	 * Similar to `a/b/*`. Makes little sense to set to false when recursiceDirs is true, although may still 
tikhomirov@443: 	 * be useful in certain scenarios, e.g. PathScope(false, true, false, "a/") matches files under "a/b/*" and "a/b/c/*", but not files "a/*".
tikhomirov@443: 	 * 
tikhomirov@443: 	 * @param matchParentDirs true to accept parent dirs of supplied paths
tikhomirov@443: 	 * @param recursiveDirs true to include subdirectories and files of supplied paths
tikhomirov@443: 	 * @param includeDirContent
tikhomirov@443: 	 * @param paths files and folders to match
tikhomirov@443: 	 */
tikhomirov@443: 	public PathScope(boolean matchParentDirs, boolean recursiveDirs, boolean matchDirContent, Path... paths) {
tikhomirov@229: 		if (paths == null) {
tikhomirov@229: 			throw new IllegalArgumentException();
tikhomirov@229: 		}
tikhomirov@443: 		includeParentDirs = matchParentDirs;
tikhomirov@443: 		includeNestedDirs = recursiveDirs;
tikhomirov@443: 		includeDirContent = matchDirContent;
tikhomirov@229: 		ArrayList f = new ArrayList(5);
tikhomirov@229: 		ArrayList d = new ArrayList(5);
tikhomirov@229: 		for (Path p : paths) {
tikhomirov@229: 			if (p.isDirectory()) {
tikhomirov@229: 				d.add(p);
tikhomirov@229: 			} else {
tikhomirov@229: 				f.add(p);
tikhomirov@229: 			}
tikhomirov@229: 		}
tikhomirov@229: 		files = f.toArray(new Path[f.size()]);
tikhomirov@229: 		dirs = d.toArray(new Path[d.size()]);
tikhomirov@229: 	}
tikhomirov@229: 
tikhomirov@229: 	public boolean accept(Path path) {
tikhomirov@229: 		if (path.isDirectory()) {
tikhomirov@443: 			// either equals to or a parent of a directory we know about (i.e. configured dir is *nested* in supplied arg). 
tikhomirov@443: 			// Also, accept arg if it happened to be nested into configured dir (i.e. one of them is *parent* for the arg), 
tikhomirov@443: 			//       and recursiveDirs is true. 
tikhomirov@229: 			for (Path d : dirs) {
tikhomirov@229: 				switch(d.compareWith(path)) {
tikhomirov@229: 				case Same : return true;
tikhomirov@443: 				case ImmediateChild :
tikhomirov@443: 				case Nested : return includeParentDirs; // path is parent to one of our locations
tikhomirov@443: 				case ImmediateParent :
tikhomirov@443: 				case Parent : return includeNestedDirs; // path is nested in one of our locations
tikhomirov@229: 				}
tikhomirov@229: 			}
tikhomirov@443: 			if (!includeParentDirs) {
tikhomirov@443: 				return false;
tikhomirov@443: 			}
tikhomirov@443: 			// If one of configured files is nested under the path, and we shall report parents, accept.
tikhomirov@443: 			// Note, I don't respect includeDirContent here as with file it's easy to add parent to paths explicitly, if needed.
tikhomirov@443: 			// (if includeDirContent == .f and includeParentDirs == .f, directory than owns a scope file won't get reported)  
tikhomirov@229: 			for (Path f : files) {
tikhomirov@443: 				CompareResult cr = f.compareWith(path);
tikhomirov@443: 				if (cr == Nested || cr == ImmediateChild) {
tikhomirov@229: 					return true;
tikhomirov@229: 				}
tikhomirov@229: 			}
tikhomirov@229: 		} else {
tikhomirov@229: 			for (Path f : files) {
tikhomirov@229: 				if (f.equals(path)) {
tikhomirov@229: 					return true;
tikhomirov@229: 				}
tikhomirov@229: 			}
tikhomirov@443: 			// if interested in nested/recursive dirs, shall check if supplied file is under any of our configured locations 
tikhomirov@443: 			if (!includeNestedDirs && !includeDirContent) {
tikhomirov@443: 				return false;
tikhomirov@443: 			}
tikhomirov@443: 			for (Path d : dirs) {
tikhomirov@443: 				CompareResult cr = d.compareWith(path);
tikhomirov@443: 				if (includeNestedDirs && cr == Parent) {
tikhomirov@443: 					// file is nested in one of our locations
tikhomirov@443: 					return true;
tikhomirov@443: 				}
tikhomirov@443: 				if (includeDirContent && cr == ImmediateParent) {
tikhomirov@443: 					// file is right under one of our directories, and includeDirContents is .t
tikhomirov@443: 					return true;
tikhomirov@443: 				}
tikhomirov@443: 				// try another directory
tikhomirov@443: 			}
tikhomirov@229: 		}
tikhomirov@229: 		return false;
tikhomirov@229: 	}
tikhomirov@229: }