/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.shell.syntax;
import java.util.ArrayList;
import java.util.List;
import org.jnode.driver.console.CompletionInfo;
import org.jnode.shell.CommandLine.Token;
/**
* The Argument class is the base class for argument value holders in the org.jnode.shell.syntax
* system. An command instance object creates an Argument instance for each formal
* command-line parameter, and assembled them into an ArgumentBundle. As a command line is parsed
* against the syntax, the parser matches syntactic elements with argument strings, locates the
* corresponding Argument instances, and calls the Argument.accept(Token) method. This then calls
* doAccept(Token) in a subclass which typically tries to convert the Token's value to an instance
* of the <V> type. The doAccept method should either return a non-null V to be accepted,
* or throw an exception.
* <p>
* An Argument has the following attributes:
* <ul>
* <li>The 'argName' is a label that allows the Argument to be matched against nodes in the Syntax
* or MuSyntax. It needs to be unique in the context of the ArgumentBundle containing the Argument.
* <li>The 'flags' word holds common and subtype specific flags that constrain the way it may
* be populated. The common flags are described in the constants section below.
* <li>The 'description' string contains optional documentation for the Argument.
* </ul>
*
* Many methods on the Argument class check that the Argument is a member of an ArgumentBundle,
* throw the unchecked exception {@link SyntaxFailureException} if this is not the case.
*
* @author crawley@jnode.org
*
* @param <V> this is the value type for the Argument.
*/
public abstract class Argument<V> {
/**
* This Argument flag indicates that the Argument is optional. This is the
* opposite of MANDATORY, and is the default if neither are specified.
*/
public static final int OPTIONAL = 0x001;
/**
* This Argument flag indicates that the Argument is mandatory; i.e that least
* one instance of this Argument must be supplied in a command line. This is
* the opposite of OPTIONAL.
*/
public static final int MANDATORY = 0x002;
/**
* This Argument flag indicates that the Argument may have at most one value.
* This is the opposite of MULTIPLE and the default if neither are specified.
*/
public static final int SINGLE = 0x004;
/**
* This Argument flag indicates that multiple instances of this Argument may
* be provided. This is the opposite of SINGLE.
*/
public static final int MULTIPLE = 0x008;
/**
* This Argument flag indicates that an Argument's value must denote an entity
* that already exists in whatever domain that the Argument values corresponds to.
* Note that this is <b>not</b> the logical negation of NONEXISTENT!
*/
public static final int EXISTING = 0x010;
/**
* This Argument flag indicates that an Argument's value must denote an entity
* that does not exists in whatever domain that the Argument values corresponds to.
* Note that this is <b>not</b> the logical negation of EXISTING!
*/
public static final int NONEXISTENT = 0x020;
/**
* Flag bits in this bitset are either common flags, or reserved for future use as
* common flags.
*/
public static final int COMMON_FLAGS = 0x0000ffff;
/**
* Flag bits in this bitset are available for use as Argument-subclass specific flags.
* Flags in this range may be overridden by a Syntax.
*/
public static final int SPECIFIC_OVERRIDABLE_FLAGS = 0x00ff0000;
/**
* Flag bits in this bitset are available for use as Argument-subclass specific flags.
* Flags in this range may NOT be overridden by a Syntax.
*/
private static final int SPECIFIC_NONOVERRIDABLE_FLAGS = 0xff000000;
/**
* Flag bits in this bitset may not be overridden by a Syntax.
*/
public static final int NONOVERRIDABLE_FLAGS =
SINGLE | MULTIPLE | MANDATORY | OPTIONAL | SPECIFIC_NONOVERRIDABLE_FLAGS;
private final String label;
private final int flags;
private final String description;
protected final List<V> values = new ArrayList<V>();
final V[] vArray;
private ArgumentBundle bundle;
/**
* @param label The label that is used associate this Argument object to
* a component of a Syntax.
* @param flags This specifies the Argument attributes as a compact form
* @param vArray A template array used by the getValues method. It is
* typically zero length.
* @param description Optional documentation for the argument.
* @throws IllegalArgumentException if the flags are inconsistent
*/
protected Argument(String label, int flags, V[] vArray, String description)
throws IllegalArgumentException {
super();
checkFlags(flags);
this.label = label;
this.description = description;
this.flags = flags;
this.vArray = vArray;
}
/**
* Check that the supplied flags are consistent.
* <p>
* Note: this method may be overridden in child classes, but an override should
* call this method to check the common flags.
* @param flags the flags to be checked.
* @throws IllegalArgumentException
*/
protected void checkFlags(int flags) throws IllegalArgumentException {
if ((flags & EXISTING) != 0 && (flags & NONEXISTENT) != 0) {
throw new IllegalArgumentException("inconsistent flags: EXISTING and NONEXISTENT");
}
if ((flags & SINGLE) != 0 && (flags & MULTIPLE) != 0) {
throw new IllegalArgumentException("inconsistent flags: SINGLE and MULTIPLE");
}
if ((flags & MANDATORY) != 0 && (flags & OPTIONAL) != 0) {
throw new IllegalArgumentException("inconsistent flags: MANDATORY and OPTIONAL");
}
}
/**
* Return the flags as passed to the constructor.
* @return the flags.
*/
public int getFlags() {
return flags;
}
/**
* Convert a comma-separated list of names to a flags word. The current implementation
* will silently ignore empty names; e.g. in {@code "MANDATORY,,SINGLE"} or
* {@code ",SINGLE"}.
*
* @param names the names separated by commas and optional whitespace.
* @return the flags
* @throws IllegalArgument if the list contains unknown flag names.
*/
public final int namesToFlags(String names) throws IllegalArgumentException {
String[] nameList = names.trim().split("\\s*,\\s*");
int res = 0;
for (String name : nameList) {
if (name != null && name.length() > 0) {
res |= nameToFlag(name);
}
}
return res;
}
/**
* Convert a flag name to a flag.
* <p>
* Note: this method may be overridden in child
* classes, but an override should end by calling this method to deal
* with flag names that it doesn't understand.
*
* @param name the name to be converted
* @return the corresponding flag
* @throws IllegalArgumentWxception if the name is not recognized
*/
public int nameToFlag(String name) throws IllegalArgumentException {
if (name.equals("MANDATORY")) {
return MANDATORY;
} else if (name.equals("OPTIONAL")) {
return OPTIONAL;
} else if (name.equals("SINGLE")) {
return SINGLE;
} else if (name.equals("MULTIPLE")) {
return MULTIPLE;
} else if (name.equals("EXISTING")) {
return EXISTING;
} else if (name.equals("NONEXISTENT")) {
return NONEXISTENT;
} else {
throw new IllegalArgumentException("unknown flag name '" + name + "'");
}
}
/**
* If this method returns <code>true</code>, this Argument must be bound to an
* argument in a CommandLine if it is used in a given concrete syntax.
*/
public boolean isMandatory() {
return isMandatory(flags);
}
/**
* If this method returns <code>true</code>, this Argument need not be bound to an
* argument in a CommandLine if it is used in a given concrete syntax.
*/
public boolean isOptional() {
return isOptional(flags);
}
/**
* If this method returns <code>true</code>, this element may have
* multiple instances in a CommandLine.
*/
public boolean isMultiple() {
return isMultiple(flags);
}
/**
* If this method returns <code>true</code>, this element must have at
* most one instance in a CommandLine.
*/
public boolean isSingle() {
return isSingle(flags);
}
/**
* If this method returns <code>true</code>, an Argument value must correspond
* to an existing entity in the domain of entities denoted by the Argument type.
*/
public boolean isExisting() {
return isExisting(flags);
}
/**
* If this method returns <code>true</code>, an Argument value must <i>not</i> correspond
* to an existing entity in the domain of entities denoted by the Argument type.
*/
public boolean isNonexistent() {
return isNonexistent(flags);
}
/**
* If this method returns <code>true</code>, the flags say that the corresponding Argument
* must be bound to an argument in a CommandLine if it is used in a given concrete syntax.
*/
public static boolean isMandatory(int flags) {
return (flags & MANDATORY) != 0;
}
/**
* If this method returns <code>true</code>, the flags say that the corresponding Argument
* need not be bound to an argument in a CommandLine if it is used in a given concrete syntax.
*/
public static boolean isOptional(int flags) {
return (flags & MANDATORY) == 0;
}
/**
* If this method returns <code>true</code>, the corresponding Argument may have
* multiple instances in a CommandLine.
*/
public static boolean isMultiple(int flags) {
return (flags & MULTIPLE) != 0;
}
/**
* If this method returns <code>true</code>, the corresponding Argument must have at
* most one instance in a CommandLine.
*/
public static boolean isSingle(int flags) {
return (flags & MULTIPLE) == 0;
}
/**
* If this method returns <code>true</code>, the corresponding Argument value must denote
* an existing entity.
*/
public static boolean isExisting(int flags) {
return (flags & EXISTING) != 0;
}
/**
* If this method returns <code>true</code>, the corresponding Argument value must
* <i>not</i> denote an existing entity.
*/
public boolean isNonexistent(int flags) {
return (flags & NONEXISTENT) != 0;
}
/**
* The label is the application's identifier for the Argument. It is used to identify
* the Argument in a concrete syntax specification. The label could also be used by the
* application for name lookup of the Argument in the {@link ArgumentBundle}, but the
* normal design pattern is for a Command class to retain references to each Argument
* in private attributes.
*/
public String getLabel() {
return label;
}
/**
* Test if this Argument currently has a bound value or values.
*/
public boolean isSet() {
checkArgumentsSet();
return values.size() != 0;
}
/**
* Get this Arguments bound values as an array.
* @return an array of values, possibly empty but never {@code null}.
*/
public V[] getValues() {
checkArgumentsSet();
return values.toArray(vArray);
}
/**
* Get this Argument's single bound value.
* @return the value or {@code null}.
* @throws SyntaxMultiplicityException if this is a multi-valued Argument
* bound to more than one value.
*/
public V getValue() throws SyntaxMultiplicityException {
checkArgumentsSet();
int size = values.size();
if (size == 0) {
return null;
} else if (size == 1) {
return values.get(0);
} else {
throw new SyntaxMultiplicityException(
label + " is bound to " + size + " values");
}
}
private void checkArgumentsSet() {
if (bundle == null) {
throw new SyntaxFailureException(
"This Argument is not associated with an ArgumentBundle");
}
switch (bundle.getStatus()) {
case ArgumentBundle.UNPARSED:
throw new SyntaxFailureException(
"This Argument's ArgumentBundle has not been " +
"populated by the syntax parser");
case ArgumentBundle.PARSE_FAILED:
throw new SyntaxFailureException(
"The syntax parser failed for this Argument's ArgumentBundle");
}
}
final void addValue(V value) {
values.add(value);
}
/**
* Try to accept the Token as the value of this argument. If the method call returns,
* the caller should treat the Token as consumed.
* <p>
* After merging the flags and doing some preliminary checks, this method calls
* the 'doAccept' method to perform the appropriate checking and token-to-value
* conversion. The value returned by the 'doAccept' call is then bound to
* this Argument.
*
* @param value the token that will supply the Argument's value.
* @param flags extra flags from the syntax system. These will be OR'ed with
* the Arguments existing flags, after masking out an in the flag set defined
* by {@link #NONOVERRIDABLE_FLAGS}.
* @throws CommandSyntaxException if the value is unacceptable, or if an attempt
* is made to repeat a single-valued Argument.
*/
public final void accept(Token value, int flags)
throws CommandSyntaxException, IllegalArgumentException {
if (isSet() && !isMultiple()) {
throw new SyntaxMultiplicityException("this argument cannot be repeated");
}
flags = (flags & ~NONOVERRIDABLE_FLAGS) | this.flags;
checkFlags(flags);
addValue(doAccept(value, flags));
}
/**
* This method is called by 'accept' after performing multiplicity checks to
* check that the supplied token is valid and to convert it into a value of
* the required type. It should either 'accept' the value by returning
* a non-null V, or throw an exception whose message says why the value is
* unacceptable.
*
* @param value the token that will supply the Argument's value.
* @param flags the flags to be used.
* @return a (non-{@code null}) value to be accepted
* @throws CommandSyntaxException if the value is unacceptable
*/
protected abstract V doAccept(Token value, int flags) throws CommandSyntaxException;
/**
* Perform argument completion on the supplied (partial) argument value. The
* results of the completion should be added to the supplied CompletionInfo.
* <p>
* The default behavior is to set no completion.
* Subtypes of Argument should override this method if they are capable of doing
* non-trivial completion. Completions should be registered by calling one
* of the 'addCompletion' methods on the CompletionInfo.
*
* @param completions the CompletionInfo object for registering any completions.
* @param partial the argument string to be completed.
*/
public final void complete(CompletionInfo completions, String partial, int flags) {
if (isSet() && !isMultiple()) {
throw new SyntaxMultiplicityException("this argument cannot be repeated");
}
flags = (flags & ~NONOVERRIDABLE_FLAGS) | this.flags;
checkFlags(flags);
doComplete(completions, partial, flags);
}
/**
* Perform argument completion on the supplied (partial) argument value. The
* results of the completion should be added to the supplied CompletionInfo.
* Completions posted by calling {@link CompletionInfo#addCompletion(String)}
* or {@link CompletionInfo#addCompletion(String, boolean)}.
* <p>
* The default behavior of this method is to do no completion. Subtypes of
* Argument should override this method if they are capable of doing <i>useful</i>
* completion. Note that not all completion is useful. For example, it is a
* bad idea post all legal completions for a large integer range. Also, a
* an override should avoid posting completions that would not be accepted
* by the {@link #doAccept} method, as this will lead to confusing behavior.
*
* @param completions the {@link CompletionInfo} object for posting possible
* completions.
* @param partial the argument string to be completed.
*/
public void doComplete(CompletionInfo completions, String partial, int flags) {
// set no completion
}
void setBundle(ArgumentBundle bundle) {
this.bundle = bundle;
}
public ArgumentBundle getBundle() {
return bundle;
}
/**
* Clear the argument's values
*/
void clear() {
values.clear();
}
/**
* Render this Argument for debug purposes.
*/
@Override
public String toString() {
return this.getClass().getSimpleName() + "{" + state() + "}";
}
/**
* Render this Argument's state for debug purposes. Override this
* method in child classes to dump any relevant child class state.
*/
protected String state() {
return "label=" + label;
}
/**
* This method is called by MuParser while backtracking.
*/
void undoLastValue() {
values.remove(values.size() - 1);
}
/**
* Format this argument for a usage message.
*/
public final String formatForUsage() {
return label;
}
/**
* Get the argument's optional description
* @return the description or <code>null</code>
*/
public String getDescription() {
return description;
}
/**
* Return a String that describes the 'kind' of the Argument; e.g. a
* "class name" or an "integer".
*/
protected abstract String argumentKind();
/**
* Get a description of the argument's type
* @return the argument type description.
*/
public String getTypeDescription() {
return argumentKind();
}
}