GMCSection.java
package edu.udel.cis.vsl.gmc;
import java.io.PrintStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import edu.udel.cis.vsl.gmc.Option.OptionType;
/**
* <p>
* A GMCSection has a unique name and it encapsulates a set of key-value pairs,
* where the keys correspond to commandline parameters and the value is the
* value assigned to that parameter. In addition, there can be any number of
* "free arguments" that are not assigned to any parameter. The free arguments
* are always strings.
* </p>
*
* <p>
* A section is typically generated by parsing a command line, or at least the
* suffix of a command line after the initial command(s). The command line
* suffix <code>-verbose -errBound=10 filename.c</code>, for example, would
* yield an anonymous section in which "verbose" is mapped to true, "errBound"
* is mapped to the integer 10, and with one free argument "fileName.c". An
* anonymous section has the name "anonymous" which is a reserved name for
* anonymous sections.
* </p>
*
* <p>
* Each GMCSection has associated to it a configuration, which has it either as
* the anonymous section or in its section map.
* </p>
*
* <p>
* Each option is an instance of class {@link Option}. An option has a name and
* a type. The type is one of STRING, INTEGER, DOUBLE, BOOLEAN, or MAP. The type
* determines the kind of value that can be assigned to an option. The first
* four are "scalar" types and take values of type String, Integer, Double, and
* Boolean, respectively.
* </p>
*
* <p>
* An option of MAP type may be assigned a value of type Map<String,Object>.
* This kind of option is provided for command line arguments such as
* <code>-inputX=10 -inputB=true -inputZ="hello"</code>. This constructs a map
* from String to Object which maps "X" to the Integer 10, "B" to the Boolean
* true, and "Z" to the String "hello". This map is the value that is assigned
* to the option named "input". In general, the values of the map can be any
* scalar values, and their types will be inferred from their format. For
* example 1.0 will be interpreted as a Double, 1 as an Integer, "1" (with
* quotes) a String.
* </p>
*
*
* @author Manchun Zheng (zmanchun)
*
*/
public class GMCSection implements Serializable {
// Instance fields...
/**
* required and generated by eclipse
*/
private static final long serialVersionUID = -4500313731497066831L;
/**
* The GMC configuration that this section belongs to.
*/
private GMCConfiguration config;
/**
* The name of this section
*/
private String name;
/**
* Map from option to value assigned to that option. Only options that have
* a non-null value assigned to them will have an entry in this map. Any
* option added to this map has to be contained in the configuration
* associated with this section.
*/
private Map<Option, Object> valueMap = new LinkedHashMap<>();
/**
* The list of free arguments associated to this configuration.
*/
private ArrayList<String> freeArgs = new ArrayList<>();
// Constructors...
/**
* Constructs a new GMCSection with the given name
*
* @param name
* the name of the section
*/
public GMCSection(String name) {
this.name = name;
}
/**
* Constructs a new instance of GMCSection with the given GMCConfiguration
* and name.
*
* @param config
* The GMCConfiguraiton that the new section associates
* with.
* @param name
* The name of the new section
*/
public GMCSection(GMCConfiguration config, String name) {
this.name = name;
this.config = config;
}
// Public methods...
/**
* Gets the value associated to an option or null if no value is associated
* to that option.
*
* @param option
* an option associated to this configuration
* @return the value assigned to the option or null
* @throws IllegalArgumentException
* if the given option is not
* associated to this configuration
*/
public Object getValue(Option option) {
config.checkContainsOption(option);
return valueMap.get(option);
}
/**
* Returns the value associated to an option or the default value for that
* option if no value is associated to it.
*
* @param option
* an option associated to this configuration
* @return the value assigned to the option or the option's default value
* @throws IllegalArgumentException
* if the given option is not
* associated to this configuration
*/
public Object getValueOrDefault(Option option) {
Object value = getValue(option);
if (value == null)
return option.defaultValue();
else
return value;
}
/**
* Gets the map value associated to an option of map type, or null if no
* value is associated to that option.
*
* @param option
* an option of map type controlled by this configuration
* @return the map value associated to the option or null
* @throws IllegalArgumentException
* if the given option does not have
* map type or is not associated to
* this configuration
*/
@SuppressWarnings("unchecked")
public Map<String, Object> getMapValue(Option option) {
config.checkContainsOption(option);
if (option.type() != OptionType.MAP)
throw new IllegalArgumentException(
"Expected option of map type, say type " + option.type()
+ " in option " + option.name());
return (Map<String, Object>) valueMap.get(option);
}
/**
* Given an option of map type, and a key, this returns the value associated
* to the key in the map associated to option. If the map associated to this
* option is null, this returns null. If there is no entry in the map for
* the key, this returns null.
*
* @param option
* an option of map type associated to this configuration
* @param key
* a string to be used as the key in the map
* @return the value associated to the key or null
* @thros IllegalArgumentException if the given option does not have map
* type or is not associated to this configuration
*/
public Object getMapEntry(Option option, String key) {
Map<String, Object> map = getMapValue(option);
if (map == null)
return null;
else
return map.get(key);
}
/**
* Determines whether the value associated to a boolean option should be
* construed as true in most circumstances. Specifically: if there is a
* value associated to this option, this method will return that value. If
* there is no value associated to this option but the default value for the
* option is true, this method will return true, otherwise it will return
* false.
*
* @param option
* an option of boolean type controlled by this
* configuration
* @return true iff there is a value associated to that option and it is
* true or there is no value associated to the option and the
* option's default value is true
* @throws IllegalArgumentException
* if the given option is not
* associated to this configuration or
* does not have boolean type
*/
public boolean isTrue(Option option) {
config.checkContainsOption(option);
if (option.type() != OptionType.BOOLEAN)
throw new IllegalArgumentException(
"Expected option of boolean type, saw type " + option.type()
+ " in option " + option.name());
else {
Boolean value = (Boolean) getValue(option);
if (value != null)
return value;
value = (Boolean) option.defaultValue();
return value != null && value;
}
}
/**
* Returns the current number of free arguments associated to this
* configuration.
*
* @return number of free arguments
*/
public int getNumFreeArgs() {
return freeArgs.size();
}
/**
* Returns the i-th free argument, indexed from 0.
*
* @param i
* integer in range 0..n-1, where n is the current number of
* free arguments assigned to this configuration
* @return the i-th free argument
*/
public String getFreeArg(int i) {
return freeArgs.get(i);
}
/**
* Returns the name of this section.
*
* @return the name of this section
*/
public String getName() {
return this.name;
}
/**
* Adds a free argument to the list of free arguments associated to this
* configuration.
*
* @param arg
* a String
*/
public void addFreeArg(String arg) {
freeArgs.add(arg);
}
/**
* Adds the key-value pair for a scalar value to the parameter map. If an
* entry with that key already exists, it is replaced by the new one. If the
* value is null, this instead removes the entry with that key (if one
* exists), returning the old entry.
*
* @param key
* a non-null string, the name of the parameter
* @param value
* the value to associate to the parameter; either null or
* a non-null Boolean, Integer, Double, or String
* @return the previous value associated to key, or null if there was none
* @throws IllegalArgumentException
* if option is not associated to this
* configuration, or if value does not
* have a type compatible with the type
* of the option, or if the option has
* MAP type
*/
public Object setScalarValue(Option option, Object value) {
config.checkContainsOption(option);
if (value == null)
return valueMap.remove(option);
switch (option.type()) {
case BOOLEAN :
if (value instanceof Boolean)
return valueMap.put(option, value);
else
throw new IllegalArgumentException("Option " + option.name()
+ ": expected boolean, saw " + value);
case DOUBLE :
if (value instanceof Double)
return valueMap.put(option, value);
if (value instanceof Float)
return valueMap.put(option, Double.valueOf((Float) value));
if (value instanceof Integer)
return valueMap.put(option,
Double.valueOf((Integer) value));
else
throw new IllegalArgumentException("Option " + option.name()
+ ": expected double, saw " + value);
case INTEGER :
if (value instanceof BigInteger)
return valueMap.put(option, value);
else if (value instanceof Integer)
return valueMap.put(option, value);
else
throw new IllegalArgumentException("Option " + option.name()
+ ": expected integer, saw " + value);
case MAP :
throw new IllegalArgumentException(
"Expected scalar value, saw map");
case STRING :
if (value instanceof String)
return valueMap.put(option, value);
else
throw new IllegalArgumentException("Option " + option.name()
+ ": expected string, saw " + value);
default :
throw new RuntimeException("unreachable");
}
}
/**
* Sets map value or removes map entry from this configuration.
*
* @param option
* an option of MAP type associated to this configuration
* @param value
* a map to assign to that option, or null
* @return the old map value associated to the option, or null if there
* wasn't one
* @throws IllegalArgumentException
* if the option is not associated to
* this configuration, or if option's
* type is not MAP
*/
public Map<String, Object> setMapValue(Option option,
Map<String, Object> value) {
config.checkContainsOption(option);
if (value == null) {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) valueMap
.remove(option);
return result;
}
if (option.type() != OptionType.MAP)
throw new IllegalArgumentException(
"Expected option of map type, saw type " + option.type()
+ " in option " + option.name());
else {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) valueMap
.put(option, value);
return result;
}
}
/**
* Given an option of map type, adds or removes the key-value pair to the
* map corresponding to that option.
*
* If the given option does not currently have an assigned value, a new
* empty map is created for it.
*
* If the key is null, this removes the entry with that key if one exists.
*
* @param option
* an option of map type that is associated to this
* configuration
* @param key
* a string which is the key for the map entry
* @param value
* a scalar value which is an instance of one of String,
* Integer, Double, or Boolean
* @return the previous value associated to that key or null if there was
* none
*
* @throws IllegalArgumentException
* if the given option is not
* associated to this configuration, or
* it does not have map type
*/
public Object putMapEntry(Option option, String key, Object value) {
Map<String, Object> map = getMapValue(option);
if (map == null) {
map = new LinkedHashMap<String, Object>();
valueMap.put(option, map);
}
if (value == null)
return map.remove(key);
else
return map.put(key, value);
}
/**
* Returns a deep copy of this configuration. The two configurations will
* share references to the same options, and to the same Strings, but not to
* anything else. As options and strings are immutable, this should not be a
* problem.
*
* @return deep copy of this section
*/
@Override
public GMCSection clone() {
GMCSection result = new GMCSection(this.name);
int numArgs = getNumFreeArgs();
result.config = config;
for (int i = 0; i < numArgs; i++)
result.addFreeArg(getFreeArg(i));
for (Entry<Option, Object> entry : valueMap.entrySet()) {
Option option = entry.getKey();
OptionType type = option.type();
if (type == OptionType.MAP) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) entry
.getValue();
for (Entry<String, Object> mapEntry : map.entrySet())
result.putMapEntry(option, mapEntry.getKey(),
mapEntry.getValue());
} else {
result.setScalarValue(option, entry.getValue());
}
}
return result;
}
/**
* Modifies this section by reading in the values of the given configuration
* and using those to set values of this one. Existing entries in this one
* may be overwritten in the process.
*
* @param that
* another configuration; the set of options associated to
* that should be a subset of the set of options associated
* to this
*/
public void read(GMCSection that) {
for (Entry<Option, Object> entry : that.valueMap.entrySet()) {
Option option = entry.getKey();
OptionType type = option.type();
if (type == OptionType.MAP) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) entry
.getValue();
for (Entry<String, Object> mapEntry : map.entrySet())
putMapEntry(option, mapEntry.getKey(), mapEntry.getValue());
} else {
setScalarValue(option, entry.getValue());
}
}
}
/**
* Prints the current state of this configuration in a manner similar to
* what would appear on a commandline. Each scalar assignment appears on one
* line. At the end the free arguments are printed, one on each line. Never
* prints the name of anonymous sections.
*
* @param out
* print stream to which to print
*/
public void print(PrintStream out) {
if (!this.name.equals(GMCConfiguration.ANONYMOUS_SECTION)) {
out.print("--");
out.println(this.name);
}
for (Entry<Option, Object> entry : valueMap.entrySet()) {
Option option = entry.getKey();
OptionType optionType = option.type();
String optionName = option.name();
if (optionType == OptionType.MAP) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) entry
.getValue();
for (Entry<String, Object> mapEntry : map.entrySet()) {
out.print("-" + optionName + mapEntry.getKey());
out.print("=");
GMCConfiguration.printValue(out, mapEntry.getValue());
out.println();
}
} else {
out.print("-" + optionName + "=");
GMCConfiguration.printValue(out, entry.getValue());
out.println();
}
}
for (String arg : freeArgs)
out.println(arg);
out.flush();
}
/**
* Updates the configuration associates with this section.
*
* @param config
* The configuration that this section belongs to.
*/
public void setConfiguration(GMCConfiguration config) {
this.config = config;
}
}