/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.config.core;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is a wrapper for configuration settings of {@link Thing}s.
*
* @author Dennis Nobel - Initial API and contribution, Changed Logging
* @author Kai Kreuzer - added constructors and normalization
* @author Gerhard Riegler - added converting BigDecimal values to the type of the configuration class field
* @author Chris Jackson - fix concurrent modification exception when removing properties
*/
public class Configuration {
private final Map<String, Object> properties;
private transient final Logger logger = LoggerFactory.getLogger(Configuration.class);
public Configuration() {
this(null);
}
/**
* Create a new configuration.
*
* @param properties the properties the configuration should be filled. If null, an empty configuration is created.
*/
public Configuration(Map<String, Object> properties) {
this.properties = properties == null ? new HashMap<String, Object>() : ConfigUtil.normalizeTypes(properties);
}
public <T> T as(Class<T> configurationClass) {
synchronized (this) {
T configuration = null;
try {
configuration = configurationClass.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
logger.error("Could not create configuration instance: " + ex.getMessage(), ex);
return null;
}
List<Field> fields = getAllFields(configurationClass);
for (Field field : fields) {
String fieldName = field.getName();
String typeName = field.getType().getSimpleName();
Object value = properties.get(fieldName);
if (value == null && field.getType().isPrimitive()) {
logger.debug("Skipping field '{}', because it's primitive data type and value is not set",
fieldName);
continue;
}
try {
if (value != null && value instanceof BigDecimal && !typeName.equals("BigDecimal")) {
BigDecimal bdValue = (BigDecimal) value;
if (typeName.equalsIgnoreCase("Float")) {
value = bdValue.floatValue();
} else if (typeName.equalsIgnoreCase("Double")) {
value = bdValue.doubleValue();
} else if (typeName.equalsIgnoreCase("Long")) {
value = bdValue.longValue();
} else if (typeName.equalsIgnoreCase("Integer") || typeName.equalsIgnoreCase("int")) {
value = bdValue.intValue();
}
}
if (value != null) {
logger.debug("Setting value ({}) {} to field '{}' in configuration class {}", typeName, value,
fieldName, configurationClass.getName());
FieldUtils.writeField(configuration, fieldName, value, true);
}
} catch (Exception ex) {
logger.warn("Could not set field value for field '" + fieldName + "': " + ex.getMessage(), ex);
}
}
return configuration;
}
}
private List<Field> getAllFields(Class<?> clazz) {
List<Field> fields = new ArrayList<Field>();
while (clazz != null) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
return fields;
}
/**
* Check if the given key is present in the configuration.
*
* @param key the key that existence should be checked
* @return true if the key is part of the configuration, false if not
*/
public boolean containsKey(String key) {
synchronized (this) {
return properties.containsKey(key);
}
}
/**
* @deprecated Use {@link #get(String)} instead.
*/
@Deprecated
public Object get(Object key) {
return this.get((String) key);
}
public Object get(String key) {
synchronized (this) {
return properties.get(key);
}
}
public Object put(String key, Object value) {
synchronized (this) {
return properties.put(key, value);
}
}
/**
* @deprecated Use {@link #remove(String)} instead.
*/
@Deprecated
public Object remove(Object key) {
return remove((String) key);
}
public Object remove(String key) {
synchronized (this) {
return properties.remove(key);
}
}
public Set<String> keySet() {
synchronized (this) {
return Collections.unmodifiableSet(new HashSet<>(properties.keySet()));
}
}
public Collection<Object> values() {
synchronized (this) {
return Collections.unmodifiableCollection(new ArrayList<>(properties.values()));
}
}
public Map<String, Object> getProperties() {
synchronized (this) {
return Collections.unmodifiableMap(new HashMap<>(properties));
}
}
public void setProperties(Map<String, Object> properties) {
for (Entry<String, Object> entrySet : properties.entrySet()) {
this.put(entrySet.getKey(), ConfigUtil.normalizeType(entrySet.getValue()));
}
for (Iterator<String> it = this.properties.keySet().iterator(); it.hasNext();) {
String entry = it.next();
if (!properties.containsKey(entry)) {
it.remove();
}
}
}
@Override
public int hashCode() {
synchronized (this) {
return properties.hashCode();
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Configuration)) {
return false;
}
return this.hashCode() == obj.hashCode();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Configuration[");
boolean first = true;
for (final Map.Entry<String, Object> prop : properties.entrySet()) {
if (first) {
first = false;
} else {
sb.append(", ");
}
Object value = prop.getValue();
sb.append(String.format("{key=%s; type=%s; value=%s}", prop.getKey(),
value != null ? value.getClass().getSimpleName() : "?", value));
}
sb.append("]");
return sb.toString();
}
}