Package org.jnode.command.file

Source Code of org.jnode.command.file.GrepCommand$Walker

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.command.file;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.log4j.Logger;
import org.jnode.command.util.AbstractDirectoryWalker;
import org.jnode.command.util.AbstractDirectoryWalker.PathnamePatternFilter;
import org.jnode.command.util.IOUtils;
import org.jnode.shell.AbstractCommand;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.FileArgument;
import org.jnode.shell.syntax.FlagArgument;
import org.jnode.shell.syntax.IntegerArgument;
import org.jnode.shell.syntax.StringArgument;

/**
* TODO implement Fixed/Basic/Ext matchers
* TODO implement --color (if/when possible)
* @author peda
* @author crawley@jnode.org
* @author chris boertien
*/
public class GrepCommand extends AbstractCommand {

    private static final Logger log = Logger.getLogger(GrepCommand.class);
    private static final boolean DEBUG = false;
    private static final int BUFFER_SIZE = 8192;
   
    private static final String help_matcher_fixed = "Patterns are fixed strings, seperated by new lines. Any of " +
                                                     "which is to be matched.";
    private static final String help_matcher_basic = "Use basic regular expressions (Default).";
    private static final String help_matcher_ext = "Use extended regular expressions.";
    private static final String help_matcher_perl = "Use perl regular expressions.";
    private static final String help_case = "Ignore case in both the <pattern> and the input files.";
    private static final String help_invert = "Invert the matching to select non-matching lines.";
    private static final String help_match_word = "Select only those lines containing matches that form whole words." +
                                                 "The test is that the matching substring must either be at the " +
                                                 "beginning of the line, or preceded by a non-word character. It " +
                                                 "also must be at the end of a line, or followed by a non-word " +
                                                 "character. Word characters are letters, digits and the underscore.";
    private static final String help_match_line = "Select only those matches that match the whole line";
    private static final String help_count = "Suppress normal output, instead printing a count of matching lines for " +
                                            "input file. With --invert-match, count non-matching lines.";
    private static final String help_file_nomatch = "Suppress normal output, instead printing the name of each input " +
                                                    "file from which no output would normally be printed. Scanning " +
                                                    "stops after making a single match.";
    private static final String help_file_match = "Suppress normal output, isntead printing the name of each input " +
                                                 "file from which output would normally be printed. Scanning stops " +
                                                 "after making a single match.";
    private static final String help_max = "Stop reading a file after n matches. If the input is stdin from a " +
                                          "regular file, and n matching lines are output, grep ensures that the " +
                                          "standard input is positioned, to just after the last matching line before " +
                                          "exiting, regardless of trailing context lines. When grep stops after n " +
                                          "matching lines, it will output trailing context lines. When used with " +
                                          "--count, count wil never be more than n. When used with --invert, grep " +
                                          "stops after n non-matching lines.";
    private static final String help_only_matching = "Print only the matched parts of the matching line. with each " +
                                                    "part on a seperate output line.";
    private static final String help_quiet = "Do not write _anything_ to stdout. Exit immediately with zero status if" +
                                            " any match is found, even if an error was detected.";
    private static final String help_suppress = "Suppress error messages about nonexistant or unreadable files.";
    private static final String help_debug = "Output debug information.";
    private static final String help_prefix_byte = "Print the 0-based byte offset within the input file before each " +
                                                  "line of output. If --only-matching is set, print the offset of " +
                                                  "the matching part itself.";
    private static final String help_prefix_file = "Print the file name for each match. This is the default when " +
                                                  "there are multiple files.";
    private static final String help_prefix_nofile = "Suppress the prefixing of file names on output. This is the " +
                                                    "default when there is only one file, or only stdin to search.";
    private static final String help_prefix_label = "Display input coming from stdin as actually coming from the " +
                                                   "given label.";
    private static final String help_prefix_line = "Prefix each line of output with the 1-based line number within " +
                                                   "its input file.";
    private static final String help_prefix_tab = "Make sure that the first character of actual line content lies on " +
                                                  "a tab stop so that the alignment looks normal. This also causes " +
                                                  "the line number and byte offset, if output, to be printed in a " +
                                                  "minimum size field width.";
    private static final String help_prefix_null = "Output a zero byte instead of the character that normally " +
                                                   "a file name, which is normally a newline.";
    private static final String help_context_after = "Print n lines of trailing context lines after matching lines. " +
                                                     "Places a line containing a group seperator '--' between " +
                                                     "contiguous groups of matches. With --only-matching this has no " +
                                                     "effect and a warning is given.";
    private static final String help_context_before = "Print n lines of leading context lines before matching lines. " +
                                                      "Places a line containing a group seperator '--' between " +
                                                      "contiguous groups of matches. With --only-matching this has " +
                                                      "no effect and a waring is given.";
    private static final String help_context_both = "Print n lines of leading and trailing context. Places a line " +
                                                    "containing a group seperator '--' between contiguous groups of " +
                                                    "matches. With --only-matching this has no effect and a warning " +
                                                    "is given.";
    private static final String help_mode_binary = "If the first few bytes of a file indicate that the file contains " +
                                                   "binary data, assume the file is the specified type. By default " +
                                                   "the type is 'binary' and grep outputs a one line message saying " +
                                                   "that the binary file matches or nothing if there is no match. If " +
                                                   "type is 'without-match' grep assumes that binary files do not " +
                                                   "match. If type is 'text' grep processes a binary file as if it " +
                                                   "were text. __Warning__: --binary text may output binary garbage " +
                                                   "which can have side effects if the output is a terminal.";
    private static final String help_mode_binary_text = "Same as --binary text";
    private static final String help_mode_binary_skip = "Same as --binary without-match";
    private static final String help_mode_device = "If an input file is a device, fifo or socket, then set this to " +
                                                   "'read' to read the data like a normal file, or 'skip' to not " +
                                                   "read these types of files. The default is read.";
    private static final String help_mode_dir = "If an input file is a directory, then set this to 'read' to read " +
                                                "directory file itself. Set it to 'skip' to skip listed directories. " +
                                                "Set it to recurse in order to tell grep to search listed " +
                                                "directories for files to match recursively. The default is read.";
    private static final String help_mode_dir_recurse = "Same as --directories recurse.";
    private static final String help_exclude = "Skip processing files whose basename matches the given glob-pattern.";
    private static final String help_exclude_file = "Skip processing files whos basenames matches any of the " +
                                                    "glob patterns in the given file, one per line.";
    private static final String help_exclude_dir = "Exclude directories matching the given glob-pattern.";
    private static final String help_include = "Only process files whose basename matches the given glob-pattern.";
    private static final String help_null_term = "Treat the input as a set of lines terminated by a null byte " +
                                                 "instead of a newline.";
    private static final String help_patterns = "Match the given pattern(s) against the input files.";
    private static final String help_pattern_files = "File with patterns to match, one per line.";
    private static final String help_files = "The files to match against. If there are no files, or if any file is " +
                                             "'-' then match stdandard input.";
    private static final String err_ex_walker = "Exception while walking.";
   
