content: /*
   * Copyright 2010 Aalto University, ComNet
   * Released under GPLv3. See LICENSE.txt for details.
   */
  package core;
  
  import java.io.*;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.Properties;
  import java.util.Scanner;
  import java.util.Set;
  import java.util.Stack;
  
  import util.Range;
  
  /**
   * Interface for simulation settings stored in setting file(s). Settings
   * class should be initialized before using (with {@link #init(String)}). If
   * Settings isn't initialized, only settings in {@link #DEF_SETTINGS_FILE}
   * are read. Normally, after initialization, settings in the given file can
   * override any settings defined in the default settings file and/or define
   * new settings.
   * <P> All settings are key-value pairs. For parsing details see
   * {@link java.util.Properties#getProperty(String)}. Value can be a single
   * value or comma separated list of values. With CSV values, CSV methods
   * must be used (e.g. {@link #getCsvInts(String, int)}). Setting value should
   * not start and end with a bracket since those are reserved for run-specific
   * values (see {@link #setRunIndex(int)}). In file paths directory separator
   * should always be forward slash ("/").
   * </P>
   */
  public class Settings {
  	/** properties object where the setting files are read into */
  	protected static Properties props;
  	/** file name of the default settings file ({@value}) */
  	public static final String DEF_SETTINGS_FILE ="default_settings.txt";
  
  	/**
  	 * Setting to define the file name where all read settings are written
  	 * ({@value}. If set to an empty string, standard output is used.
  	 * By default setting are not written anywhere.
  	 */
  	public static final String SETTING_OUTPUT_S = "Settings.output";
  
  	/** delimiter for requested values in strings ({@value})
  	 * @see #valueFillString(String) */
  	public static final String FILL_DELIMITER = "%%";
  
  	/** Stream where all read settings are written to */
  	private static PrintStream out = null;
  	private static Set<String> writtenSettings = new HashSet<String>();
  
  	/** run index for run-specific settings */
  	private static int runIndex = 0;
  	private String namespace = null; // namespace to look the settings from
  	private String secondaryNamespace = null;
  	private Stack<String> oldNamespaces;
  	private Stack<String> secondaryNamespaces;
  
  	/**
  	 * Creates a setting object with a namespace. Namespace is the prefix
  	 * of the all subsequent setting requests.
  	 * @param namespace Namespace to use
  	 */
  	public Settings(String namespace) {
  		this.oldNamespaces = new Stack<String>();
  		this.secondaryNamespaces = new Stack<String>();
  		setNameSpace(namespace);
  	}
  
  	/**
  	 * Create a setting object without namespace. All setting requests must
  	 * be prefixed with a valid namespace (e.g. "Report.nrofReports").
  	 */
  	public Settings() {
  		this(null);
  	}
  
  	/**
  	 * Sets the run index for the settings (only has effect on settings with
  	 * run array). A run array can be defined with syntax<BR>
  	 * <CODE>[settingFor1stRun ; settingFor2ndRun ; SettingFor3rdRun]</CODE>
  	 * <BR>I.e. settings are put in brackets and delimited with semicolon.
  	 * First run's setting is returned when index is 0, second when index is
  	 * 1 etc. If run index is bigger than run array's length, indexing wraps
  	 * around in run array (i.e. return value is the value at index
  	 * <CODE>runIndex % arrayLength</CODE>).
  	 * To disable whole run-index-thing, set index to value smaller than
  	 * zero (e.g. -1). When disabled, run-arrays are returned as normal values,
  	 * including the brackets.
  	 * @param index The run index to use for subsequent settings calls, or
  	 * -1 to disable run indexing
  	 */
  	public static void setRunIndex(int index) {
  		runIndex = index;
  		writtenSettings.clear();
  	}
  
  	/**
  	 * Checks that the given integer array contains a valid range. I.e.,
  	 * the length of the array must be two and
  	 * <code>first_value <= second_value</code>.
  	 * @param range The range array
  	 * @param sname Name of the setting (for error messages)
  	 * @throws SettingsError If the given array didn't qualify as a range
  	 */
  	public void assertValidRange(int range[], String sname)
  		throws SettingsError {
  		if (range.length != 2) {
  			throw new SettingsError("Range setting " +
  					getFullPropertyName(sname) +
  					" should contain only two comma separated integer values");
  		}
  		if (range[0] > range[1]) {
  			throw new SettingsError("Range setting's " +
  					getFullPropertyName(sname) +
  					" first value should be smaller or equal to second value");
  		}
  	}
  
  	/**
  	 * Makes sure that the given settings value is positive
  	 * @param value Value to check
  	 * @param settingName Name of the setting (for error's message)
  	 * @throws SettingsError if the value was not positive
  	 */
  	public void ensurePositiveValue(double value, String settingName) {
  		if (value < 0) {
  			throw new SettingsError("Negative value (" + value +
  					") not accepted for setting " + settingName);
  		}
  	}
  
  	/**
  	 * Sets the namespace to something else than the current namespace.
  	 * This change can be reverted using {@link #restoreNameSpace()}
  	 * @param namespace The new namespace
  	 */
  	public void setNameSpace(String namespace) {
  		this.oldNamespaces.push(this.namespace);
  		this.namespace = namespace;
  	}
  
  	/**
  	 * Appends the given namespace to the the current namespace, <strong>
  	 * for both the primary and secondary namespace </strong>.
  	 * This change can be reverted using {@link #restoreNameSpace()} and
  	 * {@link #restoreSecondaryNamespace()}.
  	 * @param namespace The new namespace to append
  	 */
  	public void setSubNameSpace(String namespace) {
  		this.oldNamespaces.push(this.namespace);
  		this.namespace = this.namespace + "." + namespace;
  		this.secondaryNamespaces.push(this.secondaryNamespace);
  		this.secondaryNamespace = this.secondaryNamespace + "." + namespace;
  	}
  
  	/**
  	 * Returns full (namespace prefixed) property name for a setting.
  	 * @param setting The name of the setting
  	 * @return The setting name prefixed with fully qualified name of the
  	 * namespace where the requested setting would be retrieved from or null
  	 * if that setting is not found from any of the current namespace(s)
  	 */
  	public String getFullPropertyName(String setting) {
  		if (!contains(setting)) {
  			return null;
  		}
  
  		if (props.getProperty(getFullPropertyName(setting, false)) != null) {
  			return getFullPropertyName(setting, false);
  		}
  
  		// not found from primary, but Settings contains -> must be from 2ndary
  		else return getFullPropertyName(setting, true);
  	}
  
  	/**
  	 * Returns the namespace of the settings object
  	 * @return the namespace of the settings object
  	 */
  	public String getNameSpace() {
  		return this.namespace;
  	}
  
  	/**
  	 * Returns the secondary namespace of the settings object
  	 * @return the secondary namespace of the settings object
  	 */
  	public String getSecondaryNameSpace() {
  		return this.secondaryNamespace;
  	}
  
  	/**
  	 * Sets a secondary namespace where a setting is searched from if it
  	 * isn't found from the primary namespace. Secondary namespace can
  	 * be used e.g. as a "default" space where the settings are looked from
  	 * if no specific setting is set.
  	 * This change can be reverted using {@link #restoreSecondaryNamespace()}
  	 * @param namespace The new secondary namespace or null if secondary
  	 * namespace is not used (default behavior)
  	 */
  	public void setSecondaryNamespace(String namespace) {
  		this.secondaryNamespaces.push(this.secondaryNamespace);
  		this.secondaryNamespace = namespace;
  	}
  
  	/**
  	 * Restores the namespace that was in use before a call to setNameSpace
  	 * @see #setNameSpace(String)
  	 */
  	public void restoreNameSpace() {
  		this.namespace = this.oldNamespaces.pop();
  	}
  
  	/**
  	 * Restores the secondary namespace that was in use before a call to
  	 * setSecondaryNameSpace
  	 * @see #setSecondaryNamespace(String)
  	 */
  	public void restoreSecondaryNamespace() {
  		this.secondaryNamespace = this.secondaryNamespaces.pop();
  	}
  
  	/**
  	 * Reverts the change made with {@link #setSubNameSpace(String)}, i.e.,
  	 * restores both the primary and secondary namespace.
  	 */
  	public void restoreSubNameSpace() {
  		restoreNameSpace();
  		restoreSecondaryNamespace();
  	}
  
  	/**
  	 * Initializes the settings all Settings objects will use. This should be
  	 * called before any setting requests. Subsequent calls replace all
  	 * old settings and then Settings contains only the new settings.
  	 * The file {@link #DEF_SETTINGS_FILE}, if exists, is always read.
  	 * @param propFile Path to the property file where additional settings
  	 * are read from or null if no additional settings files are needed.
  	 * @throws SettingsError If loading the settings file(s) didn't succeed
  	 */
  	public static void init(String propFile) throws SettingsError {
  		String outFile;
  		try {
  			if (new File(DEF_SETTINGS_FILE).exists()) {
  				Properties defProperties = new Properties();
  				defProperties.load(new FileInputStream(DEF_SETTINGS_FILE));
  				props = new Properties(defProperties);
  			}
  			else {
  				props = new Properties();
  			}
  			if (propFile != null) {
  				props.load(new FileInputStream(propFile));
  			}
  		} catch (IOException e) {
  			throw new SettingsError(e);
  		}
  
  		outFile = props.getProperty(SETTING_OUTPUT_S);
  		if (outFile != null) {
  			if (outFile.trim().length() == 0) {
  				out = System.out;
  			} else {
  				try {
  					out = new PrintStream(new File(outFile));
  				} catch (FileNotFoundException e) {
  					throw new SettingsError("Can't open Settings output file:" +
  							e);
  				}
  			}
  		}
  	}
  
  	/**
  	 * Initializes the settings all Settings objects will use. This should be
  	 * called before any setting requests. Subsequent calls replace all
  	 * old settings and then Settings contains only the new settings.
  	 *
  	 * @param settingsStream
  	 * 		InputStream where the properties are read.
  	 * @throws SettingsError
  	 * 		If loading the settings didn't succeed
  	 */
  	public static void initFromStream(final InputStream settingsStream)
  	throws SettingsError {
  		props = new Properties();
  		try {
  			props.load(settingsStream);
  		} catch (IOException e) {
  			throw new SettingsError(e);
  		}
  
  		String outFile = props.getProperty(SETTING_OUTPUT_S);
  		if (outFile != null) {
  			if (outFile.trim().length() == 0) {
  				out = System.out;
  			} else {
  				try {
  					out = new PrintStream(new File(outFile));
  				} catch (FileNotFoundException e) {
  					throw new SettingsError("Can't open Settings output file:" +
  							e);
  				}
  			}
  		}
  	}
  
  	/**
  	 * Reads another settings file and adds the key-value pairs to the current
  	 * settings overriding any values that already existed with the same keys.
  	 * @param propFile Path to the property file
  	 * @throws SettingsError If loading the settings file didn't succeed
  	 * @see #init(String)
  	 */
  	public static void addSettings(String propFile) throws SettingsError {
  		try {
  			props.load(new FileInputStream(propFile));
  		} catch (IOException e) {
  			throw new SettingsError(e);
  		}
  	}
  
  	/**
  	 * Writes the given setting string to the settings output (if any)
  	 * @param setting The string to write
  	 */
  	private static void outputSetting(String setting) {
  		if (out != null && !writtenSettings.contains(setting)) {
  			if (writtenSettings.size() == 0) {
  				out.println("# Settings for run " + (runIndex + 1));
  			}
  			out.println(setting);
  			writtenSettings.add(setting);
  		}
  	}
  
  	/**
  	 * Returns true if a setting with defined name (in the current namespace
  	 * or secondary namespace if such is set) exists and has some value
  	 * (not just white space)
  	 * @param name Name of the setting to check
  	 * @return True if the setting exists, false if not
  	 */
  	public boolean contains(String name) {
  		try {
  			String value = getSetting(name);
  			if (value == null) {
  				return false;
  			}
  
  			else return value.trim().length() > 0;
  		}
  		catch (SettingsError e) {
  			return false; // didn't find the setting
  		}
  	}
  
  	/**
  	 * Returns full (namespace prefixed) property name for setting.
  	 * @param name Name of the settings
  	 * @param secondary If true, the secondary namespace is used.
  	 * @return full (prefixed with current namespace) property name for setting
  	 */
  	private String getFullPropertyName(String name, boolean secondary) {
  		String usedNamespace = (secondary ? secondaryNamespace : namespace);
  
  		if (usedNamespace != null) {
  			return usedNamespace + "." + name;
  		}
  		else {
  			return name;
  		}
  	}
  
  	/**
  	 * Returns a String-valued setting. Setting is first looked from the
  	 * namespace that is set (if any) and then from the secondary namespace
  	 * (if any). All other getters use this method as their first step too
  	 * (so all getters may throw SettingsError and look from both namespaces).
  	 * @param name Name of the setting to get
  	 * @return The contents of the setting in a String
  	 * @throws SettingsError if the setting is not found from either one of
  	 * the namespaces
  	 */
  	public String getSetting(String name) {
  		String fullPropName;
  		if (props == null) {
  			init(null);
  		}
  		fullPropName = getFullPropertyName(name, false);
  		String value = props.getProperty(fullPropName);
  
  		if (value != null) { // found value, check if run setting can be parsed
  			value = parseRunSetting(value.trim());
  		}
  
  		if ((value == null || value.length() == 0) &&
  				this.secondaryNamespace != null) {
  			// try secondary namespace if the value wasn't found from primary
  			fullPropName = getFullPropertyName(name, true);
  			value = props.getProperty(fullPropName);
  
  			if (value != null) {
  				value = parseRunSetting(value.trim());
  			}
  		}
  
  		if (value == null || value.length() == 0) {
  			throw new SettingsError("Can't find setting " +
  					getPropertyNamesString(name));
  		}
  
  		outputSetting(fullPropName + " = " + value);
  		return value;
  	}
  
  	/**
  	 * Returns the given setting if it exists, or defaultValue if the setting
  	 * does not exist
  	 * @param name The name of the setting
  	 * @param defaultValue The value to return if the given setting didn't exist
  	 * @return The setting value or the default value if the setting didn't
  	 * exist
  	 */
  	public String getSetting(String name, String defaultValue) {
  		if (!contains(name)) {
  			return defaultValue;
  		} else {
  			return getSetting(name);
  		}
  	}
  
  	/**
  	 * Parses run-specific settings from a String value
  	 * @param value The String to parse
  	 * @return The runIndex % arrayLength'th value of the run array
  	 */
  	private static String parseRunSetting(String value) {
  		final String RUN_ARRAY_START = "[";
  		final String RUN_ARRAY_END = "]";
  		final String RUN_ARRAY_DELIM = ";";
  		final int MIN_LENGTH = 3; // minimum run is one value. e.g. "[v]"
  
  		if (!value.startsWith(RUN_ARRAY_START) ||
  			!value.endsWith(RUN_ARRAY_END) ||
  			runIndex < 0 ||
  			value.length() < MIN_LENGTH) {
  			return value; // standard format setting -> return
  		}
  
  		value = value.substring(1,value.length()-1); // remove brackets
  		String[] valueArr = value.split(RUN_ARRAY_DELIM);
  		int arrIndex = runIndex % valueArr.length;
  		value = valueArr[arrIndex].trim();
  
  		return value;
  	}
  
  	/**
  	 * Returns the setting name appended to namespace name(s) on a String
  	 * (for error messages)
  	 * @param name Name of the setting
  	 * @return the setting name appended to namespace name(s) on a String
  	 */
  	private String getPropertyNamesString(String name) {
  		if (this.secondaryNamespace != null) {
  			return "'"+ this.secondaryNamespace + "." + name + "' nor '" +
  				this.namespace + "." + name + "'";
  		}
  		else if (this.namespace != null){
  			return "'" + this.namespace + "." + name + "'";
  		}
  		else {
  			return "'" + name + "'";
  		}
  	}
  
  	/**
  	 * Returns a double-valued setting
  	 * @param name Name of the setting to get
  	 * @return Value of the setting as a double
  	 */
  	public double getDouble(String name) {
  		return parseDouble(getSetting(name), name);
  	}
  
  	/**
  	 * Returns a double-valued setting, or the default value if the given
  	 * setting does not exist
  	 * @param name Name of the setting to get
  	 * @param defaultValue The value to return if the setting doesn't exist
  	 * @return Value of the setting as a double (or the default value)
  	 */
  	public double getDouble(String name, double defaultValue) {
  		return parseDouble(getSetting(name, ""+defaultValue), name);
  	}
  
  	/**
  	 * Parses a double value from a String valued setting. Supports
  	 * kilo (k), mega (M) and giga (G) suffixes.
  	 * @param value String value to parse
  	 * @param setting The setting where this value was from (for error msgs)
  	 * @return The value as a double
  	 * @throws SettingsError if the value wasn't a numeric value
  	 * (or the suffix wasn't recognized)
  	 */
  	private double parseDouble(String value, String setting) {
  		double number;
  		int multiplier = getMultiplier(value);
  
  		if (multiplier > 1) { // take the suffix away before parsing
  			value = value.replaceAll("[^\\d.]","");
  			//replaceAll removes everything which is not a digit or point
  		}
  
  		try {
  			number = Double.parseDouble(value) * multiplier;
  		} catch (NumberFormatException e) {
  			throw new SettingsError("Invalid numeric setting '" + value +
  					"' for '" + setting +"'\n" + e.getMessage());
  		}
  		return number;
  	}
  	
  	/**
  	 * Parses a long value from a String valued setting. Supports
  	 * kilo (k), mega (M) and giga (G) suffixes.
  	 * @param value String value to parse
  	 * @param setting The setting where this value was from (for error msgs)
  	 * @return The value as a long
  	 * @throws SettingsError if the value wasn't a numeric value
  	 * (or the suffix wasn't recognized)
  	 */
  	private long parseLong(String value, String setting) {
  		long number;
  		int multiplier = getMultiplier(value);
  
  		if (multiplier > 1) { // take the suffix away before parsing
  			value = value.replaceAll("[^\\d.]","");
  		}
  
  		try {
  			number = Long.parseLong(value) * multiplier;
  		} catch (NumberFormatException e) {
  			throw new SettingsError("Invalid numeric setting '" + value +
  					"' for '" + setting +"'\n" + e.getMessage());
  		}
  		return number;
  	}
  	
  	/**
  	 * Parses the multiplier suffix from numeric setting
  	 * @param value The setting value
  	 * @return The muliplier as a number
  	 */
  	private int getMultiplier(String value) {
  		value = value.trim();
  		
  		if (value.endsWith("k")) {
  			return 1000;
  		}
  		else if (value.endsWith("M")) {
  			return 1000000;
  		}
  		else if (value.endsWith("G")) {
  			return 1000000000;
  		}
  		else if (value.endsWith("kiB")) {
  			//2^10
  			return 1024;
  		}
  		else if (value.endsWith("MiB")) {
  			//2^20
  			return 1048576;
  		}
  		else if (value.endsWith("GiB")) {
  			//2^30
  			return 1073741824;
  		} else {
  			// no multiplier
  			return 1;
  		}
  	}
  
  	/**
  	 * Returns a CSV setting. Value part of the setting must be a list of
  	 * comma separated values. Whitespace between values is trimmed away.
  	 * @param name Name of the setting
  	 * @return Array of values that were comma-separated
  	 * @throws SettingsError if something went wrong with reading
  	 */
  	public String[] getCsvSetting(String name) {
  		ArrayList<String> values = new ArrayList<String>();
  		String csv = getSetting(name);
  		Scanner s = new Scanner(csv);
  		s.useDelimiter(",");
  
  		while (s.hasNext()) {
  			values.add(s.next().trim());
  		}
  
  		s.close();
  		return values.toArray(new String[0]);
  	}
  
  	/**
  	 * Returns a CSV setting containing expected amount of values.
  	 * Value part of the setting must be a list of
  	 * comma separated values. Whitespace between values is trimmed away.
  	 * @param name Name of the setting
  	 * @param expectedCount how many values are expected
  	 * @return Array of values that were comma-separated
  	 * @throws SettingsError if something went wrong with reading or didn't
  	 * read the expected amount of values.
  	 */
  	public String[] getCsvSetting(String name, int expectedCount) {
  		String[] values = getCsvSetting(name);
  
  		if (values.length != expectedCount) {
  			throw new SettingsError("Read unexpected amount (" + values.length +
  					") of comma separated values for setting '"
  					+ name + "' (expected " + expectedCount + ")");
  		}
  
  		return values;
  	}
  
  	/**
  	 * Returns an array of CSV setting double values containing expected
  	 * amount of values.
  	 * @param name Name of the setting
  	 * @param expectedCount how many values are expected
  	 * @return Array of values that were comma-separated
  	 * @see #getCsvSetting(String, int)
  	 */
  	public double[] getCsvDoubles(String name, int expectedCount) {
  		return parseDoubles(getCsvSetting(name, expectedCount),name);
  	}
  
  	/**
  	 * Returns an array of CSV setting double values.
  	 * @param name Name of the setting
  	 * @return Array of values that were comma-separated
  	 * @see #getCsvSetting(String)
  	 */
  	public double[] getCsvDoubles(String name) {
  		return parseDoubles(getCsvSetting(name), name);
  	}
  
  	/**
  	 * Parses a double array out of a String array
  	 * @param strings The array of strings containing double values
  	 * @param name Name of the setting
  	 * @return Array of double values parsed from the string values
  	 */
  	private double[] parseDoubles(String[] strings, String name) {
  		double[] values = new double[strings.length];
  		for (int i=0; i<values.length; i++) {
  			values[i] = parseDouble(strings[i], name);
  		}
  		return values;
  	}
  
  	/**
  	 * Returns an array of CSV setting integer values
  	 * @param name Name of the setting
  	 * @param expectedCount how many values are expected
  	 * @return Array of values that were comma-separated
  	 * @see #getCsvSetting(String, int)
  	 */
  	public int[] getCsvInts(String name, int expectedCount) {
  		return convertToInts(getCsvDoubles(name, expectedCount), name);
  	}
  
  	/**
  	 * Returns an array of CSV setting integer values
  	 * @param name Name of the setting
  	 * @return Array of values that were comma-separated
  	 * @see #getCsvSetting(String, int)
  	 */
  	public int[] getCsvInts(String name) {
  		return convertToInts(getCsvDoubles(name), name);
  	}
  
  	/**
  	 * Returns comma-separated ranges (e.g., "3-5, 17-20, 15")
  	 * @param name Name of the setting
  	 * @return Array of ranges that were comma-separated in the setting
  	 * @throws SettingsError if something went wrong with reading
  	 */
  	public Range[] getCsvRanges(String name) {
  		String[] strRanges = getCsvSetting(name);
  		Range[] ranges = new Range[strRanges.length];
  
  		try {
  			for (int i=0; i < strRanges.length; i++) {
  				ranges[i] = new Range(strRanges[i]);
  			}
  		} catch (NumberFormatException nfe) {
  			throw new SettingsError("Invalid numeric range value in " +
  					name, nfe);
  		}
  
  		return ranges;
  	}
  
  	/**
  	 * Returns an integer-valued setting
  	 * @param name Name of the setting to get
  	 * @return Value of the setting as an integer
  	 */
  	public int getInt(String name) {
  		return convertToInt(getDouble(name), name);
  	}
  	
  	/**
  	 * Returns a long-valued setting
  	 * @param name Name of the setting to get
  	 * @return Value of the setting as an integer
  	 */
  	public long getLong(String name) {
  		return parseLong(getSetting(name), name);
  	}
  
  	/**
  	 * Returns an integer-valued setting, or the default value if the
  	 * setting does not exist
  	 * @param name Name of the setting to get
  	 * @param defaultValue The value to return if the setting didn't exist
  	 * @return Value of the setting as an integer
  	 */
  	public int getInt(String name, int defaultValue) {
  		return convertToInt(getDouble(name, defaultValue), name);
  	}
  
  	/**
  	 * Converts a double value that is supposedly equal to an integer value
  	 * to an integer value.
  	 * @param doubleValue The double value to convert
  	 * @param name Name of the setting where this value is from (for
  	 * SettingsError)
  	 * @return The integer value
  	 * @throws SettingsError if the double value was not equal to any integer
  	 * value
  	 */
  	private int convertToInt(double doubleValue, String name) {
  		int number = (int)doubleValue;
  
  		if (number != doubleValue) {
  			throw new SettingsError("Expected integer value for setting '" +
  					name + "' " + " got '" + doubleValue + "'");
  		}
  		return number;
  	}
  
  	/**
  	 * Converts an array of double values to int values using
  	 * {@link #convertToInt(double, String)}.
  	 * @param doubleValues The double valued array
  	 * @param name Name of the setting where this value is from (for
  	 * SettingsError)
  	 * @return Array of integer values
  	 * @see #convertToInt(double, String)
  	 */
  	private int[] convertToInts(double [] doubleValues, String name) {
  		int[] values = new int[doubleValues.length];
  		for (int i=0; i<values.length; i++) {
  			values[i] = convertToInt(doubleValues[i], name);
  		}
  		return values;
  	}
  
  	/**
  	 * Returns a boolean-valued setting
  	 * @param name Name of the setting to get
  	 * @return True if the settings value was either "true" (case ignored)
  	 *  or "1", false is the settings value was either "false" (case ignored)
  	 *  or "0".
  	 *  @throws SettingsError if the value wasn't any recognized value
  	 *  @see #getSetting(String)
  	 */
  	public boolean getBoolean(String name) {
  		String stringValue = getSetting(name);
  		boolean value;
  
  		if (stringValue.equalsIgnoreCase("true") ||
  				stringValue.equals("1")) {
  			value = true;
  		}
  		else if (stringValue.equalsIgnoreCase("false") ||
  				stringValue.equals("0")) {
  			value = false;
  		}
  		else {
  			throw new SettingsError("Not a boolean value: '"+stringValue+
  					"' for setting " + name);
  		}
  
  		return value;
  	}
  
  	/**
  	 * Returns the given boolean setting if it exists, or defaultValue if the
  	 * setting does not exist
  	 * @param name The name of the setting
  	 * @param defaultValue The value to return if the given setting didn't exist
  	 * @return The setting value or the default value if the setting didn't
  	 * exist
  	 */
  	public boolean getBoolean(String name, boolean defaultValue) {
  		if (!contains(name)) {
  			return defaultValue;
  		} else {
  			return getBoolean(name);
  		}
  	}
  
  	/**
  	 * Returns an ArithmeticCondition setting. See {@link ArithmeticCondition}
  	 * @param name Name of the setting to get
  	 * @return ArithmeticCondition from the setting
  	 * @throws SettingsError if the value wasn't a valid arithmetic expression
  	 */
  	public ArithmeticCondition getCondition(String name) {
  		return new ArithmeticCondition(getSetting(name));
  	}
  
  	/**
  	 * Creates (and dynamically loads the class of) an object that
  	 * initializes itself using the settings in this Settings object
  	 * (given as the only parameter to the constructor).
  	 * @param className Name of the class of the object
  	 * @return Initialized object
  	 * @throws SettingsError if object couldn't be created
  	 */
  	public Object createIntializedObject(String className) {
  		Class<?>[] argsClass = {Settings.class};
  		Object[] args = {this};
  
  		return loadObject(className, argsClass, args);
  	}
  
  	/**
  	 * Creates (and dynamically loads the class of) an object using the
  	 * constructor without any parameters.
  	 * @param className Name of the class of the object
  	 * @return Initialized object
  	 * @throws SettingsError if object couldn't be created
  	 */
  	public Object createObject(String className) {
  		return loadObject(className, null, null);
  	}
  
  	/**
  	 * Dynamically loads and creates an object.
  	 * @param className Name of the class of the object
  	 * @param argsClass Class(es) of the argument(s) or null if no-argument
  	 * constructor should be called
  	 * @param args Argument(s)
  	 * @return The new object
  	 * @throws SettingsError if object couldn't be created
  	 */
  	private Object loadObject(String className, Class<?>[] argsClass,
  			Object[] args) {
  		Object o = null;
  		Class<?> objClass = getClass(className);
  		Constructor<?> constructor;
  
  		try {
  			if (argsClass != null) { // use a specific constructor
  				constructor = objClass.getConstructor((Class[])argsClass);
  				o = constructor.newInstance(args);
  			}
  			else { // call empty constructor
  				o = objClass.newInstance();
  			}
  		} catch (SecurityException e) {
  			e.printStackTrace();
  			throw new SettingsError("Fatal exception " + e, e);
  		} catch (IllegalArgumentException e) {
  			e.printStackTrace();
  			throw new SettingsError("Fatal exception " + e, e);
  		} catch (NoSuchMethodException e) {
  			throw new SettingsError("Class '" + className +
  					"' doesn't have a suitable constructor", e);
  		} catch (InstantiationException e) {
  			throw new SettingsError("Can't create an instance of '" +
  					className + "'", e);
  		} catch (IllegalAccessException e) {
  			e.printStackTrace();
  			throw new SettingsError("Fatal exception " + e, e);
  		} catch (InvocationTargetException e) {
  			// this exception occurs if initialization of the object fails
  			if (e.getCause() instanceof SettingsError) {
  				throw (SettingsError)e.getCause();
  			}
  			else {
  				e.printStackTrace();
  				throw new SimError("Couldn't create settings-accepting object"+
  					" for '" + className + "'\n" + "cause: " + e.getCause(), e);
  			}
  		}
  
  		return o;
  	}
  
  	/**
  	 * Returns a Class object for the name of class of throws SettingsError
  	 * if such class wasn't found.
  	 * @param name Full name of the class (including package name)
  	 * @return A Class object of that class
  	 * @throws SettingsError if such class wasn't found or couldn't be loaded
  	 */
  	private Class<?> getClass(String name) {
  		String className = name;
  		Class<?> c;
  
  		try {
  			c = Class.forName(className);
  		} catch (ClassNotFoundException e) {
  			throw new SettingsError("Couldn't find class '" + className + "'"+
  					"\n" + e.getMessage(),e);
  		}
  
  		return c;
  	}
  
  	/**
  	 * Fills a String formatted in a special way with values from Settings.
  	 * String can contain (fully qualified) setting names surrounded by
  	 * delimiters (see {@link #FILL_DELIMITER}). Values for those settings
  	 * are retrieved and filled in the place of place holders.
  	 * @param input The input string that may contain value requests
  	 * @return A string filled with requested values (or the original string
  	 * if no requests were found)
  	 * @throws SettingsError if such settings were not found
  	 */
  	public String valueFillString(String input) {
  		if (!input.contains(FILL_DELIMITER)) {
  			return input;	// nothing to fill
  		}
  
  		Settings s = new Settings(); // don't use any namespace
  		String result = "";
  		Scanner scan = new Scanner(input);
  		scan.useDelimiter(FILL_DELIMITER);
  
  		if (input.startsWith(FILL_DELIMITER)) {
  			result += s.getSetting(scan.next());
  		}
  
  		while(scan.hasNext()) {
  			result += scan.next();
  			if (!scan.hasNext()) {
  				break;
  			}
  			result += s.getSetting(scan.next());
  		}
  		
  		scan.close();
  		return result;
  	}
  
  	/**
  	 * Returns a String representation of the stored settings
  	 * @return a String representation of the stored settings
  	 */
  	public String toString() {
  		return props.toString();
  	}
  
  }
  