options/ 0000755 0000000 0000000 00000000000 11705223122 011242 5 ustar root root options/test/ 0000755 0000000 0000000 00000000000 11646610376 012240 5 ustar root root options/build.xml 0000644 0000000 0000000 00000007204 11703661776 013111 0 ustar root root
Builds, tests, and runs the project options.
options/catalog.xml 0000644 0000000 0000000 00000000000 10627357464 013407 0 ustar root root options/src/ 0000755 0000000 0000000 00000000000 11646610374 012046 5 ustar root root options/src/ml/ 0000755 0000000 0000000 00000000000 11646610374 012456 5 ustar root root options/src/ml/options/ 0000755 0000000 0000000 00000000000 11646610376 014153 5 ustar root root options/src/ml/options/XMLParsingException.java 0000644 0000000 0000000 00000006145 10751433560 020661 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
/**
* XMLParsingException is thrown if an XML file provided to define
* option sets and options contains errors
*/
public class XMLParsingException extends RuntimeException {
private static final long serialVersionUID = 1L;
/** Constructs a new XMLParsingException exception with null as its
* detail message. The cause is not initialized, and may subsequently be
* initialized by a call to {@link #initCause}.
*/
public XMLParsingException() {
super();
}
/** Constructs a new XMLParsingException exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public XMLParsingException(String message) {
super(message);
}
/**
* Constructs a new XMLParsingException exception with the specified detail message and
* cause.
Note that the detail message associated with
* cause is not automatically incorporated in
* this XMLParsingException exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public XMLParsingException(String message, Throwable cause) {
super(message, cause);
}
/** Constructs a new XMLParsingException exception with the specified cause and a
* detail message of (cause==null ? null : cause.toString())
* (which typically contains the class and detail message of
* cause). This constructor is useful for XMLParsingException exceptions
* that are little more than wrappers for other throwables.
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public XMLParsingException(Throwable cause) {
super(cause);
}
}
options/src/ml/options/SchemaValidator.java 0000644 0000000 0000000 00000011640 10751433560 020060 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
import javax.xml.validation.*;
/**
* Validator for XML documents using XML schema. This is based on JDK 5.0 and requires
* no outside library.
*/
public class SchemaValidator extends org.xml.sax.helpers.DefaultHandler {
private final String CLASS = "SchemaValidator";
private final String xsdFile = "config/options.xsd";
private String error = null;
/**
* The actual validation method. If validation is not successful, the errors found can be retrieved
* using the {@link #getError()} method.
*
* @param xmlReader The reader for the XML file to validate
*
* @return true if the XML file could be validated against the XML schema, else false
* @throws java.io.IOException
* @throws org.xml.sax.SAXException
*/
public boolean validate(java.io.Reader xmlReader) throws java.io.IOException,
org.xml.sax.SAXException {
if (xmlReader == null) {
throw new IllegalArgumentException(CLASS + ": xmlReader may not be null");
}
//.... Get the XML schema from the JAR and create a validator
SchemaFactory factory = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
ClassLoader loader = this.getClass().getClassLoader();
java.net.URL url = loader.getResource(xsdFile);
Schema schema = factory.newSchema(url);
Validator validator = schema.newValidator();
validator.setErrorHandler(this);
//.... Try to validate the XML file given
org.xml.sax.InputSource source = new org.xml.sax.InputSource(new java.io.BufferedReader(xmlReader));
validator.validate(new javax.xml.transform.sax.SAXSource(source));
return getError() == null ? true : false;
}
//-----------------------------------------------------------------------------------------
// Below are helper methods that are required to improve the error handling (specifically,
// to make sure all thrown exceptions are reported, and row and column numbers are added
// to the output for better debugging
//-----------------------------------------------------------------------------------------
/**
* Retrieve the error message set by the org.xml.sax.ErrorHandler methods.
* If no error has been found, null is returned.
*
* @return A string describing the error encountered
*/
public String getError() {
return error;
}
/**
* A method required by the org.xml.sax.ErrorHandler interface
*
* @param ex A parsing exception
*/
@Override
public void warning(org.xml.sax.SAXParseException ex) throws org.xml.sax.SAXException {
getError("Warning", ex);
}
/**
* A method required by the org.xml.sax.ErrorHandler interface
*
* @param ex A parsing exception
*/
@Override
public void error(org.xml.sax.SAXParseException ex) throws org.xml.sax.SAXException {
getError("Error", ex);
}
/**
* A method required by the org.xml.sax.ErrorHandler interface
*
* @param ex A parsing exception
*/
@Override
public void fatalError(org.xml.sax.SAXParseException ex) throws org.xml.sax.SAXException {
getError("Fatal Error", ex);
}
/**
* A helper method for the formatting
*/
private void getError(String type, org.xml.sax.SAXParseException ex) {
StringBuilder out = new StringBuilder(200);
out.append(type);
if (ex == null) {
out.append("!!!");
}
String systemId = ex.getSystemId();
if (systemId != null) {
int index = systemId.lastIndexOf('/');
if (index != -1) {
systemId = systemId.substring(index + 1);
}
out.append(systemId);
}
out.append(": Row ");
out.append(ex.getLineNumber());
out.append(" /`Col ");
out.append(ex.getColumnNumber());
out.append(": ");
out.append(ex.getMessage());
//.... There may be multiple exceptions thrown, we don't want to miss any information
if (error == null) {
error = out.toString();
} else {
error += "\n" + out.toString();
}
}
}
options/src/ml/options/DefaultHelpPrinter.java 0000644 0000000 0000000 00000022604 10751433560 020555 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
/**
* A simple implementation of the {@link HelpPrinter} interface. This can serve as a
* basis for more complex formatting requirements.
*
* The following approach is used here for the command line syntax:
*
*
*
Option Output Format
*
Component
Example
Remark
*
OptionData.Type.SIMPLE option
*
-a
*
*
OptionData.Type.VALUE option
*
-log <logfile>
*
The text logfile can be changed using OptionData.setValueText()
*
OptionData.Type.DETAIL option
*
-D<detail>=<value>
*
The text value can be changed using OptionData.setValueText(),
* the text detail can be changed using OptionData.setDetailText()
*
Option names with alternate keys
*
(-a|--Access)
*
*
Options.Multiplicity.ZERO_OR_ONCE
*
[-a]
*
*
Options.Multiplicity.ONCE_OR_MORE
*
-v=<value1> [ -v=<value2> [...]]
*
The text value can be changed using OptionData.setValueText()
*
Options.Multiplicity.ZERO_OR_MORE
*
[-v=<value1> [ -v=<value2> [...]]]
*
The text value can be changed using OptionData.setValueText()
*
Exclusive constraints
*
{ <option1> | <option2> | <option3> }
*
<optionN> is a placeholder for the general option syntax described above
* which is grouped here using the curly brackets and the pipe symbol
*
*
*/
public class DefaultHelpPrinter implements HelpPrinter {
private final static String CLASS = "DefaultHelpPrinter";
/**
* Return the help text describing the different options and data arguments
*
* @param set The {@link OptionSet} to format the output for
*
* @return A string with the help text for this option set
*/
@Override
public String getHelpText(OptionSet set) {
if (set == null) {
throw new IllegalArgumentException(CLASS + ": set may not be null");
}
//.... Collect option and data item names
String st = null;
StringBuilder sb = null;
java.util.List out = new java.util.ArrayList();
int maxLen = 0;
for (OptionData option : set.getOptionData()) {
st = option.getSyntax();
if (st.length() > maxLen) {
maxLen = st.length();
}
out.add(st);
}
int limit = set.getMaxData();
if (set.hasUnlimitedData()) {
limit = set.getMinData() + 1;
}
for (int i = 0; i < limit; i++) {
st = dataSyntax(i, set.getDataText(i), set.getMinData(), set.getMaxData());
if (st.length() > maxLen) {
maxLen = st.length();
}
out.add(st.toString());
}
//.... Assemble final output
StringBuilder s = new StringBuilder(100);
for (int i = 0; i < maxLen + 3; i++) {
s.append(' ');
}
String blank = s.toString();
sb = new StringBuilder(300);
//.... Help texts for options
int i = 0;
String k = null;
String[] texts = null;
boolean first = true;
for (OptionData option : set.getOptionData()) {
k = out.get(i++) + blank;
texts = option.getHelpText().split("\n");
first = true;
for (String text : texts) {
if (first) {
sb.append(k.substring(0, maxLen));
sb.append(" : ");
first = false;
} else {
sb.append(blank);
}
sb.append(text);
sb.append('\n');
}
}
//.... Help texts for data items
for (int j = 0; j < limit; j++) {
k = out.get(i++) + blank;
texts = set.getHelpText(j).split("\n");
first = true;
for (String text : texts) {
if (first) {
sb.append(k.substring(0, maxLen));
sb.append(" : ");
first = false;
} else {
sb.append(blank);
}
sb.append(text);
sb.append('\n');
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
} // Delete last \n
return sb.toString();
}
/**
* Return a string with the command line syntax for the given option set
*
* @param set The {@link OptionSet} to format the output for
* @param leadingText The text to precede the command line
* @param lineBreak A boolean indicating whether the command line for the option set should
* be printed with line breaks after each option or not
*
* @return A string with the command line syntax for this option set
*/
@Override
public String getCommandLine(OptionSet set, String leadingText, boolean lineBreak) {
if (set == null) {
throw new IllegalArgumentException(CLASS + ": set may not be null");
}
int limit = set.getMaxData();
if (set.hasUnlimitedData()) {
limit = set.getMinData() + 1;
}
StringBuilder sb = new StringBuilder(200);
StringBuilder s = new StringBuilder();
String[] d = null;
if (leadingText != null) {
sb.append(leadingText.trim());
sb.append(' ');
d = leadingText.trim().split("\\n"); // We may have \n in the leading text string
for (int i = 0; i <= d[d.length - 1].length(); i++) {
s.append(' ');
}
}
//.... Options (not part of an ExclusiveConstraint)
boolean first = true;
for (OptionData option : set.getOptionData()) {
if (!option.isExclusive()) {
if (lineBreak && !first) {
sb.append(s);
} else {
first = false;
}
sb.append(option.getSyntax());
if (lineBreak) {
sb.append('\n');
} else {
sb.append(' ');
}
}
}
//.... ExclusiveConstraint options
if (set.getConstraints() != null) {
ExclusiveConstraint ec = null;
for (Constraint constraint : set.getConstraints()) {
if (constraint instanceof ExclusiveConstraint) {
ec = (ExclusiveConstraint) constraint;
if (lineBreak && !first) {
sb.append(s);
} else {
first = false;
}
sb.append('{'); // This identifies this type of constraint
for (OptionData option : ec.getOptionData()) {
sb.append(option.getSyntax());
sb.append('|'); // Separator in between options
}
sb.deleteCharAt(sb.length() - 1); // Remove last | character
sb.append('}'); // End of constraint output
if (lineBreak) {
sb.append('\n');
} else {
sb.append(' ');
}
}
}
}
//.... Data
if (set.acceptsData()) {
if (lineBreak && set.getOptionData().size() > 0) {
sb.append(s);
}
for (int i = 0; i < limit; i++) {
sb.append(dataSyntax(i, set.getDataText(i), set.getMinData(), set.getMaxData()));
sb.append(' ');
}
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
/**
* Helper
*/
private String dataSyntax(int index, String text, int minData, int maxData) {
if (index < minData) {
return "<" + text + ">";
}
if (maxData == OptionSet.INF) {
return "[<" + text + "> [...]]";
}
return "[<" + text + ">]";
}
}
options/src/ml/options/OptionSet.java 0000644 0000000 0000000 00000054674 11110551004 016734 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
/**
* This class holds the information for a set of options. A set can hold any number of
* OptionData instances which are checked together to determine success or failure.
*
* The approach to use this class looks like this:
*
*
*
The user uses any of the Options.addSet() methods
* (e. g. {@link Options#addSet(String)}) to create
* any number of sets required (or just relies on the default set, if only one set is required)
*
The user adds all required option definitions to each set
*
Using any of the Options.check() methods, each set can be checked whether the options
* that were specified on the command line satisfy its requirements
*
If the check was successful for a given set, several data items are available from this class:
*
*
All options defined for the set (through which e. g. values, details, and multiplicity are available)
*
All data items found (these are the items on the command line which do not start with the prefix,
* i. e. non-option arguments)
*
All unmatched arguments on the command line (these are the items on the command line which start
* with the prefix, but do not match to one of the options).
* Programs can elect to ignore these, or react with an error
*
*
*/
public class OptionSet implements Constrainable {
private final static String CLASS = "OptionSet";
private static java.util.regex.Pattern keyPattern = java.util.regex.Pattern.compile("\\w+");
private java.util.ArrayList options = new java.util.ArrayList();
private java.util.HashMap keys = new java.util.HashMap();
private java.util.HashSet altKeys = new java.util.HashSet();
private java.util.ArrayList unmatched = new java.util.ArrayList();
private java.util.ArrayList data = new java.util.ArrayList();
private String name = null;
private String[] dataText = null;
private String[] helpText = null;
private int minData = 0;
private int maxData = 0;
private Options.Prefix prefix = null;
private Options.Prefix altPrefix = null;
private Options.Multiplicity defaultMultiplicity = null;
private Options.Separator valueSeparator = null;
private Options.Separator detailSeparator = null;
private boolean isDefault = false;
private boolean unlimitedData = false;
private int limit = 0;
private java.util.List constraints = null;
/**
* A constant indicating an unlimited number of supported data items
*/
public static final int INF = -1;
/**
* A copying constructor. This is for setup purposes only and does not copy any result data.
*/
OptionSet(String name, OptionSet os) {
this(name, os.getPrefix(), os.getAltPrefix(), os.getValueSeparator(), os.getDetailSeparator(),
os.getDefaultMultiplicity(), os.getMinData(), os.getMaxData(), false);
this.limit = os.getLimit();
for (int i = 0; i < limit; i++) {
helpText[i] = os.getHelpText(i);
dataText[i] = os.getDataText(i);
}
for (String s : os.getAltKeys()) {
altKeys.add(s);
}
OptionData nod = null;
for (OptionData od : os.getOptionData()) {
nod = new OptionData(od);
options.add(nod);
keys.put(od.getKey(), nod);
}
}
Options.Multiplicity getDefaultMultiplicity() {
return defaultMultiplicity;
}
java.util.HashMap getKeys() {
return keys;
}
java.util.HashSet getAltKeys() {
return altKeys;
}
/**
* Constructor
*/
OptionSet(String name,
Options.Prefix prefix,
Options.Prefix altPrefix,
Options.Separator valueSeparator,
Options.Separator detailSeparator,
Options.Multiplicity defaultMultiplicity,
int minData,
int maxData,
boolean isDefault) {
if (name == null) {
throw new IllegalArgumentException(CLASS + ": name may not be null");
}
if (minData < 0) {
throw new IllegalArgumentException(CLASS + ": minData must be >= 0");
}
if (maxData < minData) {
throw new IllegalArgumentException(CLASS + ": maxData must be >= minData");
}
this.prefix = prefix;
this.altPrefix = altPrefix;
this.defaultMultiplicity = defaultMultiplicity;
this.valueSeparator = valueSeparator;
this.detailSeparator = detailSeparator;
this.name = name;
this.minData = minData;
this.maxData = maxData;
//.... Unless we support an unlimited number of data items, we can define texts for up to maxData
// data items. Otherwise, we only can use up to (minData + 1) definitions: minData for the
// first required ones, and 1 more which goes into the [] brackets
limit = maxData;
if (maxData == Integer.MAX_VALUE) {
unlimitedData = true;
limit = minData + 1;
}
dataText = new String[limit];
for (int i = 0; i < limit; i++) // Set the default for the data text
{
dataText[i] = "data";
}
helpText = new String[limit];
for (int i = 0; i < limit; i++) // Set the default for the help text
{
helpText[i] = "";
}
this.isDefault = isDefault; // Whether this is the default set
}
/**
* Indicate whether this set has no upper limit for the number of allowed data items
*
* @return A boolean indicating whether this set has no upper limit for the number of allowed data items
*/
public boolean hasUnlimitedData() {
return unlimitedData;
}
/**
* Indicate whether this set is the default set or not
*
* @return A boolean indicating whether this set is the default set or not
*/
public boolean isDefault() {
return isDefault;
}
/**
* Add a constraint for this option set
*
* @param constraint The {@link Constraint} to add
*/
@Override
public void addConstraint(Constraint constraint) {
if (constraint == null) {
throw new IllegalArgumentException(CLASS + ": constraint may not be null");
}
if (!constraint.supports(this)) {
throw new IllegalArgumentException(CLASS + ": the given constraint can not be applied to option sets");
}
if (constraints == null) {
constraints = new java.util.ArrayList();
}
constraints.add(constraint);
}
/**
* Get the constraints defined for this option set
*
* @return The defined constraints for this option (or null if no constraints have been defined)
*/
@Override
public java.util.List getConstraints() {
return constraints;
}
/**
* Set the data text for a data item on the command line. This is exploited e. g. in {@link HelpPrinter} instances.
*
* @param index The index for this data item on the command line. Must be within the allowed range of
* 0 ... maxData - 1 for this set. If this set supports an unlimited
* number of data items, the allowed range is 0 ... minData.
* @param text The text to use for this data item in the command line syntax
*
* @return This set to allow for invocation chaining
*/
public OptionSet setDataText(int index, String text) {
if (text == null) {
throw new IllegalArgumentException(CLASS + ": text may not be null");
}
if (index < 0 || index >= limit) {
throw new IllegalArgumentException(CLASS + ": invalid value for index");
}
this.dataText[index] = text.trim();
return this;
}
/**
* Set the help text for a data item on the command line. This is exploited e. g. in {@link HelpPrinter} instances.
*
* @param index The index for this data item on the command line. Must be within the allowed range of
* 0 ... maxData - 1 for this set. If this set supports an unlimited
* number of data items, the allowed range is 0 ... minData.
* @param text The help text to use to describe the purpose of the data item
*
* @return This set to allow for invocation chaining
*/
public OptionSet setHelpText(int index, String text) {
if (text == null) {
throw new IllegalArgumentException(CLASS + ": text may not be null");
}
if (index < 0 || index >= limit) {
throw new IllegalArgumentException(CLASS + ": invalid value for index");
}
this.helpText[index] = text.trim();
return this;
}
/**
* Get the data text for a data item on the command line. This is only useful if such a data text is used.
*
* @param index The index for this data item on the command line. Must be within the allowed range of
* 0 ... maxData - 1 for this set. If this set supports an unlimited
* number of data items, the allowed range is 0 ... minData.
*
* @return The text used for this data item in the command line syntax
*/
public String getDataText(int index) {
if (index < 0 || index >= limit) {
throw new IllegalArgumentException(CLASS + ": invalid value for index");
}
return dataText[index];
}
/**
* Get the help text for a data item on the command line. This is only useful if such a help text is used.
*
* @param index The index for this data item on the command line. Must be within the allowed range of
* 0 ... maxData - 1 for this set. If this set supports an unlimited
* number of data items, the allowed range is 0 ... minData.
*
* @return The help text used to describe the purpose of the data item
*/
public String getHelpText(int index) {
if (index < 0 || index >= limit) {
throw new IllegalArgumentException(CLASS + ": invalid value for index");
}
return helpText[index];
}
/**
* Get the primary {@link Options.Prefix} for this set. This is primarily intended for use by
* {@link HelpPrinter} instances to format their output.
*
* @return The {@link Options.Prefix} instance used as primary prefix for this set
*/
Options.Prefix getPrefix() {
return prefix;
}
/**
* Get the alternate {@link Options.Prefix} for this set. This is primarily intended for use by
* {@link HelpPrinter} instances to format their output.
*
* @return The {@link Options.Prefix} instance used as alternate prefix for this set
*/
Options.Prefix getAltPrefix() {
return altPrefix;
}
/**
* Get a list of all the options defined for this set
*
* @return A list of {@link OptionData} instances defined for this set
*/
public java.util.List getOptionData() {
return options;
}
/**
* Get the data for a specific option, identified by its key name (which is unique)
*
* @param key The key for the option
*
* @return The {@link OptionData} instance
*/
public OptionData getOption(String key) {
if (key == null) {
throw new IllegalArgumentException(CLASS + ": key may not be null");
}
if (!keys.containsKey(key)) {
throw new IllegalArgumentException(CLASS + ": unknown key: " + key);
}
return keys.get(key);
}
/**
* Check whether a specific option is set, i. e. whether it was specified at least once on the command line.
*
* @param key The key for the option
*
* @return true or false, depending on the outcome of the check
*/
public boolean isSet(String key) {
if (key == null) {
throw new IllegalArgumentException(CLASS + ": key may not be null");
}
if (!keys.containsKey(key)) {
throw new IllegalArgumentException(CLASS + ": unknown key: " + key);
}
return keys.get(key).isSet();
}
/**
* Return the name of the set
*
* @return The name of the set
*/
public String getName() {
return name;
}
/**
* Getter method for minData property
*
* @return The value for the minData property
*/
public int getMinData() {
return minData;
}
/**
* Getter method for maxData property
*
* @return The value for the maxData property
*/
public int getMaxData() {
if (hasUnlimitedData()) {
return INF;
} else {
return maxData;
}
}
/**
* Getter method for valueSeparator property
*
* @return The value for the valueSeparator property
*/
Options.Separator getValueSeparator() {
return valueSeparator;
}
/**
* Getter method for detailSeparator property
*
* @return The value for the detailSeparator property
*/
Options.Separator getDetailSeparator() {
return detailSeparator;
}
/**
* Helper method required for option set cloning
*/
int getLimit() {
return limit;
}
/**
* Indicate whether this set accepts data (which means that maxData is 1 or larger).
*
* @return A boolean indicating whether this set accepts data
*/
public boolean acceptsData() {
return (minData + maxData) == 0 ? false : true;
}
/**
* Return the data items found (these are the items on the command line
* which do not start with the prefix, i. e. non-option arguments)
*
* @return A list of strings with all data items found
*/
public java.util.List getData() {
return data;
}
/**
* Return the number of data items found (these are the items on the command line
* which do not start with the prefix, i. e. non-option arguments)
*
* @return The number of all data items found
*/
public int getDataCount() {
return data.size();
}
/**
* Return a specific data item.
*
* @param index
* @return The requested data item
*/
public String getData(int index) {
if (index < 0 || index >= getDataCount()) {
if (getDataCount() == 0) {
throw new IllegalArgumentException(CLASS + ": No data items are available");
} else {
int n = getDataCount() - 1;
throw new IllegalArgumentException(CLASS + ": Invalid index value - must be between 0 and " + n);
}
}
return data.get(index);
}
/**
* Return all unmatched items found (these are the items on the
* command line which start with the prefix, but do not
* match to one of the options)
*
* @return A list of strings with all unmatched items found
*/
public java.util.List getUnmatched() {
return unmatched;
}
/**
* Return the number of unmatched items found (these are the items on the
* command line which start with the prefix, but do not
* match to one of the options)
*
* @return The number of all unmatched items found
*/
public int getUnmatchedCount() {
return unmatched.size();
}
/**
* Return a specific unmatched item.
*
* @param index
* @return The requested unmatched item
*/
public String getUnmatched(int index) {
if (index < 0 || index >= getUnmatchedCount()) {
if (getUnmatchedCount() == 0) {
throw new IllegalArgumentException(CLASS + ": No unmatched items are available");
} else {
int n = getUnmatchedCount() - 1;
throw new IllegalArgumentException(CLASS + ": Invalid index value - must be between 0 and " + n);
}
}
return unmatched.get(index);
}
// ==========================================================================================
// Add a non-value option
// ==========================================================================================
/**
* Add the given option to the set.
*
* @param type The type of the option
* @param key The name of the option
*
* @return The newly created option (to support invocation chaining)
*/
public OptionData addOption(OptionData.Type type, String key) {
return addOption(type,
key,
null,
type.detail() ? detailSeparator : valueSeparator,
defaultMultiplicity);
}
/**
* Add the given option to the set.
*
* @param type The type of the option
* @param key The name of the option
* @param multiplicity The multiplicity of the option
*
* @return The newly created option (to support invocation chaining)
*/
public OptionData addOption(OptionData.Type type, String key, Options.Multiplicity multiplicity) {
return addOption(type,
key,
null,
type.detail() ? detailSeparator : valueSeparator,
multiplicity);
}
/**
* Add the given option to the set.
*
* @param type The type of the option
* @param key The name of the option
* @param altKey The alternate name of the option
*
* @return The newly created option (to support invocation chaining)
*/
public OptionData addOption(OptionData.Type type, String key, String altKey) {
return addOption(type,
key,
altKey,
type.detail() ? detailSeparator : valueSeparator,
defaultMultiplicity);
}
/**
* Add the given option to the set.
*
* @param type The type of the option
* @param key The name of the option
* @param altKey The alternate name of the option
* @param multiplicity The multiplicity of the option
*
* @return The newly created option (to support invocation chaining)
*/
public OptionData addOption(OptionData.Type type, String key, String altKey, Options.Multiplicity multiplicity) {
return addOption(type,
key,
altKey,
type.detail() ? detailSeparator : valueSeparator,
multiplicity);
}
/**
* The master method to add an option. Since there are combinations which are not
* acceptable (like a NONE separator and a true value), this method is not public.
* Internally, we only supply acceptable combinations.
*/
OptionData addOption(OptionData.Type type,
String key,
String altKey,
Options.Separator separator,
Options.Multiplicity multiplicity) {
if (type == null) {
throw new IllegalArgumentException(CLASS + ": type may not be null");
}
if (key == null) {
throw new IllegalArgumentException(CLASS + ": key may not be null");
}
if (multiplicity == null) {
throw new IllegalArgumentException(CLASS + ": multiplicity may not be null");
}
if (keys.containsKey(key)) {
throw new IllegalArgumentException(CLASS + ": the key " + key + " has already been defined for this OptionSet");
}
if (altKey != null && altKeys.contains(altKey)) {
throw new IllegalArgumentException(CLASS + ": the alternate key " + altKey + " has already been defined for this OptionSet");
}
//.... Check keys for valid names (especially no whitespace)
java.util.regex.Matcher m = keyPattern.matcher(key);
if (!m.matches()) {
throw new IllegalArgumentException(CLASS + ": invalid key: may only contain [a-zA-Z_0-9]");
}
if (altKey != null) {
m = keyPattern.matcher(altKey);
if (!m.matches()) {
throw new IllegalArgumentException(CLASS + ": invalid alternate key: may only contain [a-zA-Z_0-9]");
}
}
OptionData od = new OptionData(type, prefix, altPrefix, key, altKey, separator, multiplicity);
options.add(od);
keys.put(key, od);
if (altKey != null) {
altKeys.add(altKey);
}
return od;
}
/**
* A convenience method that prints all the results obtained for this option set to
* System.out. This is quite handy to quickly check whether a set definition
* yields the expected results for a given set of command line arguments.
*/
public void printResults() {
for (OptionData od : getOptionData()) {
System.out.println("Option: " + od.getSyntax() + " (found " + od.getResultCount() + " time(s))");
for (int i = 0; i < od.getResultCount(); i++) {
if (od.useDetail()) {
System.out.println("- Detail " + i + ": " + od.getResultDetail(i));
}
if (od.useValue()) {
System.out.println("- Value " + i + ": " + od.getResultValue(i));
}
}
}
for (String s : getData()) {
System.out.println("Data : " + s);
}
for (String s : getUnmatched()) {
System.out.println("Unmatched : " + s);
}
}
}
options/src/ml/options/XMLConstraint.java 0000644 0000000 0000000 00000004020 10751433560 017511 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
/**
* Constraints implementing this interface have - beyond the {@link Constraint} interface - the capability
* to be created through XML configuration files. In this case, a public no-arg constructor
* is also required.
*/
public interface XMLConstraint extends Constraint {
/**
* This method is used to initialize a constraint based on data read from an XML configuration
* file. The method is invoked internally during setup with the instance of
* {@link Constrainable} to which the constraint applies and a list of JDOM elements,
* which contain the details about the constraint itself.
*
* This method initializes the constraint and attaches it to the list of constraints
* of the {@link Constrainable} instance.
*
* @param constrainable The {@link Constrainable} instance to which this constraint applies
* @param list A list of JDOM elements to be used to initialize the constraint. Specifically,
* these are tags of the form
*
* <param name="..." value="..." />
*
* containing key/value pairs with information. The expected pairs are specific
* to each implementation.
*/
public void init(Constrainable constrainable, java.util.List list);
}
options/src/ml/options/Constraint.java 0000644 0000000 0000000 00000002554 10751433560 017142 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
/**
* The interface for all constraints. Custom constraints need to implement this interface.
*/
public interface Constraint {
/**
* Check whether a constraint is satisfied. This method can be invoked after a set of
* command line arguments has been analyzed such that the results are known for each
* option and option set.
*
* @return A boolean to indicate whether a constraint is satisfied or not
*/
public boolean isSatisfied();
/**
* Indicates whether a constraint supports a given type of {@link Constrainable}
*
* @param constrainable
* @return A boolean to indicate whether this {@link Constrainable} is supported
*/
public boolean supports(Constrainable constrainable);
}
options/src/ml/options/Constrainable.java 0000644 0000000 0000000 00000002246 10751433560 017600 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
/**
* The interface for objects which can be constrained, i. e. {@link Constraint}s can
* be attached to such objects.
*/
public interface Constrainable {
/**
* Add a constraint to this instance.
*
* @param constraint The {@link Constraint} to add to the list of constraints for this instance
*/
public void addConstraint(Constraint constraint);
/**
* Access all known constraints
*
* @return A list of {@link Constraint}s for this instance
*/
public java.util.List getConstraints();
}
options/src/ml/options/Options.java 0000644 0000000 0000000 00000152427 10751433560 016456 0 ustar root root /**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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 ml.options;
/**
* The central class for option processing. Sets are identified by their name, but there is also
* an anonymous default set, which is very convenient if an application requieres only one set.
*
* The default values used in this class are:
*
*
*
Default Values
*
ID
Parameter
Default
Individual Setting
*
1
Prefix
*
Prefix.SLASH (Windows) Prefix.DASH (all others)
*
No
*
2
Alternate Prefix
*
Prefix.DOUBLEDASH
*
No
*
3
Separator for value options
*
Separator.BLANK
*
No
*
4
Separator for detail options
*
Separator.EQUALS
*
No
*
5
Min. Data
*
0
*
Option set level
*
6
Max. Data
*
0
*
Option set level
*
7
Multiplicity
*
Multiplicity.ZERO_OR_ONCE
*
Option level
*
*
* All of these values can be changed using one of the setDefault() methods. However, for
* 1 - 4 this can only be done before any actual set or option has been created (otherwise an
* UnsupportedOperationException is thrown). 5 - 7 can be called
* anytime, but they affect only sets and options which are created afterwards.
*/
public class Options {
private final static String CLASS = "Options";
/**
* The name used internally for the default set
*/
private final static String DEFAULT_SET = "DEFAULT_OPTION_SET";
// ==========================================================================================
// Helper enums
// ==========================================================================================
/**
* An enum encapsulating the possible separators between value options and their actual values.
*/
public enum Separator {
/**
* Separate option and value by ":"
*/
COLON(':'),
/**
* Separate option and value by "="
*/
EQUALS('='),
/**
* Separate option and value by blank space
*/
BLANK(' '); // Or, more precisely, whitespace (as allowed by the CLI)
private char c;
private Separator(char c) {
this.c = c;
}
/**
* Return the actual separator character
*
* @return The actual separator character
*/
char getName() {
return c;
}
}
// ==========================================================================================
/**
* An enum encapsulating the possible prefixes identifying options (and separating them from command line data items)
*/
public enum Prefix {
/**
* Options start with a "-" (typically on Unix platforms)
*/
DASH("-"),
/**
* Options start with a "--" (like GNU-style options on Unix platforms)
*/
DOUBLEDASH("--"),
/**
* Options start with a "/" (typically on Windows platforms)
*/
SLASH("/");
private String c;
private Prefix(String c) {
this.c = c;
}
/**
* Return the actual prefix character
*
* @return The actual prefix character
*/
String getName() {
return c;
}
}
// ==========================================================================================
/**
* An enum encapsulating the possible multiplicities for options
*/
public enum Multiplicity {
/**
* Option needs to occur exactly once
*/
ONCE(true),
/**
* Option needs to occur at least once
*/
ONCE_OR_MORE(true),
/**
* Option needs to occur either once or not at all
*/
ZERO_OR_ONCE(false),
/**
* Option can occur any number of times
*/
ZERO_OR_MORE(false);
private boolean required = false;
private Multiplicity(boolean required) {
this.required = required;
}
boolean isRequired() {
return required;
}
}
// ==========================================================================================
// Instance members
// ==========================================================================================
private java.util.TreeMap optionSets = new java.util.TreeMap();
private String[] arguments = null;
private boolean ignoreUnmatched = false;
private StringBuilder checkErrors = new StringBuilder();
//.... Defaults
private Prefix defaultPrefix = getDefaultPrefix();
private Prefix defaultAltPrefix = Prefix.DOUBLEDASH;
private Separator defaultValueSeparator = Separator.BLANK;
private Separator defaultDetailSeparator = Separator.EQUALS;
private Multiplicity defaultMultiplicity = Multiplicity.ZERO_OR_ONCE;
private int defaultMinData = 0;
private int defaultMaxData = 0;
/**
* Constructor
*
* @param args The command line arguments to check
*/
public Options(String args[]) {
if (args == null) {
throw new IllegalArgumentException(CLASS + ": args may not be null");
}
arguments = new String[args.length];
int i = 0;
for (String s : args) {
arguments[i++] = s;
}
}
/**
* This constructor uses the XML file provided by the reader to set up option sets and options.
*
* @param args The command line arguments to check
* @param reader The reader instance providing the XML file
* @throws org.jdom.JDOMException
*/
@SuppressWarnings("unchecked")
public Options(String args[], java.io.Reader reader) throws org.jdom.JDOMException {
this(args);
if (reader == null) {
throw new IllegalArgumentException(CLASS + ": reader may not be null");
}
//.... Copy the XML content into a string. This is done since we need to read the data
// twice (once for validation, once for evaluation), and not all readers support the
// reset() method.
StringBuilder sb = new StringBuilder(1000);
String line = null;
java.io.BufferedReader r = new java.io.BufferedReader(reader);
try {
while ((line = r.readLine()) != null) {
sb.append(line);
sb.append('\n');
}
} catch (java.io.IOException ex) {
throw new XMLParsingException(CLASS + ": Error while reading XML file!\n" + ex.getMessage());
}
line = sb.toString();
//.... Try to validate the XML document against the schema
SchemaValidator validator = new SchemaValidator();
try {
if (!validator.validate(new java.io.BufferedReader(new java.io.StringReader(line)))) {
throw new XMLParsingException(CLASS + ": Error in XML file validation against schema!\n" + validator.getError());
}
} catch (java.io.IOException ex) {
throw new XMLParsingException(CLASS + ": Error in XML file validation against schema!\n" + ex.getMessage());
} catch (org.xml.sax.SAXException ex) {
throw new XMLParsingException(CLASS + ": Error in XML file validation against schema!\n" + ex.getMessage());
}
validator = null;
//.... Retrieve the data and create the option sets and options
try {
org.jdom.input.SAXBuilder builder = new org.jdom.input.SAXBuilder();
org.jdom.Document doc = builder.build(new java.io.BufferedReader(new java.io.StringReader(line)));
//... Process the tag
org.jdom.Element root = doc.getRootElement();
String value = null;
String values[] = null;
if (root.getAttribute("defData") != null) {
value = root.getAttributeValue("defData");
if (value.indexOf(':') < 0) {
setDefault(Integer.parseInt(value));
} else {
values = value.split(":");
if (values[1].equals("INF")) {
setDefault(Integer.parseInt(values[0]), Integer.MAX_VALUE);
} else {
setDefault(Integer.parseInt(values[0]), Integer.parseInt(values[1]));
}
}
}
if (root.getAttribute("defMult") != null) {
setDefault(Multiplicity.valueOf(root.getAttributeValue("defMult")));
}
if (root.getAttribute("defSep") != null) {
value = root.getAttributeValue("defSep");
if (value.indexOf(':') < 0) {
setDefault(Separator.valueOf(value));
} else {
values = value.split(":");
setDefault(Separator.valueOf(values[0]), Separator.valueOf(values[1]));
}
}
if (root.getAttribute("defPrefix") != null) {
value = root.getAttributeValue("defPrefix");
if (value.indexOf(':') < 0) {
setDefault(Prefix.valueOf(value));
} else {
values = value.split(":");
setDefault(Prefix.valueOf(values[0]), Prefix.valueOf(values[1]));
}
}
//... Process the tag(s)
OptionSet set = null;
boolean found = false;
for (org.jdom.Element element : (java.util.List) root.getChildren("set")) {
if (element.getAttribute("data") != null) { // Create the set
value = element.getAttributeValue("data");
if (value.indexOf(':') < 0) {
set = addSet(element.getAttributeValue("name"),
Integer.parseInt(value));
} else {
values = value.split(":");
if (values[1].equals("INF")) { // Allow unlimited number of data items
set = addSet(element.getAttributeValue("name"),
Integer.parseInt(values[0]), Integer.MAX_VALUE);
} else {
set = addSet(element.getAttributeValue("name"),
Integer.parseInt(values[0]), Integer.parseInt(values[1]));
}
}
} else {
set = addSet(element.getAttributeValue("name"));
}
processSet(set, element);
found = true;
}
//... Process the tag (if present)
if (root.getChild("defaultSet") != null) {
processSet(getSet(), root.getChild("defaultSet"));
found = true;
}
if (!found) {
throw new XMLParsingException(CLASS + ": At least one option set needs to be defined");
}
//.... Process the