/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.market.explain;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import com.opengamma.strata.collect.ArgChecker;
/**
* A builder for the map of explanatory values.
* <p>
* This is a mutable builder for {@link ExplainMap} that must be used from a single thread.
*/
public final class ExplainMapBuilder {
/**
* The parent builder.
*/
private final ExplainMapBuilder parent;
/**
* The map of explanatory values.
*/
private final Map<ExplainKey<?>, Object> map = new LinkedHashMap<>();
/**
* Creates a new instance.
*/
ExplainMapBuilder() {
this.parent = null;
}
/**
* Creates a new instance.
*
* @param parent the parent builder
*/
ExplainMapBuilder(ExplainMapBuilder parent) {
this.parent = parent;
}
//-------------------------------------------------------------------------
/**
* Opens a list entry to be populated.
* <p>
* This returns the builder for the new list entry.
* If the list does not exist, it is created and the first entry added.
* If the list has already been created, the entry is appended.
* <p>
* Once opened, the child builder resulting from this method must be used.
* The method {@link #closeListEntry(ExplainKey)} must be used to close the
* child and receive an instance of the parent back again.
*
* @param <R> the type of the value
* @param key the list key to open
* @return the child builder
*/
@SuppressWarnings("unchecked")
public <R extends List<?>> ExplainMapBuilder openListEntry(ExplainKey<R> key) {
// list entry is a ExplainMapBuilder, making use of erasure in generics
// builder is converted to ExplainMap when entry is closed
ExplainMapBuilder child = new ExplainMapBuilder(this);
Object value = map.get(key);
ArrayList<Object> list;
if (value instanceof ArrayList) {
list = (ArrayList<Object>) value;
} else {
list = new ArrayList<>();
map.put(key, list);
}
list.add(child);
return child;
}
/**
* Closes the currently open list.
* <p>
* This returns the parent builder.
*
* @param <R> the type of the value
* @param key the list key to close
* @return the parent builder
*/
public <R extends List<?>> ExplainMapBuilder closeListEntry(ExplainKey<R> key) {
Object value = parent.map.get(key);
if (value instanceof ArrayList == false) {
throw new IllegalStateException("ExplainMapBuilder.closeList() called but no list found to close");
}
// close entry by converting it from ExplainMapBuilder to ExplainMap
@SuppressWarnings("unchecked")
ArrayList<Object> list = (ArrayList<Object>) value;
ExplainMapBuilder closedEntry = (ExplainMapBuilder) list.get(list.size() - 1);
list.set(list.size() - 1, closedEntry.build());
return parent;
}
//-------------------------------------------------------------------------
/**
* Adds a list entry using a consumer callback function.
* <p>
* This is an alternative to using {@link #openListEntry(ExplainKey)} and
* {@link #closeListEntry(ExplainKey)} directly.
* The consumer function receives the child builder and must add data to it.
*
* @param <R> the type of the value
* @param key the list key to open
* @param consumer the consumer that receives the list entry builder and adds to it
* @return this builder
*/
public <R extends List<?>> ExplainMapBuilder addListEntry(ExplainKey<R> key, Consumer<ExplainMapBuilder> consumer) {
ExplainMapBuilder child = openListEntry(key);
consumer.accept(child);
return child.closeListEntry(key);
}
/**
* Adds a list entry using a consumer callback function, including the list index.
* <p>
* This is an alternative to using {@link #openListEntry(ExplainKey)} and
* {@link #closeListEntry(ExplainKey)} directly.
* The consumer function receives the child builder and must add data to it.
*
* @param <R> the type of the value
* @param key the list key to open
* @param consumer the consumer that receives the list entry builder and adds to it
* @return this builder
*/
public <R extends List<?>> ExplainMapBuilder addListEntryWithIndex(ExplainKey<R> key, Consumer<ExplainMapBuilder> consumer) {
ExplainMapBuilder child = openListEntry(key);
// find index
Object value = map.get(key);
@SuppressWarnings("unchecked")
ArrayList<Object> list = (ArrayList<Object>) value;
child.put(ExplainKey.ENTRY_INDEX, list.size() - 1);
consumer.accept(child);
return child.closeListEntry(key);
}
//-------------------------------------------------------------------------
/**
* Puts a single value into the map.
* <p>
* If the key already exists, the value will be replaced.
*
* @param <R> the type of the value
* @param key the key to add
* @param value the value to add
* @return this builder
*/
public <R> ExplainMapBuilder put(ExplainKey<R> key, R value) {
ArgChecker.notNull(key, "key");
ArgChecker.notNull(value, "value");
map.put(key, value);
return this;
}
//-------------------------------------------------------------------------
/**
* Builds the map.
*
* @return the resulting map
*/
public ExplainMap build() {
return ExplainMap.of(map);
}
}