Package org.jnode.shell

Source Code of org.jnode.shell.RedirectingInterpreter$CommandDescriptor

/*
* $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.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;
import org.jnode.shell.help.Help;
import org.jnode.shell.help.HelpException;
import org.jnode.shell.help.HelpFactory;
import org.jnode.shell.io.CommandIO;
import org.jnode.shell.io.CommandInput;
import org.jnode.shell.io.CommandOutput;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.FileArgument;

/**
* This command interpreter supports simple input and output redirection and
* pipelines.
*
* @author crawley@jnode.org
*/
public class RedirectingInterpreter extends DefaultInterpreter implements
        ThreadExitListener {

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

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

    private static final Logger LOG = Logger.getLogger(RedirectingInterpreter.class);

    @Override
    public String getName() {
        return "redirecting";
    }

    @Override
    protected int interpret(CommandShell shell, String line) throws ShellException {
        Tokenizer tokenizer = new Tokenizer(line, REDIRECTS_FLAG);
        List<CommandDescriptor> commands = new LinkedList<CommandDescriptor>();
        parse(tokenizer, commands, false);
        int len = commands.size();
        if (len == 0) {
            return 0; // empty command line.
        } else if (len == 1) {
            return runCommand(shell, commands.get(0));
        } else {
            return runPipeline(shell, commands);
        }
    }

    @Override
    public Completable parsePartial(CommandShell shell, String line)
        throws ShellException {
        Tokenizer tokenizer = new Tokenizer(line, REDIRECTS_FLAG);
        List<CommandDescriptor> commands = new LinkedList<CommandDescriptor>();
        return parse(tokenizer, commands, true);
    }
   
    @Override
    public boolean help(CommandShell shell, String line, PrintWriter pw) throws ShellException {
        Tokenizer tokenizer = new Tokenizer(line, REDIRECTS_FLAG);
        List<CommandDescriptor> commands = new LinkedList<CommandDescriptor>();
        parse(tokenizer, commands, true);
        int len = commands.size();
        if (len == 0) {
            return false;
        }
        // We'll show the help for the last command in the pipeline.
        CommandLine cmd = commands.get(len - 1).commandLine;
        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;
    }

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

    /**
     * This method parses the shell input into command lines.  If we are completing,
     * then we return a Completable that will do completion for / after the last token
     * according to the parser's syntactic context.  (Normally the Completable is a
     * CommandLine, but if we at / expecting a redirection filename, it will be a
     * Completer for the filename.)
     *
     * @param tokenizer the source of shell input tokens
     * @param commands a list for accumulating the parsed commands / redirections
     * @param completing if <code>true</code> we are completing.
     * @return a Completer or <code>null</code>
     * @throws ShellSyntaxException
     */
    private Completable parse(Tokenizer tokenizer,
            List<CommandDescriptor> commands, boolean completing)
        throws ShellSyntaxException {
        boolean wspAfter = tokenizer.whitespaceAfterLast();
        boolean pipeTo = false;
        List<CommandLine.Token> args = new ArrayList<CommandLine.Token>();
        while (tokenizer.hasNext()) {
            CommandLine.Token commandToken = tokenizer.next();
            if (commandToken.tokenType == SPECIAL) {
                throw new ShellSyntaxException("Misplaced '" +
                        commandToken.text + "': expected a command name");
            }
           
            CommandLine.Token from = null;
            CommandLine.Token to = null;
            pipeTo = false;
            args.clear();
            while (tokenizer.hasNext()) {
                CommandLine.Token token = tokenizer.next();
                if (token.tokenType == SPECIAL) {
                    if (token.text.equals("<")) {
                        from = parseFileName(tokenizer, "<");
                        if (from == null && !completing) {
                            throw new ShellSyntaxException("no filename after '<'");
                        } else if (completing &&
                                (from == null || (!tokenizer.hasNext() && !wspAfter))) {
                            return new ArgumentCompleter(
                                    new FileArgument("?", Argument.MANDATORY, null), from);
                        }
                        continue;
                    } else if (token.text.equals(">")) {
                        to = parseFileName(tokenizer, ">");
                        if (to == null && !completing) {
                            throw new ShellSyntaxException("no filename after '>'");
                        } else if (completing &&
                                (to == null || (!tokenizer.hasNext() && !wspAfter))) {
                            return new ArgumentCompleter(
                                    new FileArgument("?", Argument.MANDATORY, null), to);
                        }
                        continue;
                    } else if (token.text.equals("|")) {
                        pipeTo = true;
                        break;
                    } else {
                        throw new ShellSyntaxException(
                                "unrecognized symbol: '" + token + "'");
                    }
                } else {
                    args.add(token);
                }
            }
            CommandLine.Token[] argVec =
                    args.toArray(new CommandLine.Token[args.size()]);

            CommandLine cl = new CommandLine(commandToken, argVec, null);
            commands.add(new CommandDescriptor(cl, from, to, pipeTo));
        }
        if (pipeTo && !completing) {
            throw new ShellSyntaxException("no command after '|'");
        }
        if (completing) {
            if (pipeTo || commands.isEmpty()) {
                return new CommandLine("", null);
            } else {
                CommandLine res = commands.get(commands.size() - 1).commandLine;
                res.setArgumentAnticipated(wspAfter);
                return res;
            }
        } else {
            return null;
        }
    }

    private CommandLine.Token parseFileName(Tokenizer tokenizer, String special)
        throws ShellSyntaxException {
        if (!tokenizer.hasNext()) {
            return null;
        }
        CommandLine.Token token = tokenizer.next();
        if (token.tokenType == SPECIAL) {
            throw new ShellSyntaxException("misplaced '" + token + "'");
        }
        if (token.text.isEmpty()) {
            throw new ShellSyntaxException("empty '" + special + "' file name");
        }
        return token;
    }

    private int runCommand(CommandShell shell, CommandDescriptor desc)
        throws ShellException {
        CommandIO in = CommandLine.DEFAULT_STDIN;
        CommandIO out = CommandLine.DEFAULT_STDOUT;
        CommandIO err = CommandLine.DEFAULT_STDERR;
        try {
            try {
                if (desc.fromFileName != null) {
                    in = new CommandInput(new FileInputStream(desc.fromFileName.text));
                }
            } catch (IOException ex) {
                throw new ShellInvocationException("cannot open '" +
                        desc.fromFileName.text + "': " + ex.getMessage());
            }
            try {
                if (desc.toFileName != null) {
                    out = new CommandOutput(new FileOutputStream(desc.toFileName.text));
                }
            } catch (IOException ex) {
                throw new ShellInvocationException("cannot open '" +
                        desc.toFileName.text + "': " + ex.getMessage());
            }
            desc.commandLine.setStreams(new CommandIO[] {in, out, err, CommandLine.DEFAULT_STDERR});
            return shell.invoke(desc.commandLine, null, null);
        } finally {
            try {
                if (desc.fromFileName != null) {
                    in.close();
                }
            } catch (IOException ex) {
                // squash
            }
            try {
                if (desc.toFileName != null && out != null) {
                    out.close();
                }
            } catch (IOException ex) {
                // squash
            }
        }
    }

    private synchronized int runPipeline(CommandShell shell,
            List<CommandDescriptor> descs) throws ShellException {
        int nosStages = descs.size();
        try {
            // Create all the threads for the pipeline, wiring up their input
            // and output streams.
            int stageNo = 0;
            PipedOutputStream pipeOut = null;
            for (CommandDescriptor desc : descs) {
                CommandIO in = CommandLine.DEFAULT_STDIN;
                CommandIO out = CommandLine.DEFAULT_STDOUT;
                CommandIO err = CommandLine.DEFAULT_STDERR;
                desc.openedStreams = new ArrayList<CommandIO>(2);
                try {
                    // redirect from
                    if (desc.fromFileName != null) {
                        in = new CommandInput(new FileInputStream(desc.fromFileName.text));
                        desc.openedStreams.add(in);
                    }
                } catch (IOException ex) {
                    throw new ShellInvocationException("cannot open '" +
                            desc.fromFileName.text + "': " + ex.getMessage());
                }
                try {
                    // redirect to
                    if (desc.toFileName != null) {
                        out = new CommandOutput(new FileOutputStream(desc.toFileName.text));
                        desc.openedStreams.add(out);
                    }
                } catch (IOException ex) {
                    throw new ShellInvocationException("cannot open '" +
                            desc.toFileName + "': " + ex.getMessage());
                }
                if (stageNo > 0) {
                    // pipe from
                    if (pipeOut != null) {
                        // the previous stage is sending stdout to the pipe
                        if (in == CommandLine.DEFAULT_STDIN) {
                            // this stage is going to read from the pipe
                            PipedInputStream pipeIn = new PipedInputStream();
                            try {
                                pipeIn.connect(pipeOut);
                            } catch (IOException ex) {
                                throw new ShellInvocationException(
                                        "Problem connecting pipe", ex);
                            }
                            in = new CommandInput(pipeIn);
                            desc.openedStreams.add(in);
                        } else {
                            // this stage has redirected stdin from a file ...
                            // so go back and replace the previous stage's
                            // pipeOut with devnull
                            CommandDescriptor prev = descs.get(stageNo - 1);
                            CommandIO[] prevIOs = prev.commandLine.getStreams();
                            try {
                                pipeOut.close();
                            } catch (IOException ex) {
                                // squash
                            }
                            prevIOs[Command.STD_OUT] = CommandLine.DEVNULL;
                        }
                    } else {
                        // the previous stage has explicitly redirected stdout
                        if (in == CommandLine.DEFAULT_STDIN) {
                            // this stage hasn't redirected stdin, so we need to
                            // give it a NullInputStream to suck on.
                            in = CommandLine.DEVNULL;
                        }
                    }
                }
                if (stageNo < nosStages - 1) {
                    // this stage is not the last one, and it hasn't redirected
                    // its stdout, so it will write to a pipe
                    if (out == CommandLine.DEFAULT_STDOUT) {
                        pipeOut = new PipedOutputStream();
                        out = new CommandOutput(new PrintStream(pipeOut));
                        desc.openedStreams.add(out);
                    }
                }
                desc.commandLine.setStreams(new CommandIO[] {in, out, err, CommandLine.DEFAULT_STDERR});
                try {
                    desc.thread = shell.invokeAsynchronous(desc.commandLine);
                } catch (UnsupportedOperationException ex) {
                    throw new ShellInvocationException(
                            "The current invoker does not support pipelines", ex);
                }
                stageNo++;
            }

            currentDescriptors = descs;
            threadsLeft = descs.size();

            // Start all threads.
            for (CommandDescriptor desc : descs) {
                desc.thread.start(this);
            }

            // Wait until they have finished
            while (threadsLeft > 0) {
                try {
                    wait();
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                    return -1;
                }
            }
            return descs.get(nosStages - 1).thread.getReturnCode();
        } finally {
            // Close any remaining streams.
            for (CommandDescriptor desc : descs) {
                if (desc.openedStreams != null) {
                    for (CommandIO stream : desc.openedStreams) {
                        try {
                            stream.close();
                        } catch (IOException ex) {
                            // squash
                        }
                    }
                }
            }
            // TODO deal with any left over threads
        }
    }

    private List<CommandDescriptor> currentDescriptors;
    private int threadsLeft;

    /**
     * Callback to deal with a thread that has exited.
     *
     * @param thread
     */
    public synchronized void notifyThreadExited(CommandThread thread) {
        // If the thread owned any input or output streams, they need to
        // be closed. In particular, this will cause the next downstream
        // command in a pipeline to see an "end of file".
        for (CommandDescriptor desc : currentDescriptors) {
            if (thread == desc.thread) {
                if (desc.openedStreams == null) {
                    throw new ShellFailureException("bad thread exit callback");
                }
                for (CommandIO stream : desc.openedStreams) {
                    try {
                        stream.close();
                    } catch (IOException ex) {
                        // squash
                    }
                }
                desc.openedStreams = null;
                threadsLeft--;
                notify();
                break;
            }
        }
    }

    private static class CommandDescriptor {
        public final CommandLine commandLine;
        public final CommandLine.Token fromFileName;
        public final CommandLine.Token toFileName;
        public final boolean pipeTo;
        public List<CommandIO> openedStreams;
        public CommandThread thread;

        public CommandDescriptor(CommandLine commandLine,
                CommandLine.Token fromFileName, CommandLine.Token toFileName,
                boolean pipeTo) {
            super();
            this.commandLine = commandLine;
            this.fromFileName = fromFileName;
            this.toFileName = toFileName;
            this.pipeTo = pipeTo;
        }
    }
}
TOP

Related Classes of org.jnode.shell.RedirectingInterpreter$CommandDescriptor

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.