tikhomirov@58: /* tikhomirov@608: * Copyright (c) 2011-2013 TMate Software Ltd tikhomirov@74: * tikhomirov@74: * This program is free software; you can redistribute it and/or modify tikhomirov@74: * it under the terms of the GNU General Public License as published by tikhomirov@74: * the Free Software Foundation; version 2 of the License. tikhomirov@74: * tikhomirov@74: * This program is distributed in the hope that it will be useful, tikhomirov@74: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@74: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@74: * GNU General Public License for more details. tikhomirov@74: * tikhomirov@74: * For information on how to redistribute this software under tikhomirov@74: * the terms of a license other than GNU General Public License tikhomirov@102: * contact TMate Software at support@hg4j.com tikhomirov@58: */ tikhomirov@74: package org.tmatesoft.hg.util; tikhomirov@58: tikhomirov@58: import java.io.File; tikhomirov@58: import java.util.LinkedList; tikhomirov@58: import java.util.NoSuchElementException; tikhomirov@58: tikhomirov@425: import org.tmatesoft.hg.core.SessionContext; tikhomirov@413: import org.tmatesoft.hg.internal.Internals; tikhomirov@413: tikhomirov@58: /** tikhomirov@413: * Implementation of {@link FileIterator} using regular {@link java.io.File} tikhomirov@74: * tikhomirov@74: * @author Artem Tikhomirov tikhomirov@74: * @author TMate Software Ltd. tikhomirov@58: */ tikhomirov@141: public class FileWalker implements FileIterator { tikhomirov@58: tikhomirov@58: private final File startDir; tikhomirov@141: private final Path.Source pathHelper; tikhomirov@58: private final LinkedList dirQueue; tikhomirov@58: private final LinkedList fileQueue; tikhomirov@229: private final Path.Matcher scope; tikhomirov@413: private final boolean execCap, linkCap; tikhomirov@425: private final SessionContext sessionContext; tikhomirov@287: private RegularFileInfo nextFile; tikhomirov@141: private Path nextPath; tikhomirov@58: tikhomirov@425: public FileWalker(SessionContext ctx, File dir, Path.Source pathFactory) { tikhomirov@425: this(ctx, dir, pathFactory, null); tikhomirov@229: } tikhomirov@608: tikhomirov@608: /** tikhomirov@608: * @see FileWalker#FileWalker(SessionContext, File, Path.Source, Matcher) tikhomirov@608: */ tikhomirov@608: public FileWalker(SessionContext.Source ctxSource, File dir, Path.Source pathFactory, Path.Matcher scopeMatcher) { tikhomirov@608: this(ctxSource.getSessionContext(), dir, pathFactory, scopeMatcher); tikhomirov@608: } tikhomirov@229: tikhomirov@229: /** tikhomirov@608: * Implementation of {@link FileIterator} with regular {@link java.io.File}. tikhomirov@229: * tikhomirov@608: * @param dir directory to start at, not null tikhomirov@608: * @param pathFactory factory to create {@link Path} instances, not null tikhomirov@229: * @param scopeMatcher - this matcher shall be capable to tell not only files of interest, but tikhomirov@229: * also whether directories shall be traversed or not (Paths it gets in {@link Path.Matcher#accept(Path)} may tikhomirov@608: * point to directories); may be null tikhomirov@229: */ tikhomirov@425: public FileWalker(SessionContext ctx, File dir, Path.Source pathFactory, Path.Matcher scopeMatcher) { tikhomirov@425: sessionContext = ctx; tikhomirov@141: startDir = dir; tikhomirov@141: pathHelper = pathFactory; tikhomirov@58: dirQueue = new LinkedList(); tikhomirov@58: fileQueue = new LinkedList(); tikhomirov@229: scope = scopeMatcher; tikhomirov@413: execCap = Internals.checkSupportsExecutables(startDir); tikhomirov@413: linkCap = Internals.checkSupportsSymlinks(startDir); tikhomirov@58: reset(); tikhomirov@58: } tikhomirov@58: tikhomirov@58: public void reset() { tikhomirov@58: fileQueue.clear(); tikhomirov@58: dirQueue.clear(); tikhomirov@58: dirQueue.add(startDir); tikhomirov@425: nextFile = new RegularFileInfo(sessionContext, supportsExecFlag(), supportsLinkFlag()); tikhomirov@58: nextPath = null; tikhomirov@58: } tikhomirov@58: tikhomirov@58: public boolean hasNext() { tikhomirov@58: return fill(); tikhomirov@58: } tikhomirov@58: tikhomirov@58: public void next() { tikhomirov@58: if (!fill()) { tikhomirov@58: throw new NoSuchElementException(); tikhomirov@58: } tikhomirov@287: File next = fileQueue.removeFirst(); tikhomirov@287: nextFile.init(next); tikhomirov@287: nextPath = pathHelper.path(next.getPath()); tikhomirov@58: } tikhomirov@58: tikhomirov@141: public Path name() { tikhomirov@58: return nextPath; tikhomirov@58: } tikhomirov@58: tikhomirov@287: public FileInfo file() { tikhomirov@58: return nextFile; tikhomirov@58: } tikhomirov@58: tikhomirov@226: public boolean inScope(Path file) { tikhomirov@229: /* by default, no limits, all files are of interest */ tikhomirov@229: return scope == null ? true : scope.accept(file); tikhomirov@226: } tikhomirov@226: tikhomirov@413: public boolean supportsExecFlag() { tikhomirov@413: return execCap; tikhomirov@413: } tikhomirov@413: tikhomirov@413: public boolean supportsLinkFlag() { tikhomirov@413: return linkCap; tikhomirov@413: } tikhomirov@413: tikhomirov@228: // returns non-null tikhomirov@58: private File[] listFiles(File f) { tikhomirov@58: // in case we need to solve os-related file issues (mac with some encodings?) tikhomirov@228: File[] rv = f.listFiles(); tikhomirov@228: // there are chances directory we query files for is missing (deleted), just treat it as empty tikhomirov@228: return rv == null ? new File[0] : rv; tikhomirov@58: } tikhomirov@58: tikhomirov@58: // return true when fill added any elements to fileQueue. tikhomirov@58: private boolean fill() { tikhomirov@58: while (fileQueue.isEmpty()) { tikhomirov@58: if (dirQueue.isEmpty()) { tikhomirov@58: return false; tikhomirov@58: } tikhomirov@58: while (!dirQueue.isEmpty()) { tikhomirov@58: File dir = dirQueue.removeFirst(); tikhomirov@58: for (File f : listFiles(dir)) { tikhomirov@229: final boolean isDir = f.isDirectory(); tikhomirov@229: Path path = pathHelper.path(isDir ? ensureTrailingSlash(f.getPath()) : f.getPath()); tikhomirov@229: if (!inScope(path)) { tikhomirov@229: continue; tikhomirov@229: } tikhomirov@229: if (isDir) { tikhomirov@461: // do not dive into /.hg and tikhomirov@461: // if there's .hg/ under f/, it's a nested repository, which shall not be walked into tikhomirov@461: if (".hg".equals(f.getName()) || new File(f, ".hg").isDirectory()) { tikhomirov@461: continue; tikhomirov@461: } else { tikhomirov@58: dirQueue.addLast(f); tikhomirov@58: } tikhomirov@58: } else { tikhomirov@58: fileQueue.addLast(f); tikhomirov@58: } tikhomirov@58: } tikhomirov@58: break; tikhomirov@58: } tikhomirov@58: } tikhomirov@58: return !fileQueue.isEmpty(); tikhomirov@58: } tikhomirov@229: tikhomirov@229: private static String ensureTrailingSlash(String dirName) { tikhomirov@229: if (dirName.length() > 0) { tikhomirov@229: char last = dirName.charAt(dirName.length() - 1); tikhomirov@229: if (last == '/' || last == File.separatorChar) { tikhomirov@229: return dirName; tikhomirov@229: } tikhomirov@229: // if path already has platform-specific separator (which, BTW, it shall, according to File#getPath), tikhomirov@229: // add similar, otherwise use our default. tikhomirov@229: return dirName.indexOf(File.separatorChar) != -1 ? dirName.concat(File.separator) : dirName.concat("/"); tikhomirov@229: } tikhomirov@229: return dirName; tikhomirov@229: } tikhomirov@58: }