Package org.apache.accumulo.core.util.shell

Source Code of org.apache.accumulo.core.util.shell.Shell$QuitCommand

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.accumulo.core.util.shell;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import jline.ConsoleReader;
import jline.History;

import org.apache.accumulo.core.Constants;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.BatchScanner;
import org.apache.accumulo.core.client.BatchWriter;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.Instance;
import org.apache.accumulo.core.client.MutationsRejectedException;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.ScannerBase;
import org.apache.accumulo.core.client.TableExistsException;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.client.ZooKeeperInstance;
import org.apache.accumulo.core.client.admin.ActiveScan;
import org.apache.accumulo.core.client.admin.InstanceOperations;
import org.apache.accumulo.core.client.admin.ScanType;
import org.apache.accumulo.core.client.admin.TimeType;
import org.apache.accumulo.core.client.impl.HdfsZooInstance;
import org.apache.accumulo.core.client.mock.MockInstance;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.ConstraintViolationSummary;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.KeyExtent;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.PartialKey;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.data.thrift.TConstraintViolationSummary;
import org.apache.accumulo.core.iterators.AggregatingIterator;
import org.apache.accumulo.core.iterators.FilteringIterator;
import org.apache.accumulo.core.iterators.GrepIterator;
import org.apache.accumulo.core.iterators.IteratorUtil;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.iterators.NoLabelIterator;
import org.apache.accumulo.core.iterators.OptionDescriber;
import org.apache.accumulo.core.iterators.OptionDescriber.IteratorOptions;
import org.apache.accumulo.core.iterators.RegExIterator;
import org.apache.accumulo.core.iterators.SortedKeyIterator;
import org.apache.accumulo.core.iterators.VersioningIterator;
import org.apache.accumulo.core.iterators.aggregation.conf.AggregatorConfiguration;
import org.apache.accumulo.core.iterators.filter.AgeOffFilter;
import org.apache.accumulo.core.iterators.filter.RegExFilter;
import org.apache.accumulo.core.master.thrift.MasterGoalState;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.security.ColumnVisibility;
import org.apache.accumulo.core.security.SystemPermission;
import org.apache.accumulo.core.security.TablePermission;
import org.apache.accumulo.core.security.thrift.AuthInfo;
import org.apache.accumulo.core.security.thrift.SecurityErrorCode;
import org.apache.accumulo.core.tabletserver.thrift.ConstraintViolationException;
import org.apache.accumulo.core.trace.DistributedTrace;
import org.apache.accumulo.core.trace.TraceDump;
import org.apache.accumulo.core.trace.TraceDump.Printer;
import org.apache.accumulo.core.util.BadArgumentException;
import org.apache.accumulo.core.util.BulkImportHelper.AssignmentStats;
import org.apache.accumulo.core.util.ColumnFQ;
import org.apache.accumulo.core.util.Duration;
import org.apache.accumulo.core.util.LocalityGroupUtil;
import org.apache.accumulo.core.util.TextUtil;
import org.apache.accumulo.core.util.UtilWaitThread;
import org.apache.accumulo.core.util.format.DefaultFormatter;
import org.apache.accumulo.core.util.format.DeleterFormatter;
import org.apache.accumulo.core.util.format.Formatter;
import org.apache.accumulo.core.util.format.FormatterFactory;
import org.apache.accumulo.core.util.shell.ShellCommandException.ErrorCode;
import org.apache.accumulo.core.zookeeper.ZooUtil;
import org.apache.accumulo.core.zookeeper.ZooUtil.NodeExistsPolicy;
import org.apache.accumulo.start.classloader.AccumuloClassLoader;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.codec.binary.Base64;
import org.apache.hadoop.io.Text;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import cloudtrace.instrument.Trace;

/**
* A convenient console interface to perform basic accumulo functions Includes auto-complete, help, and quoted strings with escape sequences
*/
public class Shell {
  private static final Logger log = Logger.getLogger(Shell.class);
 
  private static final int NO_FIXED_ARG_LENGTH_CHECK = -1;
  private static final String SHELL_DESCRIPTION = "Shell - Apache Accumulo Interactive Shell";
  private static final String DEFAULT_AUTH_TIMEOUT = "60"; // in minutes
 
  private int exitCode = 0;
 
  private String tableName;
  private Instance instance;
  private Connector connector;
  public ConsoleReader reader;
  private AuthInfo credentials;
  private Class<? extends Formatter> formatterClass = DefaultFormatter.class;
  private Map<String,Map<String,Map<String,String>>> scanIteratorOptions = new HashMap<String,Map<String,Map<String,String>>>();
 
  private Token rootToken;
  private CommandFactory commandFactory;
  private boolean configError = false;
 
  // Global options flags
  static final String userOption = "u";
  static final String tableOption = "t";
  static final String helpOption = "?";
  static final String helpLongOption = "help";
 
  // exit if true
  private boolean exit = false;
 
  // file to execute commands from
  private String execFile = null;
  private boolean verbose = true;
 
  private boolean tabCompletion;
  private boolean disableAuthTimeout;
  private long authTimeout;
  private long lastUserActivity = System.currentTimeMillis();
 
  public void config(String... args) {
    Options opts = new Options();
   
    Option usernameOption = new Option("u", "user", true, "username (defaults to your OS user)");
    usernameOption.setArgName("user");
    opts.addOption(usernameOption);
   
    Option passwOption = new Option("p", "password", true, "password (prompt for password if this option is missing)");
    passwOption.setArgName("pass");
    opts.addOption(passwOption);
   
    Option tabCompleteOption = new Option(null, "disable-tab-completion", false, "disables tab completion (for less overhead when scripting)");
    opts.addOption(tabCompleteOption);
   
    Option debugOption = new Option(null, "debug", false, "enables client debugging");
    opts.addOption(debugOption);
   
    Option fakeOption = new Option(null, "fake", false, "fake a connection to accumulo");
    opts.addOption(fakeOption);
   
    Option helpOpt = new Option(helpOption, helpLongOption, false, "display this help");
    opts.addOption(helpOpt);
   
    OptionGroup execFileGroup = new OptionGroup();
   
    Option execfileOption = new Option("f", "execute-file", true, "executes commands from a file at startup");
    execfileOption.setArgName("file");
    execFileGroup.addOption(execfileOption);
   
    Option execfileVerboseOption = new Option("fv", "execute-file-verbose", true, "executes commands from a file at startup, with commands shown");
    execfileVerboseOption.setArgName("file");
    execFileGroup.addOption(execfileVerboseOption);
   
    opts.addOptionGroup(execFileGroup);
   
    OptionGroup instanceOptions = new OptionGroup();
   
    Option hdfsZooInstance = new Option("h", "hdfsZooInstance", false, "use hdfs zoo instance");
    instanceOptions.addOption(hdfsZooInstance);
   
    Option zooKeeperInstance = new Option("z", "zooKeeperInstance", true, "use a zookeeper instance with the given instance name and list of zoo hosts");
    zooKeeperInstance.setArgName("name hosts");
    zooKeeperInstance.setArgs(2);
    instanceOptions.addOption(zooKeeperInstance);
   
    opts.addOptionGroup(instanceOptions);
   
    OptionGroup authTimeoutOptions = new OptionGroup();
   
    Option authTimeoutOpt = new Option(null, "auth-timeout", true, "minutes the shell can be idle without re-entering a password (default "
        + DEFAULT_AUTH_TIMEOUT + " min)");
    authTimeoutOpt.setArgName("minutes");
    authTimeoutOptions.addOption(authTimeoutOpt);
   
    Option disableAuthTimeoutOpt = new Option(null, "disable-auth-timeout", false, "disables requiring the user to re-type a password after being idle");
    authTimeoutOptions.addOption(disableAuthTimeoutOpt);
   
    opts.addOptionGroup(authTimeoutOptions);
   
    CommandLine cl;
    try {
      cl = new BasicParser().parse(opts, args);
      if (cl.getArgs().length > 0)
        throw new ParseException("Unrecognized arguments: " + cl.getArgList());
     
      if (cl.hasOption(helpOpt.getOpt())) {
        configError = true;
        printHelp("shell", SHELL_DESCRIPTION, opts);
        return;
      }
     
      setDebugging(cl.hasOption(debugOption.getLongOpt()));
      authTimeout = Integer.parseInt(cl.getOptionValue(authTimeoutOpt.getLongOpt(), DEFAULT_AUTH_TIMEOUT)) * 60 * 1000;
      disableAuthTimeout = cl.hasOption(disableAuthTimeoutOpt.getLongOpt());
     
      if (cl.hasOption(zooKeeperInstance.getOpt()) && cl.getOptionValues(zooKeeperInstance.getOpt()).length != 2)
        throw new MissingArgumentException(zooKeeperInstance);
     
    } catch (Exception e) {
      configError = true;
      printException(e);
      printHelp("shell", SHELL_DESCRIPTION, opts);
      return;
    }
   
    // get the options that were parsed
    String sysUser = System.getProperty("user.name");
    if (sysUser == null)
      sysUser = "root";
    String user = cl.getOptionValue(usernameOption.getOpt(), sysUser);
   
    String passw = cl.getOptionValue(passwOption.getOpt(), null);
    tabCompletion = !cl.hasOption(tabCompleteOption.getLongOpt());
   
    // should only be one instance option set
    instance = null;
    if (cl.hasOption(fakeOption.getLongOpt())) {
      instance = new MockInstance();
    } else if (cl.hasOption(hdfsZooInstance.getOpt())) {
      instance = HdfsZooInstance.getInstance();
    } else if (cl.hasOption(zooKeeperInstance.getOpt())) {
      String[] zkOpts = cl.getOptionValues(zooKeeperInstance.getOpt());
      instance = new ZooKeeperInstance(zkOpts[0], zkOpts[1]);
    } else {
      instance = HdfsZooInstance.getInstance();
    }
   
    // process default parameters if unspecified
    byte[] pass;
    try {
      if (!cl.hasOption(fakeOption.getLongOpt())) {
        DistributedTrace.enable(instance, "shell", InetAddress.getLocalHost().getHostName());
      }
     
      this.reader = new ConsoleReader();
     
      if (passw == null)
        passw = reader.readLine("Enter current password for '" + user + "'@'" + instance.getInstanceName() + "': ", '*');
      if (passw == null) {
        reader.printNewline();
        configError = true;
        return;
      } // user canceled
     
      pass = passw.getBytes();
      this.tableName = "";
      connector = instance.getConnector(user, pass);
      this.credentials = new AuthInfo(user, pass, connector.getInstance().getInstanceID());
     
    } catch (Exception e) {
      printException(e);
      configError = true;
    }
   
    // decide whether to execute commands from a file and quit
    if (cl.hasOption(execfileOption.getOpt())) {
      execFile = cl.getOptionValue(execfileOption.getOpt());
      verbose = false;
    } else if (cl.hasOption(execfileVerboseOption.getOpt()))
      execFile = cl.getOptionValue(execfileVerboseOption.getOpt());
   
    rootToken = new Token();
    commandFactory = new CommandFactory();
  }
 
  Connector getConnector() {
    return connector;
  }
 
  public static void main(String args[]) throws IOException {
    Shell shell = new Shell();
    shell.config(args);
   
    System.exit(shell.start());
  }
 
  public int start() throws IOException {
    if (configError)
      return 1;
   
    String input;
    if (verbose)
      printInfo();
   
    String configDir = System.getenv("HOME") + "/.accumulo";
    String historyPath = configDir + "/shell_history.txt";
    File accumuloDir = new File(configDir);
    if (!accumuloDir.exists() && !accumuloDir.mkdirs())
      log.warn("Unable to make directory for history at " + accumuloDir);
    try {
      History history = new History();
      history.setHistoryFile(new File(historyPath));
      reader.setHistory(history);
    } catch (IOException e) {
      log.warn("Unable to load history file at " + historyPath);
    }
   
    commandFactory.buildCommands();
   
    ShellCompletor userCompletor = null;
   
    if (execFile != null) {
      java.util.Scanner scanner = new java.util.Scanner(new File(execFile));
      while (scanner.hasNextLine())
        execCommand(scanner.nextLine(), true, verbose);
    }
   
    while (true) {
      if (exit)
        return exitCode;
     
      // If tab completion is true we need to reset
      if (tabCompletion) {
        if (userCompletor != null)
          reader.removeCompletor(userCompletor);
       
        userCompletor = setupCompletion();
        reader.addCompletor(userCompletor);
      }
     
      reader.setDefaultPrompt(getDefaultPrompt());
      input = reader.readLine();
      if (input == null) {
        reader.printNewline();
        return exitCode;
      } // user canceled
     
      execCommand(input, disableAuthTimeout, false);
    }
  }
 
  private void printInfo() throws IOException {
    reader.printString("\n" + SHELL_DESCRIPTION + "\n" + "- \n" + "- version: " + Constants.VERSION + "\n" + "- instance name: "
        + connector.getInstance().getInstanceName() + "\n" + "- instance id: " + connector.getInstance().getInstanceID() + "\n" + "- \n"
        + "- type 'help' for a list of available commands\n" + "- \n");
  }
 
  private void printVerboseInfo() throws IOException {
    StringBuilder sb = new StringBuilder("-\n");
    sb.append("- Current user: ").append(connector.whoami()).append("\n");
    if (execFile != null)
      sb.append("- Executing commands from: ").append(execFile).append("\n");
    if (disableAuthTimeout)
      sb.append("- Authorization timeout: disabled\n");
    else
      sb.append("- Authorization timeout: ").append(String.format("%.2fs\n", authTimeout / 1000.0));
    sb.append("- Debug: ").append(isDebuggingEnabled() ? "on" : "off").append("\n");
    if (formatterClass != null && formatterClass != DefaultFormatter.class) {
      sb.append("- Active formatter class: ").append(formatterClass.getSimpleName()).append("\n");
    }
    if (!scanIteratorOptions.isEmpty()) {
      for (Entry<String,Map<String,Map<String,String>>> entry : scanIteratorOptions.entrySet()) {
        sb.append("- Session scan iterators for table ").append(entry.getKey()).append(":\n");
        for (Entry<String,Map<String,String>> namedEntry : entry.getValue().entrySet()) {
          sb.append("-    Iterator ").append(namedEntry.getKey()).append(" options:\n");
          for (Entry<String,String> optEntry : namedEntry.getValue().entrySet()) {
            sb.append("-        ").append(optEntry.getKey()).append(" = ").append(optEntry.getValue()).append("\n");
          }
        }
      }
    }
    sb.append("-\n");
    reader.printString(sb.toString());
  }
 
  private String getDefaultPrompt() {
    return connector.whoami() + "@" + connector.getInstance().getInstanceName() + (tableName.isEmpty() ? "" : " ") + tableName + "> ";
  }
 
