/** * 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); } }