Package org.jnode.shell

Source Code of org.jnode.shell.DefaultInterpreter$Tokenizer

/*
* $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.shell;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.NoSuchElementException;

import org.apache.log4j.Logger;
import org.jnode.shell.CommandLine.Token;
import org.jnode.shell.help.Help;
import org.jnode.shell.help.HelpException;
import org.jnode.shell.help.HelpFactory;

/**
* This interpreter simply parses the command line into a command name and
* arguments, with simple quoting and escaping.  This class also provides
* infrastructure that is reused in the RedirectingInterpreter class.
*
* @author crawley@jnode.org
*/
public class DefaultInterpreter implements CommandInterpreter {

    public static final Factory FACTORY = new Factory() {
        public CommandInterpreter create() {
            return new DefaultInterpreter();
        }

        public String getName() {
            return "default";
        }
    };

    static final String[] NO_ARGS = new String[0];
   
    private static final Logger LOG = Logger.getLogger(DefaultInterpreter.class);

    public static final int REDIRECTS_FLAG = 0x01;

    // Token types.
    public static final int LITERAL = 0;
    public static final int STRING = 1;
    public static final int CLOSED = 2;
    public static final int SPECIAL = 4;

    // Recognized meta-characters
    public static final char ESCAPE_CHAR = '\\';
    public static final char FULL_ESCAPE_CHAR = '\'';
    public static final char QUOTE_CHAR = '"';
    public static final char SPACE_CHAR = ' ';
    public static final char SEND_OUTPUT_TO_CHAR = '>';
    public static final char GET_INPUT_FROM_CHAR = '<';
    public static final char PIPE_CHAR = '|';
    public static final char COMMENT_CHAR = '#';

    // Recognized '\' escapes
    private static final char ESCAPE_B = '\b';
    private static final char B = 'b';
    private static final char ESCAPE_N = '\n';
    private static final char N = 'n';
    private static final char ESCAPE_R = '\r';
    private static final char R = 'r';
    private static final char ESCAPE_T = '\t';
    private static final char T = 't';

    @Override
    public String getName() {
        return "default";
    }
   
