# KeyValueRecordMapping.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.ReadWritePropertyAccessPoint;
    import org.codefilarete.stalactite.mapping.ComposedIdMapping;
    import org.codefilarete.stalactite.mapping.DefaultEntityMapping;
    import org.codefilarete.stalactite.mapping.EmbeddedBeanMapping;
    import org.codefilarete.stalactite.mapping.IdAccessor;
    import org.codefilarete.stalactite.mapping.IdMapping;
    import org.codefilarete.stalactite.mapping.id.assembly.ComposedIdentifierAssembler;
    import org.codefilarete.stalactite.mapping.id.assembly.IdentifierAssembler;
    import org.codefilarete.stalactite.mapping.id.manager.AlreadyAssignedIdentifierManager;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.ColumnedRow;
    import org.codefilarete.stalactite.sql.result.Row;
    import org.codefilarete.tool.VisibleForTesting;
    import org.codefilarete.tool.collection.Maps;

    /**
     * Mapping strategy dedicated to {@link KeyValueRecord}. Very close to {@link org.codefilarete.stalactite.engine.configurer.AssociationRecordMapping}
     * in its principle.
     *
     * @param <K> embedded key bean type
     * @param <V> embedded value bean type
     * @param <I> source identifier type
     * @param <T> relation table type
     * @author Guillaume Mary
     */
    public class KeyValueRecordMapping<K, V, I, T extends Table<T>> extends DefaultEntityMapping<KeyValueRecord<K, V, I>, RecordId<K, I>, T> {

    	@VisibleForTesting
    	public KeyValueRecordMapping(T targetTable,
    						  Map<? extends ReadWritePropertyAccessPoint<KeyValueRecord<K, V, I>, ?>, Column<T, ?>> propertyToColumn,
    						  KeyValueRecordIdMapping<K, I, T> idMapping) {
    		super((Class) KeyValueRecord.class,
    				targetTable,
    				propertyToColumn,
    				// cast because idMapping has KeyValueRecord<K, ?, I> as generics instead of KeyValueRecord<K, V, I>
    				(ComposedIdMapping<KeyValueRecord<K, V, I>, RecordId<K, I>>) (ComposedIdMapping) idMapping);

    	}

    	/**
    	 * {@link IdMapping} for {@link KeyValueRecord} : a composed id made of
    	 * - {@link KeyValueRecord#getId()}
    	 * - {@link KeyValueRecord#getKey()}
    	 */
    	@VisibleForTesting
    	public static class KeyValueRecordIdMapping<K, I, T extends Table<T>> extends ComposedIdMapping<KeyValueRecord<K, ?, I>, RecordId<K, I>> {

    		private KeyValueRecordIdMapping(
    				RecordIdAssembler recordIdAssembler) {
    			super(new KeyValueRecordIdAccessor<>(),
    					new AlreadyAssignedIdentifierManager<>((Class<RecordId<K, I>>) (Class) RecordId.class,
    							KeyValueRecord::markAsPersisted,
    							KeyValueRecord::isPersisted),
    					recordIdAssembler);
    		}

    		public <LEFTTABLE extends Table<LEFTTABLE>> KeyValueRecordIdMapping(
    				T targetTable,
    				Function<ColumnedRow, K> entryKeyAssembler,
    				Function<K, Map<Column<T, ?>, ?>> entryKeyColumnValueProvider,
    				IdentifierAssembler<I, LEFTTABLE> sourceIdentifierAssembler,
    				Map<Column<LEFTTABLE, ?>, Column<T, ?>> primaryKey2ForeignKeyMapping) {
    			this(new RecordIdAssembler<>(targetTable, entryKeyAssembler, entryKeyColumnValueProvider, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping));
    		}

    		public <LEFTTABLE extends Table<LEFTTABLE>> KeyValueRecordIdMapping(
    				T targetTable,
    				EmbeddedBeanMapping<K, T> entryKeyMapping,
    				IdentifierAssembler<I, LEFTTABLE> sourceIdentifierAssembler,
    				Map<Column<LEFTTABLE, ?>, Column<T, ?>> primaryKey2ForeignKeyMapping) {
    			this(new RecordIdAssembler<>(targetTable, entryKeyMapping, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping));
    		}

    		/**
    		 * Override because {@link ComposedIdMapping} is based on null identifier to determine newness, which is always false for {@link KeyValueRecord}
    		 * because they always have one. We delegate its computation to the entity.
    		 *
    		 * @param entity any non-null entity
    		 * @return true or false based on {@link KeyValueRecord#isNew()}
    		 */
    		@Override
    		public boolean isNew(KeyValueRecord<K, ?, I> entity) {
    			return entity.isNew();
    		}

    		public static class KeyValueRecordIdAccessor<K, I> implements IdAccessor<KeyValueRecord<K, ?, I>, RecordId<K, I>> {

    			@Override
    			public RecordId<K, I> getId(KeyValueRecord<K, ?, I> associationRecord) {
    				return associationRecord.getId();
    			}

    			@Override
    			public void setId(KeyValueRecord<K, ?, I> associationRecord, RecordId<K, I> identifier) {
    				associationRecord.setId(new RecordId<>(identifier.getId(), associationRecord.getKey()));
    				associationRecord.setKey(identifier.getKey());
    			}
    		}

    		/**
    		 * Identifier assembler for {@link RecordId}
    		 *
    		 * @param <K> embedded key bean type
    		 * @param <ID> source identifier type
    		 */
    		@VisibleForTesting
    		static class RecordIdAssembler<K, ID, T extends Table<T>> extends ComposedIdentifierAssembler<RecordId<K, ID>, T> {

    			private final Function<ColumnedRow, K> entryKeyAssembler;
    			private final Function<K, Map<Column<T, ?>, ?>> entryKeyColumnValueProvider;
    			private final IdentifierAssembler<ID, ?> sourceIdentifierAssembler;
    			private final Map<Column<?, ?>, Column<T, ?>> primaryKey2ForeignKeyMapping;

    			/**
    			 * Constructor mapping given values to fields
    			 *
    			 * @param targetTable association table on which value should be inserted an read
    			 * @param entryKeyAssembler builder of K instances from {@link Column} found in a {@link Row}
    			 * @param entryKeyColumnValueProvider provides {@link Column} values to be inserted / updated from a K instance
    			 * @param sourceIdentifierAssembler assemble / disassemble ID instances from / to {@link Column}s
    			 * @param primaryKey2ForeignKeyMapping used to remap {@link Column}s of sourceIdentifierAssembler to association table since it gives it for LEFTTABLE
    			 * @param <LEFTTABLE> table type of source declaring the relation
    			 */
    			@VisibleForTesting
    			<LEFTTABLE extends Table<LEFTTABLE>> RecordIdAssembler(
    					T targetTable,
    					Function<ColumnedRow, K> entryKeyAssembler,
    					Function<K, Map<Column<T, ?>, ?>> entryKeyColumnValueProvider,
    					IdentifierAssembler<ID, LEFTTABLE> sourceIdentifierAssembler,
    					Map<? extends Column<LEFTTABLE, ?>, ? extends Column<T, ?>> primaryKey2ForeignKeyMapping
    			) {
    				super(targetTable);
    				this.entryKeyAssembler = entryKeyAssembler;
    				this.entryKeyColumnValueProvider = entryKeyColumnValueProvider;
    				this.sourceIdentifierAssembler = sourceIdentifierAssembler;
    				this.primaryKey2ForeignKeyMapping = (Map) primaryKey2ForeignKeyMapping;
    			}

    			/**
    			 * Constructor for cases where K is a simple (embedded) bean.
    			 *
    			 * @param targetTable association table on which value should be inserted an read
    			 * @param entryKeyMapping persistence mapping of K instance
    			 * @param sourceIdentifierAssembler assemble / disassemble ID instances from / to {@link Column}s
    			 * @param primaryKey2ForeignKeyMapping used to remap {@link Column}s of sourceIdentifierAssembler to association table since it gives it for LEFTTABLE
    			 * @param <LEFTTABLE> table type of source declaring the relation
    			 */
    			@VisibleForTesting
    			<LEFTTABLE extends Table<LEFTTABLE>> RecordIdAssembler(
    					T targetTable,
    					EmbeddedBeanMapping<K, T> entryKeyMapping,
    					IdentifierAssembler<ID, LEFTTABLE> sourceIdentifierAssembler,
    					Map<? extends Column<LEFTTABLE, ?>, ? extends Column<T, ?>> primaryKey2ForeignKeyMapping
    					) {
    				this(targetTable, entryKeyMapping::transform, entryKeyMapping::getInsertValues, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping);
    			}

    			/**
    			 * Constructor for cases where entry key an entity referencing another table. Then K type becomes its identifier.
    			 *
    			 * @param targetTable association table on which value should be inserted an read
    			 * @param rightTableIdentifierAssembler entity identifier mapping (K type mapping)
    			 * @param sourceIdentifierAssembler assemble / disassemble ID instances from / to {@link Column}s
    			 * @param primaryKey2ForeignKeyMapping used to remap {@link Column}s of sourceIdentifierAssembler to association table since it gives it for LEFTTABLE
    			 * @param <LEFTTABLE> table type of source declaring the relation
    			 */
    			@VisibleForTesting
    			<LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>> RecordIdAssembler(
    					T targetTable,
    					IdentifierAssembler<K, RIGHTTABLE> rightTableIdentifierAssembler,
    					IdentifierAssembler<ID, LEFTTABLE> sourceIdentifierAssembler,
    					Map<? extends Column<LEFTTABLE, ?>, ? extends Column<T, ?>> primaryKey2ForeignKeyMapping,
    					Map<Column<RIGHTTABLE, ?>, Column<T, ?>> rightTable2EntryKeyMapping
    					) {
    				this(targetTable, rightTableIdentifierAssembler::assemble, k -> {
    					Map<Column<RIGHTTABLE, ?>, ?> keyColumnValues = rightTableIdentifierAssembler.getColumnValues(k);
    					return Maps.innerJoin(rightTable2EntryKeyMapping, keyColumnValues);

    				}, sourceIdentifierAssembler, primaryKey2ForeignKeyMapping);
    			}

    			@Override
    			public RecordId<K, ID> assemble(ColumnedRow row) {
    				ID id = sourceIdentifierAssembler.assemble(row);
    				K entryKey = entryKeyAssembler.apply(row);

    				return id == null ? null : new RecordId<>(id, entryKey);
    			}

    			@Override
    			public Map<Column<T, ?>, ?> getColumnValues(RecordId<K, ID> record) {
    				Map<Column<?, ?>, Object> sourceColumnValues = (Map) sourceIdentifierAssembler.getColumnValues(record.getId());
    				Map<Column<T, ?>, Object> idColumnValues = Maps.innerJoin(primaryKey2ForeignKeyMapping, sourceColumnValues);
    				Map<Column<T, ?>, ?> entryKeyColumnValues = entryKeyColumnValueProvider.apply(record.getKey());
    				Map<Column<T, ?>, Object> result = new HashMap<>();
    				result.putAll(idColumnValues);
    				result.putAll(entryKeyColumnValues);
    				return result;
    			}
    		}
    	}
    }