/*
* Copyright (C) 2012 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.crsh.cli.descriptor;
import org.crsh.cli.impl.completion.CompletionMatcher;
import org.crsh.cli.impl.descriptor.IntrospectionException;
import org.crsh.cli.impl.Multiplicity;
import org.crsh.cli.impl.invocation.CommandInvoker;
import org.crsh.cli.impl.invocation.InvocationMatch;
import org.crsh.cli.impl.invocation.InvocationMatcher;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
public abstract class CommandDescriptor<T> {
/** . */
private final String name;
/** . */
private final Description description;
/** . */
private final Map<String, OptionDescriptor> optionMap;
/** . */
private final Set<String> shortOptionNames;
/** . */
private final Set<String> longOptionNames;
/** . */
private boolean listArgument;
/** . */
private final List<OptionDescriptor> options;
/** . */
private final List<ArgumentDescriptor> arguments;
/** . */
private final List<ParameterDescriptor> parameters;
/** . */
private final Map<String, OptionDescriptor> uOptionMap;
/** . */
private final Set<String> uShortOptionNames;
/** . */
private final Set<String> uLongOptionNames;
/** . */
private final List<OptionDescriptor> uOptions;
/** . */
private final List<ArgumentDescriptor> uArguments;
/** . */
private final List<ParameterDescriptor> uParameters;
protected CommandDescriptor(String name, Description description) throws IntrospectionException {
//
int nameLength = name.length();
if (nameLength == 0) {
throw new IntrospectionException("Command name cannot be null");
} else {
for (int i = 0;i < nameLength;i++) {
char c = name.charAt(i);
if (i == 0) {
if (!Character.isLetter(c)) {
throw new IntrospectionException("Invalid command name <" + name + "> does not start with a letter");
}
} else {
if (!Character.isLetter(c) && !Character.isDigit(c) && c != '_' && c != '-') {
throw new IntrospectionException("Invalid command name <" + name + "> char " + c + " at position " + i + " is now allowed");
}
}
}
}
//
this.description = description;
this.optionMap = new LinkedHashMap<String, OptionDescriptor>();
this.arguments = new ArrayList<ArgumentDescriptor>();
this.options = new ArrayList<OptionDescriptor>();
this.name = name;
this.parameters = new ArrayList<ParameterDescriptor>();
this.listArgument = false;
this.shortOptionNames = new HashSet<String>();
this.longOptionNames = new HashSet<String>();
//
this.uOptionMap = Collections.unmodifiableMap(optionMap);
this.uParameters = Collections.unmodifiableList(parameters);
this.uOptions = Collections.unmodifiableList(options);
this.uArguments = Collections.unmodifiableList(arguments);
this.uShortOptionNames = shortOptionNames;
this.uLongOptionNames = longOptionNames;
}
/**
* Add a parameter to the command.
*
* @param parameter the parameter to add
* @throws IntrospectionException any introspection exception that would prevent the parameter to be added
* @throws NullPointerException if the parameter is null
* @throws IllegalArgumentException if the parameter is already associated with another command
*/
protected void addParameter(ParameterDescriptor parameter) throws IntrospectionException, NullPointerException, IllegalArgumentException {
//
if (parameter == null) {
throw new NullPointerException("No null parameter accepted");
}
//
if (parameter instanceof OptionDescriptor) {
OptionDescriptor option = (OptionDescriptor)parameter;
for (String optionName : option.getNames()) {
String name;
if (optionName.length() == 1) {
name = "-" + optionName;
if (shortOptionNames.contains(name)) {
throw new IntrospectionException("Duplicate option " + name);
} else {
shortOptionNames.add(name);
}
} else {
name = "--" + optionName;
if (longOptionNames.contains(name)) {
throw new IntrospectionException();
} else {
longOptionNames.add(name);
}
}
optionMap.put(name, option);
}
options.add(option);
ListIterator<ParameterDescriptor> i = parameters.listIterator();
while (i.hasNext()) {
ParameterDescriptor next = i.next();
if (next instanceof ArgumentDescriptor) {
i.previous();
break;
}
}
i.add(parameter);
} else if (parameter instanceof ArgumentDescriptor) {
ArgumentDescriptor argument = (ArgumentDescriptor)parameter;
if (argument.getMultiplicity() == Multiplicity.MULTI) {
if (listArgument) {
throw new IntrospectionException();
}
listArgument = true;
}
arguments.add(argument);
parameters.add(argument);
} else {
throw new AssertionError("Unreachable");
}
}
public abstract CommandDescriptor<T> getOwner();
public final int getDepth() {
CommandDescriptor<T> owner = getOwner();
return owner == null ? 0 : 1 + owner.getDepth();
}
public final void printUsage(Appendable to) throws IOException {
print(Format.USAGE, to);
}
public final void printMan(Appendable to) throws IOException {
print(Format.MAN, to);
}
public final void print(Format format, Appendable to) throws IOException {
format.print(this, to);
}
/**
* @return the command subordinates as a map.
*/
public abstract Map<String, ? extends CommandDescriptor<T>> getSubordinates();
/**
* Returns a specified subordinate.
*
* @param name the subordinate name
* @return the subordinate command or null
*/
public final CommandDescriptor<T> getSubordinate(String name) {
return getSubordinates().get(name);
}
/**
* Returns the command parameters, the returned collection contains the command options and
* the command arguments.
*
* @return the command parameters
*/
public final List<ParameterDescriptor> getParameters() {
return uParameters;
}
/**
* Returns the command option names.
*
* @return the command option names
*/
public final Set<String> getOptionNames() {
return uOptionMap.keySet();
}
/**
* Returns the command short option names.
*
* @return the command long option names
*/
public final Set<String> getShortOptionNames() {
return uShortOptionNames;
}
/**
* Returns the command long option names.
*
* @return the command long option names
*/
public final Set<String> getLongOptionNames() {
return uLongOptionNames;
}
/**
* Returns the command options.
*
* @return the command options
*/
public final Collection<OptionDescriptor> getOptions() {
return uOptions;
}
/**
* Returns a command option by its name.
*
* @param name the option name
* @return the option
*/
public final OptionDescriptor getOption(String name) {
return optionMap.get(name);
}
/**
* Find an command option by its name, this will look through the command hierarchy.
*
* @param name the option name
* @return the option or null
*/
public final OptionDescriptor resolveOption(String name) {
OptionDescriptor option = getOption(name);
if (option == null) {
CommandDescriptor<T> owner = getOwner();
if (owner != null) {
option = owner.resolveOption(name);
}
}
return option;
}
/**
* Returns a list of the command arguments.
*
* @return the command arguments
*/
public final List<ArgumentDescriptor> getArguments() {
return uArguments;
}
/**
* Returns a a specified argument by its index.
*
* @param index the argument index
* @return the command argument
* @throws IllegalArgumentException if the index is not within the bounds
*/
public final ArgumentDescriptor getArgument(int index) throws IllegalArgumentException {
if (index < 0) {
throw new IllegalArgumentException();
}
if (index >= arguments.size()) {
throw new IllegalArgumentException();
}
return arguments.get(index);
}
/**
* Returns the command name.
*
* @return the command name
*/
public final String getName() {
return name;
}
/**
* Returns the command description.
*
* @return the command description
*/
public final Description getDescription() {
return description;
}
/**
* Returns the command usage, shortcut for invoking <code>getDescription().getUsage()</code> on this
* object.
*
* @return the command usage
*/
public final String getUsage() {
return description != null ? description.getUsage() : "";
}
public abstract CommandInvoker<T, ?> getInvoker(InvocationMatch<T> match);
public final InvocationMatcher<T> matcher() {
return new InvocationMatcher<T>(this);
}
public final CompletionMatcher<T> completer() {
return new CompletionMatcher<T>(this);
}
}