# MapRelation.java

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

    import java.util.Map;
    import java.util.function.Supplier;
    import javax.annotation.Nullable;

    import org.codefilarete.reflection.AccessorByMethodReference;
    import org.codefilarete.reflection.Accessors;
    import org.codefilarete.reflection.MutatorByMethodReference;
    import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
    import org.codefilarete.reflection.SerializableAccessor;
    import org.codefilarete.reflection.SerializableMutator;
    import org.codefilarete.reflection.SerializablePropertyAccessor;
    import org.codefilarete.reflection.SerializablePropertyMutator;
    import org.codefilarete.reflection.ValueAccessPoint;
    import org.codefilarete.reflection.ValueAccessPointMap;
    import org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfigurationProvider;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfigurationProvider;
    import org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode;
    import org.codefilarete.stalactite.sql.ddl.Size;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;

    /**
     * Storage of configuration of a {@link Map} relation (kind of "element collection" but with a {@link Map}).
     *
     * @param <SRC> entity type owning relation
     * @param <K> Map entry key type
     * @param <V> Map entry value key type
     * @param <M> Map type
     * @author Guillaume Mary
     */
    public class MapRelation<SRC, K, V, M extends Map<K, V>> {

    	/** The method that gives the entities from the "root" entity */
    	private final ReadWritePropertyAccessPoint<SRC, M> mapProvider;
    	private final Class<K> keyType;
    	private final Class<V> valueType;
    	/** Optional provider of {@link Map} instance to be used if collection value is null */
    	private Supplier<M> mapFactory;

    	private Table targetTable;
    	private String targetTableName;
    	private Column<Table, ?> reverseColumn;
    	private String reverseColumnName;

    	/** key column name override */
    	@Nullable
    	private String keyColumnName;

    	/** key column size override */
    	@Nullable
    	private Size keyColumnSize;

    	/** value column name override */
    	@Nullable
    	private String valueColumnName;

    	/** value column size override */
    	@Nullable
    	private Size valueColumnSize;

    	/** Key complex type mapping override, to be used when {@link #keyEmbeddableConfigurationProvider} is not null */
    	private final ValueAccessPointMap<K, String, ValueAccessPoint<K>> overriddenKeyColumnNames = new ValueAccessPointMap<>();

    	/** Key complex type mapping override, to be used when {@link #valueEmbeddableConfigurationProvider} is not null */
    	private final ValueAccessPointMap<K, Size, ValueAccessPoint<K>> overriddenKeyColumnSizes = new ValueAccessPointMap<>();

    	/** Value complex type mapping override, to be used when {@link #valueEmbeddableConfigurationProvider} is not null */
    	private final ValueAccessPointMap<V, String, ValueAccessPoint<V>> overriddenValueColumnNames = new ValueAccessPointMap<>();

    	/** Value complex type mapping override, to be used when {@link #valueEmbeddableConfigurationProvider} is not null */
    	private final ValueAccessPointMap<V, Size, ValueAccessPoint<V>> overriddenValueColumnSizes = new ValueAccessPointMap<>();

    	/** Complex type mapping, optional */
    	@Nullable
    	private EntityMappingConfigurationProvider<K, ?> keyEntityConfigurationProvider;
    	@Nullable
    	private EmbeddableMappingConfigurationProvider<K> keyEmbeddableConfigurationProvider;

    	@Nullable
    	private EntityMappingConfigurationProvider<V, ?> valueEntityConfigurationProvider;
    	@Nullable
    	private EmbeddableMappingConfigurationProvider<V> valueEmbeddableConfigurationProvider;

    	/** Default relation mode is {@link RelationMode#ALL} */
    	private RelationMode keyEntityRelationMode = RelationMode.ALL;

    	/** Default relation mode is {@link RelationMode#ALL} */
    	private RelationMode valueEntityRelationMode = RelationMode.ALL;

    	private boolean fetchSeparately;

    	public MapRelation(SerializablePropertyMutator<SRC, M> setter,
    					   Class<K> keyType,
    					   Class<V> valueType) {
    		this(Accessors.readWriteAccessPoint(setter), keyType, valueType);
    	}

    	public MapRelation(SerializablePropertyAccessor<SRC, M> getter,
    					   Class<K> keyType,
    					   Class<V> valueType) {
    		this(Accessors.readWriteAccessPoint(getter), keyType, valueType);
    	}

    	public MapRelation(ReadWritePropertyAccessPoint<SRC, M> mapProvider,
    					   Class<K> keyType,
    					   Class<V> valueType) {
    		this.keyType = keyType;
    		this.valueType = valueType;
    		this.mapProvider = mapProvider;
    	}

    	public ReadWritePropertyAccessPoint<SRC, M> getMapProvider() {
    		return mapProvider;
    	}

    	public Class<K> getKeyType() {
    		return keyType;
    	}

    	public Class<V> getValueType() {
    		return valueType;
    	}

    	public Supplier<M> getMapFactory() {
    		return mapFactory;
    	}

    	public void setMapFactory(Supplier<? extends M> mapFactory) {
    		this.mapFactory = (Supplier<M>) mapFactory;
    	}

    	public Table getTargetTable() {
    		return targetTable;
    	}

    	public void setTargetTable(Table targetTable) {
    		this.targetTable = targetTable;
    	}

    	public String getTargetTableName() {
    		return targetTableName;
    	}

    	public void setTargetTableName(String targetTableName) {
    		this.targetTableName = targetTableName;
    	}

    	public <I> Column<Table, I> getReverseColumn() {
    		return (Column<Table, I>) reverseColumn;
    	}

    	public void setReverseColumn(Column<Table, ?> reverseColumn) {
    		this.reverseColumn = reverseColumn;
    	}

    	public String getReverseColumnName() {
    		return reverseColumnName;
    	}

    	public void setReverseColumnName(String reverseColumnName) {
    		this.reverseColumnName = reverseColumnName;
    	}

    	public void setKeyConfigurationProvider(@Nullable EntityMappingConfigurationProvider<K, ?> keyConfigurationProvider) {
    		this.keyEntityConfigurationProvider = keyConfigurationProvider;
    	}

    	@Nullable
    	public EmbeddableMappingConfigurationProvider<K> getKeyEmbeddableConfigurationProvider() {
    		return keyEmbeddableConfigurationProvider;
    	}

    	public void setKeyConfigurationProvider(@Nullable EmbeddableMappingConfigurationProvider<K> keyConfigurationProvider) {
    		this.keyEmbeddableConfigurationProvider = keyConfigurationProvider;
    	}

    	@Nullable
    	public EntityMappingConfigurationProvider<K, ?> getKeyEntityConfigurationProvider() {
    		return keyEntityConfigurationProvider;
    	}

    	public void setValueConfigurationProvider(@Nullable EntityMappingConfigurationProvider<V, ?> valueConfigurationProvider) {
    		this.valueEntityConfigurationProvider = valueConfigurationProvider;
    	}

    	@Nullable
    	public EntityMappingConfigurationProvider<V, ?> getValueEntityConfigurationProvider() {
    		return valueEntityConfigurationProvider;
    	}

    	public void setValueConfigurationProvider(@Nullable EmbeddableMappingConfigurationProvider<V> valueConfigurationProvider) {
    		this.valueEmbeddableConfigurationProvider = valueConfigurationProvider;
    	}

    	@Nullable
    	public EmbeddableMappingConfigurationProvider<V> getValueEmbeddableConfigurationProvider() {
    		return valueEmbeddableConfigurationProvider;
    	}

    	public void setKeyColumnName(String keyColumnName) {
    		this.keyColumnName = keyColumnName;
    	}

    	@Nullable
    	public String getKeyColumnName() {
    		return keyColumnName;
    	}

    	public void setKeyColumnSize(Size keyColumnSize) {
    		this.keyColumnSize = keyColumnSize;
    	}

    	@Nullable
    	public Size getKeyColumnSize() {
    		return keyColumnSize;
    	}

    	public void setValueColumnName(String valueColumnName) {
    		this.valueColumnName = valueColumnName;
    	}

    	@Nullable
    	public String getValueColumnName() {
    		return valueColumnName;
    	}

    	public void setValueColumnSize(Size valueColumnSize) {
    		this.valueColumnSize = valueColumnSize;
    	}

    	@Nullable
    	public Size getValueColumnSize() {
    		return valueColumnSize;
    	}

    	public ValueAccessPointMap<K, String, ValueAccessPoint<K>> getOverriddenKeyColumnNames() {
    		return this.overriddenKeyColumnNames;
    	}

    	public void overrideKeyName(SerializableAccessor<K, ?> methodRef, String columnName) {
    		this.overriddenKeyColumnNames.put(new AccessorByMethodReference<>(methodRef), columnName);
    	}

    	public void overrideKeyName(SerializableMutator<K, ?> methodRef, String columnName) {
    		this.overriddenKeyColumnNames.put(new MutatorByMethodReference<>(methodRef), columnName);
    	}

    	public ValueAccessPointMap<V, String, ValueAccessPoint<V>> getOverriddenValueColumnNames() {
    		return this.overriddenValueColumnNames;
    	}

    	public void overrideKeySize(SerializableAccessor<K, ?> methodRef, Size columnSize) {
    		this.overriddenKeyColumnSizes.put(new AccessorByMethodReference<>(methodRef), columnSize);
    	}

    	public void overrideKeySize(SerializableMutator<K, ?> methodRef, Size columnSize) {
    		this.overriddenKeyColumnSizes.put(new MutatorByMethodReference<>(methodRef), columnSize);
    	}

    	public ValueAccessPointMap<K, Size, ValueAccessPoint<K>> getOverriddenKeyColumnSizes() {
    		return this.overriddenKeyColumnSizes;
    	}

    	public void overrideValueName(SerializableAccessor<V, ?> methodRef, String columnName) {
    		this.overriddenValueColumnNames.put(new AccessorByMethodReference<>(methodRef), columnName);
    	}

    	public void overrideValueName(SerializableMutator<V, ?> methodRef, String columnName) {
    		this.overriddenValueColumnNames.put(new MutatorByMethodReference<>(methodRef), columnName);
    	}

    	public void overrideValueSize(SerializableAccessor<V, ?> methodRef, Size columnSize) {
    		this.overriddenValueColumnSizes.put(new AccessorByMethodReference<>(methodRef), columnSize);
    	}

    	public void overrideValueSize(SerializableMutator<V, ?> methodRef, Size columnSize) {
    		this.overriddenValueColumnSizes.put(new MutatorByMethodReference<>(methodRef), columnSize);
    	}

    	public ValueAccessPointMap<V, Size, ValueAccessPoint<V>> getOverriddenValueColumnSizes() {
    		return this.overriddenValueColumnSizes;
    	}

    	public RelationMode getKeyEntityRelationMode() {
    		return keyEntityRelationMode;
    	}

    	public void setKeyEntityRelationMode(RelationMode keyEntityRelationMode) {
    		this.keyEntityRelationMode = keyEntityRelationMode;
    	}

    	public RelationMode getValueEntityRelationMode() {
    		return valueEntityRelationMode;
    	}

    	public void setValueEntityRelationMode(RelationMode valueEntityRelationMode) {
    		this.valueEntityRelationMode = valueEntityRelationMode;
    	}

    	public boolean isFetchSeparately() {
    		return fetchSeparately;
    	}

    	public void fetchSeparately() {
    		this.fetchSeparately = true;
    	}
    }