    private class ContextLineWriter implements Closeable, Flushable {
       
        protected LineNumberReader reader;
       
        private PrintWriter writer;
        private Deque<String> contextStack;
        private int linesUntilFlush;
        private int linesForFlush;
        private int contextBefore;
        private int contextAfter;
        private boolean haveLine;
        private boolean firstFlush;
       
        public ContextLineWriter(Writer writer, int before, int after) {
            if (writer instanceof PrintWriter) {
                this.writer = (PrintWriter) writer;
            } else {
                this.writer = new PrintWriter(writer);
            }
           
            firstFlush      = true;
            contextBefore   = before;
            contextAfter    = after;
            linesForFlush   = before + after + 1;
            contextStack    = new ArrayDeque<String>();
        }
       
        public ContextLineWriter(OutputStream out, int before, int after) {
            this(new PrintWriter(out), before, after);
        }
       
        @Override
        public void close() {
            if (reader != null) {
                finish();
            }
            IOUtils.close(true, writer);
            writer = null;
        }
       
        @Override
        public void flush() {
            doFlush();
            while (contextStack.size() > 0) {
                writer.println(contextStack.removeFirst());
            }
            haveLine = false;
            writer.flush();
        }
       
        public void setIn(InputStream in) throws IOException {
            setIn(new InputStreamReader(in));
        }
       