  private void execCommand(String input, boolean ignoreAuthTimeout, boolean echoPrompt) throws IOException {
    if (echoPrompt) {
      reader.printString(getDefaultPrompt());
      reader.printString(input);
      reader.printNewline();
    }
   
    String fields[];
    try {
      fields = new QuotedStringTokenizer(input).getTokens();
    } catch (BadArgumentException e) {
      printException(e);
      ++exitCode;
      return;
    }
    if (fields.length == 0)
      return;
   
    String command = fields[0];
    fields = fields.length > 1 ? Arrays.copyOfRange(fields, 1, fields.length) : new String[] {};
   
    Command sc = null;
    if (command.length() > 0) {
      try {
        // Obtain the command from the command table
        sc = commandFactory.getCommandByName(command);
       
        if (!(sc instanceof ExitCommand) && !ignoreAuthTimeout && System.currentTimeMillis() - lastUserActivity > authTimeout) {
          reader.printString("Shell has been idle for too long. Please re-authenticate.\n");
          boolean authFailed = true;
          do {
            String pwd = reader.readLine("Enter current password for '" + connector.whoami() + "': ", '*');
            if (pwd == null) {
              reader.printNewline();
              return;
            } // user canceled
           
            try {
              authFailed = !connector.securityOperations().authenticateUser(connector.whoami(), pwd.getBytes());
            } catch (Exception e) {
              ++exitCode;
              printException(e);
            }
           
            if (authFailed)
              reader.printString("Invalid password. ");
          } while (authFailed);
          lastUserActivity = System.currentTimeMillis();
        }
       
        // Get the options from the command on how to parse the string
        Options parseOpts = sc.getOptionsWithHelp();
       
        // Parse the string using the given options
        CommandLine cl = new BasicParser().parse(parseOpts, fields);
       
        int actualArgLen = cl.getArgs().length;
        int expectedArgLen = sc.numArgs();
        if (cl.hasOption(helpOption)) {
          // Display help if asked to; otherwise execute the command
          sc.printHelp();
        } else if (expectedArgLen != NO_FIXED_ARG_LENGTH_CHECK && actualArgLen != expectedArgLen) {
          ++exitCode;
          // Check for valid number of fixed arguments (if not
          // negative; negative means it is not checked, for
          // vararg-like commands)
          printException(new IllegalArgumentException(String.format("Expected %d argument%s. There %s %d.", expectedArgLen, expectedArgLen == 1 ? "" : "s",
              actualArgLen == 1 ? "was" : "were", actualArgLen)));
          sc.printHelp();
        } else {
          int tmpCode = sc.execute(input, cl, this);
          exitCode += tmpCode;
          reader.flushConsole();
        }
       
      } catch (ConstraintViolationException e) {
        ++exitCode;
        printConstraintViolationException(e);
      } catch (TableNotFoundException e) {
        ++exitCode;
        if (tableName.equals(e.getTableName()))
          tableName = "";
        printException(e);
      } catch (ParseException e) {
        // not really an error if the exception is a missing required
        // option when the user is asking for help
        if (!(e instanceof MissingOptionException && (Arrays.asList(fields).contains("-" + helpOption) || Arrays.asList(fields).contains("--" + helpLongOption)))) {
          ++exitCode;
          printException(e);
        }
        if (sc != null)
          sc.printHelp();
      } catch (Exception e) {
        ++exitCode;
        printException(e);
      }
    } else {
      ++exitCode;
      printException(new BadArgumentException("Unrecognized empty command", command, -1));
    }
  }
 
  /**
   * The command tree is built in reverse so that the references are more easily linked up. There is some code in token to allow forward building of the command
   * tree.
   */
  private ShellCompletor setupCompletion() {
    rootToken = new Token();
   
    Set<String> tableNames = null;
    try {
      tableNames = connector.tableOperations().list();
    } catch (Exception e) {
      log.debug("Unable to obtain list of tables", e);
      tableNames = Collections.emptySet();
    }
   
    Set<String> userlist = null;
    try {
      userlist = connector.securityOperations().listUsers();
    } catch (Exception e) {
      log.debug("Unable to obtain list of users", e);
      userlist = Collections.emptySet();
    }
   
    Map<Command.CompletionSet,Set<String>> options = new HashMap<Command.CompletionSet,Set<String>>();
   
    Set<String> commands = new HashSet<String>();
    for (String a : commandFactory.getCommandTable().keySet())
      commands.add(a);
   
    Set<String> modifiedUserlist = new HashSet<String>();
    Set<String> modifiedTablenames = new HashSet<String>();
   
    for (String a : tableNames)
      modifiedTablenames.add(a.replaceAll("([\\s'\"])", "\\\\$1"));
    for (String a : userlist)
      modifiedUserlist.add(a.replaceAll("([\\s'\"])", "\\\\$1"));
   
    options.put(Command.CompletionSet.USERNAMES, modifiedUserlist);
    options.put(Command.CompletionSet.TABLENAMES, modifiedTablenames);
    options.put(Command.CompletionSet.COMMANDS, commands);
   
    for (Entry<String,CommandFactory.Attributes> a : commandFactory.getCommandTable().entrySet()) {
      switch (a.getValue().codetype) {
        case JAVA:
          try {
            Command c = commandFactory.getCommandByName(a.getKey());
            c.getOptionsWithHelp(); // prep the options for the command
            // so that the completion can
            // include them
            c.registerCompletion(rootToken, options);
          } catch (ShellCommandException e) {
            ++exitCode;
            printException(e);
          }
        default:
          // no other code supported
      }
    }
    return new ShellCompletor(rootToken, options);
  }
 
  /**
   * Command Factory is a class that holds the command table, which is a mapping from a string representing the command to a class. It has methods to build the
   * command table from the classes in this file and a currently unused method to build the command table from XML. The classes that the table returns all
   * extend Shell$Command class.
   */
  public static class CommandFactory {
    private TreeMap<String,Attributes> commandTable = new TreeMap<String,Attributes>();
   
    public enum CodeType {
      JAVA, PYTHON, RUBY, C;
    }
   
    private static class Attributes {
      public String location;
      public CodeType codetype;
    }
   
    public Map<String,Attributes> getCommandTable() {
      return commandTable;
    }
   
    public void buildCommandsFromXML(File file) throws ShellCommandException {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder docbuilder;
      Document d = null;
     
      try {
        docbuilder = dbf.newDocumentBuilder();
        d = docbuilder.parse(file);
      } catch (Exception e) {
        throw new ShellCommandException(ShellCommandException.ErrorCode.XML_PARSING_ERROR);
      }
     
      Attributes a;
     
      NodeList nodelist = d.getDocumentElement().getElementsByTagName("command");
      NodeList sub_nl;
      for (int i = 0; i < nodelist.getLength(); i++) {
        a = new Attributes();
        Element el = (Element) nodelist.item(i);
       
        sub_nl = el.getElementsByTagName("name");
        Element name = (Element) sub_nl.item(0);
       
        sub_nl = el.getElementsByTagName("location");
        Element loc = (Element) sub_nl.item(0);
       
        sub_nl = el.getElementsByTagName("codetype");
        Element ct = (Element) sub_nl.item(0);
       
        a.location = loc.getTextContent();
        a.codetype = CodeType.valueOf(ct.getTextContent());
       
        commandTable.put(name.getTextContent(), a);
      }
    }
   
    public Command getCommandByName(String command) throws ShellCommandException {
      command = command.toLowerCase(Locale.ENGLISH);
      Attributes toexec = commandTable.get(command);
     
      if (toexec != null) {
        Command sc = null;
        switch (toexec.codetype) {
          case JAVA:
            try {
              sc = (Command) Class.forName(toexec.location).newInstance();
            } catch (Exception e) {
              throw new ShellCommandException(ShellCommandException.ErrorCode.INITIALIZATION_FAILURE, command);
            }
            break;
          case PYTHON:
          case RUBY:
          case C:
          default:
            throw new ShellCommandException(ShellCommandException.ErrorCode.UNSUPPORTED_LANGUAGE, command);
        }
        return sc;
      }
      throw new ShellCommandException(ShellCommandException.ErrorCode.UNRECOGNIZED_COMMAND, command);
    }
   
    public void buildCommands() {
      for (Class<?> subclass : Shell.class.getClasses()) {
        Object t;
        try {
          t = subclass.newInstance();
        } catch (Exception e) {
          continue;
        }
        Attributes attr;
        if (t instanceof Command) {
          Command sci = (Command) t;
          attr = new Attributes();
          attr.location = sci.getClass().getName();
          if (attr.location == null || attr.location.isEmpty())
            continue;
          attr.codetype = CodeType.JAVA;
         
          // Don't want to add duplicate entries
          if (!commandTable.containsKey(sci.getName()))
            commandTable.put(sci.getName(), attr);
        }
      }
    }
   
  }
 
  /**
   * The Command class represents a command to be run in the shell. It contains the methods to execute along with some methods to help tab completion, and
   * return the command name, help, and usage.
   */
  public static abstract class Command {
    // Helper methods for completion
    public enum CompletionSet {
      TABLENAMES, USERNAMES, COMMANDS
    }
   
    static Set<String> getCommandNames(Map<CompletionSet,Set<String>> objects) {
      return objects.get(CompletionSet.COMMANDS);
    }
   
    static Set<String> getTableNames(Map<CompletionSet,Set<String>> objects) {
      return objects.get(CompletionSet.TABLENAMES);
    }
   
    static Set<String> getUserNames(Map<CompletionSet,Set<String>> objects) {
      return objects.get(CompletionSet.USERNAMES);
    }
   
    public void registerCompletionGeneral(Token root, Set<String> args, boolean caseSens) {
      Token t = new Token(args);
      t.setCaseSensitive(caseSens);
     
      Token command = new Token(getName());
      command.addSubcommand(t);
     
      root.addSubcommand(command);
    }
   
    public void registerCompletionForTables(Token root, Map<CompletionSet,Set<String>> completionSet) {
      registerCompletionGeneral(root, completionSet.get(CompletionSet.TABLENAMES), true);
    }
   
    public void registerCompletionForUsers(Token root, Map<CompletionSet,Set<String>> completionSet) {
      registerCompletionGeneral(root, completionSet.get(CompletionSet.USERNAMES), true);
    }
   
    public void registerCompletionForCommands(Token root, Map<CompletionSet,Set<String>> completionSet) {
      registerCompletionGeneral(root, completionSet.get(CompletionSet.COMMANDS), false);
    }
   
    // abstract methods to override
    public abstract int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception;
   
    public abstract String description();
   
    public abstract int numArgs();
   
    // OPTIONAL methods to override:
   
    // the general version of getname uses reflection to get the class name
    // and then cuts off the suffix -Command to get the name of the command
    public String getName() {
      String s = this.getClass().getName();
      int st = Math.max(s.lastIndexOf('$'), s.lastIndexOf('.'));
      int i = s.indexOf("Command");
      return i > 0 ? s.substring(st + 1, i).toLowerCase(Locale.ENGLISH) : null;
    }
   
    // The general version of this method adds the name
    // of the command to the completion tree
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completion_set) {
      root.addSubcommand(new Token(getName()));
    }
   
    // The general version of this method uses the HelpFormatter
    // that comes with the apache Options package to print out the help
    public final void printHelp() {
      Shell.printHelp(usage(), "description: " + this.description(), getOptionsWithHelp());
    }
   
    // Get options with help
    public final Options getOptionsWithHelp() {
      Options opts = getOptions();
      opts.addOption(new Option(helpOption, helpLongOption, false, "display this help"));
      return opts;
    }
   
    // General usage is just the command
    public String usage() {
      return getName();
    }
   
