All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.unkrig.zz.find.Parser Maven / Gradle / Ivy

There is a newer version: 1.3.10
Show newest version

/*
 * de.unkrig.find - An advanced version of the UNIX FIND utility
 *
 * Copyright (c) 2011, Arno Unkrig
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. The name of the author may not be used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package de.unkrig.zz.find;

import java.io.File;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import de.unkrig.commons.lang.ExceptionUtil;
import de.unkrig.commons.lang.protocol.Predicate;
import de.unkrig.commons.lang.protocol.ProducerWhichThrows;
import de.unkrig.commons.nullanalysis.Nullable;
import de.unkrig.commons.text.parser.AbstractParser;
import de.unkrig.commons.text.parser.ParseException;
import de.unkrig.commons.text.scanner.AbstractScanner;
import de.unkrig.commons.text.scanner.AbstractScanner.Token;
import de.unkrig.commons.text.scanner.ScanException;
import de.unkrig.zz.find.Find.AndTest;
import de.unkrig.zz.find.Find.CatAction;
import de.unkrig.zz.find.Find.ChecksumAction;
import de.unkrig.zz.find.Find.ChecksumAction.ChecksumType;
import de.unkrig.zz.find.Find.CommaTest;
import de.unkrig.zz.find.Find.CopyAction;
import de.unkrig.zz.find.Find.DigestAction;
import de.unkrig.zz.find.Find.DisassembleAction;
import de.unkrig.zz.find.Find.EchoAction;
import de.unkrig.zz.find.Find.ExecAction;
import de.unkrig.zz.find.Find.ExecutabilityTest;
import de.unkrig.zz.find.Find.Expression;
import de.unkrig.zz.find.Find.LsAction;
import de.unkrig.zz.find.Find.ModificationTimeTest;
import de.unkrig.zz.find.Find.NameTest;
import de.unkrig.zz.find.Find.NotExpression;
import de.unkrig.zz.find.Find.OrTest;
import de.unkrig.zz.find.Find.PathTest;
import de.unkrig.zz.find.Find.PipeAction;
import de.unkrig.zz.find.Find.PrintAction;
import de.unkrig.zz.find.Find.ReadabilityTest;
import de.unkrig.zz.find.Find.SizeTest;
import de.unkrig.zz.find.Find.Test;
import de.unkrig.zz.find.Find.TypeTest;
import de.unkrig.zz.find.Find.WritabilityTest;

/**
 * Parses the predicate syntax of the UNIX FIND command line utility.
 */
public
class Parser {

    /**
     * The token types known to this scanner.
     *
     * @see AbstractScanner#AbstractScanner()
     */
    enum TokenType { LITERAL }

    private final AbstractParser parser;
    private final OutputStream              outOS;

    private boolean hadAction;

    /**
     * @param producer The source of tokens to parse
     * @param out      Where the {@link CatAction} writes its output
     */
    public 
    Parser(final ProducerWhichThrows producer, OutputStream out) {

        this.parser = new AbstractParser(new ProducerWhichThrows, ScanException>() {

            @Override @Nullable public Token
            produce() throws ScanException {

                String text;
                try {
                    text = producer.produce();
                } catch (Exception e) {
                    throw ExceptionUtil.wrap(null, e, ScanException.class);
                }

                if (text == null) return null;

                return new Token(TokenType.LITERAL, text);
            }
        });

        this.outOS = out;
    }

