# RelationsStep.java

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

    import java.util.Collection;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    import org.codefilarete.stalactite.dsl.RelationalMappingConfiguration;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
    import org.codefilarete.stalactite.engine.EntityPersister;
    import org.codefilarete.stalactite.engine.configurer.embeddable.Inset;
    import org.codefilarete.stalactite.engine.configurer.NamingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.RelationConfigurer;
    import org.codefilarete.stalactite.engine.configurer.builder.InheritanceMappingStep.Mapping;
    import org.codefilarete.stalactite.engine.configurer.builder.InheritanceMappingStep.MappingPerTable;
    import org.codefilarete.stalactite.engine.configurer.elementcollection.ElementCollectionRelation;
    import org.codefilarete.stalactite.engine.configurer.manyToOne.ManyToOneRelation;
    import org.codefilarete.stalactite.engine.configurer.manytomany.ManyToManyRelation;
    import org.codefilarete.stalactite.engine.configurer.map.MapRelation;
    import org.codefilarete.stalactite.engine.configurer.onetomany.OneToManyRelation;
    import org.codefilarete.stalactite.engine.configurer.onetoone.OneToOneRelation;
    import org.codefilarete.stalactite.engine.runtime.SimpleRelationalEntityPersister;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.tool.collection.Iterables;

    /***
     * Configure relations using {@link RelationConfigurer}.
     * Visit parent entity mapping configuration to also configure relations.
     * Will handle graph cycle with {@link PersisterBuilderContext#runInContext(EntityPersister, Runnable)}.
     *
     * @param <C>
     * @param <I>
     * @author Guillaume Mary
     */
    public class RelationsStep<C, I> {

    	void configureRelations(SimpleRelationalEntityPersister<C, I, ?> mainPersister,
    							MappingPerTable<C> inheritanceMappingPerTable,
    							PersisterBuilderContext persisterBuilderContext,
    							NamingConfiguration namingConfiguration,
    							Dialect dialect,
    							ConnectionConfiguration connectionConfiguration) {
    		RelationConfigurer<C, I> relationConfigurer = new RelationConfigurer<>(dialect, connectionConfiguration, mainPersister,
    				namingConfiguration, persisterBuilderContext);

    		persisterBuilderContext.runInContext(mainPersister, () -> {
    			// registering relations on parent entities
    			// WARN : this MUST BE DONE BEFORE POLYMORPHISM HANDLING because it needs them to create adhoc joins on sub entities tables
    			configuredInheritedRelations(inheritanceMappingPerTable, relationConfigurer);

    			configuredEmbeddableRelations(inheritanceMappingPerTable, relationConfigurer, mainPersister.getClassToPersist());
    		});
    	}

    	private void configuredInheritedRelations(MappingPerTable<C> inheritanceMappingPerTable, RelationConfigurer<C, I> relationConfigurer) {
    		inheritanceMappingPerTable.getMappings().stream()
    				.map(Mapping::getMappingConfiguration)
    				.filter(RelationalMappingConfiguration.class::isInstance)
    				.map(RelationalMappingConfiguration.class::cast)
    				.forEach(relationConfigurer::configureRelations);
    	}

    	private <D> void configuredEmbeddableRelations(MappingPerTable<C> inheritanceMappingPerTable, RelationConfigurer<C, I> relationConfigurer, Class<C> rootEntityType) {
    		inheritanceMappingPerTable.getMappings().stream()
    				.map(Mapping::getMappingConfiguration)
    				.filter(EntityMappingConfiguration.class::isInstance)
    				.map(EntityMappingConfiguration.class::cast)
    				.map(EntityMappingConfiguration::getPropertiesMapping)
    				// we the configurations of all embedded bean through the configuration insets
    				.flatMap(conf -> ((Collection<Inset<C, D>>) conf.getInsets()).stream()
    						// we must check the inherited class of the embeddable because they may also define some relations
    						.flatMap(inset -> Iterables.stream(inset.getConfigurationProvider().getConfiguration().inheritanceIterable())
    								.map(confInHierarchy -> new SlidedRelationalMappingConfiguration<>(rootEntityType, confInHierarchy, inset))))
    				.forEach(slidedRelationalMappingConfiguration -> {
    					relationConfigurer.configureRelations((RelationalMappingConfiguration<C>) slidedRelationalMappingConfiguration);
    				});
    	}

    	/**
    	 * A {@link RelationalMappingConfiguration} which relations are slided by a prefix that is the accessor to the embeddable which is embedded
    	 * in the main entity.
    	 *
    	 * @param <D>
    	 * @author Guillaume Mary
    	 */
    	private class SlidedRelationalMappingConfiguration<D> implements RelationalMappingConfiguration<C> {
    		private final Class<C> rootEntityType;
    		private final RelationalMappingConfiguration<D> configuration;
    		private final Inset<C, D> inset;

    		public SlidedRelationalMappingConfiguration(Class<C> rootEntityType, RelationalMappingConfiguration<D> configuration, Inset<C, D> inset) {
    			this.rootEntityType = rootEntityType;
    			this.configuration = configuration;
    			this.inset = inset;
    		}

    		@Override
    		public Class<C> getEntityType() {
    			return rootEntityType;
    		}

    		@Override
    		public <TRGT, TRGTID> List<OneToOneRelation<C, TRGT, TRGTID>> getOneToOnes() {
    			return configuration.<TRGT, TRGTID>getOneToOnes().stream()
    					.map(oneToOne -> oneToOne.embedInto(inset.getAccessor()))
    					.collect(Collectors.toList());
    		}

    		@Override
    		public <TRGT, TRGTID> List<OneToManyRelation<C, TRGT, TRGTID, Collection<TRGT>>> getOneToManys() {
    			return configuration.<TRGT, TRGTID>getOneToManys().stream()
    					.map(oneToMany -> oneToMany.embedInto(inset.getAccessor(), configuration.getEntityType()))
    					.collect(Collectors.toList());
    		}

    		@Override
    		public <TRGT, TRGTID> List<ManyToManyRelation<C, TRGT, TRGTID, Collection<TRGT>, Collection<C>>> getManyToManys() {
    			return configuration.<TRGT, TRGTID>getManyToManys().stream()
    					.map(manyToMany -> manyToMany.embedInto(inset.getAccessor(), configuration.getEntityType()))
    					.collect(Collectors.toList());
    		}

    		@Override
    		public <TRGT, TRGTID> List<ManyToOneRelation<C, TRGT, TRGTID, Collection<C>>> getManyToOnes() {
    			return configuration.<TRGT, TRGTID>getManyToOnes().stream()
    					.map(manyToOne -> manyToOne.embedInto(inset.getAccessor()))
    					.collect(Collectors.toList());
    		}

    		@Override
    		public <TRGT> List<ElementCollectionRelation<C, TRGT, ? extends Collection<TRGT>>> getElementCollections() {
    			return configuration.<TRGT>getElementCollections().stream()
    					.map(collectionRelation -> collectionRelation.embedInto(inset.getAccessor(), configuration.getEntityType()))
    					.collect(Collectors.toList());
    		}

    		@Override
    		public List<MapRelation<C, ?, ?, ? extends Map>> getMaps() {
    			return Collections.emptyList();
    		}
    	}
    }