/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.utils.incubator;
import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* A typed map, disallowing wrong types after initial put operations.
* Null is not allowed, neither as key nor as value.
*
* @param <KeyType> The key of the data structure
* @version $LastChangedRevision: 0$
* @author Arne Bachmann
*/
public class TypedProperties<KeyType extends Serializable> {
/**
* Contains all values.
*/
private Map<KeyType, Serializable> values = new LinkedHashMap<KeyType, Serializable>();
/**
* Contains the type constraints of all values.
*/
private Map<KeyType, Class<? extends Serializable>> types = new LinkedHashMap<KeyType, Class<? extends Serializable>>();
/**
* Number of elements in the properties map.
*
* @return The number of elements
*/
public int size() {
return values.size();
}
/**
* Put a typed value into the map.
*
* @param propertyName The name (key) of the property
* @param value The value to put
* @exception IllegalStateException If the provided type differs from an already contained
* value with the same key
*/
public void put(final KeyType propertyName, final Serializable value) throws IllegalStateException {
put(propertyName, value, /* raise exception if type differs */ true);
}
/**
* Put a typed value into the map.
*
* @param propertyName The name (key) of the property
* @param value The value to put
* @param throwException If existing type differs from provided one, true means throwing an
* exception, false overwrites
* @exception IllegalStateException If the provided type differs from an already contained
* value with the same key
*/
protected void put(final KeyType propertyName, final Serializable value, final boolean throwException) throws IllegalStateException {
assert propertyName != null;
assert value != null;
final Class<? extends Serializable> newType = value.getClass();
if (throwException) {
final Class<? extends Serializable> type = types.get(propertyName);
if (type != null) {
if ((type != newType) && (!(type.isAssignableFrom(newType)))) { // incompatible type
throw new IllegalStateException("The provided type differs from the type already contained or not a subclass");
}
} else {
types.put(propertyName, newType);
}
} else {
types.put(propertyName, newType);
}
values.put(propertyName, value);
}
/**
* If you want to ensure the correct type for a key, but no value is available yet, use this method.
* The value will be null, but the type will be enforced with every set
*
* @param propertyName The key
* @param type The desired type
*/
public void setType(final KeyType propertyName, final Class<? extends Serializable> type) {
assert propertyName != null;
assert type != null;
values.put(propertyName, null);
types.put(propertyName, type);
}
/**
* Get the stored value of the expected type. If the stored type differs, we throw an exception
*
* @param propertyName The name of the property to get
* @param clazz The expected type
* @return The value or null if not found
* @exception IllegalStateException If the stored type differs from the requested one
* @param <U> type to which the property is restricted
*/
@SuppressWarnings("unchecked")
public <U extends Serializable> U get(final KeyType propertyName, final Class<U> clazz) throws IllegalStateException {
assert propertyName != null;
assert clazz != null;
final Class<? extends Serializable> type = types.get(propertyName);
if (type == null) {
return null;
}
if ((clazz != type) && !clazz.isAssignableFrom(type)) {
throw new IllegalStateException("The contained type differs from the type requested");
}
return (U) values.get(propertyName); // this cast is necessary, but safe
}
/**
* Check property existence.
*
* @param propertyName The name to check
* @return True if contained
*/
public boolean containsKey(final KeyType propertyName) {
return values.containsKey(propertyName);
}
/**
* Remove the entry.
*
* @param propertyName The entry name to remove
* @return True if the entry name was found
*/
public boolean remove(final KeyType propertyName) {
final boolean found = (values.remove(propertyName) != null) || (propertyName == null);
types.remove(propertyName);
return found;
}
/**
* Remove the entry, if it has the right type, otherwise return false.
*
* @param propertyName The entry name to remove
* @param clazz The desired type to remove
* @return True if removed, false if not
*/
public boolean remove(final KeyType propertyName, final Class<? extends Serializable> clazz) {
if (!containsKey(propertyName) || !hasType(propertyName, clazz)) {
return false;
}
return remove(propertyName);
}
/**
* Check if the contained value is of the right type.
*
* @param propertyName The property to check
* @param clazz The assumed class type
* @return True if the contained class type is the same as the provided one
*/
public boolean hasType(final KeyType propertyName, final Class<? extends Serializable> clazz) {
final Class<? extends Serializable> type = types.get(propertyName);
if (type == null) {
throw new IllegalStateException("Property not contained");
}
return clazz.isAssignableFrom(type);
}
/**
* Get the contained type of the property.
*
* @param propertyName Name of the property
* @return The type of the property
*/
public Class<? extends Serializable> getType(final KeyType propertyName) {
return types.get(propertyName);
}
/**
* Return the keys of all properties in an ordered set.
*
* @return The set containing all keys, usable in foreach loops
*/
public Set<KeyType> keySet() {
return Collections.unmodifiableSet(values.keySet());
}
/**
* Return the set containing all values.
*
* @return The values set
*/
public Set<Entry<KeyType, Serializable>> valuesEntrySet() {
return Collections.unmodifiableSet(values.entrySet());
}
/**
* Return the set containing all types.
*
* @return The types set
*/
public Set<Entry<KeyType, Class<? extends Serializable>>> typesEntrySet() {
return Collections.unmodifiableSet(types.entrySet());
}
/**
* Empty the properties.
*/
public void clear() {
values.clear();
types.clear();
}
/**
* Add all entries in the map to the properties.
*
* @param map The entries to add
* @param disallowWrongTypes
* @exception IllegalStateException If disalloWrongTypes is true and if any entries had the
* wrong type (after adding all).
*/
protected void addAll(final Map<KeyType, Serializable> map, final boolean disallowWrongTypes) {
IllegalStateException exception = null;
for (final Entry<KeyType, Serializable> entry : map.entrySet()) {
try {
put(entry.getKey(), entry.getValue(), /* throw exception? */disallowWrongTypes);
} catch (final IllegalStateException e) {
exception = e;
}
}
if (exception != null) {
throw new IllegalStateException("At least one entry in the provided map had the wrong type. First occurrence: "
+ exception.getMessage());
}
}
/**
* Add all entries in the map to the properties, throwing an exception if some provided types
* had the wrong type (not overwriting existing values).
*
* @param map The entries to add
* @exception IllegalStateException If any entries had the wrong type (after adding all).
*/
public void addAll(final Map<KeyType, Serializable> map) throws IllegalStateException {
addAll(map, /* throw exception */true);
}
/**
* Add all entries in the map to the properties, overwriting existing ones.
*
* @param map The entries to add
*/
public void addAllOverwriting(final Map<KeyType, Serializable> map) {
addAll(map, /* throw exception */false);
}
/**
* Add all entries in the map to the properties.
*
* @param properties The entries to add
* @param disallowWrongTypes
* @exception IllegalStateException If disalloWrongTypes is true and if any entries had the
* wrong type (after adding all).
*/
protected void addAll(final TypedProperties<KeyType> properties, final boolean disallowWrongTypes) throws IllegalStateException {
IllegalStateException exception = null;
for (final KeyType propertyName: properties.keySet()) {
final Class<? extends Serializable> type = properties.getType(propertyName);
final Serializable value = (Serializable) properties.get(propertyName, type);
try {
get(propertyName, type);
} catch (final IllegalStateException e) {
if (disallowWrongTypes) {
exception = e;
continue;
}
}
put(propertyName, value, false); // no need to check here, we did it already by get
}
if (exception != null) {
throw new IllegalStateException("At least one entry in the provided properties had the wrong type. First occurrence: "
+ exception.getMessage());
}
}
/**
* Add all entries in the map to the properties, throwing an exception if some provided types
* had the wrong type (not overwriting existing values).
*
* @param properties The entries to add
* @exception IllegalStateException If any entries had the wrong type (after adding all).
*/
public void addAll(final TypedProperties<KeyType> properties) throws IllegalStateException {
addAll(properties, /* throw exception */true);
}
/**
* Add all entries in the map to the properties, overwriting existing ones.
*
* @param properties The entries to add
*/
public void addAllOverwriting(final TypedProperties<KeyType> properties) {
addAll(properties, /* throw exception */false);
}
/**
* Use this method to check if all properties necessary for e.g. an execution are provided and
* have the right types.
*
* @param typesToCheck The property name -> property type to check for
* @exception IllegalStateException If any entries has the wrong type
*/
public void hasValidTypes(final Map<KeyType, Class<? extends Serializable>> typesToCheck) throws IllegalStateException {
for (final Entry<KeyType, Class<? extends Serializable>> type: typesToCheck.entrySet()) {
if (!typesToCheck.containsKey(type.getKey())) {
throw new IllegalStateException("Missing property " + type.getKey() + " in execution context");
} else if (typesToCheck.get(type.getKey()) != type.getValue()) {
throw new IllegalStateException("Property " + type.getKey() + " has wrong type "
+ type.getClass().getTypeParameters().toString());
}
}
}
}