    /**
     * {@inheritDoc}
     *
     * The default interpreter treats a command script as a sequence of commands.
     * Commands are expected to consist of exactly one line.  Any line whose first non-whitespace
     * character is '#' will be ignored.  Command line arguments from the script are not supported,
     * and will result in a {@link ShellException} being thrown.
     */
    @Override
    public int interpret(CommandShell shell, Reader reader, boolean script, String alias, String[] args)
        throws ShellException {
        if (args != null && args.length > 0) {
            throw new ShellInvocationException(
                    "The " + getName() + " interpreter does not support script file arguments");
        }
        try {
            BufferedReader br = new BufferedReader(reader);
            String line;
            int rc = 0;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.length() > 0 && !line.startsWith("#")) {
                    rc = interpret(shell, line);
                }
                if (!script) {
                    break;
                }
            }
            return rc;
        } catch (IOException ex) {
            throw new ShellInvocationException("Problem reading command: " + ex.getMessage(), ex);
        } finally {
            try {
                reader.close();
            } catch (IOException ex) {
                // ignore
            }
        }
    }
   
    @Override
    public Completable parsePartial(CommandShell shell, String line) throws ShellException {
        CommandLine res = doParseCommandLine(line);
        return res == null ? new CommandLine("", null) : res;
    }
   
    @Override
    public boolean help(CommandShell shell, String line, PrintWriter pw) throws ShellException {
        CommandLine cmd = doParseCommandLine(line);
        CommandInfo cmdInfo = cmd.getCommandInfo(shell);
        if (cmdInfo != null) {
            try {
                Help help = HelpFactory.getHelpFactory().getHelp(cmd.getCommandName(), cmdInfo);
                help.usage(pw);
                return true;
            } catch (HelpException ex) {
                LOG.info("Unexpected error while getting help for alias / class '" +
                        cmd.getCommandName() + "': " + ex.getMessage(), ex);
            }
        }
        return false;
    }
   
    protected int interpret(CommandShell shell, String line)
        throws ShellException {
        CommandLine cmd = doParseCommandLine(line);
        if (cmd == null) {
            return 0;
        }
        return shell.invoke(cmd, null, null);
    }

    private CommandLine doParseCommandLine(String line) throws ShellException {
        Tokenizer tokenizer = new Tokenizer(line);
        if (!tokenizer.hasNext()) {
            return null;
        }
        CommandLine.Token commandToken = tokenizer.next();
        LinkedList<CommandLine.Token> tokenList =
                new LinkedList<CommandLine.Token>();
        while (tokenizer.hasNext()) {
            tokenList.add(tokenizer.next());
        }
        CommandLine.Token[] argTokens =
                tokenList.toArray(new CommandLine.Token[tokenList.size()]);
        CommandLine res = new CommandLine(commandToken, argTokens, null);
        res.setArgumentAnticipated(tokenizer.whitespaceAfterLast());
        return res;
    }

    @Override
    public String escapeWord(String word) {
        return escapeWord(word, false);
    }

    protected String escapeWord(String word, boolean escapeRedirects) {
        final int len = word.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char ch = word.charAt(i);
            switch (ch) {
                case ESCAPE_B:
                    sb.append(ESCAPE_CHAR).append(B);
                    break;
                case ESCAPE_N:
                    sb.append(ESCAPE_CHAR).append(N);
                    break;
                case ESCAPE_R:
                    sb.append(ESCAPE_CHAR).append(R);
                    break;
                case ESCAPE_T:
                    sb.append(ESCAPE_CHAR).append(T);
                    break;
                case ESCAPE_CHAR:
                    sb.append(ESCAPE_CHAR).append(ESCAPE_CHAR);
                    break;
                case FULL_ESCAPE_CHAR:
                    sb.append(ESCAPE_CHAR).append(FULL_ESCAPE_CHAR);
                    break;
                case QUOTE_CHAR:
                    sb.append(ESCAPE_CHAR).append(QUOTE_CHAR);
                    break;
                case COMMENT_CHAR:
                    sb.append(ESCAPE_CHAR).append(COMMENT_CHAR);
                    break;
                case SPACE_CHAR:
                    sb.append(ESCAPE_CHAR).append(SPACE_CHAR);
                    break;
                case PIPE_CHAR:
                    if (escapeRedirects) {
                        sb.append(ESCAPE_CHAR).append(PIPE_CHAR);
                    } else {
                        sb.append(PIPE_CHAR);
                    }
                    break;
                case SEND_OUTPUT_TO_CHAR:
                    if (escapeRedirects) {
                        sb.append(ESCAPE_CHAR).append(SEND_OUTPUT_TO_CHAR);
                    } else {
                        sb.append(SEND_OUTPUT_TO_CHAR);
                    }
                    break;
                case GET_INPUT_FROM_CHAR:
                    if (escapeRedirects) {
                        sb.append(ESCAPE_CHAR).append(GET_INPUT_FROM_CHAR);
                    } else {
                        sb.append(GET_INPUT_FROM_CHAR);
                    }
                    break;
                default:
                    sb.append(ch);
            }
        }
        return sb.toString();
    }

    /**
     * Get and expand the default command prompt.
     */
    public String getPrompt(CommandShell shell, boolean continuation) {
        String prompt = shell.getProperty(CommandShell.PROMPT_PROPERTY_NAME);
        final StringBuffer result = new StringBuffer();
        boolean commandMode = false;
        StringReader reader = new StringReader(prompt);
        int i;
        try {
            while ((i = reader.read()) != -1) {
                char c = (char) i;
                if (commandMode) {
                    switch (c) {
                        case 'P':
                            result.append(new File(System.getProperty(CommandShell.DIRECTORY_PROPERTY_NAME, "")));
                            break;
                        case 'G':
                            result.append("> ");
                            break;
                        case 'D':
                            final Date now = new Date();
                            DateFormat.getDateTimeInstance().format(now, result, null);
                            break;
                        default:
                            result.append(c);
                    }
                    commandMode = false;
                } else {
                    switch (c) {
                        case '$':
                            commandMode = true;
                            break;
                        default:
                            result.append(c);
                    }
                }
            }
        } catch (IOException ex) {
            // A StringReader shouldn't give an IOException unless we close it ... which we don't!
            LOG.error("Impossible", ex);
        }
        return result.toString();
    }
   
    @Override
    public boolean supportsMultiline() {
        return false;
    }

    /**
     * A simple command line tokenizer for the 'built-in' interpreters. It
     * understands quoting, some '\' escapes, and (depending on constructor
     * flags) certain "special" symbols.
     */
    protected static class Tokenizer implements SymbolSource<CommandLine.Token> {
        private int pos = 0;
        private final ArrayList<CommandLine.Token> tokens =
                new ArrayList<Token>(8);
        private boolean whiteSpaceAfterLast;

        /**
         * Instantiate a command line tokenizer for a given input String.
         *
         * @param line the input String.
         * @param flags flags controlling the tokenization.
         * @throws ShellException
         */
        public Tokenizer(String line, int flags) throws ShellSyntaxException {
            tokenize(line, flags);
        }

        public Tokenizer(String line) throws ShellSyntaxException {
            this(line, 0);
        }

        /**
         * Returns if there are no more tokens to return.
         *
         * @return <code>true</code> if there is another token;
         *         <code>false</code> otherwise
         */
        public boolean hasNext() {
            return pos < tokens.size();
        }

        /**
         * Extract the next token string and return it.
         *
         * @return the next token
         */
        public CommandLine.Token next() throws NoSuchElementException {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return tokens.get(pos++);
        }

        private void tokenize(String s, int flags) throws ShellSyntaxException {
            int pos = 0;

            while (true) {
                // Skip spaces before start of token
                whiteSpaceAfterLast = false;
                while (pos < s.length() && s.charAt(pos) == SPACE_CHAR) {
                    pos++;
                    whiteSpaceAfterLast = true;
                }
                if (pos >= s.length()) {
                    break;
                }

                // Parse a token
                boolean inFullEscape = false;
                boolean inQuote = false;
                int type = LITERAL;
                int start = pos;
                StringBuilder token = new StringBuilder(5);
                char currentChar;
                boolean finished = false;

                while (!finished && pos < s.length()) {
                    currentChar = s.charAt(pos++);

                    switch (currentChar) {
                        case ESCAPE_CHAR:
                            if (pos >= s.length()) {
                                throw new ShellSyntaxException(
                                    "escape char ('\\') not followed by a character");
                            }
                            char ch;
                            switch (ch = s.charAt(pos++)) {
                                case N:
                                    token.append(ESCAPE_N);
                                    break;
                                case B:
                                    token.append(ESCAPE_B);
                                    break;
                                case R:
                                    token.append(ESCAPE_R);
                                    break;
                                case T:
                                    token.append(ESCAPE_T);
                                    break;
                                default:
                                    token.append(ch);
                            }
                            break;

                        case FULL_ESCAPE_CHAR:
                            if (inQuote) {
                                token.append(currentChar);
                            } else {
                                inFullEscape = !inFullEscape; // just a toggle
                                type = STRING;
                                if (!inFullEscape) {
                                    type |= CLOSED;
                                }
                            }
                            break;
                        case QUOTE_CHAR:
                            if (inFullEscape) {
                                token.append(currentChar);
                            } else {
                                inQuote = !inQuote;
                                type = STRING;
                                if (!inQuote) {
                                    type |= CLOSED;
                                }
                            }
                            break;
                        case SPACE_CHAR:
                            if (inFullEscape || inQuote) {
                                token.append(currentChar);
                            } else {
                                if (token.length() != 0) { // don't return an empty
                                    // token
                                    finished = true;
                                    pos--; // to return trailing space as empty
                                    // last
                                    // token
                                }
                            }
                            break;
                        case COMMENT_CHAR:
                            if (inFullEscape || inQuote) {
                                token.append(currentChar);
                            } else {
                                finished = true;
                                pos = s.length(); // ignore EVERYTHING
                            }
                            break;
                        case GET_INPUT_FROM_CHAR:
                        case SEND_OUTPUT_TO_CHAR:
                        case PIPE_CHAR:
                            if (inFullEscape || inQuote ||
                                    (flags & REDIRECTS_FLAG) == 0) {
                                token.append(currentChar);
                            } else {
                                finished = true;
                                if (token.length() == 0) {
                                    token.append(currentChar);
                                    type = SPECIAL;
                                } else {
                                    pos--; // the special character terminates the
                                    // literal.
                                }
                            }
                            break;
                        default:
                            token.append(currentChar);
                    }
                }
                tokens.add(new CommandLine.Token(token.toString(), type, start, pos));
            }
        }

        /**
         * This operation it not supported.
         */
        public void remove() {
            throw new UnsupportedOperationException("remove");
        }

        /**
         * Return the Token returned by the last successful call to next().
         *
         * @return the last token.
         */
        public CommandLine.Token last() {
            return tokens.get(pos - 1);
        }

        public Token peek() throws NoSuchElementException {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return tokens.get(pos + 1);
        }

        public void seek(int pos) throws NoSuchElementException {
            if (pos < 0 || pos > tokens.size()) {
                throw new NoSuchElementException();
            }
            this.pos = pos;
        }

        public int tell() {
            return pos;
        }

        public boolean whitespaceAfterLast() {
            return whiteSpaceAfterLast;
        }
    }
}
TOP

Related Classes of org.jnode.shell.DefaultInterpreter$Tokenizer

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.