/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.options.container.impl;
import static org.hibernate.ogm.util.impl.CollectionHelper.newHashMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.hibernate.ogm.options.spi.Option;
import org.hibernate.ogm.options.spi.UniqueOption;
/**
* Container for a group of options. Can hold unique as well as non-unique options. While several options of a given
* non-unique option type can be stored in this container, at most one option of a given unique option type can be
* stored.
* <p>
* This class is not thread-safe, callers need to synchronize externally when accessing this container from several
* threads in parallel.
*
* @author Davide D'Alto <davide@hibernate.org>
* @author Gunnar Morling
*/
public class OptionsContainerBuilder {
/**
* Holds all the options of this container, keyed by option type. The value container for an option will hold one or
* more than one value depending on whether the option is unique or not.
*/
private final Map<Class<? extends Option<?, ?>>, ValueContainerBuilder<?, ?>> optionValues;
public OptionsContainerBuilder() {
this.optionValues = newHashMap();
}
/**
* Adds an {@link Option} with the given value to this container.
*
* @param option to add to the container
* @param value the value of the option to add
* @param <I> the option identifier type
* @param <V> the oprion value type
*/
public <I, V> void add(Option<I, V> option, V value) {
getOrCreateValueContainer( option ).add( option.getOptionIdentifier(), value );
}
/**
* Adds all options from the passed container to this container.
*
* @param container a container with options to add
*/
public void addAll(OptionsContainerBuilder container) {
for ( Entry<Class<? extends Option<?, ?>>, ValueContainerBuilder<?, ?>> entry : container.optionValues.entrySet() ) {
addAll( entry.getKey(), entry.getValue().build() );
}
}
public OptionsContainer build() {
if ( optionValues.isEmpty() ) {
return OptionsContainer.EMPTY;
}
Map<Class<? extends Option<?, ?>>, ValueContainer<?, ?>> values = newHashMap( optionValues.size() );
for ( Entry<Class<? extends Option<?, ?>>, ValueContainerBuilder<?, ?>> option : optionValues.entrySet() ) {
values.put( option.getKey(), option.getValue().build() );
}
return new ImmutableOptionsContainer( values );
}
@Override
public String toString() {
return "OptionsContainerBuilder [optionValues=" + optionValues + "]";
}
@SuppressWarnings("unchecked")
private <I, V> void addAll(Class<? extends Option<?, ?>> optionType, ValueContainer<?, ?> values) {
ValueContainerBuilder<I, V> valueContainer = getOrCreateValueContainer( (Class<? extends Option<I, V>>) optionType );
valueContainer.addAll( ( (ValueContainer<I, V>) values ).getAll() );
}
private <V, I> ValueContainerBuilder<I, V> getOrCreateValueContainer(Option<I, V> option) {
@SuppressWarnings("unchecked")
Class<? extends Option<I, V>> optionType = (Class<? extends Option<I, V>>) option.getClass();
return getOrCreateValueContainer( optionType );
}
private <V, I> ValueContainerBuilder<I, V> getOrCreateValueContainer(Class<? extends Option<I, V>> optionType) {
ValueContainerBuilder<I, V> values = getValueContainer( optionType );
if ( values == null ) {
values = createValueContainer( optionType );
optionValues.put( optionType, values );
}
return values;
}
private <V, I> ValueContainerBuilder<I, V> getValueContainer(Class<? extends Option<I, V>> optionType) {
@SuppressWarnings("unchecked")
ValueContainerBuilder<I, V> values = (ValueContainerBuilder<I, V>) optionValues.get( optionType );
return values;
}
private <I, V> ValueContainerBuilder<I, V> createValueContainer(Class<? extends Option<I, V>> optionType) {
if ( UniqueOption.class.isAssignableFrom( optionType ) ) {
return new UniqueValueContainerBuilder<I, V>();
}
else {
return new NonUniqueValueContainerBuilder<I, V>();
}
}
/**
* Implementations store one or more values of a given option, depending on whether the option is unique or not.
*
* @param <I> the option identifier type
* @param <V> the option value type
*/
interface ValueContainer<I, V> {
V get(I identifier);
V getUnique();
Map<I, V> getAll();
}
private static class UniqueValueContainer<I, V> implements ValueContainer<I, V> {
private final I identifier;
private final V value;
public UniqueValueContainer(I identifier, V value) {
this.identifier = identifier;
this.value = value;
}
@Override
public V get(I identifier) {
return value;
}
@Override
public V getUnique() {
return value;
}
@Override
public Map<I, V> getAll() {
return Collections.singletonMap( identifier, value );
}
@Override
public String toString() {
return "UniqueValueContainer [identifier=" + identifier + ", value=" + value + "]";
}
}
private static class NonUniqueValueContainer<I, V> implements ValueContainer<I, V> {
private final Map<I, V> values;
public NonUniqueValueContainer(Map<I, V> values) {
this.values = Collections.unmodifiableMap( values );
}
@Override
public V get(I identifier) {
return values.get( identifier );
}
@Override
public V getUnique() {
throw new UnsupportedOperationException();
}
@Override
public Map<I, V> getAll() {
return values;
}
@Override
public String toString() {
return "NonUniqueValueContainer [values=" + values + "]";
}
}
/**
* Implementations build {@link ValueContainer} instances.
*
* @author Gunnar Morling
*/
private interface ValueContainerBuilder<I, V> {
void add(I identifier, V value);
void addAll(Map<I, V> values);
ValueContainer<I, V> build();
}
private static class UniqueValueContainerBuilder<I, V> implements ValueContainerBuilder<I, V> {
private I identifier;
private V value;
@Override
public void add(I identifier, V value) {
this.identifier = identifier;
this.value = value;
}
@Override
public void addAll(Map<I, V> values) {
for ( Entry<I, V> entry : values.entrySet() ) {
identifier = entry.getKey();
value = entry.getValue();
}
}
@Override
public ValueContainer<I, V> build() {
return new UniqueValueContainer<I, V>( identifier, value );
}
}
private static class NonUniqueValueContainerBuilder<I, V> implements ValueContainerBuilder<I, V> {
private final Map<I, V> values = new HashMap<I, V>();
@Override
public void add(I identifier, V value) {
values.put( identifier, value );
}
@Override
public void addAll(Map<I, V> values) {
this.values.putAll( values );
}
@Override
public ValueContainer<I, V> build() {
return new NonUniqueValueContainer<I, V>( values );
}
}
/**
* An immutable {@link OptionsContainer}.
*
* @author Gunnar Morling
*/
private static class ImmutableOptionsContainer implements OptionsContainer {
private final Map<Class<? extends Option<?, ?>>, ValueContainer<?, ?>> optionValues;
public ImmutableOptionsContainer(Map<Class<? extends Option<?, ?>>, ValueContainer<?, ?>> optionValues) {
this.optionValues = Collections.unmodifiableMap( optionValues );
}
@Override
public <I, V> V get(Class<? extends Option<I, V>> optionType, I identifier) {
ValueContainer<I, V> value = getValueContainer( optionType );
return value != null ? value.get( identifier ) : null;
}
@Override
public <V> V getUnique(Class<? extends UniqueOption<V>> optionType) {
ValueContainer<?, V> value = getValueContainer( optionType );
return value != null ? value.getUnique() : null;
}
@Override
public <I, V, T extends Option<I, V>> Map<I, V> getAll(Class<T> optionType) {
ValueContainer<I, V> value = getValueContainer( optionType );
return value != null ? value.getAll() : Collections.<I, V>emptyMap();
}
private <V, I> ValueContainer<I, V> getValueContainer(Class<? extends Option<I, V>> optionType) {
@SuppressWarnings("unchecked")
ValueContainer<I, V> values = (ValueContainer<I, V>) optionValues.get( optionType );
return values;
}
@Override
public String toString() {
return "ImmutableOptionsContainer [optionValues=" + optionValues + "]";
}
}
}