        public LineNumberReader setIn(Reader reader) throws IOException {
            if (isClosed()) {
                throw new IOException("Stream closed");
            }
            if (this.reader != null) {
                finish();
            }
           
            this.reader = new LineNumberReader(reader, BUFFER_SIZE) {
                @Override
                public String readLine() throws IOException {
                    String line = super.readLine();
                    if (line != null) {
                        if (!haveLine) {
                            if (contextStack.size() > 0) {
                                contextStack.addLast(doContextLine(contextStack.removeLast()));
                            }
                            if (contextStack.size() > contextBefore) {
                                contextStack.removeFirst();
                            }
                        } else {
                            if (linesUntilFlush < (linesForFlush)) {
                                contextStack.addLast(doContextLine(contextStack.removeLast()));
                            }
                            if (linesUntilFlush == 0) {
                                String[] saveLines = new String[contextBefore];
                                for (int i = 0; i < contextBefore; i++) {
                                    saveLines[i] = contextStack.removeLast();
                                }
                                contextStack.removeLast();
                                flush();
                                for (int i = contextBefore - 1; i >= 0; i--) {
                                    contextStack.addLast(saveLines[i]);
                                }
                            } else {
                                linesUntilFlush--;
                            }
                        }
                        contextStack.addLast(line);
                    } else {
                        finish();
                    }
                    return line;
                }
            };
           
            return this.reader;
        }
       
        public void addLine(String s) throws IOException {
            if (isClosed()) {
                throw new IOException("Stream closed");
            }
            contextStack.addLast(s);
            writeLast();
        }
       
        public void rewriteLast(String s) throws IOException {
            if (isClosed()) {
                throw new IOException("Stream closed");
            }
            contextStack.removeLast();
            contextStack.addLast(s);
            writeLast();
        }
       
        public void writeLast() throws IOException {
            if (isClosed()) {
                throw new IOException("Stream closed");
            }
            if (!haveLine) {
                haveLine = true;
            }
            linesUntilFlush = linesForFlush;
        }
       
        private void finish() {
            if (reader == null) return;
            if (!haveLine) {
                contextStack.clear();
            } else {
                int excessLines = (linesForFlush - linesUntilFlush) - contextAfter;
                for (int i = 0; i < excessLines; i++) {
                    contextStack.removeLast();
                }
                flush();
            }
            doFinish();
            IOUtils.close(reader);
            reader = null;
        }
       
        private boolean isClosed() {
            return writer == null;
        }
       
        @SuppressWarnings("unused")
        private boolean haveLine() {
            return haveLine;
        }
       
        protected void doFlush() {
            if (!firstFlush) {
                contextStack.addFirst("-----");
            }
            firstFlush = false;
        }
       
        protected void doFinish() {
            // no-op
        }
       
        protected String doContextLine(String s) {
            return prefixLine(s, currentFile, currentLine, currentByte, '-');
        }
    }

    private final StringArgument Patterns;
    private final FileArgument PatternFiles;
    private final FileArgument Files;
    private final FlagArgument MatcherFixed;
    private final FlagArgument MatcherBasic;
    private final FlagArgument MatcherExt;
    private final FlagArgument MatcherPerl;
   
    private final FlagArgument IgnoreCase;
    private final FlagArgument Invert;
    private final FlagArgument MatchWord;
    private final FlagArgument MatchLine;
   
    private final FlagArgument ShowCount;
    private final FlagArgument ShowFileNoMatch;
    private final FlagArgument ShowFileMatch;
    private final FlagArgument ShowOnlyMatch;
    private final IntegerArgument MaxCount;
    private final FlagArgument Quiet;
    private final FlagArgument Suppress;
   
    private final FlagArgument PrefixByte;
    private final FlagArgument PrefixFile;
    private final FlagArgument PrefixNoFile;
    private final StringArgument PrefixLabel;
    private final FlagArgument PrefixLine;
    private final FlagArgument PrefixTab;
    private final FlagArgument PrefixNull;
   
    private final IntegerArgument ContextAfter;
    private final IntegerArgument ContextBefore;
    private final IntegerArgument ContextBoth;
   
    private final StringArgument ModeBinary;
    private final StringArgument ModeDevice;
    private final StringArgument ModeDir;
    private final FlagArgument ModeBinaryText;
    private final FlagArgument ModeBinarySkip;
    private final FlagArgument ModeDirRecurse;
    private final StringArgument Exclude;
    private final FileArgument ExcludeFile;
    private final StringArgument ExcludeDir;
    private final StringArgument Include;
   
    private final FlagArgument NullTerm;
    private final FlagArgument Debug;
   
    private static final int MATCHER_FIXED = 1;
    private static final int MATCHER_BASIC = 2;
    private static final int MATCHER_EXT   = 3;
    private static final int MATCHER_PERL  = 4;
   
