Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
edu.isi.nlp.parameters.Parameters Maven / Gradle / Ivy
package edu.isi.nlp.parameters;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import edu.isi.nlp.StringUtils;
import edu.isi.nlp.converters.StrictStringToBoolean;
import edu.isi.nlp.converters.StringConverter;
import edu.isi.nlp.converters.StringToDouble;
import edu.isi.nlp.converters.StringToEnum;
import edu.isi.nlp.converters.StringToFile;
import edu.isi.nlp.converters.StringToInteger;
import edu.isi.nlp.converters.StringToOSFile;
import edu.isi.nlp.converters.StringToStringList;
import edu.isi.nlp.converters.StringToStringSet;
import edu.isi.nlp.converters.StringToSymbolList;
import edu.isi.nlp.converters.StringToSymbolSet;
import edu.isi.nlp.files.FileUtils;
import edu.isi.nlp.parameters.exceptions.InvalidEnumeratedPropertyException;
import edu.isi.nlp.parameters.exceptions.MissingRequiredParameter;
import edu.isi.nlp.parameters.exceptions.ParameterConversionException;
import edu.isi.nlp.parameters.exceptions.ParameterException;
import edu.isi.nlp.parameters.exceptions.ParameterValidationException;
import edu.isi.nlp.parameters.serifstyle.SerifStyleParameterFileLoader;
import edu.isi.nlp.symbols.Symbol;
import edu.isi.nlp.symbols.SymbolUtils;
import edu.isi.nlp.validators.AlwaysValid;
import edu.isi.nlp.validators.And;
import edu.isi.nlp.validators.FileExists;
import edu.isi.nlp.validators.IsDirectory;
import edu.isi.nlp.validators.IsFile;
import edu.isi.nlp.validators.IsInRange;
import edu.isi.nlp.validators.IsNonNegative;
import edu.isi.nlp.validators.IsPositive;
import edu.isi.nlp.validators.ValidationException;
import edu.isi.nlp.validators.Validator;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Represents a set of parameters passed into a program. The parameters are assumed to originate as
* key-value pairs of String
s, which can then be accessed in various validated ways.
* This class is immutable. Keys will never be null or empty. Values will never be null.
*
* For all methods to get parameters, looking up a missing parameter throws an unchecked {@link
* MissingRequiredParameter} exception.
*
* @author rgabbard
* @author clignos
*/
public final class Parameters {
public static final String DO_OS_CONVERSION_PARAM = "os_filepath_conversion";
private static final String DELIM = ".";
private static final Joiner JOINER = Joiner.on(DELIM);
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s");
private Parameters(final Map params, final List namespace) {
this.namespace = ImmutableList.copyOf(namespace);
this.params = ImmutableMap.copyOf(params);
for (final Map.Entry param : params.entrySet()) {
checkNotNull(param.getKey());
checkNotNull(param.getValue());
checkArgument(!param.getKey().isEmpty());
}
}
/**
* Creates a new set of parameters with only those parameters in the specified namespace (that is,
* prefixed by "namespace.". The namespace prefix and period will be removed from parameter names
* in the new {@code Parameters}. The name space name should *not* have a trailing ".".
*/
public Parameters copyNamespace(final String requestedNamespace) {
checkArgument(!requestedNamespace.isEmpty());
checkArgument(!requestedNamespace.endsWith(DELIM));
final ImmutableMap.Builder ret = ImmutableMap.builder();
final String dottedNamespace = requestedNamespace + DELIM;
for (final Map.Entry param : params.entrySet()) {
if (param.getKey().startsWith(dottedNamespace)) {
ret.put(param.getKey().substring(dottedNamespace.length()), param.getValue());
}
}
final List newNamespace = Lists.newArrayList();
newNamespace.addAll(namespace);
newNamespace.add(requestedNamespace);
final Parameters paramsRet = new Parameters(ret.build(), newNamespace);
// our children inherit our listeners
for (final Listener listener : listeners) {
paramsRet.registerListener(listener);
}
return paramsRet;
}
/**
* If the specified namespace is present, return a copy of that namespace as a parameter set.
* Otherwise, return a copy of this parameter set. The name space name should *not* have a
* trailing ".".
*/
public Parameters copyNamespaceIfPresent(final String requestedNamespace) {
// checkArgument ensures namespaces are specified consistently
checkArgument(!requestedNamespace.isEmpty());
checkArgument(!requestedNamespace.endsWith(DELIM));
if (isNamespacePresent(requestedNamespace)) {
return copyNamespace(requestedNamespace);
} else {
return copy();
}
}
/**
* Returns if any parameter in this parameter set begins the the specified string, followed by a
* dot. The argument may not be empty. The name space name should *not* have a trailing ".".
*/
public boolean isNamespacePresent(final String requestedNamespace) {
checkArgument(requestedNamespace.length() > 0);
checkArgument(!requestedNamespace.endsWith(DELIM));
final String probe = requestedNamespace + DELIM;
return Iterables.any(params.keySet(), StringUtils.startsWith(probe));
}
/** Creates a copy of this parameter set. */
public Parameters copy() {
return new Parameters(params, namespace);
}
public String dump() {
return dump(true, true);
}
public String dumpWithoutNamespacePrefix() {
return dump(true, false);
}
public String dump(final boolean printDateTime) {
return dump(printDateTime, true);
}
/**
* Dumps the parameters object as colon-separated key-value pairs. If {@code printDateTime} is
* true, will put a #-style comment with the current date and time at the top. If
* includeNamespacePrefix is true, will prefix its parameter with its full namespace instead of
* writing all keys relative to the current namespace.
*/
public String dump(final boolean printDateTime, final boolean includeNamespacePrefix) {
final StringWriter sOut = new StringWriter();
final PrintWriter out = new PrintWriter(sOut);
if (printDateTime) {
// output a timestamp comment
final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
out.format("#%s\n", timeFormat.format(new Date()));
}
List keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
for (final String rawKey : keys) {
final String key;
if (includeNamespacePrefix) {
key = fullString(rawKey);
} else {
key = rawKey;
}
out.format("%s: %s\n", key, params.get(rawKey));
}
out.close();
return sOut.toString();
}
public static Parameters loadSerifStyle(final File f) throws IOException {
return new SerifStyleParameterFileLoader.Builder().build().load(f);
}
public static Parameters fromMap(Map map) {
return new Parameters(map, ImmutableList.of());
}
public static Parameters fromMap(Map map, List namespace) {
return new Parameters(map, namespace);
}
/**
* Creates a {@code Parameters} from a {@link java.util.Properties} by turning each key and value
* in the {@code Properties} into a string. If multiple keys in the properties object have the
* same string representation or if any key or value is null, an {@link
* java.lang.IllegalArgumentException} or {@link java.lang.NullPointerException} will be thrown.
*/
public static Parameters fromProperties(Properties properties) {
final ImmutableMap.Builder ret = ImmutableMap.builder();
for (final Map.Entry property : properties.entrySet()) {
ret.put(property.getKey().toString(), property.getValue().toString());
}
return fromMap(ret.build());
}
/**
* Combines these parameters with the others supplied to make a new Parameters
. The
* new parameters will contain all mappings present in either. If a mapping is present in both,
* the other
argument parameters take precedence.
*/
// this is currently unused anywhere, and it will require a little
// thought how best to make it interact with namespacing
/*public Parameters compose(final Parameters other) {
checkNotNull(other);
final Map newMap = Maps.newHashMap();
newMap.putAll(params);
newMap.putAll(other.params);
return new Parameters(newMap);
}*/
/** Returns true iff the key param
is assigned a value. */
public boolean isPresent(final String param) {
return params.containsKey(checkNotNull(param));
}
/** Gets the value for a parameter as a raw string. */
public String getString(final String param) {
checkNotNull(param);
checkArgument(!param.isEmpty());
final String ret = params.get(param);
observeWithListeners(param);
if (ret != null) {
return ret;
} else {
throw new MissingRequiredParameter(fullString(param));
}
}
public Symbol getSymbol(final String param) {
return Symbol.from(getString(param));
}
public Optional getOptionalSymbol(final String param) {
if (isPresent(param)) {
return Optional.of(getSymbol(param));
} else {
return Optional.absent();
}
}
public Optional> getOptionalSymbolList(final String param) {
if (isPresent(param)) {
return Optional.of(
getList(param, SymbolUtils.StringToSymbol(), new AlwaysValid(), "Symbol"));
} else {
return Optional.absent();
}
}
public Optional> getOptionalSymbolSet(final String param) {
if (isPresent(param)) {
// we know get() will succeed because of isPresent
//noinspection OptionalGetWithoutIsPresent
return Optional.>of(ImmutableSet.copyOf(getOptionalSymbolList(param).get()));
} else {
return Optional.absent();
}
}
private String fullString(final String param) {
if (namespace.isEmpty()) {
return param;
} else {
return joinNamespace(namespace) + DELIM + param;
}
}
/**
* Gets the parameter string for the key param
, then runs it throught the converter
* and checks it with the validator.
*
* @param expectation What we expected to see, for produceing error messages. e.g. "integer" or
* "comma-separated list of strings"
*/
public T get(
final String param,
final StringConverter converter,
final Validator validator,
final String expectation) {
checkNotNull(param);
checkNotNull(converter);
checkNotNull(validator);
checkNotNull(expectation);
final String value = getString(param);
T ret;
try {
ret = converter.decode(value);
} catch (final Exception e) {
throw new ParameterConversionException(fullString(param), value, e, expectation);
}
try {
validator.validate(ret);
} catch (final ValidationException e) {
throw new ParameterValidationException(fullString(param), value, e);
}
if (ret == null) {
throw new RuntimeException(
"Parameter converters not allowed to return null for non-null input.");
}
return ret;
}
/**
* Gets the parameter string *list* for the key param
, then runs each element
* throught the converter and checks it with the validator.
*
* @param expectation What we expected to see, for produceing error messages. e.g. "integer" or
* "comma-separated list of strings"
*/
public List getList(
final String param,
final StringConverter converter,
final Validator validator,
final String expectation) {
checkNotNull(param);
checkNotNull(converter);
checkNotNull(validator);
checkNotNull(expectation);
final List values = getStringList(param);
final ImmutableList.Builder retList = ImmutableList.builder();
for (final String value : values) {
T ret;
try {
ret = converter.decode(value);
} catch (final Exception e) {
throw new ParameterConversionException(fullString(param), value, e, expectation);
}
try {
validator.validate(ret);
} catch (final ValidationException e) {
throw new ParameterValidationException(fullString(param), value, e);
}
if (ret == null) {
throw new RuntimeException(
"Parameter converters not allowed to return null for non-null input.");
}
retList.add(ret);
}
return retList.build();
}
/**
* Looks up a parameter. If the value is not in possibleValues
, throws and exception.
*
* @param possibleValues May not be null. May not be empty.
* @throws InvalidEnumeratedPropertyException if the parameter value is not on the list.
*/
public String getStringOf(final String param, final List possibleValues) {
checkNotNull(possibleValues);
checkArgument(!possibleValues.isEmpty());
final String value = getString(param);
if (possibleValues.contains(value)) {
return value;
} else {
throw new InvalidEnumeratedPropertyException(fullString(param), value, possibleValues);
}
}
/**
* Looks up a parameter, then uses the value as a key in a map lookup. If the value is not a key
* in the map, throws an exception.
*
* @param possibleValues May not be null. May not be empty.
* @throws InvalidEnumeratedPropertyException if the parameter value is not on the list.
*/
public T getMapped(final String param, final Map possibleValues) {
checkNotNull(possibleValues);
checkArgument(!possibleValues.isEmpty());
final String value = getString(param);
final T ret = possibleValues.get(value);
if (ret == null) {
throw new InvalidEnumeratedPropertyException(
fullString(param), value, possibleValues.keySet());
}
return ret;
}
public > T getEnum(final String param, final Class clazz) {
return this.get(param, new StringToEnum<>(clazz), new AlwaysValid(), "enumeration");
}
public > Optional getOptionalEnum(final String param, final Class clazz) {
if (isPresent(param)) {
return Optional.of(
this.get(param, new StringToEnum<>(clazz), new AlwaysValid(), "enumeration"));
} else {
return Optional.absent();
}
}
/** Gets a parameter whose value is a (possibly empty) list of enums. */
public > List getEnumList(final String param, final Class clazz) {
return this.getList(param, new StringToEnum<>(clazz), new AlwaysValid(), "enumeration");
}
public Class> getClassObjectForString(final String className) throws ClassNotFoundException {
if (!className.contains(" ")) {
return Class.forName(className);
} else {
throw new ParameterException("Class names cannot contain spaces: " + className);
}
}
public Class> getClassObject(final String param) {
final String className = getString(param);
try {
return getClassObjectForString(className);
} catch (final ClassNotFoundException e) {
throw new ParameterConversionException(fullString(param), className, e, "existing class");
}
}
/**
* Turns a parameter whose value is a comma-separated list of class names into a list of the
* corresponding {@link Class} objects.
*/
public ImmutableList> getClassObjects(final String param) {
final ImmutableList.Builder> ret = ImmutableList.builder();
for (final String className : getStringList(param)) {
try {
ret.add(getClassObjectForString(className));
} catch (ClassNotFoundException e) {
throw new ParameterConversionException(fullString(param), className, e, "class");
}
}
return ret.build();
}
@SuppressWarnings("unchecked")
public T getParameterInitializedObject(final String param, final Class superClass) {
final Class> clazz = getClassObject(param);
return parameterInitializedObjectForClass(clazz, param, superClass);
}
public Optional getOptionalParameterInitializedObject(
final String param, final Class superClass) {
if (isPresent(param)) {
return Optional.of(getParameterInitializedObject(param, superClass));
} else {
return Optional.absent();
}
}
private T parameterInitializedObjectForClass(
final Class> clazz, final String param, final Class superClass) {
Optional ret;
try {
ret = createViaParamConstructor(clazz, param);
if (!ret.isPresent()) {
ret = createViaStaticFactoryMethod(clazz, param);
}
if (!ret.isPresent()) {
ret = createViaZeroArgConstructor(clazz, param);
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException iae) {
throw new ParameterException(
"While attempting to load parameter-initialized object from " + param + " :", iae);
}
if (!ret.isPresent()) {
throw new ParameterValidationException(
fullString(param),
getString(param),
new RuntimeException(
String.format(
"Class %s has neither fromParameters(params) "
+ "static factory method or constructor which takes params",
clazz.getName())));
}
if (superClass.isInstance(ret.get())) {
return (T) ret.get();
} else {
throw new ParameterValidationException(
fullString(param),
getString(param),
new RuntimeException(
String.format("Can't cast %s to %s", clazz.getName(), superClass.getName())));
}
}
private Optional createViaZeroArgConstructor(final Class> clazz, final String param)
throws IllegalAccessException, InvocationTargetException, InstantiationException {
try {
return Optional.of(clazz.getConstructor().newInstance());
} catch (NoSuchMethodException nsme) {
return Optional.absent();
}
}
private Optional createViaParamConstructor(Class> clazz, String param)
throws IllegalAccessException, InvocationTargetException, InstantiationException {
try {
return Optional.of(clazz.getConstructor(Parameters.class).newInstance(this));
} catch (NoSuchMethodException nsme) {
return Optional.absent();
}
}
private Optional createViaStaticFactoryMethod(Class> clazz, String param)
throws InvocationTargetException, IllegalAccessException {
try {
return Optional.of(clazz.getMethod("fromParameters", Parameters.class).invoke(null, this));
} catch (NoSuchMethodException e) {
return Optional.absent();
}
}
@SuppressWarnings("unchecked")
public ImmutableList getParameterInitializedObjects(
final String param, final Class superClass) {
final List classNames = getStringList(param);
final ImmutableList.Builder ret = ImmutableList.builder();
for (final String className : classNames) {
Class> clazz;
try {
clazz = getClassObjectForString(className);
} catch (final ClassNotFoundException e) {
throw new ParameterValidationException(fullString(param), getString(param), e);
}
ret.add((T) parameterInitializedObjectForClass(clazz, param, superClass));
}
return ret.build();
}
/** Gets a parameter whose value is a (possibly empty) list of integers. */
public List getIntegerList(final String param) {
return getList(param, new StringToInteger(), new AlwaysValid(), "integer");
}
/** Gets a "true/false" parameter. */
public boolean getBoolean(final String param) {
return get(param, new StrictStringToBoolean(), new AlwaysValid(), "boolean");
}
public Optional getOptionalBoolean(final String param) {
if (isPresent(param)) {
return Optional.of(getBoolean(param));
} else {
return Optional.absent();
}
}
/** Gets a parameter whose value is a (possibly empty) list of booleans. */
public List getBooleanList(final String param) {
return getList(param, new StrictStringToBoolean(), new AlwaysValid(), "boolean");
}
public Optional getOptionalString(final String param) {
if (isPresent(param)) {
return Optional.of(getString(param));
} else {
return Optional.absent();
}
}
/** Gets an integer parameter. */
public int getInteger(final String param) {
return get(param, new StringToInteger(), new AlwaysValid(), "integer");
}
public Optional getOptionalInteger(final String param) {
if (isPresent(param)) {
return Optional.of(getInteger(param));
} else {
return Optional.absent();
}
}
public Optional getOptionalPositiveInteger(final String param) {
if (isPresent(param)) {
return Optional.of(
get(param, new StringToInteger(), new IsPositive(), "positive integer"));
} else {
return Optional.absent();
}
}
/** Gets an positive integer parameter. */
public int getPositiveInteger(final String param) {
return get(param, new StringToInteger(), new IsPositive(), "positive integer");
}
/** Gets a parameter whose value is a (possibly empty) list of positive integers. */
public List getPositiveIntegerList(final String param) {
return getList(param, new StringToInteger(), new IsPositive(), "positive integer");
}
/** Gets a positive double parameter. */
public double getPositiveDouble(final String param) {
return get(param, new StringToDouble(), new IsPositive(), "positive double");
}
public Optional getOptionalPositiveDouble(final String param) {
if (isPresent(param)) {
return Optional.of(getPositiveDouble(param));
}
return Optional.absent();
}
/** Gets a parameter whose value is a (possibly empty) list of positive doubles. */
public List getPositiveDoubleList(final String param) {
return getList(param, new StringToDouble(), new IsPositive(), "positive double");
}
/** Gets a non-negative double parameter. */
public double getNonNegativeDouble(final String param) {
return get(param, new StringToDouble(), new IsNonNegative(), "non-negative double");
}
/** Gets a parameter whose value is a (possibly empty) list of non-negative doubles. */
public List getNonNegativeDoubleList(final String param) {
return getList(param, new StringToDouble(), new IsNonNegative(), "non-negative double");
}
/** Gets a non-negative integer number parameter. */
public int getNonNegativeInteger(final String param) {
return get(param, new StringToInteger(), new IsNonNegative(), "non-negative integer");
}
/** Gets a double parameter. */
public double getDouble(final String param) {
return get(param, new StringToDouble(), new AlwaysValid(), "double");
}
/** Gets a double between 0.0 and 1.0, inclusive. */
public double getProbability(final String param) {
return get(param, new StringToDouble(), new IsInRange<>(Range.closed(0.0, 1.0)), "probability");
}
private StringConverter getFileConverter() {
if (isPresent(DO_OS_CONVERSION_PARAM) && getBoolean(DO_OS_CONVERSION_PARAM)) {
return new StringToOSFile();
} else {
return new StringToFile();
}
}
/** Gets a file, which is required to exist. */
public File getExistingFile(final String param) {
return get(
param, getFileConverter(), new And<>(new FileExists(), new IsFile()), "existing file");
}
public File getFirstExistingFile(String param) {
final List fileStrings = getStringList(param);
for (final String fileName : fileStrings) {
final File f = new File(fileName.trim());
if (f.isFile()) {
return f;
}
}
throw new ParameterConversionException(
fullString(param), fileStrings.toString(), "No provided path is an existing file");
}
/** Gets a file or directory, which is required to exist. */
public File getExistingFileOrDirectory(final String param) {
return get(param, getFileConverter(), new FileExists(), "existing file or directory");
}
/**
* Gets a directory which is guaranteed to exist after the execution of this method. If the
* directory does not already exist, it and its parents are created. If this is not possible, an
* exception is throws.
*/
public File getAndMakeDirectory(final String param) {
final File f =
get(param, new StringToFile(), new AlwaysValid(), "existing or creatable directory");
if (f.exists()) {
if (f.isDirectory()) {
return f.getAbsoluteFile();
} else {
throw new ParameterValidationException(
fullString(param),
f.getAbsolutePath().toString(),
new ValidationException("Not an existing or creatable directory"));
}
} else {
f.getAbsoluteFile().mkdirs();
return f.getAbsoluteFile();
}
}
/** Gets a directory which already exists. */
public File getExistingDirectory(final String param) {
return get(
param,
new StringToFile(),
new And<>(new FileExists(), new IsDirectory()),
"existing directory");
}
/**
* Gets a file or directory parameter without specifying whether it exists. Prefer a more specific
* parameter accessor when possible.
*/
public File getFileOrDirectory(final String param) {
return get(param, new StringToFile(), new AlwaysValid(), "file or directory");
}
/**
* Gets a (possibly empty) list of existing directories. Will throw a {@link
* ParameterValidationException} if any of the supplied paths are not existing directories.
*/
public ImmutableList getExistingDirectories(String param) {
final List fileStrings = getStringList(param);
final ImmutableList.Builder ret = ImmutableList.builder();
for (final String dirName : fileStrings) {
final File dir = new File(dirName.trim());
if (!dir.isDirectory()) {
throw new ParameterValidationException(
fullString(param), dirName, "path does not exist or is not a directory");
}
ret.add(dir);
}
return ret.build();
}
/**
* Gets the first existing directory in a common-separated list. If none exists, throws an {@link
* ParameterValidationException}.
*/
public File getFirstExistingDirectory(String param) {
final List directoryStrings = getStringList(param);
for (final String dirName : directoryStrings) {
final File dir = new File(dirName.trim());
if (dir.isDirectory()) {
return dir;
}
}
throw new ParameterConversionException(
fullString(param),
directoryStrings.toString(),
"No provided path is an existing directory");
}
public Optional getOptionalExistingDirectory(final String param) {
if (isPresent(param)) {
return Optional.of(getExistingDirectory(param));
}
return Optional.absent();
}
/** Gets a ,-separated set of Strings. */
public Set getStringSet(final String param) {
return get(
param,
new StringToStringSet(","),
new AlwaysValid>(),
"comma-separated list of strings");
}
/** Gets a parameter whose value is a (possibly empty) comma-separated list of Strings. */
public List getStringList(final String param) {
return get(
param,
new StringToStringList(","),
new AlwaysValid>(),
"comma-separated list of strings");
}
/**
* Gets a parameter whose value is a (possibly empty) comma-separated list of Strings, if present.
*/
public Optional> getOptionalStringList(final String param) {
if (isPresent(param)) {
return Optional.of(getStringList(param));
}
return Optional.absent();
}
public Optional> getOptionalStringSet(final String param) {
if (isPresent(param)) {
return Optional.of(ImmutableSet.copyOf(getStringSet(param)));
} else {
return Optional.absent();
}
}
/** Gets a ,-separated set of Symbols */
public Set getSymbolSet(final String param) {
return get(
param,
new StringToSymbolSet(","),
new AlwaysValid>(),
"comma-separated list of strings");
}
/** Gets a parameter whose value is a (possibly empty) comma-separated list of Symbols. */
public List getSymbolList(final String param) {
return get(
param,
new StringToSymbolList(","),
new AlwaysValid>(),
"comma-separated list of strings");
}
public File getCreatableFile(final String param) {
final String val = getString(param);
final File ret = new File(val);
if (ret.exists()) {
if (ret.isDirectory()) {
throw new ParameterValidationException(
fullString(param), val, "Requested a file, but directory exists with that filename");
}
} else {
ret.getAbsoluteFile().getParentFile().mkdirs();
}
return ret;
}
/**
* Gets a file, with no requirements about whether it exists or not. if you intend to write to
* this file, you may prefer {@link #getCreatableFile(String)}, which will create its parent
* directories.
*/
public File getPossiblyNonexistentFile(final String param) {
return new File(getString(param));
}
public File getCreatableDirectory(final String param) {
final String val = getString(param);
final File ret = new File(val);
if (ret.exists()) {
if (!ret.isDirectory()) {
throw new ParameterValidationException(
fullString(param), val, "Requested a directory, but a file exists with that filename");
}
} else {
ret.getAbsoluteFile().mkdirs();
}
return ret;
}
public File getEmptyDirectory(final String param) {
final File dir = getCreatableDirectory(param);
final int numFilesContained = dir.list().length;
if (numFilesContained != 0) {
throw new ParameterValidationException(
fullString(param),
getString(param),
String.format(
"Requested an empty directory, but directory contains %d files.", numFilesContained));
}
return dir;
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadSymbolSet(CharSource)} on it.
*/
public ImmutableSet getFileAsSymbolSet(String param) throws IOException {
return FileUtils.loadSymbolSet(Files.asCharSource(getExistingFile(param), Charsets.UTF_8));
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadSymbolSet(CharSource)} on it, if the param is present. If the param is missing,
* {@link Optional#absent()} is returned.
*/
public Optional> getOptionalFileAsSymbolSet(String param)
throws IOException {
if (isPresent(param)) {
return Optional.of(
FileUtils.loadSymbolSet(Files.asCharSource(getExistingFile(param), Charsets.UTF_8)));
} else {
return Optional.absent();
}
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadSymbolList(CharSource)} on it.
*/
public ImmutableList getFileAsSymbolList(String param) throws IOException {
return FileUtils.loadSymbolList(Files.asCharSource(getExistingFile(param), Charsets.UTF_8));
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadSymbolList(CharSource)} on it, if the param is present. If the param is missing,
* {@link Optional#absent()} is returned.
*/
public Optional> getOptionalFileAsSymbolList(String param)
throws IOException {
if (isPresent(param)) {
return Optional.of(
FileUtils.loadSymbolList(Files.asCharSource(getExistingFile(param), Charsets.UTF_8)));
} else {
return Optional.absent();
}
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadStringSet(CharSource)} on it.
*/
public ImmutableSet getFileAsStringSet(String param) throws IOException {
return FileUtils.loadStringSet(Files.asCharSource(getExistingFile(param), Charsets.UTF_8));
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadStringSet(CharSource)} on it, if the param is present. If the param is missing,
* {@link Optional#absent()} is returned.
*/
public Optional> getOptionalFileAsStringSet(String param)
throws IOException {
if (isPresent(param)) {
return Optional.of(
FileUtils.loadStringSet(Files.asCharSource(getExistingFile(param), Charsets.UTF_8)));
} else {
return Optional.absent();
}
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadStringList(CharSource)} on it.
*/
public ImmutableList getFileAsStringList(String param) throws IOException {
return FileUtils.loadStringList(Files.asCharSource(getExistingFile(param), Charsets.UTF_8));
}
/**
* Convenience method to call {@link #getExistingFile(String)} and then apply {@link
* FileUtils#loadStringList(CharSource)} on it, if the param is present. If the param is missing,
* {@link Optional#absent()} is returned.
*/
public Optional> getOptionalFileAsStringList(String param)
throws IOException {
if (isPresent(param)) {
return Optional.of(
FileUtils.loadStringList(Files.asCharSource(getExistingFile(param), Charsets.UTF_8)));
} else {
return Optional.absent();
}
}
public ImmutableMap getFileAsSymbolToFileMap(String param) throws IOException {
return FileUtils.loadSymbolToFileMap(
Files.asCharSource(getExistingFile(param), Charsets.UTF_8));
}
public Parameters getSubParameters(final String param) throws IOException {
final File paramFile = getExistingFile(param);
return Parameters.loadSerifStyle(paramFile);
}
/** Throws a ParameterException if neither parameter is defined. */
public void assertAtLeastOneDefined(final String param1, final String param2) {
if (!isPresent(param1) && !isPresent(param2)) {
throw new ParameterException(
String.format("At least one of %s and %s must be defined.", param1, param2));
}
}
/** Throws a ParameterException if none of the supplied parameters are defined. */
public void assertAtLeastOneDefined(final String param1, final String... moreParams) {
if (!isPresent(param1)) {
for (final String moreParam : moreParams) {
if (isPresent(moreParam)) {
return;
}
}
final List paramsForError = Lists.newArrayList();
paramsForError.add(param1);
paramsForError.addAll(Arrays.asList(moreParams));
throw new ParameterException(
String.format(
"At least one of %s must be defined.",
StringUtils.commaSpaceJoiner().join(paramsForError)));
}
}
/** Throws a ParameterException unless exactly one parameter is defined. */
public void assertExactlyOneDefined(final String param1, final String param2) {
// Asserting that exactly one is defined is the same as asserting that they do not have the same
// value for definedness.
if (isPresent(param1) == isPresent(param2)) {
throw new ParameterException(
String.format("Exactly one of %s and %s must be defined.", param1, param2));
}
}
/** Throws a ParameterException unless exactly one parameter is defined. */
public void assertExactlyOneDefined(final String... params) {
int definedCount = 0;
for (final String param : params) {
if (isPresent(param)) {
if (++definedCount == 2) {
// No point in going past two
break;
}
}
}
if (definedCount != 1) {
throw new ParameterException(
String.format(
"Exactly one of %s must be defined.", StringUtils.commaSpaceJoiner().join(params)));
}
}
public Optional getOptionalExistingFile(final String param) {
if (isPresent(param)) {
return Optional.of(getExistingFile(param));
} else {
return Optional.absent();
}
}
public Optional getOptionalCreatableFile(final String param) {
if (isPresent(param)) {
return Optional.of(getCreatableFile(param));
} else {
return Optional.absent();
}
}
/**
* If {@code param} is present, calls {@link #getCreatableDirectory(String)} with it. Otherwise,
* returns {@link Optional#absent()}
*/
public Optional getOptionalCreatableDirectory(final String param) {
if (isPresent(param)) {
return Optional.of(getCreatableDirectory(param));
} else {
return Optional.absent();
}
}
public File getExistingFileRelativeTo(final File root, final String param) {
if (!root.exists()) {
throw new ParameterException(
String.format("Cannot resolve parameter %s relative to non-existent directory", param),
new FileNotFoundException(String.format("Not found: %s", root)));
}
final String val = getString(param);
final File ret = new File(root, val);
if (!ret.exists()) {
throw new ParameterValidationException(
fullString(param),
ret.getAbsolutePath(),
"Requested existing file, but the file does not exist");
}
return ret;
}
/**
* Given a list of strings, returns the first string in the list which exists as a parameter. If
* none do, a {@link ParameterException} is thrown.
*/
public String getFirstExistingParamName(String[] paramNames) {
for (final String paramName : paramNames) {
if (isPresent(paramName)) {
return paramName;
}
}
throw new ParameterException("One of " + Arrays.toString(paramNames) + " must be present");
}
/**
* Gets the parameter associated with an annotation. Provided with an annotation class, this will
* check first if it has a {@code String} field called {@code param}. If it does, its value is
* returned. If not, it checks for a {@code String} field called {@code params}. If it exists, it
* is split on ",", the elements are trimmed, and the return value of {@link
* #getFirstExistingParamName(String[])} on the resulting array is returned. If neither is
* present, a {@link ParameterException} is thrown.
*
* The reason this hack-y thing exists is that it is often convenient for Guice annotations to
* include on the annotation the parameter typically used to set it when configuring from a param
* file. However, we cannot specify array valued fields on annotations, so we need to use a
* comma-separated {@code String}.
*/
public String getParamForAnnotation(Class> clazz) {
try {
return (String) clazz.getField("param").get("");
} catch (NoSuchFieldException e) {
try {
return getFirstExistingParamName(
StringUtils.onCommas()
.splitToList((String) clazz.getField("params").get(""))
.toArray(new String[] {}));
} catch (NoSuchFieldException e1) {
throw new ParameterException("Annotation " + clazz + " must have param or params field");
} catch (IllegalAccessException e1) {
throw new ParameterException("While fetching parameter from annotation " + clazz, e);
}
} catch (IllegalAccessException e) {
throw new ParameterException("While fetching parameter from annotation " + clazz, e);
}
}
/** Returns the dot-separated namespace. */
public String namespace() {
return joinNamespace(namespace);
}
/** Returns the namespace as a list. */
public ImmutableList namespaceAsList() {
return namespace;
}
/**
* Returns the map of string parameter names to their string values. The namespace is not included
* in the keys; use {@link #namespace()} to get it.
*/
public ImmutableMap asMap() {
return params;
}
/** package-private */
void registerListener(Listener listener) {
listeners.add(listener);
}
private void observeWithListeners(final String param) {
// all parameter requests eventually get routed through here,
// so this is where we observe
for (final Parameters.Listener listener : listeners) {
listener.observeParameterRequest(JOINER.join(FluentIterable.from(namespace).append(param)));
}
}
private final ImmutableMap params;
private final ImmutableList namespace;
private final List listeners = Lists.newArrayList();
/**
* Creates a new {@code Parameters} which has all the parameters in both this one and {@code
* paramsToAdd}. If there are collisions between this and {@code paramsToAdd}, {@code
* paramsToAdd}'s value is preferred.
*
* The result maintains the namespace of {@code this} and parameters from {@code paramsToAdd}
* are copied into the current namespace using their names relative to the {@code paramsToAdd}'s
* working namespace. For example, suppose {@code this}'s namespace is {@code com.bbn.foo} and
* {@code paramsToAdd}'s namespace is {@code com.bbn.bar.meep}. If {code paramsToAdd} has a
* parameter whose original absolute name was {@code com.bbn.bar.meep.lalala.myParam}, it will
* become {@code com.bbn.foo.lalala.myParam} in the result.
*
*
Beware this behavior may be confusing to users because error messages may refer to
* parameters which do not appear to exist.
*/
public Parameters copyMergingIntoCurrentNamespace(final Parameters paramsToAdd) {
// we use the immutable map builder even though it requires tracking seen
// items separately to maintain determinism
final ImmutableMap.Builder newParamsMap = ImmutableMap.builder();
final Set seen = Sets.newHashSet();
newParamsMap.putAll(paramsToAdd.params);
seen.addAll(paramsToAdd.params.keySet());
newParamsMap.putAll(Maps.filterKeys(params, not(in(seen))));
return new Parameters(newParamsMap.build(), namespace);
}
public Builder modifiedCopyBuilder() {
final Builder ret = new Builder(namespace);
ret.putAll(params);
return ret;
}
/** Creates a new builder with the default (empty) namespace. */
public static Builder builder() {
return new Builder(ImmutableList.of());
}
/** Creates a new builder with the specified namespace. */
public static Builder builder(List namespace) {
return new Builder(namespace);
}
/**
* Returns the specified namespace split into a list, for example {@code ["foo", "bar"]} for
* {@code "foo.bar"}.
*/
public static List splitNamespace(final String namespace) {
return StringUtils.onDots().splitToList(namespace);
}
/**
* Returns the specified namespace joined into a string, for example {@code "foo.bar"} for {@code
* ["foo", "bar"]}. The namespace may consist of any number of elements, including none at all. No
* element in the namespace may begin or end with a period.
*/
public static String joinNamespace(final List namespace) {
for (final String element : namespace) {
checkArgument(
!element.startsWith(DELIM), "Namespace element may not begin with a period: " + element);
checkArgument(
!element.endsWith(DELIM), "Namespace element may not end with a period: " + element);
}
return JOINER.join(namespace);
}
/**
* Returns the specified namespace joined into a string, for example {@code "foo.bar"} for
* arguments {@code ["foo", "bar"]}. To match the behavior of {@link #joinNamespace(List)}, the
* namespace may consist of any number of elements, including none at all. No element in the
* namespace may begin or end with a period.
*/
public static String joinNamespace(final String... elements) {
return JOINER.join(elements);
}
public static final class Builder {
private final Map params = Maps.newHashMap();
private final List namespace;
private Builder(final List namespace) {
this.namespace = ImmutableList.copyOf(namespace);
}
public Builder set(String key, String value) {
checkNotNull(key);
checkArgument(!key.isEmpty(), "Key must be non-empty");
checkArgument(!WHITESPACE_PATTERN.matcher(key).find(), "Key cannot contain whitespace");
checkNotNull(value);
// Medial whitespace is allowed, but we remove initial/final whitespace as it will not
// preserved in loading.
value = value.trim();
checkArgument(!value.isEmpty(), "Value cannot be empty or only whitespace");
params.put(key, value);
return this;
}
public Builder putAll(Map data) {
params.putAll(data);
return this;
}
public Parameters build() {
return new Parameters(params, namespace);
}
}
interface Listener {
void observeParameterRequest(String param);
}
/**
* Gets objects defined by namespaces. It frequently happens that a program needs to have
* specified some set of objects to use, where each object is defined by some namespace. For
* example, in the name finder, we might need to use a number of different name set groups, where
* each group is defined by either a single name list or a map of name list names to name lists.
* Using this we would have a parameter file like this:
*
*
* # note foo is not used
* com.bbn.serif.names.lists.activeListGroups: standard,geonames,single
* com.bbn.serif.names.lists.standard.mapFile: /nfs/.....
* com.bbn.serif.names.lists.geonames.mapFile: /nfs/....
* com.bbn.serif.names.lists.foo.listPath: /nfs/....
* com.bbn.serif.names.lists.foo.listName: single
* com.bbn.serif.names.lists.foo.listPath: /nfs/...
* com.bbn.serif.names.lists.foo.listName: foo
*
*
* The user could load these by {@code objectsFromNameSpace("com.bbn.serif.names.lists",
* "activeListGroups", aNameSpaceToObjectMapperImplementation)}.
*
* If {@code activeNameSpacesFeature} is absent this will thrown a {@link ParameterException}.
*/
public ImmutableSet objectsFromNamespaces(
String baseNamespace,
String activeNamespacesFeature,
NamespaceToObjectMapper extends T> nameSpaceToObjectMapper) {
final Parameters subNamespace = copyNamespace(baseNamespace);
final ImmutableSet.Builder ret = ImmutableSet.builder();
for (final String activeNamespace : subNamespace.getStringList(activeNamespacesFeature)) {
if (subNamespace.isNamespacePresent(activeNamespace)) {
ret.add(nameSpaceToObjectMapper.fromNameSpace(subNamespace.copyNamespace(activeNamespace)));
} else {
throw new ParameterException(
"Expected namespace "
+ baseNamespace
+ DELIM
+ activeNamespace
+ "to exist because of value of "
+ activeNamespacesFeature
+ " but "
+ "it did not");
}
}
return ret.build();
}
public interface NamespaceToObjectMapper {
T fromNameSpace(Parameters params);
}
}