    // General Options are empty
    public Options getOptions() {
      return new Options();
    }
  }
 
  public static class UsersCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
      for (String user : shellState.connector.securityOperations().listUsers())
        shellState.reader.printString(user + "\n");
      return 0;
    }
   
    @Override
    public String description() {
      return "displays a list of existing users";
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class MasterStateCommand extends Command {
   
    @Override
    public String description() {
      return "set the master state: NORMAL, SAFE_MODE or CLEAN_STOP";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
      String state = cl.getArgs()[0];
      state = state.toUpperCase();
      if (MasterGoalState.valueOf(state) == null) {
        throw new IllegalArgumentException(state + " is not a valid master state");
      }
      ZooUtil.putPersistentData(ZooUtil.getRoot(shellState.connector.getInstance()) + Constants.ZMASTER_GOAL_STATE, state.getBytes(),
          NodeExistsPolicy.OVERWRITE);
      return 0;
    }
   
    @Override
    public String usage() {
      return getName() + " <NORMAL|SAFE_MODE|CLEAN_STOP>";
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
   
  }
 
  public static class TablePermissionsCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws IOException {
      for (String p : TablePermission.printableValues())
        shellState.reader.printString(p + "\n");
      return 0;
    }
   
    @Override
    public String description() {
      return "displays a list of valid table permissions";
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class ClasspathCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) {
      AccumuloClassLoader.printClassPath();
      return 0;
    }
   
    @Override
    public String description() {
      return "lists the current files on the classpath";
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class SystemPermissionsCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws IOException {
      for (String p : SystemPermission.printableValues())
        shellState.reader.printString(p + "\n");
      return 0;
    }
   
    @Override
    public String description() {
      return "displays a list of valid system permissions";
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class AuthenticateCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
      String user = cl.getArgs()[0];
      String p = shellState.reader.readLine("Enter current password for '" + user + "': ", '*');
      if (p == null) {
        shellState.reader.printNewline();
        return 0;
      } // user canceled
      byte[] password = p.getBytes();
      boolean valid = shellState.connector.securityOperations().authenticateUser(user, password);
      shellState.reader.printString((valid ? "V" : "Not v") + "alid\n");
      return 0;
    }
   
    @Override
    public String description() {
      return "verifies a user's credentials";
    }
   
    @Override
    public String usage() {
      return getName() + " <username>";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completionSet) {
      registerCompletionForUsers(root, completionSet);
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class CreateUserCommand extends Command {
    private Option scanOptAuths;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, TableNotFoundException, AccumuloSecurityException,
        TableExistsException, IOException {
      String user = cl.getArgs()[0];
      String password = null;
      String passwordConfirm = null;
     
      password = shellState.reader.readLine("Enter new password for '" + user + "': ", '*');
      if (password == null) {
        shellState.reader.printNewline();
        return 0;
      } // user canceled
      passwordConfirm = shellState.reader.readLine("Please confirm new password for '" + user + "': ", '*');
      if (passwordConfirm == null) {
        shellState.reader.printNewline();
        return 0;
      } // user canceled
     
      if (!password.equals(passwordConfirm))
        throw new IllegalArgumentException("Passwords do not match");
     
      Authorizations authorizations = parseAuthorizations(cl.hasOption(scanOptAuths.getOpt()) ? cl.getOptionValue(scanOptAuths.getOpt()) : "");
      shellState.connector.securityOperations().createUser(user, password.getBytes(), authorizations);
      log.debug("Created user " + user + " with" + (authorizations.isEmpty() ? " no" : "") + " initial scan authorizations"
          + (!authorizations.isEmpty() ? " " + authorizations : ""));
      return 0;
    }
   
    @Override
    public String usage() {
      return getName() + " <username>";
    }
   
    @Override
    public String description() {
      return "creates a new user";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      scanOptAuths = new Option("s", "scan-authorizations", true, "scan authorizations");
      scanOptAuths.setArgName("comma-separated-authorizations");
      o.addOption(scanOptAuths);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class DeleteUserCommand extends DropUserCommand {}
 
  public static class DropUserCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException {
      String user = cl.getArgs()[0];
      if (shellState.connector.whoami().equals(user))
        throw new BadArgumentException("You cannot delete yourself", fullCommand, fullCommand.indexOf(user));
      shellState.connector.securityOperations().dropUser(user);
      log.debug("Deleted user " + user);
      return 0;
    }
   
    @Override
    public String description() {
      return "deletes a user";
    }
   
    @Override
    public String usage() {
      return getName() + " <username>";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completionSet) {
      registerCompletionForUsers(root, completionSet);
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class PasswdCommand extends Command {
    private Option userOpt;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
      String currentUser = shellState.connector.whoami();
      String user = cl.getOptionValue(userOpt.getOpt(), currentUser);
     
      String password = null;
      String passwordConfirm = null;
      String oldPassword = null;
     
      oldPassword = shellState.reader.readLine("Enter current password for '" + currentUser + "': ", '*');
      if (oldPassword == null) {
        shellState.reader.printNewline();
        return 0;
      } // user canceled
     
      if (!shellState.connector.securityOperations().authenticateUser(currentUser, oldPassword.getBytes()))
        throw new AccumuloSecurityException(user, SecurityErrorCode.BAD_CREDENTIALS);
     
      password = shellState.reader.readLine("Enter new password for '" + user + "': ", '*');
      if (password == null) {
        shellState.reader.printNewline();
        return 0;
      } // user canceled
      passwordConfirm = shellState.reader.readLine("Please confirm new password for '" + user + "': ", '*');
      if (passwordConfirm == null) {
        shellState.reader.printNewline();
        return 0;
      } // user canceled
     
      if (!password.equals(passwordConfirm))
        throw new IllegalArgumentException("Passwords do not match");
     
      byte[] pass = password.getBytes();
      shellState.connector.securityOperations().changeUserPassword(user, pass);
      // update the current credentials if the password changed was for
      // the current user
      if (shellState.connector.whoami().equals(user))
        shellState.credentials = new AuthInfo(user, pass, shellState.connector.getInstance().getInstanceID());
      log.debug("Changed password for user " + user);
      return 0;
    }
   
    @Override
    public String description() {
      return "changes a user's password";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      userOpt = new Option(Shell.userOption, "user", true, "user to operate on");
      userOpt.setArgName("user");
      o.addOption(userOpt);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class SetAuthsCommand extends Command {
    private Option userOpt;
    private Option scanOptAuths;
    private Option clearOptAuths;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException {
      String user = cl.getOptionValue(userOpt.getOpt(), shellState.connector.whoami());
      String scanOpts = cl.hasOption(clearOptAuths.getOpt()) ? null : cl.getOptionValue(scanOptAuths.getOpt());
      shellState.connector.securityOperations().changeUserAuthorizations(user, parseAuthorizations(scanOpts));
      log.debug("Changed record-level authorizations for user " + user);
      return 0;
    }
   
    @Override
    public String description() {
      return "sets the maximum scan authorizations for a user";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completionSet) {
      registerCompletionForUsers(root, completionSet);
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      OptionGroup setOrClear = new OptionGroup();
      scanOptAuths = new Option("s", "scan-authorizations", true, "set the scan authorizations");
      scanOptAuths.setArgName("comma-separated-authorizations");
      setOrClear.addOption(scanOptAuths);
      clearOptAuths = new Option("c", "clear-authorizations", false, "clears the scan authorizations");
      setOrClear.addOption(clearOptAuths);
      setOrClear.setRequired(true);
      o.addOptionGroup(setOrClear);
      userOpt = new Option(Shell.userOption, "user", true, "user to operate on");
      userOpt.setArgName("user");
      o.addOption(userOpt);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class GetAuthsCommand extends Command {
    private Option userOpt;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
      String user = cl.getOptionValue(userOpt.getOpt(), shellState.connector.whoami());
      shellState.reader.printString(shellState.connector.securityOperations().getUserAuthorizations(user) + "\n");
      return 0;
    }
   
    @Override
    public String description() {
      return "displays the maximum scan authorizations for a user";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      userOpt = new Option(Shell.userOption, "user", true, "user to operate on");
      userOpt.setArgName("user");
      o.addOption(userOpt);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class UserPermissionsCommand extends Command {
    private Option userOpt;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
      String user = cl.getOptionValue(userOpt.getOpt(), shellState.connector.whoami());
     
      String delim = "";
      shellState.reader.printString("System permissions: ");
      for (SystemPermission p : SystemPermission.values()) {
        if (shellState.connector.securityOperations().hasSystemPermission(user, p)) {
          shellState.reader.printString(delim + "System." + p.name());
          delim = ", ";
        }
      }
     
      shellState.reader.printString(delim.equals("") ? "NONE" : "");
      shellState.reader.printNewline();
     
      for (String t : shellState.connector.tableOperations().list()) {
        delim = "";
        shellState.reader.printString("Table permissions (" + t + "): ");
        for (TablePermission p : TablePermission.values()) {
          if (shellState.connector.securityOperations().hasTablePermission(user, t, p)) {
            shellState.reader.printString(delim + "Table." + p.name());
            delim = ", ";
          }
        }
        shellState.reader.printString(delim.equals("") ? "NONE" : "");
        shellState.reader.printNewline();
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "displays a user's system and table permissions";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      userOpt = new Option(Shell.userOption, "user", true, "user to operate on");
      userOpt.setArgName("user");
      o.addOption(userOpt);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class GrantCommand extends Command {
    private Option systemOpt;
    private Option tableOpt;
    private Option userOpt;
    private Option tablePatternOpt;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException {
      String user = cl.hasOption(userOpt.getOpt()) ? cl.getOptionValue(userOpt.getOpt()) : shellState.connector.whoami();
     
      String permission[] = cl.getArgs()[0].split("\\.", 2);
      if (cl.hasOption(systemOpt.getOpt()) && permission[0].equalsIgnoreCase("System")) {
        try {
          shellState.connector.securityOperations().grantSystemPermission(user, SystemPermission.valueOf(permission[1]));
          log.debug("Granted " + user + " the " + permission[1] + " permission");
        } catch (IllegalArgumentException e) {
          throw new BadArgumentException("No such system permission", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
        }
      } else if (permission[0].equalsIgnoreCase("Table")) {
        if (cl.hasOption(tableOpt.getOpt())) {
          String tableName = cl.getOptionValue(tableOpt.getOpt());
          try {
            shellState.connector.securityOperations().grantTablePermission(user, tableName, TablePermission.valueOf(permission[1]));
            log.debug("Granted " + user + " the " + permission[1] + " permission on table " + tableName);
          } catch (IllegalArgumentException e) {
            throw new BadArgumentException("No such table permission", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
          }
        } else if (cl.hasOption(tablePatternOpt.getOpt())) {
          for (String tableName : shellState.connector.tableOperations().list()) {
            if (tableName.matches(cl.getOptionValue(tablePatternOpt.getOpt()))) {
              try {
                shellState.connector.securityOperations().grantTablePermission(user, tableName, TablePermission.valueOf(permission[1]));
                log.debug("Granted " + user + " the " + permission[1] + " permission on table " + tableName);
              } catch (IllegalArgumentException e) {
                throw new BadArgumentException("No such table permission", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
              }
            }
          }
        } else {
          throw new BadArgumentException("You must provide a table name", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
        }
      } else {
        throw new BadArgumentException("Unrecognized permission", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "grants system or table permissions for a user";
    }
   
    @Override
    public String usage() {
      return getName() + " <permission>";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completionSet) {
      Token cmd = new Token(getName());
      cmd.addSubcommand(new Token(TablePermission.printableValues()));
      cmd.addSubcommand(new Token(SystemPermission.printableValues()));
      root.addSubcommand(cmd);
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      OptionGroup group = new OptionGroup();
     
      tableOpt = new Option(Shell.tableOption, "table", true, "grant a table permission on this table");
      systemOpt = new Option("s", "system", false, "grant a system permission");
      tablePatternOpt = new Option("p", "pattern", true, "regex pattern of tables to grant permissions on");
      tablePatternOpt.setArgName("pattern");
     
      tableOpt.setArgName("table");
     
      group.addOption(systemOpt);
      group.addOption(tableOpt);
      group.addOption(tablePatternOpt);
      group.setRequired(true);
     
      o.addOptionGroup(group);
      userOpt = new Option(Shell.userOption, "user", true, "user to operate on");
      userOpt.setArgName("username");
      userOpt.setRequired(true);
      o.addOption(userOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class RevokeCommand extends Command {
    private Option systemOpt;
    private Option tableOpt;
    private Option userOpt;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException {
      String user = cl.hasOption(userOpt.getOpt()) ? cl.getOptionValue(userOpt.getOpt()) : shellState.connector.whoami();
     
      String permission[] = cl.getArgs()[0].split("\\.", 2);
      if (cl.hasOption(systemOpt.getOpt()) && permission[0].equalsIgnoreCase("System")) {
        try {
          shellState.connector.securityOperations().revokeSystemPermission(user, SystemPermission.valueOf(permission[1]));
          log.debug("Revoked from " + user + " the " + permission[1] + " permission");
        } catch (IllegalArgumentException e) {
          throw new BadArgumentException("No such system permission", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
        }
      } else if (cl.hasOption(tableOpt.getOpt()) && permission[0].equalsIgnoreCase("Table")) {
        String tableName = cl.getOptionValue(tableOpt.getOpt());
        try {
          shellState.connector.securityOperations().revokeTablePermission(user, tableName, TablePermission.valueOf(permission[1]));
          log.debug("Revoked from " + user + " the " + permission[1] + " permission on table " + tableName);
        } catch (IllegalArgumentException e) {
          throw new BadArgumentException("No such table permission", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
        }
      } else {
        throw new BadArgumentException("Unrecognized permission", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "revokes system or table permissions from a user";
    }
   
    @Override
    public String usage() {
      return getName() + " <permission>";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completionSet) {
      Token cmd = new Token(getName());
      cmd.addSubcommand(new Token(TablePermission.printableValues()));
      cmd.addSubcommand(new Token(SystemPermission.printableValues()));
      root.addSubcommand(cmd);
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      OptionGroup group = new OptionGroup();
     
      tableOpt = new Option(tableOption, "table", true, "revoke a table permission on this table");
      systemOpt = new Option("s", "system", false, "revoke a system permission");
     
      tableOpt.setArgName("table");
     
      group.addOption(systemOpt);
      group.addOption(tableOpt);
      group.setRequired(true);
     
      o.addOptionGroup(group);
     
      userOpt = new Option(userOption, "user", true, "user to operate on");
      userOpt.setArgName("username");
      userOpt.setRequired(true);
      o.addOption(userOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class QuitCommand extends ExitCommand {}
 
  public static class ByeCommand extends ExitCommand {}
 
  public static class ExitCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) {
      shellState.exit = true;
      return 0;
    }
   
    @Override
    public String description() {
      return "exits the shell";
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class QuestionCommand extends HelpCommand {
    @Override
    public String getName() {
      return "?";
    }
  }
 
  public static class HelpCommand extends Command {
    private Option disablePaginationOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws ShellCommandException, IOException {
      // print help summary
      if (cl.getArgs().length == 0) {
        int i = 0;
        for (String cmd : shellState.commandFactory.getCommandTable().keySet())
          i = Math.max(i, cmd.length());
        ArrayList<String> output = new ArrayList<String>();
        for (String cmd : shellState.commandFactory.getCommandTable().keySet()) {
          Command c = shellState.commandFactory.getCommandByName(cmd);
          if (!(c instanceof HiddenCommand))
            output.add(String.format("%-" + i + "s  -  %s", c.getName(), c.description()));
        }
        shellState.printLines(output.iterator(), !cl.hasOption(disablePaginationOpt.getOpt()));
      }
     
      // print help for every command on command line
      for (String cmd : cl.getArgs()) {
        try {
          shellState.commandFactory.getCommandByName(cmd).printHelp();
        } catch (ShellCommandException e) {
          printException(e);
          return 1;
        }
      }
      return 0;
    }
   
    public String description() {
      return "provides information about the available commands";
    }
   
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> special) {
      registerCompletionForCommands(root, special);
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      disablePaginationOpt = new Option("np", "no-pagination", false, "disables pagination of output");
      o.addOption(disablePaginationOpt);
      return o;
    }
   
    @Override
    public String usage() {
      return getName() + " [ <command>{ <command>} ]";
    }
   
    @Override
    public int numArgs() {
      return Shell.NO_FIXED_ARG_LENGTH_CHECK;
    }
  }
 
  public static class DebugCommand extends Command {
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws IOException {
      if (cl.getArgs().length == 1) {
        if (cl.getArgs()[0].equalsIgnoreCase("on"))
          Shell.setDebugging(true);
        else if (cl.getArgs()[0].equalsIgnoreCase("off"))
          Shell.setDebugging(false);
        else
          throw new BadArgumentException("Argument must be 'on' or 'off'", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
      } else if (cl.getArgs().length == 0) {
        shellState.reader.printString(Shell.isDebuggingEnabled() ? "on\n" : "off\n");
      } else {
        printException(new IllegalArgumentException("Expected 0 or 1 argument. There were " + cl.getArgs().length + "."));
        printHelp();
        return 1;
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "turns debug logging on or off";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> special) {
      Token debug_command = new Token(getName());
      debug_command.addSubcommand(Arrays.asList(new String[] {"on", "off"}));
      root.addSubcommand(debug_command);
    }
   
    @Override
    public String usage() {
      return getName() + " [ on | off ]";
    }
   
    @Override
    public int numArgs() {
      return Shell.NO_FIXED_ARG_LENGTH_CHECK;
    }
  }
 
  public static class TraceCommand extends DebugCommand {
   
    public int execute(String fullCommand, CommandLine cl, final Shell shellState) throws IOException {
      if (cl.getArgs().length == 1) {
        if (cl.getArgs()[0].equalsIgnoreCase("on")) {
          Trace.on("shell:" + shellState.credentials.user);
        } else if (cl.getArgs()[0].equalsIgnoreCase("off")) {
          if (Trace.isTracing()) {
            long trace = Trace.currentTrace().traceId();
            Trace.off();
            for (int i = 0; i < 10; i++) {
              try {
                String table = AccumuloConfiguration.getSystemConfiguration().get(Property.TRACE_TABLE);
                String user = shellState.connector.whoami();
                Authorizations auths = shellState.connector.securityOperations().getUserAuthorizations(user);
                Scanner scanner = shellState.connector.createScanner(table, auths);
                scanner.setRange(new Range(new Text(Long.toHexString(trace))));
                final StringBuffer sb = new StringBuffer();
                if (TraceDump.printTrace(scanner, new Printer() {
                  @Override
                  public void print(String line) {
                    try {
                      sb.append(line + "\n");
                    } catch (Exception ex) {
                      throw new RuntimeException(ex);
                    }
                  }
                }) > 0) {
                  shellState.reader.printString(sb.toString());
                  break;
                }
              } catch (Exception ex) {
                printException(ex);
              }
              shellState.reader.printString("Waiting for trace information\n");
              shellState.reader.flushConsole();
              UtilWaitThread.sleep(500);
            }
          } else {
            shellState.reader.printString("Not tracing\n");
          }
        } else
          throw new BadArgumentException("Argument must be 'on' or 'off'", fullCommand, fullCommand.indexOf(cl.getArgs()[0]));
      } else if (cl.getArgs().length == 0) {
        shellState.reader.printString(Trace.isTracing() ? "on\n" : "off\n");
      } else {
        printException(new IllegalArgumentException("Expected 0 or 1 argument. There were " + cl.getArgs().length + "."));
        printHelp();
        return 1;
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "turns trace logging on or off";
    }
  }
 
  public static class DeleteIterCommand extends Command {
    private Option tableOpt;
    private Option mincScopeOpt;
    private Option majcScopeOpt;
    private Option scanScopeOpt;
    private Option nameOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException {
      if (!cl.hasOption(tableOpt.getOpt()))
        shellState.checkTableState();
     
      String tableName = cl.getOptionValue(tableOpt.getOpt(), shellState.tableName);
      if (tableName != null && !shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      String name = cl.getOptionValue(nameOpt.getOpt());
     
      ArrayList<IteratorScope> scopes = new ArrayList<IteratorScope>(3);
      if (cl.hasOption(mincScopeOpt.getOpt()))
        scopes.add(IteratorScope.minc);
      if (cl.hasOption(majcScopeOpt.getOpt()))
        scopes.add(IteratorScope.majc);
      if (cl.hasOption(scanScopeOpt.getOpt()))
        scopes.add(IteratorScope.scan);
      if (scopes.isEmpty())
        throw new IllegalArgumentException("You must select at least one scope to delete");
     
      boolean noneDeleted = true;
      String tableId = shellState.connector.tableOperations().tableIdMap().get(tableName);
      AccumuloConfiguration config = AccumuloConfiguration.getTableConfiguration(shellState.connector.getInstance().getInstanceID(), tableId);
      for (Entry<String,String> entry : config) {
        log.debug("Considering candidate for deletion " + entry.getKey());
        for (IteratorScope scope : scopes) {
          String stem = String.format("%s%s.%s", Property.TABLE_ITERATOR_PREFIX, scope.name(), name);
          log.debug("Deleting " + stem + "*");
          if (entry.getKey().startsWith(stem)) {
            noneDeleted = false;
            log.debug("removing property " + entry.getKey());
            shellState.connector.tableOperations().removeProperty(tableName, entry.getKey());
          }
        }
      }
      if (noneDeleted)
        log.warn("no iterators found that match your criteria");
      return 0;
    }
   
    @Override
    public String description() {
      return "deletes a table-specific iterator";
    }
   
    public Options getOptions() {
      Options o = new Options();
     
      tableOpt = new Option(tableOption, "table", true, "tableName");
      tableOpt.setArgName("table");
     
      nameOpt = new Option("n", "name", true, "iterator to delete");
      nameOpt.setArgName("itername");
      nameOpt.setRequired(true);
     
      mincScopeOpt = new Option(IteratorScope.minc.name(), "minor-compaction", false, "applied at minor compaction");
      majcScopeOpt = new Option(IteratorScope.majc.name(), "major-compaction", false, "applied at major compaction");
      scanScopeOpt = new Option(IteratorScope.scan.name(), "scan-time", false, "applied at scan time");
     
      o.addOption(tableOpt);
      o.addOption(nameOpt);
     
      o.addOption(mincScopeOpt);
      o.addOption(majcScopeOpt);
      o.addOption(scanScopeOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class SetIterCommand extends Command {
    private Option tableOpt;
    private Option mincScopeOpt;
    private Option majcScopeOpt;
    private Option scanScopeOpt;
    private Option nameOpt;
    private Option priorityOpt;
   
    private Option aggTypeOpt;
    private Option filterTypeOpt;
    private Option regexTypeOpt;
    private Option versionTypeOpt;
    private Option nolabelTypeOpt;
    private Option classnameTypeOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException {
      if (!cl.hasOption(tableOpt.getOpt()))
        shellState.checkTableState();
     
      String tableName = cl.getOptionValue(tableOpt.getOpt(), shellState.tableName);
      if (tableName != null && !shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      int priority = Integer.parseInt(cl.getOptionValue(priorityOpt.getOpt()));
     
      Map<String,String> options = new HashMap<String,String>();
      boolean filter = false;
      String classname = cl.getOptionValue(classnameTypeOpt.getOpt());
      if (cl.hasOption(aggTypeOpt.getOpt()))
        classname = AggregatingIterator.class.getName();
      else if (cl.hasOption(regexTypeOpt.getOpt()))
        classname = RegExIterator.class.getName();
      else if (cl.hasOption(versionTypeOpt.getOpt()))
        classname = VersioningIterator.class.getName();
      else if (cl.hasOption(nolabelTypeOpt.getOpt()))
        classname = NoLabelIterator.class.getName();
      else if (cl.hasOption(filterTypeOpt.getOpt()) || classname.equals(FilteringIterator.class.getName())) {
        filter = true;
        classname = FilteringIterator.class.getName();
      }
     
      String name;
      name = cl.getOptionValue(nameOpt.getOpt(), setUpOptions(shellState.reader, classname, options));
     
      if (filter) {
        Map<String,String> updates = new HashMap<String,String>();
        for (String key : options.keySet()) {
          String c = options.get(key);
          if (c.equals("ageoff")) {
            c = AgeOffFilter.class.getName();
            options.put(key, c);
          } else if (c.equals("regex")) {
            c = RegExFilter.class.getName();
            options.put(key, c);
          }
          Map<String,String> filterOptions = new HashMap<String,String>();
          setUpOptions(shellState.reader, c, filterOptions);
         
          for (Entry<String,String> e : filterOptions.entrySet())
            updates.put(key + "." + e.getKey(), e.getValue());
        }
        options.putAll(updates);
      }
     
      setTableProperties(cl, shellState, tableName, priority, options, classname, name);
      return 0;
    }
   
    protected void setTableProperties(CommandLine cl, Shell shellState, String tableName, int priority, Map<String,String> options, String classname,
        String name) throws AccumuloException, AccumuloSecurityException {
      ArrayList<IteratorScope> scopes = new ArrayList<IteratorScope>(3);
      if (cl.hasOption(mincScopeOpt.getOpt()))
        scopes.add(IteratorScope.minc);
      if (cl.hasOption(majcScopeOpt.getOpt()))
        scopes.add(IteratorScope.majc);
      if (cl.hasOption(scanScopeOpt.getOpt()))
        scopes.add(IteratorScope.scan);
      if (scopes.isEmpty())
        throw new IllegalArgumentException("You must select at least one scope to configure");
     
      for (IteratorScope scope : scopes) {
        String stem = String.format("%s%s.%s", Property.TABLE_ITERATOR_PREFIX, scope.name(), name);
        log.debug("setting property " + stem + " to " + priority + "," + classname);
        String optStem = stem + ".opt.";
        for (Entry<String,String> e : options.entrySet()) {
          log.debug("setting property " + optStem + e.getKey() + " to " + e.getValue());
          shellState.connector.tableOperations().setProperty(tableName, optStem + e.getKey(), e.getValue());
        }
        shellState.connector.tableOperations().setProperty(tableName, stem, priority + "," + classname);
      }
    }
   
    private static String setUpOptions(ConsoleReader reader, String className, Map<String,String> options) throws IOException {
      String input;
      OptionDescriber skvi;
      Class<? extends OptionDescriber> clazz;
      try {
        clazz = AccumuloClassLoader.loadClass(className, OptionDescriber.class);
        skvi = clazz.newInstance();
      } catch (ClassNotFoundException e) {
        throw new IllegalArgumentException(e.getMessage());
      } catch (InstantiationException e) {
        throw new IllegalArgumentException(e.getMessage());
      } catch (IllegalAccessException e) {
        throw new IllegalArgumentException(e.getMessage());
      }
     
      IteratorOptions itopts = skvi.describeOptions();
      if (itopts.name == null)
        throw new IllegalArgumentException(className + " described its default distinguishing name as null");
     
      Map<String,String> localOptions = new HashMap<String,String>();
      do {
        // clean up the overall options that caused things to fail
        for (String key : localOptions.keySet())
          options.remove(key);
        localOptions.clear();
       
        reader.printString(itopts.description);
        reader.printNewline();
       
        String prompt;
        if (itopts.namedOptions != null) {
          for (Entry<String,String> e : itopts.namedOptions.entrySet()) {
            prompt = repeat("-", 10) + "> set " + className + " parameter " + e.getKey() + ", " + e.getValue() + ": ";
           
            input = reader.readLine(prompt);
            if (input == null) {
              reader.printNewline();
              throw new IOException("Input stream closed");
            }
           
            if (input.length() > 0)
              localOptions.put(e.getKey(), input);
          }
        }
       
        if (itopts.unnamedOptionDescriptions != null) {
          for (String desc : itopts.unnamedOptionDescriptions) {
            reader.printString(repeat("-", 10) + "> entering options: " + desc + "\n");
            input = "start";
            while (true) {
              prompt = repeat("-", 10) + "> set " + className + " option (<name> <value>, hit enter to skip): ";
             
              input = reader.readLine(prompt);
              if (input == null) {
                reader.printNewline();
                throw new IOException("Input stream closed");
              }
             
              if (input.length() == 0)
                break;
             
              String[] sa = input.split(" ", 2);
              localOptions.put(sa[0], sa[1]);
            }
          }
        }
       
        options.putAll(localOptions);
        if (!skvi.validateOptions(options))
          reader.printString("invalid options for " + clazz.getName() + "\n");
       
      } while (!skvi.validateOptions(options));
      return itopts.name;
    }
   
    @Override
    public String description() {
      return "sets a table-specific iterator";
    }
   
    public Options getOptions() {
      Options o = new Options();
     
      tableOpt = new Option(tableOption, "table", true, "tableName");
      tableOpt.setArgName("table");
     
      priorityOpt = new Option("p", "priority", true, "the order in which the iterator is applied");
      priorityOpt.setArgName("pri");
      priorityOpt.setRequired(true);
     
      nameOpt = new Option("n", "name", true, "iterator to set");
      nameOpt.setArgName("itername");
     
      mincScopeOpt = new Option(IteratorScope.minc.name(), "minor-compaction", false, "applied at minor compaction");
      majcScopeOpt = new Option(IteratorScope.majc.name(), "major-compaction", false, "applied at major compaction");
      scanScopeOpt = new Option(IteratorScope.scan.name(), "scan-time", false, "applied at scan time");
     
      OptionGroup typeGroup = new OptionGroup();
      classnameTypeOpt = new Option("class", "class-name", true, "a java class type");
      classnameTypeOpt.setArgName("name");
      aggTypeOpt = new Option("agg", "aggregator", false, "an aggregating type");
      regexTypeOpt = new Option("regex", "regular-expression", false, "a regex matching type");
      versionTypeOpt = new Option("vers", "version", false, "a versioning type");
      nolabelTypeOpt = new Option("nolabel", "no-label", false, "a no-labeling type");
      filterTypeOpt = new Option("filter", "filter", false, "a filtering type");
     
      typeGroup.addOption(classnameTypeOpt);
      typeGroup.addOption(aggTypeOpt);
      typeGroup.addOption(regexTypeOpt);
      typeGroup.addOption(versionTypeOpt);
      typeGroup.addOption(nolabelTypeOpt);
      typeGroup.addOption(filterTypeOpt);
      typeGroup.setRequired(true);
     
      o.addOption(tableOpt);
      o.addOption(priorityOpt);
      o.addOption(nameOpt);
      o.addOption(mincScopeOpt);
      o.addOption(majcScopeOpt);
      o.addOption(scanScopeOpt);
      o.addOptionGroup(typeGroup);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class SetScanIterCommand extends SetIterCommand {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException {
      return super.execute(fullCommand, cl, shellState);
    }
   
    @Override
    protected void setTableProperties(CommandLine cl, Shell shellState, String tableName, int priority, Map<String,String> options, String classname,
        String name) throws AccumuloException, AccumuloSecurityException {
      // instead of setting table properties, just put the options
      // in a map to use at scan time
      options.put("iteratorClassName", classname);
      options.put("iteratorPriority", Integer.toString(priority));
      Map<String,Map<String,String>> tableIterators = shellState.scanIteratorOptions.get(tableName);
      if (tableIterators == null) {
        tableIterators = new HashMap<String,Map<String,String>>();
        shellState.scanIteratorOptions.put(tableName, tableIterators);
      }
      tableIterators.put(name, options);
    }
   
    @Override
    public String description() {
      return "sets a table-specific scan iterator for this shell session";
    }
   
    @Override
    public Options getOptions() {
      // Remove the options that specify which type of iterator this is, since
      // they are all scan iterators with this command.
      HashSet<OptionGroup> groups = new HashSet<OptionGroup>();
      Options parentOptions = super.getOptions();
      Options modifiedOptions = new Options();
      for (Iterator<?> it = parentOptions.getOptions().iterator(); it.hasNext();) {
        Option o = (Option) it.next();
        if (!IteratorScope.majc.name().equals(o.getOpt()) && !IteratorScope.minc.name().equals(o.getOpt()) && !IteratorScope.scan.name().equals(o.getOpt())) {
          modifiedOptions.addOption(o);
          OptionGroup group = parentOptions.getOptionGroup(o);
          if (group != null)
            groups.add(group);
        }
      }
      for (OptionGroup group : groups) {
        modifiedOptions.addOptionGroup(group);
      }
      return modifiedOptions;
    }
   
  }
 
  public static class DeleteScanIterCommand extends Command {
    private Option tableOpt;
    private Option nameOpt;
    private Option allOpt;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
      if (!cl.hasOption(tableOpt.getOpt()))
        shellState.checkTableState();
     
      String tableName = cl.getOptionValue(tableOpt.getOpt(), shellState.tableName);
      if (tableName != null && !shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      if (cl.hasOption(allOpt.getOpt())) {
        Map<String,Map<String,String>> tableIterators = shellState.scanIteratorOptions.remove(tableName);
        if (tableIterators == null)
          log.info("No scan iterators set on table " + tableName);
        else
          log.info("Removed the following scan iterators from table " + tableName + ":" + tableIterators.keySet());
      } else if (cl.hasOption(nameOpt.getOpt())) {
        String name = cl.getOptionValue(nameOpt.getOpt());
        Map<String,Map<String,String>> tableIterators = shellState.scanIteratorOptions.get(tableName);
        if (tableIterators != null) {
          Map<String,String> options = tableIterators.remove(name);
          if (options == null)
            log.info("No iterator named " + name + " found for table " + tableName);
          else
            log.info("Removed scan iterator " + name + " from table " + tableName);
        } else {
          log.info("No iterator named " + name + " found for table " + tableName);
        }
      } else {
        throw new IllegalArgumentException("Must specify one of " + nameOpt.getArgName() + " or " + allOpt.getArgName());
      }
     
      return 0;
    }
   
    @Override
    public String description() {
      return "deletes a table-specific scan iterator so it is no longer used during this shell session";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
     
      tableOpt = new Option(tableOption, "table", true, "tableName");
      tableOpt.setArgName("table");
     
      nameOpt = new Option("n", "name", true, "iterator to delete");
      nameOpt.setArgName("itername");
     
      allOpt = new Option("a", "all", false, "delete all for tableName");
      allOpt.setArgName("all");
     
      o.addOption(tableOpt);
      o.addOption(nameOpt);
      o.addOption(allOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class ConfigCommand extends Command {
    private Option tableOpt;
    private Option deleteOpt;
    private Option setOpt;
    private Option filterOpt;
    private Option disablePaginationOpt;
   
    private int COL1 = 8, COL2 = 7;
    private ConsoleReader reader;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException {
      reader = shellState.reader;
     
      String tableName = cl.getOptionValue(tableOpt.getOpt());
      if (tableName != null && !shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      if (cl.hasOption(deleteOpt.getOpt())) {
        // delete property from table
        String property = cl.getOptionValue(deleteOpt.getOpt());
        if (property.contains("="))
          throw new BadArgumentException("Invalid '=' operator in delete operation.", fullCommand, fullCommand.indexOf('='));
        if (tableName != null) {
          if (!Property.isValidTablePropertyKey(property))
            log.warn("Invalid per-table property : " + property + ", still removing from zookeeper if its there.");
          shellState.connector.tableOperations().removeProperty(tableName, property);
          log.debug("Successfully deleted table configuration option.");
        } else {
          if (!Property.isValidZooPropertyKey(property))
            log.warn("Invalid per-table property : " + property + ", still removing from zookeeper if its there.");
          shellState.connector.instanceOperations().removeProperty(property);
          log.debug("Successfully deleted system configuration option");
        }
      } else if (cl.hasOption(setOpt.getOpt())) {
        // set property on table
        String property = cl.getOptionValue(setOpt.getOpt()), value = null;
        if (!property.contains("="))
          throw new BadArgumentException("Missing '=' operator in set operation.", fullCommand, fullCommand.indexOf(property));
       
        String pair[] = property.split("=", 2);
        property = pair[0];
        value = pair[1];
       
        if (tableName != null) {
          if (!Property.isValidTablePropertyKey(property))
            throw new BadArgumentException("Invalid per-table property.", fullCommand, fullCommand.indexOf(property));
         
          if (property.equals(Property.TABLE_DEFAULT_SCANTIME_VISIBILITY.getKey()))
            new ColumnVisibility(value); // validate that it is a valid
          // expression
         
          shellState.connector.tableOperations().setProperty(tableName, property, value);
          log.debug("Successfully set table configuration option.");
        } else {
          if (!Property.isValidZooPropertyKey(property))
            throw new BadArgumentException("Property cannot be modified in zookeeper", fullCommand, fullCommand.indexOf(property));
         
          shellState.connector.instanceOperations().setProperty(property, value);
          log.debug("Successfully set system configuration option");
        }
      } else {
        // display properties
        TreeMap<String,String> systemConfig = new TreeMap<String,String>();
        for (Entry<String,String> sysEntry : AccumuloConfiguration.getSystemConfiguration(shellState.instance))
          systemConfig.put(sysEntry.getKey(), sysEntry.getValue());
       
        TreeMap<String,String> siteConfig = new TreeMap<String,String>();
        for (Entry<String,String> siteEntry : AccumuloConfiguration.getSiteConfiguration())
          siteConfig.put(siteEntry.getKey(), siteEntry.getValue());
       
        TreeMap<String,String> defaults = new TreeMap<String,String>();
        for (Entry<String,String> defaultEntry : AccumuloConfiguration.getDefaultConfiguration())
          defaults.put(defaultEntry.getKey(), defaultEntry.getValue());
       
        Iterable<Entry<String,String>> acuconf = AccumuloConfiguration.getSystemConfiguration();
        if (tableName != null)
          acuconf = shellState.connector.tableOperations().getProperties(tableName);
       
        for (Entry<String,String> propEntry : acuconf) {
          String key = propEntry.getKey();
          // only show properties with similar names to that
          // specified, or all of them if none specified
          if (cl.hasOption(filterOpt.getOpt()) && !key.contains(cl.getOptionValue(filterOpt.getOpt())))
            continue;
          if (tableName != null && !Property.isValidTablePropertyKey(key))
            continue;
          COL2 = Math.max(COL2, propEntry.getKey().length() + 3);
        }
       
        ArrayList<String> output = new ArrayList<String>();
        printConfHeader(output);
       
        for (Entry<String,String> propEntry : acuconf) {
          String key = propEntry.getKey();
         
          // only show properties with similar names to that
          // specified, or all of them if none specified
          if (cl.hasOption(filterOpt.getOpt()) && !key.contains(cl.getOptionValue(filterOpt.getOpt())))
            continue;
         
          if (tableName != null && !Property.isValidTablePropertyKey(key))
            continue;
         
          String siteVal = siteConfig.get(key);
          String sysVal = systemConfig.get(key);
          String curVal = propEntry.getValue();
          String dfault = defaults.get(key);
          boolean printed = false;
         
          if (dfault != null && key.toLowerCase().contains("password")) {
            dfault = curVal = curVal.replaceAll(".", "*");
          }
          if (sysVal != null) {
            if (defaults.containsKey(key)) {
              printConfLine(output, "default", key, dfault);
              printed = true;
            }
            if (!defaults.containsKey(key) || !defaults.get(key).equals(siteVal)) {
              printConfLine(output, "site", printed ? "   @override" : key, siteVal == null ? "" : siteVal);
              printed = true;
            }
            if (!siteConfig.containsKey(key) || !siteVal.equals(sysVal)) {
              printConfLine(output, "system", printed ? "   @override" : key, sysVal == null ? "" : sysVal);
              printed = true;
            }
          }
         
          // show per-table value only if it is different (overridden)
          if (tableName != null && !curVal.equals(sysVal))
            printConfLine(output, "table", printed ? "   @override" : key, curVal);
        }
        printConfFooter(output);
        shellState.printLines(output.iterator(), !cl.hasOption(disablePaginationOpt.getOpt()));
      }
      return 0;
    }
   
    private void printConfHeader(ArrayList<String> output) {
      printConfFooter(output);
      output.add(String.format("%-" + COL1 + "s | %-" + COL2 + "s | %s", "SCOPE", "NAME", "VALUE"));
      printConfFooter(output);
    }
   
    private void printConfLine(ArrayList<String> output, String s1, String s2, String s3) {
      if (s2.length() < COL2)
        s2 += " " + repeat(".", COL2 - s2.length() - 1);
      output.add(String.format("%-" + COL1 + "s | %-" + COL2 + "s | %s", s1, s2,
          s3.replace("\n", "\n" + repeat(" ", COL1 + 1) + "|" + repeat(" ", COL2 + 2) + "|" + " ")));
    }
   
    private void printConfFooter(ArrayList<String> output) {
      int col3 = Math.max(1, Math.min(Integer.MAX_VALUE, reader.getTermwidth() - COL1 - COL2 - 6));
      output.add(String.format("%" + COL1 + "s-+-%" + COL2 + "s-+-%-" + col3 + "s", repeat("-", COL1), repeat("-", COL2), repeat("-", col3)));
    }
   
    @Override
    public String description() {
      return "prints system properties and table specific properties";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      OptionGroup og = new OptionGroup();
     
      tableOpt = new Option(tableOption, "table", true, "display/set/delete properties for specified table");
      deleteOpt = new Option("d", "delete", true, "delete a per-table property");
      setOpt = new Option("s", "set", true, "set a per-table property");
      filterOpt = new Option("f", "filter", true, "show only properties that contain this string");
      disablePaginationOpt = new Option("np", "no-pagination", false, "disables pagination of output");
     
      tableOpt.setArgName("table");
      deleteOpt.setArgName("property");
      setOpt.setArgName("property=value");
      filterOpt.setArgName("string");
     
      og.addOption(deleteOpt);
      og.addOption(setOpt);
      og.addOption(filterOpt);
     
      o.addOption(tableOpt);
      o.addOptionGroup(og);
      o.addOption(disablePaginationOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class UserCommand extends Command {
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
      // save old credentials and connection in case of failure
      AuthInfo prevCreds = shellState.credentials;
      Connector prevConn = shellState.connector;
     
      boolean revertCreds = false;
      String user = cl.getArgs()[0];
      byte[] pass;
     
      // We can't let the wrapping try around the execute method deal
      // with the exceptions because we have to do something if one
      // of these methods fails
      try {
        String p = shellState.reader.readLine("Enter password for user " + user + ": ", '*');
        if (p == null) {
          shellState.reader.printNewline();
          return 0;
        } // user canceled
        pass = p.getBytes();
        shellState.credentials = new AuthInfo(user, pass, shellState.connector.getInstance().getInstanceID());
        shellState.connector = shellState.connector.getInstance().getConnector(user, pass);
      } catch (AccumuloException e) {
        revertCreds = true;
        throw e;
      } catch (AccumuloSecurityException e) {
        revertCreds = true;
        throw e;
      } finally {
        // revert to saved credentials if failure occurred
        if (revertCreds) {
          shellState.credentials = prevCreds;
          shellState.connector = prevConn;
        }
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "switches to the specified user";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> special) {
      registerCompletionForUsers(root, special);
    }
   
    @Override
    public String usage() {
      return getName() + " <username>";
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class ScanCommand extends Command {
    private Option scanOptAuths;
    private Option scanOptStartRow;
    private Option scanOptEndRow;
    private Option scanOptColumns;
    protected Option timestampOpt;
    private Option disablePaginationOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException, ParseException {
      shellState.checkTableState();
     
      // handle first argument, if present, the authorizations list to
      // scan with
      Authorizations auths = getAuths(cl, shellState);
      Scanner scanner = shellState.connector.createScanner(shellState.tableName, auths);
     
      // handle session-specific scan iterators
      setScanIterators(shellState, scanner);
     
      // handle remaining optional arguments
      scanner.setRange(getRange(cl));
     
      // handle columns
      fetchColumns(cl, scanner);
     
      // output the records
      printRecords(cl, shellState, scanner);
      return 0;
    }
   
    protected void setScanIterators(Shell shellState, Scanner scanner) throws IOException {
      Map<String,Map<String,String>> tableIterators = shellState.scanIteratorOptions.get(shellState.tableName);
      if (tableIterators == null)
        return;
     
      for (Entry<String,Map<String,String>> entry : tableIterators.entrySet()) {
        Map<String,String> options = new HashMap<String,String>(entry.getValue());
        String className = options.remove("iteratorClassName");
        int priority = Integer.parseInt(options.remove("iteratorPriority"));
        String name = entry.getKey();
       
        log.debug("Setting scan iterator " + name + " at priority " + priority + " using class name " + className);
        scanner.setScanIterators(priority, className, name);
        for (Entry<String,String> option : options.entrySet()) {
          log.debug("Setting option for " + name + ": " + option.getKey() + "=" + option.getValue());
          scanner.setScanIteratorOption(name, option.getKey(), option.getValue());
        }
      }
    }
   
    protected void printRecords(CommandLine cl, Shell shellState, Iterable<Entry<Key,Value>> scanner) throws IOException {
      shellState.printRecords(scanner, cl.hasOption(timestampOpt.getOpt()), !cl.hasOption(disablePaginationOpt.getOpt()));
    }
   
    protected void fetchColumns(CommandLine cl, ScannerBase scanner) {
      if (cl.hasOption(scanOptColumns.getOpt())) {
        for (String a : cl.getOptionValue(scanOptColumns.getOpt()).split(",")) {
          String sa[] = a.split(":", 2);
          if (sa.length == 1)
            scanner.fetchColumnFamily(new Text(a));
          else
            scanner.fetchColumn(new Text(sa[0]), new Text(sa[1]));
        }
      }
    }
   
    protected Range getRange(CommandLine cl) {
      Text startRow = cl.hasOption(scanOptStartRow.getOpt()) ? new Text(cl.getOptionValue(scanOptStartRow.getOpt())) : null;
      Text endRow = cl.hasOption(scanOptEndRow.getOpt()) ? new Text(cl.getOptionValue(scanOptEndRow.getOpt())) : null;
      Range r = new Range(startRow, endRow);
      return r;
    }
   
    protected Authorizations getAuths(CommandLine cl, Shell shellState) throws AccumuloSecurityException, AccumuloException {
      String user = shellState.connector.whoami();
      Authorizations auths = shellState.connector.securityOperations().getUserAuthorizations(user);
      if (cl.hasOption(scanOptAuths.getOpt())) {
        auths = parseAuthorizations(cl.getOptionValue(scanOptAuths.getOpt()));
      }
      return auths;
    }
   
    @Override
    public String description() {
      return "scans the table, and displays the resulting records";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
     
      scanOptAuths = new Option("s", "scan-authorizations", true, "scan authorizations (all user auths are used if this argument is not specified)");
      scanOptStartRow = new Option("b", "begin-row", true, "begin row (inclusive)");
      scanOptEndRow = new Option("e", "end-row", true, "end row (inclusive)");
      scanOptColumns = new Option("c", "columns", true, "comma-separated columns");
      timestampOpt = new Option("st", "show-timestamps", false, "enables displaying timestamps");
      disablePaginationOpt = new Option("np", "no-pagination", false, "disables pagination of output");
     
      scanOptAuths.setArgName("comma-separated-authorizations");
      scanOptStartRow.setArgName("start-row");
      scanOptEndRow.setArgName("end-row");
      scanOptColumns.setArgName("{<columnfamily>[:<columnqualifier>]}");
     
      o.addOption(scanOptAuths);
      o.addOption(scanOptStartRow);
      o.addOption(scanOptEndRow);
      o.addOption(scanOptColumns);
      o.addOption(timestampOpt);
      o.addOption(disablePaginationOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class DeleteManyCommand extends ScanCommand {
    private Option forceOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException, ParseException {
      shellState.checkTableState();
     
      // handle first argument, if present, the authorizations list to
      // scan with
      Authorizations auths = getAuths(cl, shellState);
      final Scanner scanner = shellState.connector.createScanner(shellState.tableName, auths);
     
      scanner.setScanIterators(Integer.MAX_VALUE, SortedKeyIterator.class.getName(), "NOVALUE");
     
      // handle remaining optional arguments
      scanner.setRange(getRange(cl));
     
      // handle columns
      fetchColumns(cl, scanner);
     
      // output / delete the records
      BatchWriter writer = shellState.connector.createBatchWriter(shellState.tableName, 1024 * 1024, 1000L, 4);
      shellState.printLines(new DeleterFormatter(writer, scanner, cl.hasOption(timestampOpt.getOpt()), shellState, cl.hasOption(forceOpt.getOpt())), false);
      return 0;
    }
   
    @Override
    public String description() {
      return "scans a table and deletes the resulting records";
    }
   
    @Override
    public Options getOptions() {
      forceOpt = new Option("f", "force", false, "forces deletion without prompting");
      Options opts = super.getOptions();
      opts.addOption(forceOpt);
      return opts;
    }
   
  }
 
  public static class GrepCommand extends ScanCommand {
   
    private Option numThreadsOpt;
   
    protected void setUpIterator(int prio, String name, String term, BatchScanner scanner) throws IOException {
      if (prio < 0)
        throw new IllegalArgumentException("Priority < 0 " + prio);
     
      scanner.setScanIterators(prio, GrepIterator.class.getName(), name);
      scanner.setScanIteratorOption(name, "term", term);
    }
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException, MissingArgumentException {
      shellState.checkTableState();
     
      if (cl.getArgList().isEmpty())
        throw new MissingArgumentException("No terms specified");
     
      // handle first argument, if present, the authorizations list to
      // scan with
      int numThreads = 20;
      if (cl.hasOption(numThreadsOpt.getOpt())) {
        numThreads = Integer.parseInt(cl.getOptionValue(numThreadsOpt.getOpt()));
      }
      Authorizations auths = getAuths(cl, shellState);
      BatchScanner scanner = shellState.connector.createBatchScanner(shellState.tableName, auths, numThreads);
      scanner.setRanges(Collections.singletonList(getRange(cl)));
     
      for (int i = 0; i < cl.getArgs().length; i++)
        setUpIterator(Integer.MAX_VALUE, "grep" + i, cl.getArgs()[i], scanner);
     
      try {
        // handle columns
        fetchColumns(cl, scanner);
       
        // output the records
        printRecords(cl, shellState, scanner);
      } finally {
        scanner.close();
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "searches a table for a substring, in parallel, on the server side";
    }
   
    @Override
    public Options getOptions() {
      Options opts = super.getOptions();
      numThreadsOpt = new Option("t", "num-threads", true, "num threads");
      opts.addOption(numThreadsOpt);
      return opts;
    }
   
    @Override
    public String usage() {
      return getName() + " <term>{ <term>}";
    }
   
    @Override
    public int numArgs() {
      return NO_FIXED_ARG_LENGTH_CHECK;
    }
  }
 
  public static class EGrepCommand extends GrepCommand {
    @Override
    protected void setUpIterator(int prio, String name, String term, BatchScanner scanner) throws IOException {
      if (prio < 0)
        throw new IllegalArgumentException("Priority < 0 " + prio);
     
      scanner.setScanIterators(prio, RegExIterator.class.getName(), name);
      scanner.setScanIteratorOption(name, RegExFilter.ROW_REGEX, term);
      scanner.setScanIteratorOption(name, RegExFilter.COLF_REGEX, term);
      scanner.setScanIteratorOption(name, RegExFilter.COLQ_REGEX, term);
      scanner.setScanIteratorOption(name, RegExFilter.VALUE_REGEX, term);
      scanner.setScanIteratorOption(name, RegExFilter.OR_FIELDS, "true");
    }
   
    @Override
    public String description() {
      return "egreps a table in parallel on the server side (uses java regex)";
    }
   
    @Override
    public String usage() {
      return getName() + " <regex>{ <regex>}";
    }
  }
 
  public static class DeleteCommand extends Command {
    private Option deleteOptAuths;
    private Option timestampOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException, ConstraintViolationException {
      shellState.checkTableState();
     
      Mutation m = new Mutation(new Text(cl.getArgs()[0]));
     
      if (cl.hasOption(deleteOptAuths.getOpt())) {
        ColumnVisibility le = new ColumnVisibility(cl.getOptionValue(deleteOptAuths.getOpt()));
        if (cl.hasOption(timestampOpt.getOpt()))
          m.putDelete(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]), le, Long.parseLong(cl.getOptionValue(timestampOpt.getOpt())));
        else
          m.putDelete(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]), le);
      } else if (cl.hasOption(timestampOpt.getOpt()))
        m.putDelete(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]), Long.parseLong(cl.getOptionValue(timestampOpt.getOpt())));
      else
        m.putDelete(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]));
     
      BatchWriter bw = shellState.connector.createBatchWriter(shellState.tableName, m.estimatedMemoryUsed() + 0L, 0L, 1);
      bw.addMutation(m);
      bw.close();
      return 0;
    }
   
    @Override
    public String description() {
      return "deletes a record from a table";
    }
   
    @Override
    public String usage() {
      return getName() + " <row> <colfamily> <colqualifier>";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
     
      deleteOptAuths = new Option("l", "authorization-label", true, "formatted authorization label expression");
      deleteOptAuths.setArgName("expression");
      o.addOption(deleteOptAuths);
     
      timestampOpt = new Option("t", "timestamp", true, "timestamp to use for insert");
      timestampOpt.setArgName("timestamp");
      o.addOption(timestampOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 3;
    }
  }
 
  public static class FlushCommand extends Command {
    private Option optTablePattern;
    private Option optTableName;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
      // populate the tablesToFlush set with the tables you want to flush
      SortedSet<String> tablesToFlush = new TreeSet<String>();
      if (cl.hasOption(optTablePattern.getOpt())) {
        for (String table : shellState.connector.tableOperations().list())
          if (table.matches(cl.getOptionValue(optTablePattern.getOpt())))
            tablesToFlush.add(table);
      } else if (cl.hasOption(optTableName.getOpt())) {
        tablesToFlush.add(cl.getOptionValue(optTableName.getOpt()));
      }
     
      if (tablesToFlush.isEmpty())
        log.warn("No tables found that match your criteria");
     
      // flush the tables
      for (String tableName : tablesToFlush) {
        if (!shellState.connector.tableOperations().exists(tableName))
          throw new TableNotFoundException(null, tableName, null);
        flush(shellState, tableName);
      }
      return 0;
    }
   
    protected void flush(Shell shellState, String tableName) throws AccumuloException, AccumuloSecurityException {
      shellState.connector.tableOperations().flush(tableName);
      log.info("Flush of table " + tableName + " initiated...");
      if (tableName.equals(Constants.METADATA_TABLE_NAME)) {
        log.info("  May need to flush " + Constants.METADATA_TABLE_NAME + " table multiple times.");
        log.info("  Flushing " + Constants.METADATA_TABLE_NAME + " causes writes to itself and");
        log.info("  minor compactions, which also cause writes to itself.");
        log.info("  Check the monitor web page and give it time to settle.");
      }
    }
   
    @Override
    public String description() {
      return "makes a best effort to flush tables from memory to disk";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
     
      optTablePattern = new Option("p", "pattern", true, "regex pattern of table names to flush");
      optTableName = new Option(tableOption, "table", true, "name of a table to flush");
     
      optTablePattern.setArgName("pattern");
      optTableName.setArgName("tableName");
     
      OptionGroup opg = new OptionGroup();
      opg.addOption(optTablePattern);
      opg.addOption(optTableName);
      opg.setRequired(true);
     
      o.addOptionGroup(opg);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class OfflineCommand extends FlushCommand {
   
    @Override
    public String description() {
      return "starts the process of taking table offline";
    }
   
    protected void flush(Shell shellState, String tableName) throws AccumuloException, AccumuloSecurityException {
      if (tableName.equals(Constants.METADATA_TABLE_NAME)) {
        log.info("  You cannot take the " + Constants.METADATA_TABLE_NAME + " offline.");
      } else {
        log.info("Attempting to begin taking " + tableName + " offline");
        shellState.connector.tableOperations().offline(tableName);
      }
    }
  }
 
  public static class OnlineCommand extends FlushCommand {
   
    @Override
    public String description() {
      return "starts the process of putting a table online";
    }
   
    protected void flush(Shell shellState, String tableName) throws AccumuloException, AccumuloSecurityException {
      if (tableName.equals(Constants.METADATA_TABLE_NAME)) {
        log.info("  The " + Constants.METADATA_TABLE_NAME + " is always online.");
      } else {
        log.info("Attempting to begin bringing " + tableName + " online");
        shellState.connector.tableOperations().online(tableName);
      }
    }
  }
 
  public static class AddSplitsCommand extends Command {
    private Option optTableName;
    private Option optSplitsFile;
    private Option base64Opt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        MissingArgumentException, FileNotFoundException {
      String tableName = cl.getOptionValue(optTableName.getOpt());
      boolean decode = cl.hasOption(base64Opt.getOpt());
     
      TreeSet<Text> splits = new TreeSet<Text>();
     
      if (cl.hasOption(optSplitsFile.getOpt())) {
        String f = cl.getOptionValue(optSplitsFile.getOpt());
       
        String line;
        java.util.Scanner file = new java.util.Scanner(new File(f));
        while (file.hasNextLine()) {
          line = file.nextLine();
          if (!line.isEmpty())
            splits.add(decode ? new Text(Base64.decodeBase64(line.getBytes())) : new Text(line));
        }
      } else {
        if (cl.getArgList().isEmpty())
          throw new MissingArgumentException("No split points specified");
       
        for (String s : cl.getArgs()) {
          splits.add(new Text(s));
        }
      }
     
      if (!shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      shellState.connector.tableOperations().addSplits(tableName, splits);
     
      return 0;
    }
   
    @Override
    public String description() {
      return "add split points to an existing table";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      optTableName = new Option(tableOption, "table", true, "name of a table to add split points to");
      optSplitsFile = new Option("sf", "splits-file", true, "file with newline separated list of rows to add to table");
      optTableName.setArgName("tableName");
      optTableName.setRequired(true);
      optSplitsFile.setArgName("filename");
      base64Opt = new Option("b64", "base64encoded", false, "decode encoded split points");
     
      o.addOption(optTableName);
      o.addOption(optSplitsFile);
      o.addOption(base64Opt);
      return o;
    }
   
    @Override
    public String usage() {
      return getName() + " [<split>{ <split>} ]";
    }
   
    @Override
    public int numArgs() {
      return NO_FIXED_ARG_LENGTH_CHECK;
    }
  }
 
  public static class SelectCommand extends Command {
    private Option selectOptAuths;
    private Option timestampOpt;
    private Option disablePaginationOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException {
      shellState.checkTableState();
     
      Authorizations authorizations = cl.hasOption(selectOptAuths.getOpt()) ? parseAuthorizations(cl.getOptionValue(selectOptAuths.getOpt()))
          : Constants.NO_AUTHS;
      Scanner scanner = shellState.connector.createScanner(shellState.tableName.toString(), authorizations);
     
      Key key = new Key(new Text(cl.getArgs()[0]), new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]));
      scanner.setRange(new Range(key, key.followingKey(PartialKey.ROW_COLFAM_COLQUAL)));
     
      // output the records
      shellState.printRecords(scanner, cl.hasOption(timestampOpt.getOpt()), !cl.hasOption(disablePaginationOpt.getOpt()));
      return 0;
    }
   
    @Override
    public String description() {
      return "scans for and displays a single record";
    }
   
    @Override
    public String usage() {
      return getName() + " <row> <columnfamily> <columnqualifier>";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      selectOptAuths = new Option("s", "scan-authorizations", true, "scan authorizations");
      selectOptAuths.setArgName("comma-separated-authorizations");
      timestampOpt = new Option("st", "show-timestamps", false, "enables displaying timestamps");
      disablePaginationOpt = new Option("np", "no-pagination", false, "disables pagination of output");
      o.addOption(selectOptAuths);
      o.addOption(timestampOpt);
      o.addOption(disablePaginationOpt);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 3;
    }
  }
 
  public static class SelectrowCommand extends Command {
    private Option selectrowOptAuths;
    private Option timestampOpt;
    private Option disablePaginationOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException {
      shellState.checkTableState();
     
      Authorizations auths = cl.hasOption(selectrowOptAuths.getOpt()) ? parseAuthorizations(cl.getOptionValue(selectrowOptAuths.getOpt())) : Constants.NO_AUTHS;
      Scanner scanner = shellState.connector.createScanner(shellState.tableName.toString(), auths);
      scanner.setRange(new Range(new Text(cl.getArgs()[0])));
     
      // output the records
      shellState.printRecords(scanner, cl.hasOption(timestampOpt.getOpt()), !cl.hasOption(disablePaginationOpt.getOpt()));
      return 0;
    }
   
    @Override
    public String description() {
      return "scans a single row and displays all resulting records";
    }
   
    @Override
    public String usage() {
      return getName() + " <row>";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      selectrowOptAuths = new Option("s", "scan-authorizations", true, "scan authorizations");
      selectrowOptAuths.setArgName("comma-separated-authorizations");
      timestampOpt = new Option("st", "show-timestamps", false, "enables displaying timestamps");
      disablePaginationOpt = new Option("np", "no-pagination", false, "disables pagination of output");
      o.addOption(selectrowOptAuths);
      o.addOption(timestampOpt);
      o.addOption(disablePaginationOpt);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class InsertCommand extends Command {
    private Option insertOptAuths;
    private Option timestampOpt;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException, ConstraintViolationException {
      shellState.checkTableState();
     
      Mutation m = new Mutation(new Text(cl.getArgs()[0]));
     
      if (cl.hasOption(insertOptAuths.getOpt())) {
        ColumnVisibility le = new ColumnVisibility(cl.getOptionValue(insertOptAuths.getOpt()));
        log.debug("Authorization label will be set to: " + le.toString());
       
        if (cl.hasOption(timestampOpt.getOpt()))
          m.put(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]), le, Long.parseLong(cl.getOptionValue(timestampOpt.getOpt())),
              new Value(cl.getArgs()[3].getBytes()));
        else
          m.put(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]), le, new Value(cl.getArgs()[3].getBytes()));
      } else if (cl.hasOption(timestampOpt.getOpt()))
        m.put(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]), Long.parseLong(cl.getOptionValue(timestampOpt.getOpt())),
            new Value(cl.getArgs()[3].getBytes()));
      else
        m.put(new Text(cl.getArgs()[1]), new Text(cl.getArgs()[2]), new Value(cl.getArgs()[3].getBytes()));
     
      BatchWriter bw = shellState.connector.createBatchWriter(shellState.tableName, m.estimatedMemoryUsed() + 0L, 0L, 1);
      bw.addMutation(m);
      try {
        bw.close();
      } catch (MutationsRejectedException e) {
        ArrayList<String> lines = new ArrayList<String>();
        if (e.getAuthorizationFailures().isEmpty() == false)
          lines.add("  Authorization Failures:");
        for (KeyExtent extent : e.getAuthorizationFailures()) {
          lines.add("    " + extent);
        }
        if (e.getConstraintViolationSummaries().isEmpty() == false)
          lines.add("  Constraint Failures:");
        for (ConstraintViolationSummary cvs : e.getConstraintViolationSummaries()) {
          lines.add("    " + cvs.toString());
        }
        shellState.printLines(lines.iterator(), false);
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "inserts a record";
    }
   
    @Override
    public String usage() {
      return getName() + " <row> <colfamily> <colqualifier> <value>";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      insertOptAuths = new Option("l", "authorization-label", true, "formatted authorization label expression");
      insertOptAuths.setArgName("expression");
      o.addOption(insertOptAuths);
     
      timestampOpt = new Option("t", "timestamp", true, "timestamp to use for insert");
      timestampOpt.setArgName("timestamp");
      o.addOption(timestampOpt);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 4;
    }
  }
 
  public static class DropTableCommand extends DeleteTableCommand {}
 
  public static class DeleteTableCommand extends Command {
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        IOException {
      String tableName = cl.getArgs()[0];
      if (shellState.tableName.equals(tableName))
        shellState.tableName = "";
      shellState.connector.tableOperations().delete(tableName);
      return 0;
    }
   
    @Override
    public String description() {
      return "deletes a table";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> special) {
      registerCompletionForTables(root, special);
    }
   
    @Override
    public String usage() {
      return getName() + " <tableName>";
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class CreateTableCommand extends Command {
    private Option createTableOptCopySplits;
    private Option createTableOptCopyConfig;
    private Option createTableOptSplit;
    private Option createTableOptAgg;
    private Option createTableOptTimeLogical;
    private Option createTableOptTimeMillis;
    private Option createTableNoDefaultIters;
    private Option base64Opt;
    public static String testTable;
   
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableExistsException,
        TableNotFoundException, IOException {
     
      String testTableName = cl.getArgs()[0];
     
      if (!testTableName.matches(Constants.VALID_TABLE_NAME_REGEX)) {
        shellState.reader.printString("Only letters, numbers and underscores are allowed for use in table names. \n");
        throw new IllegalArgumentException();
      }
     
      String tableName = cl.getArgs()[0];
      if (shellState.connector.tableOperations().exists(tableName))
        throw new TableExistsException(null, tableName, null);
     
      SortedSet<Text> partitions = new TreeSet<Text>();
      List<AggregatorConfiguration> aggregators = new ArrayList<AggregatorConfiguration>();
      boolean decode = cl.hasOption(base64Opt.getOpt());
     
      if (cl.hasOption(createTableOptAgg.getOpt())) {
        String agg = cl.getOptionValue(createTableOptAgg.getOpt());
       
        EscapeTokenizer st = new EscapeTokenizer(agg, "=,");
        if (st.count() % 2 != 0) {
          printHelp();
          return 0;
        }
        Iterator<String> iter = st.iterator();
        while (iter.hasNext()) {
          String col = iter.next();
          String className = iter.next();
         
          EscapeTokenizer colToks = new EscapeTokenizer(col, ":");
          Iterator<String> tokIter = colToks.iterator();
          Text cf = null, cq = null;
          if (colToks.count() < 1 || colToks.count() > 2)
            throw new BadArgumentException("column must be in the format cf[:cq]", fullCommand, fullCommand.indexOf(col));
          cf = new Text(tokIter.next());
          if (colToks.count() == 2)
            cq = new Text(tokIter.next());
         
          aggregators.add(new AggregatorConfiguration(cf, cq, className));
        }
      }
      if (cl.hasOption(createTableOptSplit.getOpt())) {
        String f = cl.getOptionValue(createTableOptSplit.getOpt());
       
        String line;
        java.util.Scanner file = new java.util.Scanner(new File(f));
        while (file.hasNextLine()) {
          line = file.nextLine();
          if (!line.isEmpty())
            partitions.add(decode ? new Text(Base64.decodeBase64(line.getBytes())) : new Text(line));
        }
      } else if (cl.hasOption(createTableOptCopySplits.getOpt())) {
        String oldTable = cl.getOptionValue(createTableOptCopySplits.getOpt());
        if (!shellState.connector.tableOperations().exists(oldTable))
          throw new TableNotFoundException(null, oldTable, null);
        partitions.addAll(shellState.connector.tableOperations().getSplits(oldTable));
      }
     
      if (cl.hasOption(createTableOptCopyConfig.getOpt())) {
        String oldTable = cl.getOptionValue(createTableOptCopyConfig.getOpt());
        if (!shellState.connector.tableOperations().exists(oldTable))
          throw new TableNotFoundException(null, oldTable, null);
      }
     
      TimeType timeType = TimeType.MILLIS;
      if (cl.hasOption(createTableOptTimeLogical.getOpt()))
        timeType = TimeType.LOGICAL;
     
      shellState.connector.tableOperations().create(tableName, timeType); // create
      // table
     
      shellState.connector.tableOperations().addSplits(tableName, partitions);
      shellState.connector.tableOperations().addAggregators(tableName, aggregators);
     
      shellState.tableName = tableName; // switch shell to new table
      // context
     
      if (cl.hasOption(createTableNoDefaultIters.getOpt())) {
        List<AggregatorConfiguration> empty = Collections.emptyList();
        for (String key : IteratorUtil.generateInitialTableProperties(empty).keySet())
          shellState.connector.tableOperations().removeProperty(tableName, key);
      }
     
      // Copy options if flag was set
      if (cl.hasOption(createTableOptCopyConfig.getOpt())) {
        if (shellState.connector.tableOperations().exists(tableName)) {
          for (Entry<String,String> entry : AccumuloConfiguration.getTableConfiguration(shellState.connector.getInstance().getInstanceID(),
              shellState.connector.tableOperations().tableIdMap().get(cl.getOptionValue(createTableOptCopyConfig.getOpt()))))
            if (Property.isValidTablePropertyKey(entry.getKey()))
              shellState.connector.tableOperations().setProperty(tableName, entry.getKey(), entry.getValue());
        }
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "creates a new table, with optional aggregators and optionally pre-split";
    }
   
    @Override
    public String usage() {
      return getName() + " <tableName>";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
     
      createTableOptCopyConfig = new Option("cc", "copy-config", true, "table to copy configuration from");
      createTableOptCopySplits = new Option("cs", "copy-splits", true, "table to copy current splits from");
      createTableOptSplit = new Option("sf", "splits-file", true, "file with newline separated list of rows to create a pre-split table");
      createTableOptAgg = new Option("a", "aggregator", true, "comma separated column=aggregator");
      createTableOptTimeLogical = new Option("tl", "time-logical", false, "use logical time");
      createTableOptTimeMillis = new Option("tm", "time-millis", false, "use time in milliseconds");
      createTableNoDefaultIters = new Option("ndi", "no-default-iterators", false, "prevents creation of the normal default iterator set");
     
      createTableOptCopyConfig.setArgName("table");
      createTableOptCopySplits.setArgName("table");
      createTableOptSplit.setArgName("filename");
      createTableOptAgg.setArgName("{<columnfamily>[:<columnqualifier>]=<aggregation_class>}");
     
      // Splits and CopySplits are put in an optionsgroup to make them
      // mutually exclusive
      OptionGroup splitOrCopySplit = new OptionGroup();
      splitOrCopySplit.addOption(createTableOptSplit);
      splitOrCopySplit.addOption(createTableOptCopySplits);
     
      OptionGroup timeGroup = new OptionGroup();
      timeGroup.addOption(createTableOptTimeLogical);
      timeGroup.addOption(createTableOptTimeMillis);
     
      base64Opt = new Option("b64", "base64encoded", false, "decode encoded split points");
      o.addOption(base64Opt);
     
      o.addOptionGroup(splitOrCopySplit);
      o.addOptionGroup(timeGroup);
      o.addOption(createTableOptSplit);
      o.addOption(createTableOptAgg);
      o.addOption(createTableOptCopyConfig);
      o.addOption(createTableNoDefaultIters);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class RenameTableCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException,
        TableExistsException {
      shellState.connector.tableOperations().rename(cl.getArgs()[0], cl.getArgs()[1]);
      if (shellState.tableName.equals(cl.getArgs()[0]))
        shellState.tableName = cl.getArgs()[1];
      return 0;
    }
   
    @Override
    public String usage() {
      return getName() + " <current table name> <new table name>";
    }
   
    @Override
    public String description() {
      return "rename a table";
    }
   
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> completionSet) {
      registerCompletionForTables(root, completionSet);
    }
   
    @Override
    public int numArgs() {
      return 2;
    }
  }
 
  public static class TablesCommand extends Command {
    private Option tableIdOption;
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, IOException {
      if (cl.hasOption(tableIdOption.getOpt())) {
        Map<String,String> tableIds = shellState.connector.tableOperations().tableIdMap();
        for (String tableName : shellState.connector.tableOperations().list())
          shellState.reader.printString(String.format("%-15s => %10s\n", tableName, tableIds.get(tableName)));
      } else {
        for (String table : shellState.connector.tableOperations().list())
          shellState.reader.printString(table + "\n");
      }
      return 0;
    }
   
    @Override
    public String description() {
      return "displays a list of all existing tables";
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      tableIdOption = new Option("l", "list-ids", false, "display internal table ids along with the table name");
      o.addOption(tableIdOption);
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class TableCommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws AccumuloException, AccumuloSecurityException, TableNotFoundException {
      String tableName = cl.getArgs()[0];
      if (!shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      shellState.tableName = tableName;
      return 0;
    }
   
    @Override
    public String description() {
      return "switches to the specified table";
    }
   
    @Override
    public void registerCompletion(Token root, Map<CompletionSet,Set<String>> special) {
      registerCompletionForTables(root, special);
    }
   
    @Override
    public String usage() {
      return getName() + " <tableName>";
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
  }
 
  public static class WhoAmICommand extends Command {
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws IOException {
      shellState.reader.printString(shellState.connector.whoami() + "\n");
      return 0;
    }
   
    @Override
    public String description() {
      return "reports the current user name";
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class ClsCommand extends ClearCommand {}
 
  public static class ClearCommand extends Command {
    @Override
    public String description() {
      return "clears the screen";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws IOException {
      // custom clear screen, so I don't have to redraw the prompt twice
      if (!shellState.reader.getTerminal().isANSISupported())
        throw new IOException("Terminal does not support ANSI commands");
     
      // send the ANSI code to clear the screen
      shellState.reader.printString(((char) 27) + "[2J");
      shellState.reader.flushConsole();
     
      // then send the ANSI code to go to position 1,1
      shellState.reader.printString(((char) 27) + "[1;1H");
      shellState.reader.flushConsole();
     
      return 0;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
  }
 
  public static class FormatterCommand extends Command {
    private Option resetOption, formatterClassOption, listClassOption;
   
    @Override
    public String description() {
      return "specifies a formatter to use for displaying database entries";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
      if (cl.hasOption(resetOption.getOpt()))
        shellState.formatterClass = DefaultFormatter.class;
      else if (cl.hasOption(formatterClassOption.getOpt()))
        shellState.formatterClass = AccumuloClassLoader.loadClass(cl.getOptionValue(formatterClassOption.getOpt()), Formatter.class);
      else if (cl.hasOption(listClassOption.getOpt()))
        shellState.reader.printString(shellState.formatterClass.getName() + "\n");
      return 0;
    }
   
    @Override
    public Options getOptions() {
      Options o = new Options();
      OptionGroup formatGroup = new OptionGroup();
     
      resetOption = new Option("r", "reset", false, "reset to default formatter");
      formatterClassOption = new Option("f", "formatter", true, "fully qualified name of formatter class to use");
      formatterClassOption.setArgName("className");
      listClassOption = new Option("l", "list", false, "display the current formatter");
      formatGroup.addOption(resetOption);
      formatGroup.addOption(formatterClassOption);
      formatGroup.addOption(listClassOption);
      formatGroup.setRequired(true);
     
      o.addOptionGroup(formatGroup);
     
      return o;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
   
  }
 
  public static class ExecfileCommand extends Command {
    private Option verboseOption;
   
    @Override
    public String description() {
      return "specifies a file containing accumulo commands to execute";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
      java.util.Scanner scanner = new java.util.Scanner(new File(cl.getArgs()[0]));
      while (scanner.hasNextLine())
        shellState.execCommand(scanner.nextLine(), true, cl.hasOption(verboseOption.getOpt()));
      return 0;
    }
   
    @Override
    public int numArgs() {
      return 1;
    }
   
    @Override
    public Options getOptions() {
      Options opts = new Options();
      verboseOption = new Option("v", "verbose", false, "displays command prompt as commands are executed");
      opts.addOption(verboseOption);
      return opts;
    }
  }
 
  public static class CompactCommand extends FlushCommand {
    private Option overrideOpt;
    SimpleDateFormat dateParser = new SimpleDateFormat("yyyyMMddHHmmssz");
    boolean override = false;
   
    @Override
    public String description() {
      return "sets all tablets for a table to major compact as soon as possible (based on current time)";
    }
   
    protected void flush(Shell shellState, String tableName) throws AccumuloException, AccumuloSecurityException {
      // compact the tables
      Date now = new Date(System.currentTimeMillis());
      String nowStr = dateParser.format(now);
      try {
        if (!override) {
          Iterable<Entry<String,String>> iter = shellState.connector.tableOperations().getProperties(tableName);
          for (Entry<String,String> prop : iter) {
            if (prop.getKey().equals(Property.TABLE_MAJC_COMPACTALL_AT.getKey())) {
              if (dateParser.parse(prop.getValue()).after(now)) {
                log.error("Date will override a scheduled future compaction for table (" + tableName + "). Use --" + overrideOpt.getLongOpt());
                return;
              } else {
                break;
              }
            }
          }
        }
        shellState.connector.tableOperations().flush(tableName);
        shellState.connector.tableOperations().setProperty(tableName, Property.TABLE_MAJC_COMPACTALL_AT.getKey(), nowStr);
        log.info("Compaction of table " + tableName + " scheduled for " + nowStr);
      } catch (Exception ex) {
        throw new AccumuloException(ex);
      }
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
      override = cl.hasOption(overrideOpt.getLongOpt());
      return super.execute(fullCommand, cl, shellState);
    }
   
    @Override
    public Options getOptions() {
      Options opts = super.getOptions();
      overrideOpt = new Option(null, "override", false, "override a future scheduled compaction");
      opts.addOption(overrideOpt);
      return opts;
    }
  }
 
  public static class InfoCommand extends AboutCommand {}
 
  public static class AboutCommand extends Command {
    private Option verboseOption;
   
    @Override
    public String description() {
      return "displays information about this program";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws IOException {
      shellState.printInfo();
      if (cl.hasOption(verboseOption.getOpt()))
        shellState.printVerboseInfo();
      return 0;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
   
    @Override
    public Options getOptions() {
      Options opts = new Options();
      verboseOption = new Option("v", "verbose", false, "displays details session information");
      opts.addOption(verboseOption);
      return opts;
    }
  }
 
  public static class HiddenCommand extends Command {
    private static Random rand = new SecureRandom();
   
    @Override
    public String description() {
      return "The first rule of accumulo is: \"You don't talk about accumulo.\"";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
      if (rand.nextInt(10) == 0) {
        shellState.reader.beep();
        shellState.reader.printNewline();
        shellState.reader.printString("Sortacus lives!");
        shellState.reader.printNewline();
      } else
        throw new ShellCommandException(ErrorCode.UNRECOGNIZED_COMMAND, getName());
     
      return 0;
    }
   
    @Override
    public int numArgs() {
      return NO_FIXED_ARG_LENGTH_CHECK;
    }
   
    @Override
    public String getName() {
      return "\0\0\0\0";
    }
  }
 
  public static class GetGroupsCommand extends Command {
   
    private Option tableOpt;
   
    @Override
    public String description() {
      return "gets the locality groups for a given table";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
     
      String tableName = cl.getOptionValue(tableOpt.getOpt());
      if (!shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      Map<String,Set<Text>> groups = shellState.connector.tableOperations().getLocalityGroups(tableName);
     
      for (Entry<String,Set<Text>> entry : groups.entrySet())
        shellState.reader.printString(entry.getKey() + "=" + LocalityGroupUtil.encodeColumnFamilies(entry.getValue()) + "\n");
     
      return 0;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
   
    @Override
    public Options getOptions() {
      Options opts = new Options();
     
      tableOpt = new Option(tableOption, "table", true, "get locality groups for specified table");
      tableOpt.setArgName("table");
      tableOpt.setRequired(true);
      opts.addOption(tableOpt);
      return opts;
    }
   
  }
 
  public static class SetGroupsCommand extends Command {
   
    private Option tableOpt;
   
    @Override
    public String description() {
      return "sets the locality groups for a given table (for binary or commas, use Java API)";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
     
      String tableName = cl.getOptionValue(tableOpt.getOpt());
      if (!shellState.connector.tableOperations().exists(tableName))
        throw new TableNotFoundException(null, tableName, null);
     
      HashMap<String,Set<Text>> groups = new HashMap<String,Set<Text>>();
     
      for (String arg : cl.getArgs()) {
        String sa[] = arg.split("=", 2);
        if (sa.length < 2)
          throw new IllegalArgumentException("Missing '='");
        String group = sa[0];
        HashSet<Text> colFams = new HashSet<Text>();
       
        for (String family : sa[1].split(",")) {
          colFams.add(new Text(family));
        }
       
        groups.put(group, colFams);
      }
     
      shellState.connector.tableOperations().setLocalityGroups(tableName, groups);
     
      return 0;
    }
   
    @Override
    public int numArgs() {
      return NO_FIXED_ARG_LENGTH_CHECK;
    }
   
    @Override
    public String usage() {
      return getName() + " <group>=<col fam>{,<col fam>}{ <group>=<col fam>{,<col fam>}}";
    }
   
    @Override
    public Options getOptions() {
      Options opts = new Options();
     
      tableOpt = new Option(tableOption, "table", true, "get locality groups for specified table");
      tableOpt.setArgName("table");
      tableOpt.setRequired(true);
      opts.addOption(tableOpt);
      return opts;
    }
   
  }
 
  public static class ImportDirectoryCommand extends Command {
    private static final String DEFAULT_FILE_THREADS = "8";
    private static final String DEFAULT_ASSIGN_THREADS = "20";
   
    private Option numFileThreadsOpt, numAssignThreadsOpt, disableGCOpt, verboseOption;
   
    @Override
    public String description() {
      return "bulk imports an entire directory of data files to the current table";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws IOException, AccumuloException, AccumuloSecurityException,
        TableNotFoundException {
      shellState.checkTableState();
     
      String dir = cl.getArgs()[0];
      String failureDir = cl.getArgs()[1];
      int numFileThreads = Integer.parseInt(cl.getOptionValue(numFileThreadsOpt.getOpt(), DEFAULT_FILE_THREADS));
      int numAssignThreads = Integer.parseInt(cl.getOptionValue(numAssignThreadsOpt.getOpt(), DEFAULT_ASSIGN_THREADS));
      boolean disableGC = cl.hasOption(disableGCOpt.getOpt());
     
      AssignmentStats stats = shellState.connector.tableOperations().importDirectory(shellState.tableName, dir, failureDir, numFileThreads, numAssignThreads,
          disableGC);
      if (cl.hasOption(verboseOption.getOpt()))
        shellState.reader.printString(stats.toString());
     
      return 0;
    }
   
    @Override
    public int numArgs() {
      return 2;
    }
   
    @Override
    public String usage() {
      return getName() + " <directory> <failureDirectory>";
    }
   
    @Override
    public Options getOptions() {
      Options opts = new Options();
     
      verboseOption = new Option("v", "verbose", false, "displays statistics from the import");
      opts.addOption(verboseOption);
     
      numFileThreadsOpt = new Option("f", "numFileThreads", true, "number of threads to process files (default: " + DEFAULT_FILE_THREADS + ")");
      numFileThreadsOpt.setArgName("num");
      opts.addOption(numFileThreadsOpt);
     
      numAssignThreadsOpt = new Option("a", "numAssignThreads", true, "number of assign threads for import (default: " + DEFAULT_ASSIGN_THREADS + ")");
      numAssignThreadsOpt.setArgName("num");
      opts.addOption(numAssignThreadsOpt);
     
      disableGCOpt = new Option("g", "disableGC", false, "prevents imported files from being deleted by the garbage collector");
      opts.addOption(disableGCOpt);
     
      return opts;
    }
  }
 
  public interface PrintLine {
    public void print(String s);
   
    public void close();
  }
 
  public static class PrintShell implements PrintLine {
    ConsoleReader reader;
   
    public PrintShell(ConsoleReader reader) {
      this.reader = reader;
    }
   
    public void print(String s) {
      try {
        reader.printString(s + "\n");
      } catch (Exception ex) {
        throw new RuntimeException(ex);
      }
    }
   
    public void close() {}
  };
 
  public static class PrintFile implements PrintLine {
    PrintWriter writer;
   
    public PrintFile(String filename) throws FileNotFoundException {
      writer = new PrintWriter(filename);
    }
   
    public void print(String s) {
      writer.println(s);
    }
   
    public void close() {
      writer.close();
    }
  };
 
  public static class GetSplitsCommand extends Command {
   
    private Option outputFileOpt, maxSplitsOpt, base64Opt, verboseOpt;
   
    @Override
    public String description() {
      return "retrieves the current split points for tablets in the current table";
    }
   
    private static String encode(boolean encode, Text text) {
      if (text == null)
        return null;
      return encode ? new String(Base64.encodeBase64(TextUtil.getBytes(text))) : text.toString();
    }
   
    private static String obscuredTabletName(KeyExtent extent) {
      MessageDigest digester;
      try {
        digester = MessageDigest.getInstance("MD5");
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException(e);
      }
      if (extent.getEndRow() != null && extent.getEndRow().getLength() > 0) {
        digester.update(extent.getEndRow().getBytes(), 0, extent.getEndRow().getLength());
      }
      return new String(Base64.encodeBase64(digester.digest()));
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, final Shell shellState) throws IOException, AccumuloException, AccumuloSecurityException,
        TableNotFoundException {
      shellState.checkTableState();
      final String outputFile = cl.getOptionValue(outputFileOpt.getOpt());
      final String m = cl.getOptionValue(maxSplitsOpt.getOpt());
      final int maxSplits = m == null ? 0 : Integer.parseInt(m);
      final boolean encode = cl.hasOption(base64Opt.getOpt());
      final boolean verbose = cl.hasOption(verboseOpt.getOpt());
     
      PrintLine p = outputFile == null ? new PrintShell(shellState.reader) : new PrintFile(outputFile);
     
      try {
        if (!verbose) {
          for (Text row : maxSplits > 0 ? shellState.connector.tableOperations().getSplits(shellState.tableName, maxSplits) : shellState.connector
              .tableOperations().getSplits(shellState.tableName)) {
            p.print(encode(encode, row));
          }
        } else {
          final Scanner scanner = shellState.connector.createScanner(Constants.METADATA_TABLE_NAME, Constants.NO_AUTHS);
          ColumnFQ.fetch(scanner, Constants.METADATA_PREV_ROW_COLUMN);
          final Text start = new Text(shellState.connector.tableOperations().tableIdMap().get(shellState.tableName));
          final Text end = new Text(start);
          end.append(new byte[] {'<'}, 0, 1);
          scanner.setRange(new Range(start, end));
          for (Iterator<Entry<Key,Value>> iterator = scanner.iterator(); iterator.hasNext();) {
            final Entry<Key,Value> next = iterator.next();
            if (Constants.METADATA_PREV_ROW_COLUMN.hasColumns(next.getKey())) {
              KeyExtent extent = new KeyExtent(next.getKey().getRow(), next.getValue());
              final String pr = encode(encode, extent.getPrevEndRow());
              final String er = encode(encode, extent.getEndRow());
              final String line = String.format("%-26s (%s, %s%s", obscuredTabletName(extent), pr == null ? "-inf" : pr, er == null ? "+inf" : er,
                  er == null ? ") Default Tablet " : "]");
              p.print(line);
            }
          }
        }
       
      } finally {
        p.close();
      }
      return 0;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
   
    @Override
    public Options getOptions() {
      Options opts = new Options();
     
      outputFileOpt = new Option("o", "output", true, "specifies a local file to write the splits to");
      outputFileOpt.setArgName("file");
      opts.addOption(outputFileOpt);
     
      maxSplitsOpt = new Option("m", "max", true, "specifies the maximum number of splits to create");
      maxSplitsOpt.setArgName("num");
      opts.addOption(maxSplitsOpt);
     
      base64Opt = new Option("b64", "base64encoded", false, "encode the split points");
      opts.addOption(base64Opt);
     
      verboseOpt = new Option("v", "verbose", false, "print out the tablet information with start/end rows");
      opts.addOption(verboseOpt);
     
      return opts;
    }
  }
 
  private static class ActiveScanIterator implements Iterator<String> {
   
    private InstanceOperations instanceOps;
    private Iterator<String> tsIter;
    private Iterator<String> scansIter;
   
    private void readNext() {
      List<String> scans = new ArrayList<String>();
     
      while (tsIter.hasNext()) {
       
        String tserver = tsIter.next();
        try {
          List<ActiveScan> asl = instanceOps.getActiveScans(tserver);
         
          for (ActiveScan as : asl)
            scans.add(String.format("%21s |%21s |%9s |%9s |%7s |%6s |%8s |%8s |%10s |%10s |%10s | %s", tserver, as.getClient(),
                Duration.format(as.getAge(), ""), Duration.format(as.getLastContactTime(), ""), as.getState(), as.getType(), as.getUser(), as.getTable(),
                as.getColumns(), (as.getType() == ScanType.SINGLE ? as.getExtent() : "N/A"), as.getSsiList(), as.getSsio()));
         
        } catch (Exception e) {
          scans.add(tserver + " ERROR " + e.getMessage());
        }
       
        if (scans.size() > 0)
          break;
      }
     
      scansIter = scans.iterator();
    }
   
    ActiveScanIterator(List<String> tservers, InstanceOperations instanceOps) {
      this.instanceOps = instanceOps;
      this.tsIter = tservers.iterator();
     
      String header = String.format(" %-21s| %-21s| %-9s| %-9s| %-7s| %-6s| %-8s| %-8s| %-10s| %-10s| %-10s | %s", "TABLET SERVER", "CLIENT", "AGE", "LAST",
          "STATE", "TYPE", "USER", "TABLE", "COLUMNS", "TABLET", "ITERATORS", "ITERATOR OPTIONS");
     
      scansIter = Collections.singletonList(header).iterator();
    }
   
    @Override
    public boolean hasNext() {
      return scansIter.hasNext();
    }
   
    @Override
    public String next() {
      String next = scansIter.next();
     
      if (!scansIter.hasNext())
        readNext();
     
      return next;
    }
   
    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }
   
  }
 
  public static class ListScansCommand extends Command {
   
    private Option tserverOption, disablePaginationOpt;
   
    @Override
    public String description() {
      return "list what scans are currently running in accumulo. See the accumulo.core.client.admin.ActiveScan javadoc for more information about columns.";
    }
   
    @Override
    public int execute(String fullCommand, CommandLine cl, Shell shellState) throws Exception {
     
      List<String> tservers;
     
      InstanceOperations instanceOps = shellState.connector.instanceOperations();
     
      boolean paginate = !cl.hasOption(disablePaginationOpt.getOpt());
     
      if (cl.hasOption(tserverOption.getOpt())) {
        tservers = new ArrayList<String>();
        tservers.add(cl.getOptionValue(tserverOption.getOpt()));
      } else {
        tservers = instanceOps.getTabletServers();
      }
     
      shellState.printLines(new ActiveScanIterator(tservers, instanceOps), paginate);
     
      return 0;
    }
   
    @Override
    public int numArgs() {
      return 0;
    }
   
    @Override
    public Options getOptions() {
      Options opts = new Options();
     
      tserverOption = new Option("ts", "tabletServer", true, "list scans for a specific tablet server");
      tserverOption.setArgName("tablet server");
      opts.addOption(tserverOption);
     
      disablePaginationOpt = new Option("np", "no-pagination", false, "disables pagination of output");
      opts.addOption(disablePaginationOpt);
     
      return opts;
    }
   
  }
 
  private final void printLines(Iterator<String> lines, boolean paginate) throws IOException {
    int linesPrinted = 0;
    String prompt = "-- hit any key to continue or 'q' to quit --";
    int lastPromptLength = prompt.length();
    int termWidth = reader.getTermwidth();
    int maxLines = reader.getTermheight();
   
    String peek = null;
    while (lines.hasNext()) {
      String nextLine = lines.next();
      if (nextLine == null)
        continue;
      for (String line : nextLine.split("\\n")) {
        if (peek != null) {
          reader.printString(peek);
          reader.printNewline();
          if (paginate) {
            linesPrinted += peek.length() == 0 ? 0 : Math.ceil(peek.length() * 1.0 / termWidth);
           
            // check if displaying the next line would result in
            // scrolling off the screen
            if (linesPrinted + Math.ceil(lastPromptLength * 1.0 / termWidth) + Math.ceil(prompt.length() * 1.0 / termWidth)
                + Math.ceil(line.length() * 1.0 / termWidth) > maxLines) {
              linesPrinted = 0;
              int numdashes = (termWidth - prompt.length()) / 2;
              String nextPrompt = repeat("-", numdashes) + prompt + repeat("-", numdashes);
              lastPromptLength = nextPrompt.length();
              reader.printString(nextPrompt);
              reader.flushConsole();
              if (Character.toUpperCase((char) reader.readVirtualKey()) == 'Q') {
                reader.printNewline();
                return;
              }
              reader.printNewline();
              termWidth = reader.getTermwidth();
              maxLines = reader.getTermheight();
            }
          }
        }
        peek = line;
      }
    }
    if (peek != null) {
      reader.printString(peek);
      reader.printNewline();
    }
  }
 
  private final void printRecords(Iterable<Entry<Key,Value>> scanner, boolean printTimestamps, boolean paginate) throws IOException {
    printLines(FormatterFactory.getFormatter(formatterClass, scanner, printTimestamps), paginate);
  }
 
  private static String repeat(String s, int c) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < c; i++)
      sb.append(s);
    return sb.toString();
  }
 
  private static Authorizations parseAuthorizations(String field) {
    if (field == null || field.isEmpty())
      return Constants.NO_AUTHS;
    return new Authorizations(field.split(","));
  }
 
  private void checkTableState() {
    if (tableName.isEmpty())
      throw new IllegalStateException("Not in a table context. Please use 'table <tableName>' to switch to a table");
  }
 
  private final void printConstraintViolationException(ConstraintViolationException cve) {
    printException(cve, "");
    int COL1 = 50, COL2 = 14;
    int col3 = Math.max(1, Math.min(Integer.MAX_VALUE, reader.getTermwidth() - COL1 - COL2 - 6));
    log.error(String.format("%" + COL1 + "s-+-%" + COL2 + "s-+-%" + col3 + "s\n", repeat("-", COL1), repeat("-", COL2), repeat("-", col3)));
    log.error(String.format("%-" + COL1 + "s | %" + COL2 + "s | %-" + col3 + "s\n", "Constraint class", "Violation code", "Violation Description"));
    log.error(String.format("%" + COL1 + "s-+-%" + COL2 + "s-+-%" + col3 + "s\n", repeat("-", COL1), repeat("-", COL2), repeat("-", col3)));
    for (TConstraintViolationSummary cvs : cve.violationSummaries)
      log.error(String.format("%-" + COL1 + "s | %" + COL2 + "d | %-" + col3 + "s\n", cvs.constrainClass, cvs.violationCode, cvs.violationDescription));
    log.error(String.format("%" + COL1 + "s-+-%" + COL2 + "s-+-%" + col3 + "s\n", repeat("-", COL1), repeat("-", COL2), repeat("-", col3)));
  }
 
  private static final void printException(Exception e) {
    printException(e, e.getMessage());
  }
 
  private static final void printException(Exception e, String msg) {
    log.error(e.getClass().getName() + (msg != null ? ": " + msg : ""));
    log.debug(e.getClass().getName() + (msg != null ? ": " + msg : ""), e);
  }
 
  private static final void setDebugging(boolean debuggingEnabled) {
    Logger.getLogger(Constants.CORE_PACKAGE_NAME).setLevel(debuggingEnabled ? Level.TRACE : Level.INFO);
  }
 
  public static final boolean isDebuggingEnabled() {
    return Logger.getLogger(Constants.CORE_PACKAGE_NAME).isTraceEnabled();
  }
 
  private static final void printHelp(String usage, String description, Options opts) {
    PrintWriter pw = new PrintWriter(System.err);
    new HelpFormatter().printHelp(pw, Integer.MAX_VALUE, usage, description, opts, 2, 5, null, true);
    pw.flush();
  }
}
TOP

Related Classes of org.apache.accumulo.core.util.shell.Shell$QuitCommand

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.