    private static final int PREFIX_FILE   = 0x01;
    private static final int PREFIX_LINE   = 0x02;
    private static final int PREFIX_BYTE   = 0x04;
    private static final int PREFIX_NOFILE = 0x08;
    private static final int PREFIX_TAB    = 0x10;
    private static final int PREFIX_NULL   = 0x20;
    @SuppressWarnings("unused")
    private static final int PREFIX_ALL    = PREFIX_FILE | PREFIX_LINE | PREFIX_BYTE;
    @SuppressWarnings("unused")
    private static final int PREFIX_FL     = PREFIX_FILE | PREFIX_LINE;
    @SuppressWarnings("unused")
    private static final int PREFIX_FB     = PREFIX_FILE | PREFIX_BYTE;
    @SuppressWarnings("unused")
    private static final int PREFIX_LB     = PREFIX_LINE | PREFIX_BYTE;
   
    private PrintWriter err;
    private PrintWriter out;
    private ContextLineWriter contextOut;
    private Reader in;
    private List<File> files;
    private List<Pattern> patterns;
    private String prefixLabel;
    private Matcher match;
    private String currentFile;
    private int matcher;
    private int prefix;
    private int maxCount = Integer.MAX_VALUE;
    private int contextBefore;
    private int contextAfter;
    @SuppressWarnings("unused")
    private int patternFlags;
    private int rc = 1;
    private int currentLine;
    private int currentByte;
    private boolean inverse;
    private boolean matchCase;
    private boolean matchWord;
    private boolean matchLine;
    private boolean showCount;
    private boolean showFileMatch;
    private boolean showFileNoMatch;
    private boolean showOnlyMatch;
    private boolean quiet;
    private boolean suppress;
    private boolean debug;
    private boolean recurse;
    @SuppressWarnings("unused")
    private boolean dirAsFile;
    @SuppressWarnings("unused")
    private boolean binaryAsText;
    @SuppressWarnings("unused")
    private boolean binaryAsBinary;
    @SuppressWarnings("unused")
    private boolean readDevice;
    private boolean exitOnFirstMatch;
   
    public GrepCommand() {
        super("Search for lines that match a string or regex");
        MatcherFixed = new FlagArgument("matcher-fixed", 0, help_matcher_fixed);
        MatcherBasic = new FlagArgument("matcher-basic", 0, help_matcher_basic);
        MatcherExt   = new FlagArgument("matcher-ext", 0, help_matcher_ext);
        MatcherPerl  = new FlagArgument("matcher-perl", 0, help_matcher_perl);
        registerArguments(MatcherFixed, MatcherBasic, MatcherExt, MatcherPerl);
       
        IgnoreCase   = new FlagArgument("ignore-case", 0, help_case);
        Invert       = new FlagArgument("invert", 0, help_invert);
        MatchWord    = new FlagArgument("word-match", 0, help_match_word);
        MatchLine    = new FlagArgument("line-match", 0, help_match_line);
        MaxCount     = new IntegerArgument("max-matches", 0, help_max);
        Quiet        = new FlagArgument("quiet", 0, help_quiet);
        Suppress     = new FlagArgument("suppress", 0, help_suppress);
        Debug        = new FlagArgument("debug", 0, help_debug);
        registerArguments(IgnoreCase, Invert, MatchWord, MatchLine, MaxCount, Quiet, Suppress, Debug);
       
        ShowCount       = new FlagArgument("show-count", 0, help_count);
        ShowFileNoMatch = new FlagArgument("show-files-nomatch", 0, help_file_nomatch);
        ShowFileMatch   = new FlagArgument("show-files-match", 0, help_file_match);
        ShowOnlyMatch   = new FlagArgument("show-only-match", 0, help_only_matching);
        registerArguments(ShowCount, ShowFileNoMatch, ShowFileMatch, ShowOnlyMatch);
       
        PrefixByte   = new FlagArgument("prefix-byte", 0, help_prefix_byte);
        PrefixFile   = new FlagArgument("prefix-file", 0, help_prefix_file);
        PrefixNoFile = new FlagArgument("prefix-nofile", 0, help_prefix_nofile);
        PrefixLabel  = new StringArgument("prefix-label", 0, help_prefix_label);
        PrefixLine   = new FlagArgument("prefix-line", 0, help_prefix_line);
        PrefixTab    = new FlagArgument("prefix-tab", 0, help_prefix_tab);
        PrefixNull   = new FlagArgument("prefix-null", 0, help_prefix_null);
        registerArguments(PrefixByte, PrefixFile, PrefixNoFile, PrefixLabel, PrefixLine, PrefixTab, PrefixNull);
       
        ContextAfter  = new IntegerArgument("show-context-after", 0, help_context_after);
        ContextBefore = new IntegerArgument("show-context-before", 0, help_context_before);
        ContextBoth   = new IntegerArgument("show-context-both", 0, help_context_both);
        registerArguments(ContextAfter, ContextBefore, ContextBoth);
       
        ModeBinary     = new StringArgument("mode-binary", 0, help_mode_binary);
        ModeDevice     = new StringArgument("mode-device", 0, help_mode_device);
        ModeDir        = new StringArgument("mode-dir", 0, help_mode_dir);
        ModeBinaryText = new FlagArgument("mode-binary-text", 0, help_mode_binary_text);
        ModeBinarySkip = new FlagArgument("mode-binary-skip", 0, help_mode_binary_skip);
        ModeDirRecurse = new FlagArgument("mode-dir-recurse", 0, help_mode_dir_recurse);
        Exclude        = new StringArgument("pattern-exclude", 0, help_exclude);
        ExcludeFile    = new FileArgument("pattern-exclude-file", Argument.EXISTING, help_exclude_file);
        ExcludeDir     = new StringArgument("pattern-exclude-dir", 0, help_exclude_dir);
        Include        = new StringArgument("pattern-include", 0, help_include);
        registerArguments(ModeBinary, ModeBinaryText, ModeBinarySkip, ModeDevice, ModeDir, ModeDirRecurse);
        registerArguments(Exclude, ExcludeFile, ExcludeDir, Include);
       
        NullTerm = new FlagArgument("null-term", 0, help_null_term);
        Patterns = new StringArgument("patterns", Argument.MULTIPLE | Argument.MANDATORY, help_patterns);
        PatternFiles = new FileArgument("pattern-files", Argument.MULTIPLE | Argument.EXISTING, help_pattern_files);
        Files = new
            FileArgument("files", Argument.MULTIPLE | Argument.EXISTING | FileArgument.HYPHEN_IS_SPECIAL, help_files);
        registerArguments(Patterns, PatternFiles, Files, NullTerm);
       
        // Default matcher
        match = Pattern.compile(".*").matcher("");
    }