    /**
     * 
     * expression := [ or-expression ]
     * 
* "No token" means "-print". *

* An expression without any actions is silently converted to "expr && -print". *

* Example: *

     *    -name "*.java" -o ( -name "*foo*" ! -name "*foobar" )
     * 
*/ public Expression parse() throws ParseException { if (this.parser.peek() == null) return new PrintAction(); final Expression result = this.parseComma(); this.parser.eoi(); return this.hadAction ? result : new AndTest(result, new PrintAction()); } /** *
     * comma-expression := or-expression [ ',' comma-expression ]
     * 
*/ private Expression parseComma() throws ParseException { Expression lhs = this.parseOr(); return this.parser.peekRead(",") ? new CommaTest(lhs, this.parseComma()) : lhs; } /** *
     * or-expression := and-expression [ ( '-o' | '-or' ) or-expression ]
     * 
*/ private Expression parseOr() throws ParseException { Expression lhs = this.parseAnd(); if (this.parser.peekRead("-o", "-or") == -1) return lhs; Expression rhs = this.parseOr(); return new OrTest(lhs, rhs); } /** *
     * and-expression := primary-expression [ [ '-a' | '-and' ] and-expression ]
     * 
*/ private Expression parseAnd() throws ParseException { Expression lhs = this.parsePrimary(); return ( this.parser.peekRead("-a", "-and") != -1 || this.parser.peek("-o", "-or", ")", ",", null) == -1 ) ? new AndTest(lhs, this.parseAnd()) : lhs; } /** *
     * primary-expression :=
     *   '(' or-expression ')'
     *   | ( '!' | '-not' ) primary-expression
     *   | '-name' literal
     *   | '-path' literal
     *   | '-type' glob
     *   | '-readable'
     *   | '-writable'
     *   | '-executable'
     *   | '-size' numeric
     *   | '-mtime' numeric
     *   | '-mmin' numeric
     *   | '-print'
     *   | '-echo'
     *   | '-ls'
     *   | '-exec' { literal } ';'
     *   | '-pipe' { literal } ';'
     *   | '-cat'
     *   | '-copy' string
     *   | '-disassemble' boolean boolean file
     *   | '-digest' string
     *   | '-checksum' string
     *   | '-true'
     *   | '-false'
     * 
*/ private Expression parsePrimary() throws ParseException { switch (this.parser.read( "(", "!", "-not", "-name", "-path", "-type", "-readable", "-writable", "-executable", "-size", "-mtime", "-mmin", "-print", "-echo", "-ls", "-exec", "-pipe", "-cat", "-copy", "-disassemble", "-digest", "-checksum", "-true", "-false" )) { case 0: // '(' final Expression result = this.parseComma(); this.parser.read(")"); return result; case 1: // '!' case 2: // '-not' return new NotExpression(this.parsePrimary()); case 3: // '-name' return new NameTest(this.parser.read().text); case 4: // '-path' return new PathTest(this.parser.read().text); case 5: // '-type' glob return new TypeTest(this.parser.read().text); case 6: // '-readable' return new ReadabilityTest(); case 7: // '-writable' return new WritabilityTest(); case 8: // '-executable' return new ExecutabilityTest(); case 9: // '-size' return new SizeTest(Parser.parseNumericArgument(this.parser.read().text)); case 10: // '-mtime' return new ModificationTimeTest( Parser.parseNumericArgument(this.parser.read().text), ModificationTimeTest.DAYS ); case 11: // '-mmin' return new ModificationTimeTest( Parser.parseNumericArgument(this.parser.read().text), ModificationTimeTest.MINUTES ); case 12: // '-print' this.hadAction = true; return new PrintAction(); case 13: // '-echo' this.hadAction = true; return new EchoAction(this.parser.read().text); case 14: // '-ls' this.hadAction = true; return new LsAction(); case 15: // '-exec' word ... ';' { List command = new ArrayList(); while (!this.parser.peekRead(";")) command.add(this.parser.read().text); this.hadAction = true; return new ExecAction(command); } case 16: // '-pipe' word ... ';' { List command = new ArrayList(); while (!this.parser.peekRead(";")) command.add(this.parser.read().text); this.hadAction = true; return new PipeAction(command, null); } case 17: // '-cat' this.hadAction = true; return new CatAction(this.outOS); case 18: // '-copy' this.hadAction = true; return new CopyAction(new File(this.parser.read().text), false); case 19: // "-disassemble" this.hadAction = true; return new DisassembleAction( this.parser.peekRead("-hideLines"), // hideLines this.parser.peekRead("-hideVars"), // hideVars null // file ); case 20: // "-digest" this.hadAction = true; return new DigestAction(this.parser.read().text); case 21: // "-checksum" this.hadAction = true; ChecksumType cst; try { cst = ChecksumAction.ChecksumType.valueOf(this.parser.read().text); } catch (IllegalArgumentException iae) { throw new IllegalArgumentException( "Invalid checksum type \"" + this.parser.read().text + "\"; allowed values are " + Arrays.toString(ChecksumAction.ChecksumType.values()) ); } return new ChecksumAction(cst); case 22: // "-true" return Test.TRUE; case 23: // "-false" return Test.FALSE; default: throw new IllegalStateException(); } } /** *
     * numeric := [ '+' | '-' ] digit { digit } [ 'k' | 'M' | 'G' ]
     * 
*/ public static Predicate parseNumericArgument(String text) { final Mode mode; final long n; if (text.startsWith("+")) { mode = Mode.MORE_THAN; text = text.substring(1); } else if (text.startsWith("-")) { mode = Mode.LESS_THAN; text = text.substring(1); } else { mode = Mode.EXACTLY; } long multiplier; if (text.endsWith("k")) { multiplier = 1024L; text = text.substring(0, text.length() - 1); } else if (text.endsWith("M")) { multiplier = 1024L * 1024L; text = text.substring(0, text.length() - 1); } else if (text.endsWith("G")) { multiplier = 1024L * 1024L * 1024L; text = text.substring(0, text.length() - 1); } else { multiplier = 1L; } n = multiplier * Long.parseLong(text); return new Predicate() { @Override public boolean evaluate(Long subject) { switch (mode) { case EXACTLY: return subject == n; case LESS_THAN: return subject < n; case MORE_THAN: return subject > n; default: throw new IllegalStateException(); } } @Override public String toString() { return ( mode == Mode.MORE_THAN ? "> " : mode == Mode.LESS_THAN ? "< " : mode == Mode.EXACTLY ? "== " : "??? " ) + n; } }; } private enum Mode { MORE_THAN, LESS_THAN, EXACTLY } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy