001package org.unix4j.unix.find;
002
003import static java.lang.String.format;
004
005import java.io.File;
006import java.io.FileFilter;
007import java.util.ArrayList;
008import java.util.List;
009
010import org.unix4j.command.AbstractCommand;
011import org.unix4j.context.ExecutionContext;
012import org.unix4j.line.Line;
013import org.unix4j.line.SimpleLine;
014import org.unix4j.processor.LineProcessor;
015import org.unix4j.unix.Find;
016import org.unix4j.util.FileUtil;
017import org.unix4j.util.RelativePathBase;
018
019/**
020 * Implementation of the {@link Find find} command.
021 */
022class FindCommand extends AbstractCommand<FindArguments> {
023        
024        private final FileFilter staticFileFilter;
025        private final RegexFilter regexFilter;
026        private final String lineEnding;
027        
028        public FindCommand(FindArguments args) {
029                super(Find.NAME, args);
030                this.regexFilter = createRegexFilterFromArgs(args);
031                this.staticFileFilter = createFileFilterFromArgs(args);
032                this.lineEnding = args.isPrint0() ? String.valueOf(Line.ZERO) : Line.LINE_ENDING;
033        }
034
035        private RegexFilter createRegexFilterFromArgs(FindArguments args) {
036                if (args.isNameSet()) {
037                        if (args.isRegex()) {
038                                return new RegexFilter(args.getName(), args.isIgnoreCase());
039                        }
040                }
041                //no regex filter
042                return null;
043        }
044        private FileFilter createFileFilterFromArgs(FindArguments args) {
045                final CompositeFileFilter filter = new CompositeFileFilter();
046                if (args.isNameSet()) {
047                        final String name = args.getName();
048                        if (!args.isRegex()) {
049                                if (name.contains("*") || name.contains("?")) {
050                                        final String pattern = name.replace(".", "\\.").replace('?', '.').replace("*", ".*");
051                                        filter.add(new RegexFilter(pattern, args.isIgnoreCase()));
052                                } else {
053                                        filter.add(new NameFilter(name, args.isIgnoreCase()));
054                                }
055                        }
056                }
057                if (args.isSizeSet()) {
058                        filter.add(new SizeFilter(args.getSize()));
059                }
060                if (args.isTimeSet()) {
061                        filter.add(new TimeFilter(args.getTime(), args.getOptions()));
062                }
063                filter.addIfNotNull(TypeFilter.valueOf(args.getOptions()));
064                return filter.simplify();
065        }
066
067        private List<File> getArgumentPaths(ExecutionContext context, FindArguments args) {
068                if (args.isPathSet()) {
069                        return FileUtil.expandFiles(context.getCurrentDirectory(), args.getPath());
070                } else {
071                        //no input files or paths ==> use just the current directory
072                        final ArrayList<File> list = new ArrayList<File>(1);
073                        list.add(context.getCurrentDirectory());
074                        return list;
075                }
076        }
077
078        @Override
079        public LineProcessor execute(final ExecutionContext context, final LineProcessor output) {
080                final FindArguments args = getArguments(context);
081                final List<File> paths = getArgumentPaths(context, args);
082                return new LineProcessor() {
083                        @Override
084                        public boolean processLine(Line line) {
085                                return false;//we ignore all input
086                        }
087                        @Override
088                        public void finish() {
089                                final RelativePathBase base = new RelativePathBase(context.getCurrentDirectory(), RelativePathBase.CHILDREN_WITHOUT_PREFIX);
090                                final FileFilter fileFilter = getFileFilterFor(base);
091                                for(File path: paths){
092                                        final boolean keepGoing;
093
094                                        path = context.getRelativeToCurrentDirectory(path);
095                                        if(!path.exists()){
096                                                keepGoing = output.processLine(new SimpleLine(format("find: `%s': No such file or directory", path), lineEnding));
097                                        } else if(path.isDirectory()){
098                                                keepGoing = listFiles(fileFilter, base, path, output, args);
099                                        } else {
100                                                keepGoing = outputFileLine(fileFilter, output, base, path);
101                                        }
102                                        if(!keepGoing){
103                                                break;
104                                        }
105                                }
106                                output.finish();
107                        }
108                        private FileFilter getFileFilterFor(RelativePathBase base) {
109                                if (regexFilter == null) {
110                                        return staticFileFilter;
111                                }
112                                final CompositeFileFilter compositeFilter = new CompositeFileFilter();
113                                compositeFilter.add(staticFileFilter);
114                                compositeFilter.add(regexFilter.getRelativePathFilterForBase(base));
115                                return compositeFilter;
116                        }
117                };
118        }
119
120        private boolean listFiles(FileFilter fileFilter, RelativePathBase relativeTo, File parent, LineProcessor output, FindArguments args) {
121                //print directory files and recurse
122                if (outputFileLine(fileFilter, output, relativeTo, parent)) {
123                        final List<File> files = FileUtil.toList(parent.listFiles());
124                        for (File file : files) {
125                                //System.out.println("Examining file: " + file.getAbsolutePath());
126                                if (file.isDirectory()) {
127                                        if (!listFiles(fileFilter, relativeTo, file, output, args)) {
128                                                return false;
129                                        }
130                                } else {
131                                        if (!outputFileLine(fileFilter, output, relativeTo, file)) {
132                                                return false;
133                                        }
134                                }
135                        }
136                }
137                return true;//we want more output
138        }
139        private boolean outputFileLine(FileFilter fileFilter, LineProcessor output, RelativePathBase relativeTo, File file) {
140                if (fileFilter.accept(file)) {
141                        final String filePath = relativeTo.getRelativePathFor(file);
142                        return output.processLine(new SimpleLine(filePath, lineEnding));
143                }
144                return true;
145        }
146}