All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmdx.kernel.configuration.MapConfiguration Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * 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

    *
  1. Cast values are parsed upon population (e.g. "(java.lang.Integer)5") *
  2. String values are parsed upon retrieval *
  3. 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 values = values(entryName); values.put(entryIndex, entryValue); } else { entryName = (String)key; this.singleValued.put(entryName, entryValue); } } } } @SuppressWarnings("unchecked") protected SparseArray values(String entryName) { SparseArray values = this.multiValued.get(entryName); if(values == null) { values = new TreeSparseArray(); this.multiValued.put(entryName, values); } return (SparseArray) values; } /** * Class MarshallingSparseArray */ private class MarshallingSparseArray extends AbstractMap implements SortedMap { /** * Constructor */ MarshallingSparseArray(Class elementType, SortedMap delegate) { this.rawMap = delegate; this.elementType = elementType; } final Class elementType; final SortedMap rawMap; T getParsedValue(Integer key) { return parse(elementType, rawMap.get(key), null); } @Override public Comparator comparator() { return rawMap.comparator(); } @Override public SortedMap subMap(Integer fromKey, Integer toKey) { return new MarshallingSparseArray(elementType, rawMap.subMap(fromKey, toKey)); } @Override public SortedMap headMap(Integer toKey) { return new MarshallingSparseArray(elementType, rawMap.headMap(toKey)); } @Override public SortedMap tailMap(Integer fromKey) { return new MarshallingSparseArray(elementType, rawMap.tailMap(fromKey)); } @Override public Integer firstKey() { return rawMap.firstKey(); } @Override public Integer lastKey() { return rawMap.lastKey(); } @Override public Set> entrySet() { final Set keys = rawMap.keySet(); return new AbstractSet>(){ @Override public Iterator> iterator() { final Iterator delegate = keys.iterator(); return new Iterator>(){ @Override public boolean hasNext() { return delegate.hasNext(); } @Override public Map.Entry next() { final Integer key = delegate.next(); return new Map.Entry(){ T value; @Override public Integer getKey() { return key; } @Override public T getValue() { if(value == null) { value = getParsedValue(key); } return value; } @Override public T setValue(T value) { throw new UnsupportedOperationException( "A configuration is unmodifiable" ); } }; } @Override public void remove() { throw new UnsupportedOperationException( "A configuration is unmodifiable" ); } }; } @Override public int size() { return keys.size(); } }; } } }