# KeyValueRecordMappingBuilder.java

    package org.codefilarete.stalactite.engine.configurer.map;

    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;

    import org.codefilarete.reflection.AccessorChain.ValueInitializerOnNullValue;
    import org.codefilarete.reflection.ReadWriteAccessorChain;
    import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
    import org.codefilarete.stalactite.engine.configurer.map.KeyValueRecordMapping.KeyValueRecordIdMapping;
    import org.codefilarete.stalactite.mapping.EmbeddedClassMapping;
    import org.codefilarete.stalactite.mapping.id.assembly.IdentifierAssembler;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.collection.Maps;
    import org.codefilarete.tool.collection.Maps.ChainingMap;

    /**
     * Builder of {@link KeyValueRecordMapping} to let one choose between single-column entry element and composite one.
     * Managing those cases is necessary due to that single-value entry element cannot be set from database
     * through a Mutator and declared by the Map&gt;Accessor, Column&lt;
     *
     * @author Guillaume Mary
     */
    class KeyValueRecordMappingBuilder<K, V, I, T extends Table<T>, LEFTTABLE extends Table<LEFTTABLE>> {

    	private static <K> ChainingMap<ReadWritePropertyAccessPoint, Column> chainWithKeyAccessor(EmbeddedClassMapping<K, ?> entryKeyMapping) {
    		ChainingMap<ReadWritePropertyAccessPoint, Column> result = new ChainingMap<>();
    		entryKeyMapping.getPropertyToColumn().forEach((keyPropertyAccessor, column) -> {
    			ReadWriteAccessorChain key = new ReadWriteAccessorChain(KeyValueRecord.KEY_ACCESSOR, keyPropertyAccessor);
    			key.setNullValueHandler(new ValueInitializerOnNullValue((accessor, inputType) -> {
    				if (accessor == KeyValueRecord.KEY_ACCESSOR) {
    					return Reflections.newInstance(entryKeyMapping.getClassToPersist());
    				}
    				return Reflections.newInstance(inputType);
    			}));
    			result.add(key, column);
    		});
    		return result;
    	}

    	private static <V> ChainingMap<ReadWritePropertyAccessPoint, Column> chainWithValueAccessor(EmbeddedClassMapping<V, ?> entryKeyMapping) {
    		ChainingMap<ReadWritePropertyAccessPoint, Column> result = new ChainingMap<>();
    		entryKeyMapping.getPropertyToColumn().forEach((keyPropertyAccessor, column) -> {
    			ReadWriteAccessorChain key = new ReadWriteAccessorChain(KeyValueRecord.VALUE_ACCESSOR, keyPropertyAccessor)
    					.setNullValueHandler(new ValueInitializerOnNullValue((accessor, inputType) -> {
    				if (accessor == KeyValueRecord.VALUE_ACCESSOR) {
    					return Reflections.newInstance(entryKeyMapping.getClassToPersist());
    				}
    				return Reflections.newInstance(inputType);
    			}));
    			result.add(key, column);
    		});
    		return result;
    	}

    	private final T associationTable;
    	private final IdentifierAssembler<I, LEFTTABLE> sourceIdentifierAssembler;
    	private final Map<Column<LEFTTABLE, ?>, Column<T, ?>> primaryKeyForeignColumnMapping;
    	private Column<T, K> keyColumn;
    	private Column<T, V> valueColumn;
    	private EmbeddedClassMapping<K, T> entryKeyMapping;
    	private EmbeddedClassMapping<V, T> entryValueMapping;

    	KeyValueRecordMappingBuilder(
    			T associationTable,
    			IdentifierAssembler<I, LEFTTABLE> sourceIdentifierAssembler,
    			Map<Column<LEFTTABLE, ?>, Column<T, ?>> primaryKeyForeignColumnMapping) {
    		this.associationTable = associationTable;
    		this.sourceIdentifierAssembler = sourceIdentifierAssembler;
    		this.primaryKeyForeignColumnMapping = primaryKeyForeignColumnMapping;
    	}

    	void withEntryKeyIsSingleProperty(Column<T, K> keyColumn) {
    		this.keyColumn = keyColumn;
    	}

    	void withEntryValueIsSingleProperty(Column<T, V> valueColumn) {
    		this.valueColumn = valueColumn;
    	}

    	void withEntryKeyIsComplexType(EmbeddedClassMapping<K, T> entryKeyMapping) {
    		this.entryKeyMapping = entryKeyMapping;
    	}

    	void withEntryValueIsComplexType(EmbeddedClassMapping<V, T> entryValueMapping) {
    		this.entryValueMapping = entryValueMapping;
    	}

    	KeyValueRecordMapping<K, V, I, T> build() {
    		Map<ReadWritePropertyAccessPoint, Column> propertiesMapping = new HashMap<>();
    		KeyValueRecordIdMapping<K, I, T> idMapping = null;
    		if (keyColumn != null) {
    			propertiesMapping.put(KeyValueRecord.KEY_ACCESSOR, keyColumn);
    			idMapping = new KeyValueRecordIdMapping<>(
    					associationTable,
    					columnedRow -> columnedRow.get(keyColumn),
    					(Function<K, Map<Column<T, ?>, ?>>) k -> (Map) Maps.forHashMap(Column.class, Object.class).add(keyColumn, k),
    					sourceIdentifierAssembler,
    					primaryKeyForeignColumnMapping);
    		} else if (entryKeyMapping != null) {
    			propertiesMapping.putAll(chainWithKeyAccessor(entryKeyMapping));
    			idMapping = new KeyValueRecordIdMapping<>(
    					associationTable,
    					entryKeyMapping,
    					sourceIdentifierAssembler,
    					primaryKeyForeignColumnMapping);
    		}
    		if (valueColumn != null) {
    			propertiesMapping.put(KeyValueRecord.VALUE_ACCESSOR, valueColumn);
    		} else if (entryValueMapping != null) {
    			propertiesMapping.putAll(chainWithValueAccessor(entryValueMapping));
    		}

    		return new KeyValueRecordMapping<K, V, I, T>(associationTable, (Map) propertiesMapping, idMapping);
    	}
    }