    /**
     * main method, normally not used, use execute instead!!
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        new GrepCommand().execute(args);
    }

    /**
     * Primary entry point
     */
    public void execute() throws Exception {
        err = getError().getPrintWriter();
        in = getInput().getReader();
        out = getOutput().getPrintWriter();
       
        LineNumberReader reader;
        String name;
       
        try {
            parseOptions();
            if ((contextBefore > 0) || (contextAfter > 0)) {
                contextOut = new ContextLineWriter(out, contextBefore, contextAfter);
            }
           
            for (File file : files) {
                reader = null;
                name   = file.getPath();
                try {
                    if (name.equals("-")) {
                        if (contextOut != null) {
                            reader = contextOut.setIn(in);
                        } else {
                            reader = new LineNumberReader(in, BUFFER_SIZE);
                        }
                        name = prefixLabel;
                    } else {
                        if (contextOut != null) {
                            reader = contextOut.setIn(IOUtils.openReader(file));
                        } else {
                            reader = IOUtils.openLineReader(file, BUFFER_SIZE);
                        }
                    }
                    currentFile = name;
                    if (exitOnFirstMatch) {
                        if (matchUntilOne(reader)) {
                            rc = 0;
                            break;
                        }
                        continue;
                    }
                    if (showFileMatch) {
                        if (matchUntilOne(reader)) {
                            printFile(name);
                        }
                        continue;
                    }
                    if (showFileNoMatch) {
                        if (!matchUntilOne(reader)) {
                            printFile(name);
                        }
                        continue;
                    }
                    if (showCount) {
                        printFileCount(matchCount(reader));
                        continue;
                    }
                    matchNormal(reader);
                } catch (IOException e) {
                    error("IOException greping file : " + file);
                } finally {
                    IOUtils.close(reader);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            exit(rc);
        }
    }
   
    /* Each of the next few methods are inner loops for different conditions. This
     * is mostly to avoid a complex set of branches inside the inner loop. With any
     * luck they will get inlined anyway.
     */
   
    /**
     * Matches lines in a file until a single match is made.
     */
    private boolean matchUntilOne(LineNumberReader reader) throws IOException {
        String line;
        while ((line = reader.readLine()) != null) {
            if (match(line) != null) {
                return true;
            }
        }
        return false;
    }
   
    /**
     * Counts the number of matching or non-matching lines in a file.
     */
    private int matchCount(LineNumberReader reader) throws IOException {
        String line;
        int ret = 0;
        while ((ret < maxCount) && ((line = reader.readLine()) != null)) {
            if (((match(line) != null) ^ inverse)) {
                ret++;
            }
        }
        return ret;
    }
   
    /**
     * Uses the MatchResult to only print the substring of the line that matched.
     */
    @SuppressWarnings("unused")
    private void matchSubstring(LineNumberReader reader, String name) throws IOException {
        String line;
        MatchResult result;
        int byteCount = 0;
        int matches = 0;
        while ((matches < maxCount) && (line = reader.readLine()) != null) {
            result = match(line);
            if (result != null) {
                printMatch(line.substring(result.start(), result.end()),
                           name, reader.getLineNumber(), byteCount + result.start());
                rc = 0;
                matches++;
            }
            byteCount += line.length();
        }
    }
   
    /**
     * Prints matching or non-matching lines to stdout with a possible prefix.
     */
    private void matchNormal(LineNumberReader reader) throws IOException {
        String line;
        int matches = 0;
       
        while ((matches < maxCount) && ((line = reader.readLine()) != null)) {
            currentLine = reader.getLineNumber();
            if ((match(line) != null) ^ inverse) {
                printMatch(line, currentFile, currentLine, currentByte);
                rc = 0;
                matches++;
            }
            currentByte += line.length() + 1;
        }
    }
   
    /**
     * Compares the line to a list of patterns, returing the result of the first one to match.
     */
    private MatchResult match(String line) {
        match.reset(line);
        for (Pattern pattern : patterns) {
            if (match.reset().usePattern(pattern).find()) {
                return match.toMatchResult();
            }
        }
        return null;
    }
   
    private String prefixLine(String line, String name, int lineCount, int byteCount, char fieldSep) {
        if (prefix == PREFIX_NOFILE) {
            return line;
        }
       
        StringBuilder sb = new StringBuilder();
       
        if ((prefix & PREFIX_TAB) != 0) {
            if ((prefix & PREFIX_FILE) != 0) {
                sb.append(name);
                if ((prefix & (PREFIX_LINE | PREFIX_BYTE)) == 0) {
                    sb.append('\t');
                }
                sb.append(fieldSep);
            }
            if ((prefix & PREFIX_LINE) != 0) {
                sb.append(padNumber(lineCount, 4));
                if ((prefix & PREFIX_BYTE) == 0) {
                    sb.append('\t');
                }
                sb.append(fieldSep);
            }
            if ((prefix & PREFIX_BYTE) != 0) {
                sb.append(padNumber(byteCount, 9));
                sb.append('\t');
                sb.append(fieldSep);
            }
        } else {
            if ((prefix & PREFIX_FILE) != 0) {
                sb.append(name);
                sb.append(fieldSep);
            }
            if ((prefix & PREFIX_LINE) != 0) {
                sb.append(lineCount);
                sb.append(fieldSep);
            }
            if ((prefix & PREFIX_BYTE) != 0) {
                sb.append(byteCount);
                sb.append(fieldSep);
            }
        }
        return sb.append(line).toString();
    }
   
    /**
     * Outputs a matched string, in the given file, at the given line and the given byte offset. Outputs
     * to stdout the match string, along with any set prefix options.
     */
    private void printMatch(String line, String name, int lineCount, int byteCount) throws IOException {
        if (quiet) return;
        String newLine = prefixLine(line, name, lineCount, byteCount, ':');
        if (contextOut != null) {
            if (newLine == line) {
                contextOut.writeLast();
            } else {
                contextOut.rewriteLast(newLine);
            }
        } else {
            out.println(newLine);
        }
    }
   
    /**
     * Outputs the name and count seperated by a colon or null byte
     */
    private void printFileCount(int count) {
        if (quiet) return;
        out.print(currentFile);
        out.print(":");
        out.println(count);
    }
   
    /**
     * Outputs a file name and appends a null byte if PREFIX_NULL is set, otherwise it
     * appends a newline.
     */
    private void printFile(String name) {
        if (quiet) return;
        out.print(name);
        if ((prefix & PREFIX_NULL) != 0) {
            out.print('\u0000');
        } else {
            out.println();
        }
    }
   
    private String padNumber(int n, int size) {
        return String.format("%" + size + 'd', n);
    }
   
    /**
     * grep uses different mnemonics for character classes, need to convert grep-style
     * to java-style.
     * Perl regex is unformatted
     * Ext regex is formatted as follows
     * [:alnum:] = \p{Alnum}
     * [:alpha:] = \p{Alpha}
     * [:cntrl:] = \p{Cntrl}
     * [:digit:] = \p{Digit}
     * [:graph:] = ???
     * [:lower:] = \p{Lower}
     * [:print:] = ???
     * [:punct:] = \p{Punct}
     * [:upper:] = \p{Upper}
     * [:xdigit:] = [0-9A-Fa-f]
     * \< and \> = match word boundary
     * In basic
     */
    private Pattern rewritePattern(String pattern) throws PatternSyntaxException {
        debug("Pattern before: " + pattern);
        int flags = 0;
       
        if (matcher == MATCHER_FIXED) {
            pattern = Pattern.quote(pattern);
        }
        if (!matchCase) {
            flags |= Pattern.CASE_INSENSITIVE;
        }
       
        StringBuilder sb = new StringBuilder(pattern);
       
        switch(matcher) {
            case MATCHER_BASIC :
                // de-sensitize some meta-characters
                // fall through
            case MATCHER_EXT :
                // perform class synax conversion ([:class:] -> \p{Class})
                // fall through :
            case MATCHER_PERL :
                // nothing for now
                // fall through
            default :
                break;
        }
       
        if (matchWord) {
            if (!pattern.startsWith("\\b")) {
                sb.insert(0, "\\b");
            }
            if (!pattern.endsWith("\\b")) {
                sb.append("\\b");
            }
        } else if (matchLine) {
            if (!pattern.startsWith("^")) {
                sb.insert(0, '^');
            }
            if (!pattern.endsWith("$")) {
                sb.append('$');
            }
        }
       
        debug("Pattern after : " + sb);
        return Pattern.compile(sb.toString(), flags);
    }
   
    /*********************************************************/
    /************** Command Line Parsing *********************/
    /*********************************************************/
   
    private void parseOptions() {
        // parse these early so they are enforced
        quiet    = Quiet.isSet();
        suppress = Suppress.isSet();
        debug    = DEBUG || Debug.isSet();
       
        if (PrefixLabel.isSet())  prefixLabel = PrefixLabel.getValue();
        else                      prefixLabel = "stdin";
        if (PrefixByte.isSet())   prefix |= PREFIX_BYTE;
        if (PrefixFile.isSet())   prefix |= PREFIX_FILE;
        if (PrefixNoFile.isSet()) prefix |= PREFIX_NOFILE;
        if (PrefixLine.isSet())   prefix |= PREFIX_LINE;
        if (PrefixTab.isSet())    prefix |= PREFIX_TAB;
        if (PrefixNull.isSet())   prefix |= PREFIX_NULL;
       
        if ((prefix & (PREFIX_FILE | PREFIX_NOFILE)) == (PREFIX_FILE | PREFIX_NOFILE)) {
            prefix ^= PREFIX_NOFILE;
        }
       
        if (MatcherFixed.isSet()) matcher = MATCHER_FIXED;
        if (MatcherBasic.isSet()) matcher = MATCHER_BASIC;
        if (MatcherExt.isSet())   matcher = MATCHER_EXT;
        if (MatcherPerl.isSet())  matcher = MATCHER_PERL;
        if (matcher == 0)         matcher = MATCHER_BASIC;
        matchWord   = MatchWord.isSet();
        matchLine   = MatchLine.isSet();
        matchCase   = !IgnoreCase.isSet();
        inverse = Invert.isSet();
        parsePatterns()// This requires the above options be parsed already.
       
        if (MaxCount.isSet()) maxCount = MaxCount.getValue();
       
        showCount       = ShowCount.isSet();
        showFileMatch   = ShowFileMatch.isSet();
        showFileNoMatch = ShowFileNoMatch.isSet();
        showOnlyMatch   = ShowOnlyMatch.isSet();
       
        // Setup for fast-path exit(0) on first match
        if (quiet || (inverse && showOnlyMatch)) {
            exitOnFirstMatch = true;
        }
       
        String s = " ";
        if (ModeBinary.isSet()) {
            s = ModeBinary.getValue();
        }
       
        if (!(ModeBinarySkip.isSet() || s.equals("without-match"))) {
            if (ModeBinaryText.isSet() || s.equals("text")) {
                binaryAsText = true;
            } else {
                binaryAsBinary = true;
            }
        }
       
        s = " ";
        if (ModeDir.isSet()) {
            s = ModeDir.getValue();
        }
       
        if (ModeDirRecurse.isSet() || s.equals("recurse")) {
            recurse = true;
        }
       
        if (!(ModeDevice.isSet() && ModeDevice.getValue().equals("skip"))) {
            readDevice = true;
        }
       
        parseFiles();    // This requires the above options be parsed already.
        if (files.size() > 1) {
            if ((prefix & PREFIX_NOFILE) == 0) {
                prefix |= PREFIX_FILE;
            }
        } else {
            if ((prefix & PREFIX_FILE) == 0) {
                prefix |= PREFIX_NOFILE;
            }
        }
       
        if (ContextBoth.isSet()) {
            contextAfter = contextBefore = ContextBoth.getValue();
        } else if (ContextBefore.isSet()) {
            contextBefore = ContextBefore.getValue();
        } else if (ContextAfter.isSet()) {
            contextAfter = ContextAfter.getValue();
        }
        if ((contextAfter > 0 || contextBefore > 0) && showOnlyMatch) {
            contextAfter = contextBefore = 0;
        }
    }
   
    private void parsePatterns() {
        BufferedReader reader;
        String line;
        patterns = new ArrayList<Pattern>();
       
        for (String s : Patterns.getValues()) {
            try {
                patterns.add(rewritePattern(s));
            } catch (PatternSyntaxException e) {
                error("Invalid Pattern : " + s);
                exit(2);
            }
        }
       
        for (File file : PatternFiles.getValues()) {
            reader = null;
            try {
                reader = IOUtils.openBufferedReader(file, BUFFER_SIZE);
                while ((line = reader.readLine()) != null) {
                    try {
                        patterns.add(rewritePattern(line));
                    } catch (PatternSyntaxException e) {
                        error("Invalid Pattern : " + line);
                        exit(2);
                    }
                }
            } catch (IOException e) {
                debug("IOException while parsing pattern file : " + file);
                error("Error reading file: " + file);
                exit(2);
            } finally {
                IOUtils.close(reader);
            }
        }
    }
   
    private class Walker extends AbstractDirectoryWalker {
        @Override
        public void handleFile(File file) {
            files.add(file);
        }
        @Override
        public void handleDir(File dir) {
            // no-op
        }
        @Override
        public void handleRestrictedFile(File file) {
            // no-op
        }
       
        private void doFile(File file) {
            if (notFiltered(file)) {
                files.add(file);
            }
        }
    }
   
    private void parseFiles() {
        BufferedReader reader;
       
        files = new ArrayList<File>();
       
        if (!Files.isSet()) {
            files.add(new File("-"));
            return;
        }
       
        Walker walker = new Walker();
       
        for (String s : Include.getValues()) {
            walker.addFilter(new PathnamePatternFilter(s, false));
        }
       
        for (String s : Exclude.getValues()) {
            walker.addFilter(new PathnamePatternFilter(s, true));
        }
       
        for (File file : ExcludeFile.getValues()) {
            reader = IOUtils.openBufferedReader(file, BUFFER_SIZE);
            List<String> lines = null;
            try {
                lines = IOUtils.readLines(reader);
            } finally {
                IOUtils.close(reader);
            }
            if (lines != null) {
                for (String s : lines) {
                    walker.addFilter(new PathnamePatternFilter(s, true));
                }
            }
        }
       
        for (final String s : ExcludeDir.getValues()) {
            walker.addDirectoryFilter(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return !(file.isDirectory() && file.getName().equals(s));
                }
            });
        }
       
        List<File> dirs = new ArrayList<File>();
       
        for (File file : Files.getValues()) {
            if (file.isDirectory()) {
                if (recurse) {
                    dirs.add(file);
                }
            } else if (file.isFile()) {
                walker.doFile(file);
            } else {
                // skip special files
            }
        }
       
        try {
            if (dirs.size() > 0) {
                walker.walk(dirs);
            }
        } catch (IOException e) {
            // technically, the walker shouldn't let this propagate unless something
            // is really wrong.
            error(err_ex_walker);
            exit(2);
        }
    }
   
    private void error(String s) {
        if (!suppress) err.println(s);
    }
   
    private void debug(String s) {
        if (debug) log.debug(s);
    }
   
    @SuppressWarnings("unused")
    private void debugOptions() {
        debug("Files : " + files.size());
        for (File file : files) {
            debug(" - " + file);
        }
        debug("Patterns : " + patterns.size());
        for (Pattern p : patterns) {
            debug(" - " + p);
        }
    }
}
TOP

Related Classes of org.jnode.command.file.GrepCommand$Walker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.