# NamingConfigurationCollector.java

    package org.codefilarete.stalactite.engine.configurer;

    import java.util.function.Consumer;

    import org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfiguration;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
    import org.codefilarete.stalactite.dsl.naming.AssociationTableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.ElementCollectionTableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.JoinColumnNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.MapEntryTableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.TableNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.UniqueConstraintNamingStrategy;
    import org.codefilarete.tool.Nullable;
    import org.codefilarete.tool.function.Hanger.Holder;

    import static org.codefilarete.tool.Nullable.empty;

    /**
     * Build a {@link NamingConfiguration} from an {@link EntityMappingConfiguration} by iterating over its hierarchy
     *
     * @author Guillaume Mary
     */
    public class NamingConfigurationCollector {

    	private final EntityMappingConfiguration<?, ?> entityMappingConfiguration;

    	public NamingConfigurationCollector(EntityMappingConfiguration<?, ?> entityMappingConfiguration) {
    		this.entityMappingConfiguration = entityMappingConfiguration;
    	}

    	public NamingConfiguration collect() {

    		Nullable<TableNamingStrategy> optionalTableNamingStrategy = empty();
    		visitInheritedEntityMappingConfigurations(configuration -> {
    			if (configuration.getTableNamingStrategy() != null && !optionalTableNamingStrategy.isPresent()) {
    				optionalTableNamingStrategy.set(configuration.getTableNamingStrategy());
    			}
    		});
    		TableNamingStrategy tableNamingStrategy = optionalTableNamingStrategy.getOr(TableNamingStrategy.DEFAULT);

    		// When a ColumnNamingStrategy is defined on mapping, it must be applied to super classes too
    		Nullable<ColumnNamingStrategy> optionalColumnNamingStrategy = empty();
    		class ColumnNamingStrategyCollector implements Consumer<EmbeddableMappingConfiguration> {
    			@Override
    			public void accept(EmbeddableMappingConfiguration embeddableMappingConfiguration) {
    				if (embeddableMappingConfiguration.getColumnNamingStrategy() != null && !optionalColumnNamingStrategy.isPresent()) {
    					optionalColumnNamingStrategy.set(embeddableMappingConfiguration.getColumnNamingStrategy());
    				}
    			}
    		}
    		ColumnNamingStrategyCollector columnNamingStrategyCollector = new ColumnNamingStrategyCollector();
    		visitInheritedEmbeddableMappingConfigurations(entityConfigurationConsumer ->
    				columnNamingStrategyCollector.accept(entityConfigurationConsumer.getPropertiesMapping()), columnNamingStrategyCollector);
    		ColumnNamingStrategy columnNamingStrategy = optionalColumnNamingStrategy.getOr(ColumnNamingStrategy.DEFAULT);

    		Nullable<ForeignKeyNamingStrategy> optionalForeignKeyNamingStrategy = empty();
    		visitInheritedEntityMappingConfigurations(configuration -> {
    			if (configuration.getForeignKeyNamingStrategy() != null && !optionalForeignKeyNamingStrategy.isPresent()) {
    				optionalForeignKeyNamingStrategy.set(configuration.getForeignKeyNamingStrategy());
    			}
    		});
    		ForeignKeyNamingStrategy foreignKeyNamingStrategy = optionalForeignKeyNamingStrategy.getOr(ForeignKeyNamingStrategy.DEFAULT);

    		Nullable<UniqueConstraintNamingStrategy> optionalUniqueConstraintNamingStrategy = empty();
    		visitInheritedEmbeddableMappingConfigurations(configuration -> {
    			if (configuration.getUniqueConstraintNamingStrategy() != null && !optionalUniqueConstraintNamingStrategy.isPresent()) {
    				optionalUniqueConstraintNamingStrategy.set(configuration.getUniqueConstraintNamingStrategy());
    			}
    		}, embeddableMappingConfiguration -> {
    			if (embeddableMappingConfiguration.getUniqueConstraintNamingStrategy() != null && !optionalUniqueConstraintNamingStrategy.isPresent()) {
    				optionalUniqueConstraintNamingStrategy.set(embeddableMappingConfiguration.getUniqueConstraintNamingStrategy());
    			}
    		});
    		UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy = optionalUniqueConstraintNamingStrategy.getOr(UniqueConstraintNamingStrategy.DEFAULT);

    		Nullable<JoinColumnNamingStrategy> optionalJoinColumnNamingStrategy = empty();
    		visitInheritedEntityMappingConfigurations(configuration -> {
    			if (configuration.getJoinColumnNamingStrategy() != null && !optionalJoinColumnNamingStrategy.isPresent()) {
    				optionalJoinColumnNamingStrategy.set(configuration.getJoinColumnNamingStrategy());
    			}
    		});
    		JoinColumnNamingStrategy joinColumnNamingStrategy = optionalJoinColumnNamingStrategy.getOr(JoinColumnNamingStrategy.JOIN_DEFAULT);

    		Nullable<ColumnNamingStrategy> optionalIndexColumnNamingStrategy = empty();
    		visitInheritedEntityMappingConfigurations(configuration -> {
    			if (configuration.getIndexColumnNamingStrategy() != null && !optionalIndexColumnNamingStrategy.isPresent()) {
    				optionalIndexColumnNamingStrategy.set(configuration.getIndexColumnNamingStrategy());
    			}
    		});
    		ColumnNamingStrategy indexColumnNamingStrategy = optionalIndexColumnNamingStrategy.getOr(ColumnNamingStrategy.INDEX_DEFAULT);

    		Nullable<AssociationTableNamingStrategy> optionalAssociationTableNamingStrategy = empty();
    		visitInheritedEntityMappingConfigurations(configuration -> {
    			if (configuration.getAssociationTableNamingStrategy() != null && !optionalAssociationTableNamingStrategy.isPresent()) {
    				optionalAssociationTableNamingStrategy.set(configuration.getAssociationTableNamingStrategy());
    			}
    		});
    		AssociationTableNamingStrategy associationTableNamingStrategy = optionalAssociationTableNamingStrategy.getOr(AssociationTableNamingStrategy.DEFAULT);

    		Nullable<ElementCollectionTableNamingStrategy> optionalElementCollectionTableNamingStrategy = empty();
    		visitInheritedEntityMappingConfigurations(configuration -> {
    			if (configuration.getElementCollectionTableNamingStrategy() != null && !optionalElementCollectionTableNamingStrategy.isPresent()) {
    				optionalElementCollectionTableNamingStrategy.set(configuration.getElementCollectionTableNamingStrategy());
    			}
    		});
    		ElementCollectionTableNamingStrategy elementCollectionTableNamingStrategy = optionalElementCollectionTableNamingStrategy.getOr(ElementCollectionTableNamingStrategy.DEFAULT);

    		Nullable<MapEntryTableNamingStrategy> optionalEntryMapTableNamingStrategy = empty();
    		visitInheritedEntityMappingConfigurations(configuration -> {
    			if (configuration.getEntryMapTableNamingStrategy() != null && !optionalEntryMapTableNamingStrategy.isPresent()) {
    				optionalEntryMapTableNamingStrategy.set(configuration.getEntryMapTableNamingStrategy());
    			}
    		});
    		MapEntryTableNamingStrategy mapEntryTableNamingStrategy = optionalEntryMapTableNamingStrategy.getOr(MapEntryTableNamingStrategy.DEFAULT);

    		return new NamingConfiguration(
    				tableNamingStrategy,
    				columnNamingStrategy,
    				foreignKeyNamingStrategy,
    				uniqueConstraintNamingStrategy,
    				elementCollectionTableNamingStrategy,
    				mapEntryTableNamingStrategy,
    				joinColumnNamingStrategy,
    				indexColumnNamingStrategy,
    				associationTableNamingStrategy);
    	}

    	void visitInheritedEntityMappingConfigurations(Consumer<EntityMappingConfiguration> configurationConsumer) {
    		// iterating over mapping from inheritance
    		entityMappingConfiguration.inheritanceIterable().forEach(configurationConsumer);
    	}

    	/**
    	 * Visits parent {@link EntityMappingConfiguration} of current entity mapping configuration (including itself), this is an optional operation
    	 * because current configuration may not have a direct entity ancestor.
    	 * Then visits mapped super classes as {@link EmbeddableMappingConfiguration} of the last visited {@link EntityMappingConfiguration}, optional
    	 * operation too.
    	 * This is because inheritance can only have 2 paths :
    	 * - first an optional inheritance from some other entity
    	 * - then an optional inheritance from some mapped super class
    	 *
    	 * @param entityConfigurationConsumer
    	 * @param mappedSuperClassConfigurationConsumer
    	 */
    	void visitInheritedEmbeddableMappingConfigurations(Consumer<EntityMappingConfiguration> entityConfigurationConsumer,
    													   Consumer<EmbeddableMappingConfiguration> mappedSuperClassConfigurationConsumer) {
    		// iterating over mapping from inheritance
    		Holder<EntityMappingConfiguration> lastMapping = new Holder<>();
    		visitInheritedEntityMappingConfigurations(entityMappingConfiguration -> {
    			entityConfigurationConsumer.accept(entityMappingConfiguration);
    			lastMapping.set(entityMappingConfiguration);
    		});
    		if (lastMapping.get().getPropertiesMapping().getMappedSuperClassConfiguration() != null) {
    			// iterating over mapping from mapped super classes
    			lastMapping.get().getPropertiesMapping().getMappedSuperClassConfiguration().inheritanceIterable().forEach(mappedSuperClassConfigurationConsumer);
    		}
    	}
    }