org.openmdx.kernel.configuration.MapConfiguration Maven / Gradle / Ivy
/*
* ====================================================================
* Project: openMDX/Core, http://www.openmdx.org/
* Description: Map Backed Configuration
* Owner: OMEX AG, Switzerland, http://www.omex.ch
* ====================================================================
*
* This software is published under the BSD license as listed below.
*
* Copyright (c) 2014, OMEX AG, Switzerland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of the openMDX team nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* ------------------
*
* This product includes software developed by other organizations as
* listed in the NOTICE file.
*/
package org.openmdx.kernel.configuration;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openmdx.base.collection.TreeSparseArray;
import org.openmdx.kernel.exception.BasicException;
import org.openmdx.kernel.text.spi.Parser;
import org.w3c.cci2.SortedMaps;
import org.w3c.cci2.SparseArray;
/**
* Map Based Configuration
*
* Note that single- and multi-valued attributes use two distinct namespaces:
* - source entry names without square brackets are used to populate the single-
* valued entries (e.g. "entry")
*
- source entry names with square brackets are used to populate the multi-
* valued entries (e.g. "entry[5]")
*
*
* The rules for value parsing are as following
* - Cast values are parsed upon population (e.g. "(java.lang.Integer)5")
*
- String values are parsed upon retrieval
*
- Other values are returned as they are
*
*/
public class MapConfiguration implements Configuration {
/**
* Constructor
*/
protected MapConfiguration(
Parser parser
){
this.parser = parser;
this.singleValued = new HashMap();
this.multiValued = new HashMap>();
}
/**
* Constructor
*
* @param source the configuration is populated from the source
* but the source object itself is not kept by the configuration.
* Later changes to the source will therefore not affect the
* configuration.
*
* @param parser the parser is used to parse string values (lazily)
* upon value retrieval, while other values are returned as they are.
*/
public MapConfiguration(
Map source,
Parser parser
) {
this(parser);
populate(source);
}
/**
* Constructor
*
* @param source the configuration is populated from the source
* but the source object itself is not kept by the configuration.
* Later changes to the source will therefore not affect the
* configuration.
*
* @param parser the parser is used to parse string values (lazily)
* upon value retrieval, while other values are returned as they are.
*/
public MapConfiguration(
Properties source,
Parser parser
) {
this(parser);
populate(source);
}
protected final Map singleValued;
protected final Map> multiValued;
private final Parser parser;
private static final Pattern MULTI_VALUED_ENTRY_NAME = Pattern.compile(
"([A-Za-z0-9_]+)\\[([0-9]+)\\]"
);
/* (non-Javadoc)
* @see org.openmdx.kernel.configuration.Configuration#singleValuedEntryNames()
*/
@Override
public Set singleValuedEntryNames() {
return Collections.unmodifiableSet(this.singleValued.keySet());
}
/* (non-Javadoc)
* @see org.openmdx.kernel.configuration.Configuration#multiValuedEntryNames()
*/
@Override
public Set multiValuedEntryNames() {
return Collections.unmodifiableSet(this.multiValued.keySet());
}
@Override
public boolean isEnabled(String entryName, boolean defaultValue) {
return getValue(entryName, Boolean.valueOf(defaultValue)).booleanValue();
}
@Override
public T getValue(String entryName, T defaultValue) {
if(defaultValue == null) {
throw new IllegalArgumentException(
"Specify the expected class if the default value shall be null",
BasicException.newEmbeddedExceptionStack(
BasicException.Code.DEFAULT_DOMAIN,
BasicException.Code.BAD_PARAMETER,
new BasicException.Parameter("name", entryName),
new BasicException.Parameter("defaultValue", defaultValue)
)
);
}
return parse(getClass(defaultValue), this.singleValued.get(entryName), defaultValue);
}
@Override
public T getOptionalValue(String entryName, Class valueClass) {
return parse(valueClass, this.singleValued.get(entryName), null);
}
@Override
public SparseArray getValues(String entryName, Class elementType) {
if(this.multiValued.containsKey(entryName)) {
return SortedMaps.asSparseArray(
new MarshallingSparseArray(
elementType,
this.multiValued.get(entryName)
)
);
} else {
return SortedMaps.emptySparseArray();
}
}
protected T parse(Class type, final Object value, T defaultValue) {
return
value == null ? defaultValue :
parser != null && value instanceof String ? parser.parse(type, (String)value) :
cast(type, value);
}
/**
* Retrieve the value's class
*
* @param defaultValue the (non-null) value
*
* @return the type of the value
*/
@SuppressWarnings("unchecked")
private Class getClass(T defaultValue) {
return (Class) defaultValue.getClass();
}
@SuppressWarnings("unchecked")
private T cast(Class type, final Object rawValue) {
return type != null ? type.cast(
rawValue
) : (T)rawValue;
}
protected boolean containsKey(String key) {
final Matcher matcher = MULTI_VALUED_ENTRY_NAME.matcher(key);
if(matcher.matches()) {
return this.multiValued.containsKey(matcher.group(1));
} else {
return this.singleValued.containsKey(key);
}
}
protected void populate(
Map,?> source
){
final Parser parser = new CastAwareParser(this.parser);
for(Map.Entry, ?> e : source.entrySet()){
final Object key = e.getKey();
final Object value = e.getValue();
if(key instanceof String){
final Object entryValue;
if(value instanceof String){
entryValue = parser.parse((Class>)null, (String)value);
} else {
entryValue = value;
}
final String entryName;
final Matcher matcher = MULTI_VALUED_ENTRY_NAME.matcher((String) key);
if(matcher.matches()) {
entryName = matcher.group(1);
final Integer entryIndex = Integer.valueOf(matcher.group(2));
final SparseArray