/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.util.shell; import java.io.BufferedReader; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.lang.reflect.UndeclaredThrowableException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.hyperic.sigar.Sigar; import org.hyperic.sigar.SigarException; import org.hyperic.sigar.util.Getline; import org.hyperic.util.StringUtil; import org.hyperic.util.pager.PageControl; import org.hyperic.util.pager.PageFetchException; import org.hyperic.util.pager.PageFetcher; import org.hyperic.util.pager.PageList; public abstract class ShellBase implements ShellCommandMapper { // Default size for pages when doing list commands public static final String PROP_PAGE_SIZE = "page.size"; private static final int DEFAULT_PAGE_SIZE = 20; private String itsPrompt = null; private Map itsCommandHandlers = null; private File itsHistoryFile = null; private Getline gl; private PrintStream out = null; private PrintStream err = null; private boolean doHistoryAdd; private int pageSize; private boolean isRedirected; public void init ( String applicationName, PrintStream out, PrintStream err ) { this.itsPrompt = applicationName; this.out = out; this.err = err; this.doHistoryAdd = true; this.pageSize = Integer.getInteger(PROP_PAGE_SIZE, DEFAULT_PAGE_SIZE).intValue(); if(this.pageSize != -1){ this.pageSize -= 1; if(this.pageSize < 1) this.pageSize = 1; } this.isRedirected = false; try { Sigar.load(); } catch (SigarException e) { e.printStackTrace(); //should never happen } this.gl = new Getline(); //must be after Sigar.load() String historyFileName = "." + applicationName + "_history"; itsHistoryFile = new File(System.getProperty("user.home"), historyFileName); try { this.gl.initHistoryFile(itsHistoryFile); } catch (IOException e) {} // Create command handler registry itsCommandHandlers = new HashMap(); // Register help and quit commands try { ShellCommand_quit quitCommand = new ShellCommand_quit(); ShellCommand_source sourceCommand = new ShellCommand_source(); registerCommandHandler(".", sourceCommand); registerCommandHandler("exit", quitCommand); registerCommandHandler("get", new ShellCommand_get()); registerCommandHandler("help", new ShellCommand_help()); registerCommandHandler("quit", quitCommand); registerCommandHandler("set", new ShellCommand_set()); registerCommandHandler("source", sourceCommand); registerCommandHandler("sleep", new ShellCommand_sleep()); } catch ( Exception e ) { err.println("ERROR: could not register standard commands: " + e); e.printStackTrace(err); } } /** * Read a .rc file into the shell, invoking everything in it (without * saving the actions to history) * * @param rcFile File to read */ public void readRCFile(File rcFile, boolean echoCommands) throws IOException { FileInputStream is = null; boolean oldHistAdd = this.doHistoryAdd; this.doHistoryAdd = false; try { BufferedReader in; String line = null; is = new FileInputStream(rcFile); in = new BufferedReader(new InputStreamReader(is)); while((line = in.readLine()) != null) { line = line.trim(); if (line.startsWith("#") || (line.length() == 0)) { continue; } if(echoCommands) this.err.println(line); this.handleCommand(line); } } finally { if (is != null) { is.close(); } this.doHistoryAdd = oldHistAdd; } } /** * Change the prompt * @param prompt */ public void setPrompt(String prompt) { this.itsPrompt = prompt; } /** * Register a new command handler. * @param commandName The command that this handler will process. * @param handler The handler to register. */ public void registerCommandHandler ( String commandName, ShellCommandHandler handler ) throws ShellCommandInitException { itsCommandHandlers.put(commandName, handler); handler.init(commandName, this); } /** * If a command needs additional input via the console, they * can get it this way. * @param prompt The prompt to display. * @return The data that the user typed in. */ public String getInput ( String prompt ) throws EOFException, IOException { return this.gl.getLine(prompt); } /** * If a command needs additional input via the console, they * can get it this way. * @param prompt The prompt to display. * @param addToHistory If true, the input entered will be added to the * history file. * @return The data that the user typed in. */ public String getInput ( String prompt, boolean addToHistory ) throws EOFException, IOException { return this.gl.getLine(prompt, addToHistory); } /** * If a command needs additional input via the console, they * can get it this way. The characters that the user types * are not echoed. * @param prompt The prompt to display. * @return The data that the user typed in. */ public String getHiddenInput ( String prompt ) throws EOFException, IOException { return Sigar.getPassword(prompt); } /** * Write a string to this shell's output stream. * @param s The string to write to the output stream. */ public void sendToOutStream ( String s ) { out.println(s); } /** * Write a string to this shell's output stream. * @param s The string to write to the output stream. */ public void sendToErrStream ( String s ) { err.println(s); } public void run () { String input = null; while (true) { try { // We don't add it to the history until we know // that it is not an illegal command input = this.gl.getLine(itsPrompt + "> ", false); } catch ( EOFException e ) { break; } catch ( Exception e ) { err.println("Fatal error reading input line: " + e); e.printStackTrace(err); return; } if ( input == null || input.trim().length() == 0 ) { if (Getline.isTTY()) { continue; } else { break; //prevent endless loop if read from a pipe } } try { handleCommand(input); } catch ( NormalQuitCommandException nqce ) { break; } } if (Getline.isTTY()) { out.println("Goodbye."); } } public void handleCommand ( String line ) { String[] args; try { args = StringUtil.explodeQuoted(line); } catch(IllegalArgumentException exc){ out.println("Syntax error: Unbalanced quotes"); return; } if(args.length != 0) handleCommand(line, args); } public void handleCommand(String line, String[] args) { ShellCommandHandler handler = null; PrintStream oldSysOut = null, oldOut = null; String command = args[0]; String[] subArgs; int useArgs; if(args.length == 0) return; handler = getHandler(command); if ( handler == null ) { err.println("unknown command: " + command); return; } useArgs = args.length; if(args.length > 2 && args[args.length - 2].equals(">")){ PrintStream newOut; oldSysOut = System.out; oldOut = this.out; // Re-direction, baby try { FileOutputStream fOut; fOut = new FileOutputStream(args[args.length -1]); newOut = new PrintStream(fOut); } catch(IOException exc){ this.err.println("Failed to redirect to output file: " + exc); return; } this.isRedirected = true; this.out = newOut; System.setOut(newOut); useArgs = useArgs - 2; } subArgs = new String[useArgs - 1]; System.arraycopy(args, 1, subArgs, 0, subArgs.length); try { this.processCommand(handler, subArgs); } catch ( ShellCommandUsageException e ) { String msg = e.getMessage(); if ( msg == null || msg.trim().length() == 0 ) { msg = "an unknown error occurred"; } err.println(command + ": " + msg); } catch ( ShellCommandExecException e ) { err.println(e); UndeclaredThrowableException ute; ute = (UndeclaredThrowableException) e.getExceptionOfType(UndeclaredThrowableException.class); if ( ute != null ) { err.println("Within undeclared throwable was: " + ute.getUndeclaredThrowable()); } } catch ( NormalQuitCommandException e ) { throw e; } catch ( Exception e ) { err.println("Unexpected exception processing " + "command '" + command + "': " + e); e.printStackTrace(err); } finally { if(this.doHistoryAdd) this.gl.addToHistory(line); if(oldSysOut != null){ this.isRedirected = false; System.setOut(oldSysOut); this.out = oldOut; } } } public void processCommand(ShellCommandHandler handler, String args[]) throws ShellCommandUsageException, ShellCommandExecException { handler.processCommand(args); } public PrintStream getOutStream(){ return this.out; } public PrintStream getErrStream(){ return this.err; } /** * @see ShellCommandMapper#getHandler */ public ShellCommandHandler getHandler ( String command ) { if ( command == null ) return null; return (ShellCommandHandler)itsCommandHandlers.get(command.toLowerCase()); } /** * @see ShellCommandMapper#getCommandNameIterator */ public Iterator getCommandNameIterator () { return itsCommandHandlers.keySet().iterator(); } public void shutdown () { } /** * Check to see if the currently running shell command is being * redirected to a file. * * @return true if the shell is redirecting to a file, else false */ public boolean isRedirected(){ return this.isRedirected; } /** * Set the page size for data paging. * * @param size Number of rows to include in a page of data -- if * 0, then unlimited rows will be used. */ public void setPageSize(int size){ if(size == 0 || size < -1){ throw new IllegalArgumentException("Page size must be > 0 or -1"); } this.pageSize = size; } /** * Get the current page size used when paging data. * * @return the # of rows in the current page size. */ public int getPageSize() { return this.pageSize; } /** * Get the number of pages that the fetcher can fetch, given the * settings as specified by the control and the # of total entites * the fetcher can fetch * * @param control Control which dictates the page size * @param list Last pageList queried via the control */ private int getNumPages(PageControl control, PageList list){ int pageSize = control.getPagesize(); int totalElems; totalElems = list.getTotalSize(); if(pageSize == PageControl.SIZE_UNLIMITED){ return 1; } else if(pageSize == 0){ return 0; } if((totalElems % pageSize) == 0) return totalElems / pageSize; return (totalElems / pageSize) + 1; } /** * Print a page of data * * @param out Stream to print to * @param data List containing the data to print * @param lineNo Line number of the first element of data * @param printLineNumbers If true, prefix lines with their numbers * * @return the number of lines printed */ private void printPage(PrintStream out, PageList data, int lineNo, boolean printLineNumbers) { for(Iterator i=data.iterator(); i.hasNext(); ){ if(printLineNumbers){ out.print(lineNo++ + ": "); } out.println((String)i.next()); } } public PageControl getDefaultPageControl(){ PageControl res; res = new PageControl(0, this.getPageSize() == -1 ? PageControl.SIZE_UNLIMITED : this.getPageSize()); return res; } public void performPaging(PageFetcher fetcher) throws PageFetchException { this.performPaging(fetcher, this.getDefaultPageControl()); } public void performPaging(PageFetcher fetcher, PageControl control) throws PageFetchException { PrintStream out; PageControl curPage; PageList data; boolean lineNumberMode; // Don't know how to handle this case if(control.getPagesize() == 0) return; lineNumberMode = false; out = this.getOutStream(); if(this.isRedirected()){ control.setPagesize(PageControl.SIZE_UNLIMITED); } data = fetcher.getPage((PageControl)control.clone()); this.printPage(out, data, control.getPageEntityIndex() + 1, lineNumberMode); if(control.getPagesize() == PageControl.SIZE_UNLIMITED || data.size() < control.getPagesize()) { return; } while(true){ boolean printPage = false; String cmd; int totalPages; totalPages = this.getNumPages(control, data); try { cmd = this.getInput("--More-- (Page " + (control.getPagenum() + 1) + " of " + totalPages + ")", false); } catch(IOException exc){ out.println(); break; } if(cmd == null || (cmd = cmd.trim()).length() == 0){ printPage = true; control.setPagenum(control.getPagenum() + 1); } else if(cmd.equals("q")){ break; } else if(cmd.equals("b")){ printPage = true; if(control.getPagenum() > 0) control.setPagenum(control.getPagenum() - 1); } else if(cmd.equals("l")){ lineNumberMode = !lineNumberMode; printPage = true; } else if(cmd.equals("?")){ out.println(" 'b' - Scroll back one page"); out.println(" 'l' - Toggle line number mode"); out.println(" 'q' - Quit paging"); out.println(" '<number>' - Jump to the specified page #"); out.println(" '<enter>' - Scroll forward one page"); } else { int newPageNo; try { newPageNo = Integer.parseInt(cmd); } catch(NumberFormatException exc){ out.println("Unknown command '" + cmd + "' " + " type '?' for paging help"); continue; } if(newPageNo < 1 || newPageNo > totalPages){ out.println(newPageNo + " out of range (must be " + "1 to " + totalPages + ")"); } else { control.setPagenum(newPageNo - 1); printPage = true; } } if(printPage){ data = fetcher.getPage((PageControl)control.clone()); this.printPage(out, data, control.getPageEntityIndex() + 1, lineNumberMode); // Check to see if we printed the last of the data if(data.size() < control.getPagesize()){ break; } } } } }