/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.collect.io;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.opengamma.strata.collect.ArgChecker;
/**
* A map of key-value properties.
* <p>
* This class represents a map of key to value.
* Multiple values may be associated with each key.
* <p>
* This class is generally created by reading an INI or properties file.
* See {@link IniFile} and {@link PropertiesFile}.
*/
public final class PropertySet {
// this class is common between IniFile and PropertiesFile
/**
* The empty instance.
*/
private static final PropertySet EMPTY = new PropertySet(ImmutableListMultimap.of());
/**
* The key-value pairs.
*/
private final ImmutableListMultimap<String, String> keyValueMap;
//-------------------------------------------------------------------------
/**
* Obtains an empty property set.
* <p>
* The result contains no properties.
*
* @return an empty property set
*/
public static PropertySet empty() {
return EMPTY;
}
/**
* Obtains an instance from a map.
* <p>
* The returned instance will have one value for each key.
*
* @param keyValues the key-values to create the instance with
* @return the property set
*/
public static PropertySet of(Map<String, String> keyValues) {
ArgChecker.notNull(keyValues, "keyValues");
ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
for (Entry<String, String> entry : keyValues.entrySet()) {
builder.put(entry);
}
return new PropertySet(builder.build());
}
/**
* Obtains an instance from a map allowing for multiple values for each key.
* <p>
* The returned instance may have more than one value for each key.
*
* @param keyValues the key-values to create the instance with
* @return the property set
*/
public static PropertySet of(Multimap<String, String> keyValues) {
ArgChecker.notNull(keyValues, "keyValues");
return new PropertySet(ImmutableListMultimap.copyOf(keyValues));
}
//-------------------------------------------------------------------------
/**
* Restricted constructor.
*
* @param keyValues the key-value pairs
*/
private PropertySet(ImmutableListMultimap<String, String> keyValues) {
this.keyValueMap = keyValues;
}
//-------------------------------------------------------------------------
/**
* Returns the set of keys of this property set.
* <p>
* The iteration order of the map matches that of the input data.
*
* @return the set of keys
*/
public ImmutableSet<String> keys() {
return ImmutableSet.copyOf(keyValueMap.keySet());
}
/**
* Returns the property set as a multimap.
* <p>
* The iteration order of the map matches that of the input data.
*
* @return the key-value map
*/
public ImmutableListMultimap<String, String> asMultimap() {
return keyValueMap;
}
/**
* Returns the property set as a map, throwing an exception if any key has multiple values.
* <p>
* The iteration order of the map matches that of the input data.
*
* @return the key-value map
*/
public ImmutableMap<String, String> asMap() {
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (String key : keys()) {
builder.put(key, value(key));
}
return builder.build();
}
//-------------------------------------------------------------------------
/**
* Checks if this property set is empty.
*
* @return true if the set is empty
*/
public boolean isEmpty() {
return keyValueMap.isEmpty();
}
/**
* Checks if this property set contains the specified key.
*
* @param key the key name
* @return true if the key exists
*/
public boolean contains(String key) {
ArgChecker.notNull(key, "key");
return keyValueMap.containsKey(key);
}
/**
* Gets a single value from this property set.
* <p>
* This returns the value associated with the specified key.
* If more than one value, or no value, is associated with the key an exception is thrown.
*
* @param key the key name
* @return the value
* @throws IllegalArgumentException if the key does not exist, or if more than one value is associated
*/
public String value(String key) {
ArgChecker.notNull(key, "key");
ImmutableList<String> values = keyValueMap.get(key);
if (values.size() == 0) {
throw new IllegalArgumentException("Unknown key: " + key);
}
if (values.size() > 1) {
throw new IllegalArgumentException("Multiple values for key: " + key);
}
return values.get(0);
}
/**
* Gets the list of values associated with the specified key.
* <p>
* A key-values instance may contain multiple values for each key.
* This method returns that list of values.
* The iteration order of the map matches that of the input data.
* The returned list may be empty.
*
* @param key the key name
* @return the list of values associated with the key
*/
public ImmutableList<String> valueList(String key) {
ArgChecker.notNull(key, "key");
return MoreObjects.firstNonNull(keyValueMap.get(key), ImmutableList.<String>of());
}
//-------------------------------------------------------------------------
/**
* Combines this property set with another.
* <p>
* This property set takes precedence.
* Any order of any additional keys will be retained, with those keys located after the base set of keys.
*
* @param other the other property set
* @return the combined property set
*/
public PropertySet combinedWith(PropertySet other) {
ArgChecker.notNull(other, "other");
if (other.isEmpty()) {
return this;
}
if (isEmpty()) {
return other;
}
// cannot use ArrayListMultiMap as it does not retain the order of the keys
// whereas ImmutableListMultimap does retain the order of the keys
ImmutableListMultimap.Builder<String, String> map = ImmutableListMultimap.builder();
map.putAll(this.keyValueMap);
for (String key : other.keyValueMap.keySet()) {
if (!this.contains(key)) {
map.putAll(key, other.valueList(key));
}
}
return new PropertySet(map.build());
}
//-------------------------------------------------------------------------
/**
* Overrides this property set with another.
* <p>
* The specified property set takes precedence.
* The order of any existing keys will be retained, with the value replaced.
* Any order of any additional keys will be retained, with those keys located after the base set of keys.
*
* @param other the other property set
* @return the combined property set
*/
public PropertySet overrideWith(PropertySet other) {
ArgChecker.notNull(other, "other");
if (other.isEmpty()) {
return this;
}
if (isEmpty()) {
return other;
}
// cannot use ArrayListMultiMap as it does not retain the order of the keys
// whereas ImmutableListMultimap does retain the order of the keys
ImmutableListMultimap.Builder<String, String> map = ImmutableListMultimap.builder();
for (String key : this.keyValueMap.keySet()) {
if (other.contains(key)) {
map.putAll(key, other.valueList(key));
} else {
map.putAll(key, this.valueList(key));
}
}
for (String key : other.keyValueMap.keySet()) {
if (!this.contains(key)) {
map.putAll(key, other.valueList(key));
}
}
return new PropertySet(map.build());
}
//-------------------------------------------------------------------------
/**
* Checks if this property set equals another.
* <p>
* The comparison checks the content.
*
* @param obj the other section, null returns false
* @return true if equal
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof PropertySet) {
return keyValueMap.equals(((PropertySet) obj).keyValueMap);
}
return false;
}
/**
* Returns a suitable hash code for the property set.
*
* @return the hash code
*/
@Override
public int hashCode() {
return keyValueMap.hashCode();
}
/**
* Returns a string describing the property set.
*
* @return the descriptive string
*/
@Override
public String toString() {
return keyValueMap.toString();
}
}