xyz.cofe.fs.FileVisitor Maven / Gradle / Ivy
The newest version!
/*
* The MIT License
*
* Copyright 2014 Kamnev Georgiy ([email protected]).
*
* Данная лицензия разрешает, безвозмездно, лицам, получившим копию данного программного
* обеспечения и сопутствующей документации (в дальнейшем именуемыми "Программное Обеспечение"),
* использовать Программное Обеспечение без ограничений, включая неограниченное право на
* использование, копирование, изменение, объединение, публикацию, распространение, сублицензирование
* и/или продажу копий Программного Обеспечения, также как и лицам, которым предоставляется
* данное Программное Обеспечение, при соблюдении следующих условий:
*
* Вышеупомянутый копирайт и данные условия должны быть включены во все копии
* или значимые части данного Программного Обеспечения.
*
* ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ ЛЮБОГО ВИДА ГАРАНТИЙ,
* ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ,
* СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И НЕНАРУШЕНИЯ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ
* ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ
* ИЛИ ДРУГИХ ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ
* ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
* ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
*/
package xyz.cofe.fs;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import xyz.cofe.collection.Convertor;
import xyz.cofe.collection.Iterators;
import xyz.cofe.collection.NodesExtracter;
import xyz.cofe.collection.Predicate;
import xyz.cofe.collection.iterators.TreeWalk;
import xyz.cofe.collection.iterators.TreeWalkItreator;
/**
* Осуществляет обход каталога
* @author Kamnev Georgiy ([email protected])
*/
public class FileVisitor{
//
private static void logFine(String message,Object ... args){
Logger.getLogger(FileVisitor.class.getName()).log(Level.FINE, message, args);
}
private static void logFiner(String message,Object ... args){
Logger.getLogger(FileVisitor.class.getName()).log(Level.FINER, message, args);
}
private static void logFinest(String message,Object ... args){
Logger.getLogger(FileVisitor.class.getName()).log(Level.FINEST, message, args);
}
private static void logInfo(String message,Object ... args){
Logger.getLogger(FileVisitor.class.getName()).log(Level.INFO, message, args);
}
private static void logWarning(String message,Object ... args){
Logger.getLogger(FileVisitor.class.getName()).log(Level.WARNING, message, args);
}
private static void logSevere(String message,Object ... args){
Logger.getLogger(FileVisitor.class.getName()).log(Level.SEVERE, message, args);
}
private static void logException(Throwable ex){
Logger.getLogger(FileVisitor.class.getName()).log(Level.SEVERE, null, ex);
}
//
/**
* Конструктор по умолчанию
*/
public FileVisitor(){
}
/**
* Поведение при возниконовеии ошибки
*/
public static enum ErrorBehavior {
/**
* Продолжить выполнение
*/
Continue,
/**
* Завершить работу
*/
Stop
}
/**
* Опции копирования
*/
public static class CopyOptions {
public boolean checkHistory = true;
public boolean history = false;
public boolean followLink = true;
public boolean resolveLink = true;
public boolean maxRecursiveLinkLevel = true;
public boolean useCanonicalPath = true;
public boolean useAbsolutePath = true;
public boolean errorBehavior = true;
public boolean filter = true;
}
/**
* Конструктор копирования
* @param src образец
* @param opts Опции копирования
*/
public FileVisitor( FileVisitor src, CopyOptions opts ){
if( src!=null && opts!=null ){
if( opts.checkHistory )checkHistory = src.isCheckHistory();
if( opts.history ){
visitHistory.clear();
visitHistory.addAll(src.getVisitHistory());
}
if( opts.resolveLink ){
this.resolveLink = src.isResolveLink();
}
if( opts.followLink ){
this.followLink = src.isFollowLink();
}
if( opts.maxRecursiveLinkLevel ){
this.maxRecusiveLinkLevel = src.getMaxRecusiveLinkLevel();
}
if( opts.useCanonicalPath ){
this.useCanonicalPath = src.isUseCanonicalPath();
}
if( opts.useAbsolutePath ){
this.useAbsolutePath = src.isUseAbsolutePath();
}
if( opts.filter ){
this.filter = src.filter;
}
}
}
/**
* Клонирует объект
* @return копия объекта
*/
@Override
public FileVisitor clone(){
CopyOptions opts = new CopyOptions();
return clone(opts);
}
/**
* Клонирует объект
* @param opts Опции копирования
* @return копия объекта
*/
public FileVisitor clone(CopyOptions opts){
return new FileVisitor( this, opts );
}
/**
* Вызывается при входе в каталог/файл
* @param path Путь/файл в который осуществляется вход
*/
public void enter(Stack path){
}
/**
* Вызывается при выходе из каталога/файла
* @param path Путь/файл из которого осуществляется выход
*/
public void exit(Stack path){
}
/**
* Вызывается при возниконовении ошибки
* @param err Ошибка
* @return true - продолжить выполнение, false - завершить работу
*/
public ErrorBehavior error(Throwable err){
System.err.println("error "+err.getMessage());
return errorBehavior;
}
//
private Set visitHistory = new TreeSet();
/**
* Возвращает набор посещенных файлов/каталогов
* @return Список файлов
*/
public Set getVisitHistory() {
if( visitHistory==null )visitHistory = new TreeSet();
return visitHistory;
}
//
//
private ErrorBehavior errorBehavior = ErrorBehavior.Continue;
/**
* Поведение при возникновении ошибки
* @return поведение
*/
public ErrorBehavior getErrorBehavior() {
return errorBehavior;
}
/**
* Поведение при возникновении ошибки
* @param errorBehavior поведение
*/
public void setErrorBehavior(ErrorBehavior errorBehavior) {
this.errorBehavior = errorBehavior;
}
//
//
private boolean checkHistory = true;
/**
* Проверять историю посещенных каталог/файлов, что бы избежать бесконечных циклов.
* По умолчанию true.
* @return Проверять историю посещенных каталог/файлов
*/
public boolean isCheckHistory() {
return checkHistory;
}
/**
* Проверять историю посещенных каталог/файлов, что бы избежать бесконечных циклов.
* @param checkHistory Проверять историю посещенных каталог/файлов
*/
public void setCheckHistory(boolean checkHistory) {
this.checkHistory = checkHistory;
}
//
//
private boolean followLink = true;
/**
* Следовать символическим ссылкам. По умолчанию true.
* Если true - то, с. ссылки будут испоьзоватся в качестве ссылок на каталоги
* @return Следовать символическим ссылкам.
*/
public boolean isFollowLink() {
return followLink;
}
/**
* Следовать символическим ссылкам.
* @param followLink Следовать символическим ссылкам.
*/
public void setFollowLink(boolean followLink) {
this.followLink = followLink;
}
//
//
private boolean resolveLink = false;
/**
* "Разрешать" символические ссылки. По умолчанию false.
* Если установлено в true - то, при нахождении символической ссылки будет установлено куда она указывает
* и использовано разрешенное имя.
*
* Пример:
* Есть следующая структура файлов
*
* /A/
* /A/1
* /A/link -> /B
* /B/
* /B/2
* /B/3
*
*
*
* resolveLink = false resolveLink = true
*
*
*
* /A/
* /A/1
* /A/link/
* /A/link/2
* /A/link/3
* /B/
* /B/2
* /B/3
*
*
* /A/
* /A/1
* /B/
* /B/2
* /B/3
*
*
*
* @return "Разрешать" символические ссылки.
*/
public boolean isResolveLink() {
return resolveLink;
}
/**
* "Разрешать" символические ссылки.
* @param resolveLink "Разрешать" символические ссылки.
* @see #isResolveLink()
*/
public void setResolveLink(boolean resolveLink) {
this.resolveLink = resolveLink;
}
//
//
private int maxRecusiveLinkLevel = 100;
/**
* Максимальная глубина вложенности символических ссылок.
* -1 - не органичено.
* По умолчанию 100
* @return Максимальная глубина вложенности символических ссылок
*/
public int getMaxRecusiveLinkLevel() {
return maxRecusiveLinkLevel;
}
/**
* Максимальная глубина вложенности символических ссылок.
* @param maxRecusiveLinkLevel Максимальная глубина вложенности символических ссылок.
-1 - не органичено.
*/
public void setMaxRecusiveLinkLevel(int maxRecusiveLinkLevel) {
this.maxRecusiveLinkLevel = maxRecusiveLinkLevel;
}
//
//
private boolean useCanonicalPath = true;
/**
* Приводить пути файлов к канонической форме, по умолч. true.
* @return Приводить пути файлов к канонической форме
*/
public boolean isUseCanonicalPath() {
return useCanonicalPath;
}
/**
* Приводить пути файлов к канонической форме
* @param useCanonicalPath Приводить пути файлов к канонической форме
*/
public void setUseCanonicalPath(boolean useCanonicalPath) {
this.useCanonicalPath = useCanonicalPath;
}
//
//
private boolean useAbsolutePath = false;
/**
* Приводить пути файлов к абсолютной форме, по умолч. false.
* @return Приводить пути файлов к абсолютной форме
*/
public boolean isUseAbsolutePath() {
return useCanonicalPath;
}
/**
* Приводить пути файлов к абсолютной форме
* @param useCanonicalPath Приводить пути файлов к абсолютной форме
*/
public void setUseAbsolutePath(boolean useCanonicalPath) {
this.useCanonicalPath = useCanonicalPath;
}
//
//
/**
* Рекурсивно разрешает ссылки
* @param rl Получение ссылки
* @param currentResolve текущая итерация
* @param maxResolve макс. кол-во итераций или -1
* @return Ссылка или null
*/
protected File resolveLink( ResolveLink rl, int currentResolve, int maxResolve ){
if( currentResolve>maxResolve && maxResolve>=0 )return null;
File f = rl.resolveLink();
if( f==null )return null;
if( (f instanceof IsSymbolicLink) && (f instanceof ResolveLink) ){
ResolveLink frl = (ResolveLink)f;
IsSymbolicLink fis = (IsSymbolicLink)f;
if( fis.isSymbolicLink() ){
File rf = resolveLink(frl,currentResolve+1,maxResolve);
if( rf==null ){
return f;
}
return rf;
}
}
return f;
}
//
//
protected Predicate> filter = null;
/**
* Вызывается перед входом в каталог для проверки необходимости войти
* @return Предикат входа в каталог
*/
public Predicate> getFilter() {
return filter;
}
/**
* Вызывается перед входом в каталог для проверки необходимости войти
* @param filter Предикат входа в каталог
*/
public void setFilter(Predicate> filter) {
this.filter = filter;
}
//
private List getChildrenOf_post( List children ){
for( int i=0; i getChildrenOf( File parent){
ArrayList res = new ArrayList();
if( parent==null )return res;
if( isCheckHistory() ){
Set hist = getVisitHistory();
if( hist.contains(parent) )return res;
hist.add(parent);
}
if( parent instanceof IsSymbolicLink ){
boolean islink = ((IsSymbolicLink)parent).isSymbolicLink();
if( islink ){
if( !isFollowLink() ){
return res;
}
if( isResolveLink() && (parent instanceof ResolveLink) ){
File resolved = resolveLink((ResolveLink)parent,1,getMaxRecusiveLinkLevel());
if( resolved!=null ){
if( resolved.isDirectory() ){
List files = resolved.listFiles();
return getChildrenOf_post(files);
}else{
// res.add( isUseCanonicalPath() ?
// (T)resolved.getCanonical() :
// resolved );
return getChildrenOf_post(res);
}
}
}
}
}
if( parent.isDirectory() ){
List files = parent.listFiles();
for( File f : files ){
res.add( f );
}
}
return getChildrenOf_post(res);
}
private boolean walk_isRunning = false;
/**
* Осуществляет обход каталога,
* вызывает методы enter/exit
* @param root точка с которой начиается обход
* @see #enter(java.util.Stack)
* @see #exit(java.util.Stack)
*/
public void walk( File root ){
if( root==null )throw new IllegalArgumentException( "root==null" );
if( walk_isRunning )throw new IllegalStateException("walk is running");
try{
walk_isRunning = true;
getVisitHistory().clear();
Stack s = new Stack();
s.push( isUseCanonicalPath() ? (File)root.getCanonical() : root);
walk(s);
s.pop();
}finally{
walk_isRunning = false;
}
}
private int walk( Stack s ){
int res = 0;
if( filter!=null && !filter.validate(s) ){
return res;
}
enter(s);
if( !s.empty() ){
File p = s.peek();
boolean hasErr = false;
boolean stop = false;
Iterable children = null;
try{
children = getChildrenOf(p);
}catch( Throwable err ){
hasErr = true;
ErrorBehavior eb = error(err);
if( eb!=null ){
switch( eb ){
case Continue:
stop = false;
break;
case Stop:
stop = true;
break;
}
}
}
if( !hasErr ){
if( children!=null ){
for( File f : children ){
s.push(f);
int r = walk(s);
if( r!=0 ){
s.pop();
res = 1;
break;
}
s.pop();
}
}
}else{
if( stop ){
res = 1;
}else{
res = 0;
}
}
}
exit(s);
return res;
}
public Iterable> treeWalkIterable( File root ){
if( root==null )throw new IllegalArgumentException( "root==null" );
final FileVisitor fv = clone();
NodesExtracter ne = new NodesExtracter() {
@Override
public Iterable extract(File from) {
Iterable itr = null;
try{
itr = fv.getChildrenOf(from);
}catch(Throwable err){
fv.error(err);
itr = null;
}
return itr;
}
};
fv.getVisitHistory().clear();
File froot = root;
if( isUseAbsolutePath() ){
froot = (File)froot.getAbsolute();
}
if( isUseCanonicalPath() ){
File canon = (File)froot.getCanonical();
froot = canon;
}
// return Iterators.tree(froot, ne);
// TreeIterable treeItreable = new TreeIterable(ne, ne);
final Stack stack = new Stack();
Iterable> itr = TreeWalkItreator.createIterable(froot,
ne,
new Predicate>(){
@Override
public boolean validate(TreeWalk value) {
if( fv.filter==null )return true;
stack.clear();
for( File n : value.nodePath() ){
stack.add(n);
}
return fv.filter.validate(stack);
}
}
);
return itr;
}
/**
* Возвращает последовательность файлов/каталогов (включая вложенгные)
* @param root точка с которой начиается обход
* @return последовательность файлов/каталогов
*/
public Iterable walkIterable( File root ){
if( root==null )throw new IllegalArgumentException( "root==null" );
// final FileVisitor fv = clone();
// NodesExtracter ne = new NodesExtracter() {
// @Override
// public Iterable extract(File from) {
// Iterable itr = null;
// try{
// itr = fv.getChildrenOf(from);
// }catch(Throwable err){
// fv.error(err);
// itr = null;
// }
// return itr;
// }
// };
// fv.getVisitHistory().clear();
//
// File froot = root;
// if( isUseAbsolutePath() ){
// froot = (File)froot.getAbsolute();
// }
// if( isUseCanonicalPath() ){
// File canon = (File)froot.getCanonical();
// froot = canon;
// }
//
//// return Iterators.tree(froot, ne);
//// TreeIterable treeItreable = new TreeIterable(ne, ne);
//
// final Stack stack = new Stack();
//
// Iterable> itr = TreeWalkItreator.createIterable(froot,
// ne,
// new Predicate>(){
// @Override
// public boolean validate(TreeWalk value) {
// if( fv.filter==null )return true;
// stack.clear();
// for( File n : value.nodePath() ){
// stack.add(n);
// }
// return fv.filter.validate(stack);
// }
// }
// );
Iterable> itr = treeWalkIterable(root);
Iterable resitr = Iterators.convert(itr, new ConvertTreeWalk());
return resitr;
}
public static class ConvertTreeWalk
implements Convertor, T>
{
@Override
public T convert(TreeWalk from) {
return from.currentNode();
}
// @Override
// public T convert(Iterable> from) {
// return from.
// }
}
}