com.googlecode.kevinarpe.papaya.filesystem.AbstractTraversePathIteratorImpl Maven / Gradle / Ivy
package com.googlecode.kevinarpe.papaya.filesystem;
/*
* #%L
* This file is part of Papaya.
* %%
* Copyright (C) 2013 - 2014 Kevin Connor ARPE ([email protected])
* %%
* Papaya is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GPL Classpath Exception:
* This project is subject to the "Classpath" exception as provided in
* the LICENSE file that accompanied this code.
*
* Papaya 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Papaya. If not, see .
* #L%
*/
import com.google.common.collect.Lists;
import com.googlecode.kevinarpe.papaya.annotation.FullyTested;
import com.googlecode.kevinarpe.papaya.argument.ObjectArgs;
import com.googlecode.kevinarpe.papaya.exception.PathException;
import com.googlecode.kevinarpe.papaya.exception.PathRuntimeException;
import java.io.File;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
/**
* Traverses a directory hierarchy similar to the UNIX command line tool {@code find}. Use the
* builder class {@link TraversePathIterableImpl} to construct path iterators.
*
* Terminology:
*
* - For "depth last" iterators, paths in each directory are iterated before descending
* any directories
* - For "depth first" iterators, paths in each directory are iterated after descending
* any directories
*
*
* Example directory structure:
*
{@code
* topDir
* |
* file1 file2 dir1
* |
* file3 file4 dir2
* |
* file5
* }
*
* Directories are traversed in this order:
*
* - For "depth last" iterators: topDir, dir1, dir2
* - For "depth first" iterators: dir2, dir1, topDir
*
*
* Possible path iterations:
*
* - For "depth last" iterators: topDir, file1, file2, dir1, file3, file4 dir2, file5
* - For "depth first" iterators: file5, file3, file4, dir2, file1, file2, dir1, topDir
*
*
* @author Kevin Connor ARPE ([email protected])
*
* @see TraversePathIterSettingsImpl
*/
@FullyTested
abstract class AbstractTraversePathIteratorImpl
extends TraversePathIterSettingsImpl
implements TraversePathIterator {
static interface Factory {
TraversePathLevel newInstance(AbstractTraversePathIteratorImpl parent, File dirPath, int depth)
throws PathException;
}
static final class FactoryImpl
implements Factory {
static final FactoryImpl INSTANCE = new FactoryImpl();
@Override
public TraversePathLevel newInstance(AbstractTraversePathIteratorImpl parent, File dirPath, int depth)
throws PathException {
return new TraversePathLevel(parent, dirPath, depth);
}
}
private final Factory _factory;
private final LinkedList _levelList;
AbstractTraversePathIteratorImpl(
File dirPath,
TraversePathDepthPolicy depthPolicy,
TraversePathExceptionPolicy exceptionPolicy,
PathFilter optDescendDirPathFilter,
Comparator optDescendDirPathComparator,
PathFilter optIteratePathFilter,
Comparator optIteratePathComparator) {
this(
dirPath,
depthPolicy,
exceptionPolicy,
optDescendDirPathFilter,
optDescendDirPathComparator,
optIteratePathFilter,
optIteratePathComparator,
FactoryImpl.INSTANCE);
}
AbstractTraversePathIteratorImpl(
File dirPath,
TraversePathDepthPolicy depthPolicy,
TraversePathExceptionPolicy exceptionPolicy,
PathFilter optDescendDirPathFilter,
Comparator optDescendDirPathComparator,
PathFilter optIteratePathFilter,
Comparator optIteratePathComparator,
Factory factory) {
super(
dirPath,
depthPolicy,
exceptionPolicy,
optDescendDirPathFilter,
optDescendDirPathComparator,
optIteratePathFilter,
optIteratePathComparator);
_factory = ObjectArgs.checkNotNull(factory, "factory");
_levelList = Lists.newLinkedList();
}
protected final TraversePathLevel tryDescendDirPath() {
TraversePathLevel currentLevel = null;
File dirPath = withRootDirPath();
PathFilter optDescendDirFilter = withOptionalDescendDirPathFilter();
if (null == optDescendDirFilter || optDescendDirFilter.accept(dirPath, 0)) {
// If initial directory listing fails, but exceptions are ignored, '_currentLevel'
// will remain null.
currentLevel = tryAddLevel(dirPath);
}
return currentLevel;
}
protected final boolean canIterateDirPath() {
File dirPath = withRootDirPath();
PathFilter optIteratePathFilter = withOptionalIteratePathFilter();
boolean result = (null == optIteratePathFilter || optIteratePathFilter.accept(dirPath, 0));
return result;
}
/**
* Descend a directory by creating a directory listing. If result is not {@code null}, the
* depth increases by one.
*
* @param dirPath
* directory path to descend
*
* @return new deepest level or {@code null} if directory listing throws exception and exception
* policy dictates to ignore it
*
* @throws PathRuntimeException
* if directory listing throws exception and exception policy dictates to
* rethrow as a runtime (unchecked) exception
*
* @see #tryRemoveAndGetNextLevel()
* @see #getDepth()
*/
protected final TraversePathLevel tryAddLevel(File dirPath)
throws PathRuntimeException {
final int depth = 1 + _levelList.size();
TraversePathLevel level = null;
try {
level = _factory.newInstance(this, dirPath, depth);
}
catch (PathException e) {
if (TraversePathExceptionPolicy.THROW == withExceptionPolicy()) {
throw new PathRuntimeException(e);
}
}
if (null != level) {
_levelList.add(level);
return level;
}
return null;
}
/**
* Remove the deepest level and return the next deepest level. If result is not {@code null},
* the depth decreases by one.
*
* @return new deepest level or {@code null} if no remaining levels after removal
*
* @see #tryAddLevel(File)
* @see #getDepth()
*/
protected final TraversePathLevel tryRemoveAndGetNextLevel() {
if (_levelList.isEmpty()) {
return null;
}
_levelList.removeLast();
TraversePathLevel level = (_levelList.isEmpty() ? null :_levelList.getLast());
return level;
}
/**
* Tests if a next path exists for iteration. The first call to this method is comparatively
* more expensive than future calls.
*
* {@inheritDoc}
*
* @throws PathRuntimeException
* if an exception is thrown during a directory listing, and if exception policy
* dictates to throw the exception ({@link TraversePathExceptionPolicy#THROW}).
*
* @see #next()
* @see #withExceptionPolicy()
*/
@Override
public abstract boolean hasNext()
throws PathRuntimeException;
/**
* Returns the next path in the iteration.
*
* {@inheritDoc}
*
* @throws NoSuchElementException
* if the iteration is complete. This is only thrown if {@link #hasNext()} returns
* {@code false}.
* @throws PathRuntimeException
* if an exception is thrown during a directory listing, and if exception policy
* dictates to throw the exception ({@link TraversePathExceptionPolicy#THROW}).
*
* @see #hasNext()
* @see #withExceptionPolicy()
*/
@Override
public abstract File next()
throws PathRuntimeException;
/**
* Always throws {@link UnsupportedOperationException}.
*/
@Override
public final void remove() {
throw new UnsupportedOperationException(String.format(
"Class is unmodifiable: %s", this.getClass().getName()));
}
/**
* @return number of levels below {@link #withRootDirPath()}. Minimum value is zero.
*/
@Override
public final int getDepth() {
return _levelList.size();
}
protected final void assertHasNext() {
if (!hasNext()) {
throw new NoSuchElementException(
"Method hasNext() returns false: There is no next path to iterate");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy