/* * $Id$ * * Copyright (C) 2003-2015 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.InputStream; import java.io.PrintStream; import javax.naming.NameNotFoundException; import org.jnode.shell.alias.AliasManager; import org.jnode.shell.alias.NoSuchAliasException; 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.ArgumentBundle; import org.jnode.vm.VmExit; /** * This a base class for JNode native command objects. It provides default implementations * of the 'execute' entry points, and other methods defined by the Command API. It also * provides 'getter' methods for retrieving the CommandLine and CommandIO stream * objects, and an 'exit' method. * <p> * The class also provides some infrastructure that allows native commands to implement * a lightweight 'public static void main(String[])' entry point. This allows them to be * executed by the minimalist 'DefaultCommandInvoker', and (in the future) will allow them * to be run on a classic JVM. * * @author crawley@jnode.org */ public abstract class AbstractCommand implements Command { private ArgumentBundle bundle; private CommandLine commandLine; private CommandIO[] ios; /** * A child class that uses this constructor won't have an argument * bundle (in the first instant); see getArgumentBundle. */ public AbstractCommand() { this.bundle = null; } /** * A child class that uses this constructor will have an initially * empty argument; see getArgumentBundle. */ public AbstractCommand(String description) { this.bundle = new ArgumentBundle(description); } @SuppressWarnings("deprecation") public final void execute(String[] args) throws Exception { // The following code is needed to deal with the new-style syntax mechanism. // This will have created an instance of the command class and bound the // command-line arguments to it. But the "static void main(...)" method that // presumably called us will have created another instance; i.e. this one. Command command = retrieveCurrentCommand(); if (command != null) { // We'll ignore the instance created in the static void main method and // use the one that the invoker saved for us earlier. } else if (bundle != null) { // It appears that this class is designed to use the new-style syntax mechanism // but we've somehow been called without having a Command instance recorded. // We'll do our best to build a Command and parse the arguments based on the // information we have to hand. (We have no way of knowing what alias was // supplied by the user.) command = prepareCommand(args); } else { // The command does not use the new syntax mechanism, so we can just run // it and let it deal with the arguments itself. command = this; } CommandIO[] myIOs = new CommandIO[] { new CommandInput(System.in), new CommandOutput(System.out), new CommandOutput(System.err) }; command.initialize(new CommandLine(args), myIOs); command.execute(); } /** * Attempt to create a command instance and parse the command line arguments. * * @param args the command arguments * @return the command with arguments bound by the parser. * @throws Exception */ private Command prepareCommand(String[] args) throws Exception { String className = this.getClass().getCanonicalName(); String commandName = getProbableAlias(className); if (commandName == null) { commandName = className; } CommandLine cmdLine = new CommandLine(commandName, args); // This will be problematic in a classic JVM ... CommandInfo ci = cmdLine.parseCommandLine((CommandShell) ShellUtils.getCurrentShell()); return ci.getCommandInstance(); } /** * Get our best guess as to what the alias was. * * @param canonicalName the class name * @return the intuited alias name * @throws NameNotFoundException */ private String getProbableAlias(String canonicalName) throws NameNotFoundException { // This will be problematic in a classic JVM ... AliasManager mgr = ShellUtils.getAliasManager(); for (String alias : mgr.aliases()) { try { if (mgr.getAliasClassName(alias).equals(canonicalName)) { return alias; } } catch (NoSuchAliasException e) { // This can only occur if an alias is removed while we are working. // There's not much we can do about it ... so ignore this. } } return null; } /** * This method causes the command to 'exit' with the supplied return code. * This method will never return. (The current implementation works by throwing * a subclass of 'java.lang.Error', so an application command must allow * 'Error' or 'Throwable' exceptions to propagate if it wants 'exit' to work.) * * @param rc the return code. */ protected void exit(int rc) { throw new VmExit(rc); } /** * Get the bundle comprising the command's registered Arguments. If * this method returns <code>null</null>, it indicates to the command * shell that this command does not use the (new) command syntax parser. */ public final ArgumentBundle getArgumentBundle() { return bundle; } /** * A child command class should call this method to register Arguments * for use by the Syntax system. * * @param args Argument objects to be registered for use in command syntax * parsing and completion. */ protected final void registerArguments(Argument<?> ... args) { if (bundle == null) { bundle = new ArgumentBundle(); } for (Argument<?> arg : args) { bundle.addArgument(arg); } } /** * The default implementation of the 'execute()' entry point delegates to * the older 'execute(...)' entry point. A new command class should override * this method. */ @Override public void execute() throws Exception { execute(commandLine, ((CommandInput) ios[0]).getInputStream(), ((CommandOutput) ios[1]).getPrintStream(), ((CommandOutput) ios[2]).getPrintStream()); } /** * The default implementation of the 'execute(...)' entry point complains that * you haven't implemented it. A command class must override either this method * or (ideally) the 'execute()' entry point method. */ public void execute(CommandLine commandLine, InputStream in, PrintStream out, PrintStream err) throws Exception { throw new UnsupportedOperationException( "A JNode command class MUST implement one of the 'execute(...)' methods."); } /** * Get the CommandLine object representing the command name and arguments * in String form. This method should not be called from a Command constructor. * * @return the CommandLine object. */ public final CommandLine getCommandLine() { if (commandLine == null) { throw new IllegalStateException("this.initialize(...) has not been called yet"); } return commandLine; } /** * Get the CommandIO object representing the 'error' stream; i.e. fd '2'. * This method should not be called from a Command constructor. * * @return the CommandIO for the command's error stream. */ public final CommandOutput getError() { if (ios == null) { throw new IllegalStateException("this.initialize(...) has not been called yet"); } return (CommandOutput) ios[2]; } /** * Get the CommandIO object representing the 'input' stream; i.e. fd '0'. * This method should not be called from a Command constructor. * * @return the CommandIO for the command's input stream. */ public final CommandInput getInput() { if (ios == null) { throw new IllegalStateException("this.initialize(...) has not been called yet"); } return (CommandInput) ios[0]; } /** * Get the Command's stream indexed by the 'fd' number. * This method should not be called from a Command constructor. * * @param fd a non-negative 'file descriptor' number. * @return the requested CommandIO object. */ public final CommandIO getIO(int fd) { if (ios == null) { throw new IllegalStateException("this.initialize(...) has not been called yet"); } return ios[fd]; } /** * Get the CommandIO object representing the 'output' stream; i.e. fd '1'. * This method should not be called from a Command constructor. * * @return the CommandIO for the command's output stream. */ public final CommandOutput getOutput() { if (ios == null) { throw new IllegalStateException("this.initialize(...) has not been called yet"); } return (CommandOutput) ios[1]; } @Override public final void initialize(CommandLine commandLine, CommandIO[] ios) { // Check the init parameters ... commandLine.getLength(); if (ios.length < 3) { throw new IllegalArgumentException("'ios' must have at least 3 elements"); } this.commandLine = commandLine; this.ios = ios; } static ThreadLocal<Command> currentCommand = new ThreadLocal<Command>(); static void saveCurrentCommand(Command command) { currentCommand.set(command); } static Command retrieveCurrentCommand() { Command res = currentCommand.get(); currentCommand.set(null); return res; } }