/******************************************************************************* * Trombone is a flexible text processing and analysis library used * primarily by Voyant Tools (voyant-tools.org). * * Copyright (©) 2007-2012 Stéfan Sinclair & Geoffrey Rockwell * * This file is part of Trombone. * * Trombone is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Trombone is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Trombone. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.voyanttools.trombone.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Map.Entry; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import edu.stanford.nlp.util.StringUtils; /** * This class encapsulates flexible parameters that map keys to values, except * that values can be retrieved as various data types (String, int, long) and * arrays of an array of Strings. an int value. * * @author Stéfan Sinclair */ @XStreamAlias("parameters") public class FlexibleParameters implements Cloneable, Serializable { /** * the serialization ID */ private static final long serialVersionUID = 2825799029392893403L; /* obsolete code, keep for now, see also CorpusTokenizer private Map<String, List<Object>> objectEntries = new HashMap<String, List<Object>>(); public synchronized void addObjectParameters(String key, List<Object> values) { if (key == null) { throw new NullPointerException("illegal key"); } if (values == null) { throw new NullPointerException("illegal values"); } if (values.size() < 1) { throw new IllegalArgumentException("illegal values"); } if (this.objectEntries.put(key, values) != null) { throw new IllegalArgumentException("key "+key+" already mapped"); } } public synchronized List<Object> getObjectParameters(String key) { if (key == null) { throw new NullPointerException("illegal key"); } final List<Object> values = this.objectEntries.get(key); if (values == null) { throw new IllegalArgumentException("key "+key+" is not mapped"); } return values; } */ /** * An internal {@link Map} to facilitate looking up of properties. */ private Map<String, List<String>> entries = new HashMap<String, List<String>>(); /** * Constructs a new instance of the {@link FlexibleParameters} class. */ public FlexibleParameters() { } /** * Constructs a new instance of the {@link FlexibleParameters} class and * parses the array of {@link String}s adhering to the {@code key=value} pattern. * * @param args the array of {@link String}s to add */ public FlexibleParameters(String[] args) { addParameters(args); } /** * Constructs a new instance of the {@link FlexibleParameters} class and * add the specified properties * @param properties the properties to add to add */ public FlexibleParameters(Properties properties) { addProperties(properties); } public static FlexibleParameters loadFlexibleParameters(File parametersFile) throws IOException { XStream xstream = new XStream(); InputStream in = null; FlexibleParameters parameters = new FlexibleParameters(); try { in = new FileInputStream(parametersFile); parameters = (FlexibleParameters) xstream.fromXML(in); } catch (FileNotFoundException e) { } finally { if (in!=null) { in.close(); } } return parameters; } public void saveFlexibleParameters(File file) throws IOException { OutputStream outputStream = null; outputStream = new FileOutputStream(file); Writer writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8")); XStream xStream = new XStream(); xStream.toXML(this, writer); if (outputStream!=null) { outputStream.close(); } } /** * Adds a parameter with a double value. Previously added values are not * discarded. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void addParameter(String key, double value) { addParameterWithObject(key, value); } /** * Adds a parameter with an int value. Previously added values are not * discarded. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void addParameter(String key, int value) { addParameterWithObject(key, value); } /** * Adds an array of int values for a parameter. Previously added values are * not discarded. * * @param key the key of the parameter * @param values the array of int values to add */ public synchronized void addParameter(String key, int[] values) { for (int v : values) { addParameter(key, v); } } /** * Adds a parameter with a long value. Previously added values are not * discarded. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void addParameter(String key, long value) { addParameterWithObject(key, value); } /** * Adds a parameter with a String value. Previously added values are not * discarded. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void addParameter(String key, String value) { addParameterWithObject(key, value); } /** * Adds an array of String values for a parameter. Previously added values * are not discarded. * * @param key the key of the parameter * @param values the array of {@link String}s as a value */ public synchronized void addParameter(String key, String[] values) { addParameterWithObjects(key, values); } /** * Adds parameters from an array of {@link String}s adhering to the {@code * key=value} pattern. Previously added values are not discarded. * * @param parameters an array of {@link String}s adhering to the {@code key=value} pattern */ public synchronized void addParameters(String[] parameters) { for (String parameter : parameters) { final String[] parts = parameter.split("=", 2); if (parts.length != 2) { throw new IllegalArgumentException(parameter+" is not a parameter"); } addParameter(parts[0], parts[1]); } } /** * Add the specified {@link Properties} * * @param properties the {@link Properties} to add */ public synchronized void addProperties(Properties properties) { if (properties == null) { throw new NullPointerException("illegal Properties"); } for (Entry<Object, Object> entry : properties.entrySet()) { addParameter((String) entry.getKey(), (String) entry.getValue()); } } /** * Add the specified {@link FlexibleParameters} * * @param properties the {@link FlexibleParameters} to add */ public synchronized void addProperties(FlexibleParameters properties) { if (properties == null) { throw new NullPointerException("illegal Properties"); } for (String key : properties.getKeys()) { this.entries.put(key, properties.entries.get(key)); } } /** * Add a single object to the map. Previously existing values are not * discarded. * * @param key the key for the map * @param value object to add */ private synchronized void addParameterWithObject(String key, Object value) { if (this.entries.containsKey(key) == false) { this.entries.put(key, new ArrayList<String>()); } this.entries.get(key).add(String.valueOf(value)); } /** * Add an array of objects to the map. Previously existing values are not * discarded. * * @param key the key for the map * @param values the array of objects */ private synchronized void addParameterWithObjects(String key, Object[] values) { if (this.entries.containsKey(key) == false) { this.entries.put(key, new ArrayList<String>()); } final List<String> vals = this.entries.get(key); for (Object val : values) { vals.add(String.valueOf(val)); } } @Override public synchronized FlexibleParameters clone() { FlexibleParameters params = new FlexibleParameters(); for (Map.Entry<String, List<String>> entry : entries.entrySet()) { params.setParameter(entry.getKey(), entry.getValue().toArray(new String[0])); } return params; } /** * Create a clone of this object. * * @return a new instance of {@link FlexibleParameters} */ public synchronized FlexibleParameters deepClone() { final FlexibleParameters clone = new FlexibleParameters(); for (Entry<String, List<String>> entry : this.entries.entrySet()) { final List<String> entryValueClone = new ArrayList<String>(); for (String s : entry.getValue()) { entryValueClone.add(new String(s)); } clone.entries.put(entry.getKey(), entryValueClone); } return clone; } /** * Determines if a parameter is defined with the specified key. * * @param key the key of the parameter * @return whether or not a parameter is defined with the specified key. */ public synchronized boolean containsKey(String key) { int counter = -1; while (true) { final String lookupKey = key + (counter < 0 ? "" : String.valueOf(counter)); if (this.entries.containsKey(lookupKey)) { return true; } else if (counter > 1) { break; } counter++; } return false; } /** * Get a {@link Set} of keys used in these parameters * * @return a {@link Set} of keys used in these parameters */ public synchronized Set<String> getKeys() { return this.entries.keySet(); } /** * Returns a {@link Properties} view of all the properties. If a * key does not exist, a value of null is returned; if a key has multiple * values, only the last one is used. * @return a {@link Properties} view */ public synchronized Properties getAsProperties() { return getAsProperties(this.entries.keySet()); } /** * Returns a {@link Properties} view that includes the specified keys. If a * key does not exist, a value of null is returned; if a key has multiple * values, only the last one is used. * @param keys a list of keys for which to retrieve values * @return a {@link Properties} view */ public synchronized Properties getAsProperties(String... keys) { return getAsProperties(Arrays.asList(keys)); } /** * Returns a {@link Properties} view that includes the specified keys. If a * key does not exist, a value of null is returned; if a key has multiple * values, only the last one is used. * @param keys a list of keys for which to retrieve values * @return a {@link Properties} view */ public synchronized Properties getAsProperties(Collection<String> keys) { final Properties properties = new Properties(); for (String key : keys) { properties.put(key, getParameterValue(key)); } return properties; } /** * Returns a {@link String} view that can be used as a URL query. Each value * is URL encoded (in UTF-8) and a name can have multiple values: * <code>type=one&type=two</code * @return a {@link String} view * @throws UnsupportedEncodingException in the extremely unlikely event that the encoding isn't supported */ public synchronized String getAsQueryString() throws UnsupportedEncodingException { final StringBuilder query = new StringBuilder(); for (Map.Entry<String, List<String>> entry : this.entries.entrySet()) { for (String value : entry.getValue()) { if (query.length()>0) query.append("&"); query.append(entry.getKey()).append("=").append(URLEncoder.encode(value, "UTF-8")); } } return query.toString(); } /** * Gets the parameter value as an float for the specified key. If the key is * not defined, then a value of 0 is returned. * * @param key the key for the parameter value * @return the paramater value as a float */ public synchronized float getParameterFloatValue(String key) { final String value = getParameterValue(key); return value == null || value.isEmpty() ? 0f : Float.parseFloat(value); } /** * Gets the parameter value as an float for the specified key, or return the * specified default value if the key does not exist. * * @param key the key for the parameter value * @param defaultValue the default value to use if the parameter is not defined * @return the paramater value as a float */ public synchronized float getParameterFloatValue(String key, float defaultValue) { final String value = getParameterValue(key); return value == null || value.isEmpty() ? defaultValue : Float.parseFloat(value); } /** * Gets the parameter value as an int for the specified key. If the key is * not defined, then a value of 0 is returned. * * @param key the key for the parameter value * @return the paramater value as an int */ public synchronized int getParameterIntValue(String key) { final String value = getParameterValue(key); return value == null || value.isEmpty() ? 0 : Integer.parseInt(value); } /** * Gets the parameter value as an int for the specified key, or return the * specified default value if the key does not exist. * * @param key the key for the parameter value * @param defaultValue the default value to use if the parameter is not set * @return the paramater value as an int */ public synchronized int getParameterIntValue(String key, int defaultValue) { final String value = getParameterValue(key); return value == null || value.length() == 0 ? defaultValue : Integer.parseInt(value); } /** * Gets the parameter value as an arrat of int values. If the key does not * exist, an empty array of ints is returned * * @param key the key for the parameter value * @return the paramater value as an int */ public synchronized int[] getParameterIntValues(String key) { final String[] values = getParameterValues(key); final int[] ints = new int[values.length]; for (int i = 0, size = values.length; i < size; i++) { ints[i] = values[i].isEmpty() ? 0 : Integer.valueOf(values[i]); } return ints; } /** * Gets the parameter value as a long for the specified key. If the key is * not defined, then a value of 0 is returned. * @param key the key for the parameter value * @return the paramater value as a long */ public synchronized long getParameterLongValue(String key) { final String value = getParameterValue(key); return value == null || value.isEmpty() ? 0l : Long.valueOf(value); } /** * Gets the parameter value as a long for the specified key, or return the * specified default value if the key does not exist. * * @param key the key for the parameter value * @param defaultValue the default value to use if the parameter is not set * @return the paramater value as a long */ public synchronized long getParameterLongValue(String key, long defaultValue) { final String value = getParameterValue(key); return value == null ? defaultValue : Long.valueOf(value); } /** * Gets the parameter value for the specified key. If the key is not defined, * then null is returned. * * @param key the key for the parameter value * @return the paramater value as a {@link String} */ public synchronized String getParameterValue(String key) { final String[] values = getParameterValues(key); // TODO: would it be more symmetrical to return "", more like the other methods? return values.length == 0 ? null : values[0]; } /** * Gets the parameter value for the specified key, or return the specified * default value if the key does not exist. * * @param key the key for the parameter value * @param defaultValue the default value to use if the parameter is not set * @return the paramater value as a {@link String} */ public synchronized String getParameterValue(String key, String defaultValue) { final String value = getParameterValue(key); return value == null ? defaultValue : value; } /** * Gets an array of {@link String}s for the specified key. If the key is not * defined, then an empty array of Strings is returned. * * @param key the key for the parameter * @return an array of {@link String}s for the specified key */ public synchronized String[] getParameterValues(String key) { return getParameterValues(key, new String[0]); } /** * Gets an array of {@link String}s for the specified key. If the key is not * defined, then an empty array of Strings is returned. * * @param key the key for the parameter * @param defaultValue the default String array * @return an array of {@link String}s for the specified key */ public synchronized String[] getParameterValues(String key, String[] defaultValue) { final List<String> values = this.entries.get(key); return values == null ? defaultValue : (String[]) values.toArray(new String[] {}); } /** * Gets a boolean value for the key (based on the first value for that key). * This will return false if the key is not defined, if the value is empty * or if the value is "false" or 0. * @param key the key for the parameter * @return a boolean for the specified key */ public synchronized boolean getParameterBooleanValue(String key) { final String val = getParameterValue(key); if (val==null || val.equals("false") || val.equals("0") || val.isEmpty()) { return false; } return true; } /** * Sets the specified parameter while removing any existing values that * might exist. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void setParameter(String key, double value) { setParameterWithObject(key, value); } /** * Sets the specified parameter while removing any existing values that * might exist. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void setParameter(String key, int value) { setParameterWithObject(key, value); } /** * Sets the specified parameter while removing any existing values that might exist. * @param key the key of the parameter * @param values the value of the parameter */ public synchronized void setParameter(String key, int[] values) { this.entries.remove(key); addParameter(key, values); } /** * Sets the specified parameter while removing any existing values that * might exist. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void setParameter(String key, long value) { setParameterWithObject(key, value); } /** * Sets the specified parameter while removing any existing values that * might exist. * * @param key the key of the parameter * @param value the value of the parameter */ public synchronized void setParameter(String key, String value) { setParameterWithObject(key, value); } /** * Sets the specified parameter while removing any existing values that might exist. * @param key the key of the parameter * @param values the value of the parameter */ public synchronized void setParameter(String key, String[] values) { this.entries.remove(key); addParameter(key, values); } /** * Set the parameter, removing any previously existing values. * @param key the key of the parameter * @param value the Object to set */ private synchronized void setParameterWithObject(String key, Object value) { this.entries.remove(key); addParameterWithObject(key, value); } @Override public synchronized String toString() { // we'll create our own string to have a reliable ordering of map entries List<String> keys = new ArrayList<String>(); keys.addAll(entries.keySet()); Collections.sort(keys); StringBuilder sb = new StringBuilder(); for (String key : keys) { sb.append("{").append(key).append(":"); String[] strings = getParameterValues(key); if (strings.length==1) { sb.append(strings[0]); } else { sb.append("[").append(StringUtils.join(strings, ",")).append("]"); } sb.append("}"); } return sb.toString(); } /** * Remove the parameter specified by this key. * * @param key of the parameter to remove */ public synchronized void removeParameter(String key) { this.entries.remove(key); } /** * Get the number of parameters (keys). * * @return the number of parameters (keys) */ public synchronized int getKeyCount() { return this.entries.size(); } public boolean equals(FlexibleParameters parameters) { for (String key : getKeys()) { String[] values = getParameterValues(key); if (parameters.containsKey(key)) { String[] vals = parameters.getParameterValues(key); if (values.length == vals.length) { for (int i=0; i<values.length; i++) { if (values[i].equals(vals[i])==false) { return false; } } } else { return false; } } else { return false; } } return true; } public void clear() { entries.clear(); } }