# PolymorphismPolicy.java

    package org.codefilarete.stalactite.dsl;

    import javax.annotation.Nullable;
    import java.util.Map;
    import java.util.Set;

    import org.codefilarete.stalactite.dsl.subentity.SubEntityMappingConfiguration;
    import org.codefilarete.stalactite.dsl.subentity.SubEntityMappingConfigurationProvider;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.Duo;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.KeepOrderMap;
    import org.codefilarete.tool.collection.KeepOrderSet;

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

    /**
     * @author Guillaume Mary
     */
    public interface PolymorphismPolicy<C> {

    	/**
    	 * Starts a persistence configuration of a table-per-class polymorphic type.
    	 * As a difference with {@link #tablePerClass(Class)}, this method doesn't require the polymorphic type as argument,
    	 * though you should prefix its call by the polymorphic type as a generic, for instance :
    	 * <code>
    	 *     &lt;Vehicle&gt;tablePerClass()
    	 * </code>
    	 *
    	 * @return the configuration
    	 */
    	static <C> TablePerClassPolymorphism<C> tablePerClass() {
    		return new TablePerClassPolymorphism<>();
    	}

    	/**
    	 * Starts a persistence configuration of a table-per-class polymorphic type
    	 *
    	 * @param polymorphicType type for which polymorphism is declared
    	 * @return the configuration
    	 * @param <C> polymorphic type
    	 */
    	static <C> TablePerClassPolymorphism<C> tablePerClass(Class<C> polymorphicType) {
    		return new TablePerClassPolymorphism<>();
    	}

    	/**
    	 * Starts a persistence configuration of a join-table polymorphic type.
    	 * As a difference with {@link #joinTable(Class)}, this method doesn't require the polymorphic type as argument,
    	 * though you should prefix its call by the polymorphic type as a generic, for instance :
    	 * <code>
    	 *     &lt;Vehicle&gt;joinTable()
    	 * </code>
    	 *
    	 * @return the configuration
    	 */
    	static <C> JoinTablePolymorphism<C> joinTable() {
    		return new JoinTablePolymorphism<>();
    	}

    	/**
    	 * Starts a persistence configuration of a join-table polymorphic type
    	 *
    	 * @param polymorphicType type for which polymorphism is declared
    	 * @return the configuration
    	 * @param <C> polymorphic type
    	 */
    	static <C> JoinTablePolymorphism<C> joinTable(Class<C> polymorphicType) {
    		return new JoinTablePolymorphism<>();
    	}

    	/**
    	 * Starts a persistence configuration of a single-table polymorphic type with a default discriminating column name "DTYPE" of {@link String} type
    	 * As a difference with {@link #singleTable(Class)}, this method doesn't require the polymorphic type as argument,
    	 * though you should prefix its call by the polymorphic type as a generic, for instance :
    	 * <code>
    	 *     &lt;Vehicle&gt;singleTable()
    	 * </code>
    	 *
    	 * @param <C> entity type
    	 * @return a new {@link SingleTablePolymorphism} with "DTYPE" as String discriminator column
    	 */
    	static <C> SingleTablePolymorphism<C, String> singleTable() {
    		return singleTable("DTYPE");
    	}

    	/**
    	 * Starts a persistence configuration of a single-table polymorphic type with the give discriminating column name {@link String} type
    	 * As a difference with {@link #singleTable(Class)}, this method doesn't require the polymorphic type as argument,
    	 * though you should prefix its call by the polymorphic type as a generic, for instance :
    	 * <code>
    	 *     &lt;Vehicle&gt;singleTable()
    	 * </code>
    	 *
    	 * @param <C> entity type
    	 * @return a new {@link SingleTablePolymorphism} with "DTYPE" as String discriminator column
    	 */
    	static <C> SingleTablePolymorphism<C, String> singleTable(String discriminatorColumnName) {
    		return new SingleTablePolymorphism<>(discriminatorColumnName, String.class);
    	}

    	/**
    	 * Starts a persistence configuration of a single-table polymorphic type with a default discriminating column name "DTYPE" of {@link String} type
    	 *
    	 * @param polymorphicType type for which polymorphism is declared
    	 * @return the configuration
    	 * @param <C> polymorphic type
    	 */
    	static <C> SingleTablePolymorphism<C, String> singleTable(Class<C> polymorphicType) {
    		return singleTable("DTYPE");
    	}

    	/**
    	 * Starts a persistence configuration of a single-table polymorphic type with the give discriminating column name {@link String} type
    	 *
    	 * @param polymorphicType type for which polymorphism is declared
    	 * @return the configuration
    	 * @param <C> polymorphic type
    	 */
    	static <C> SingleTablePolymorphism<C, String> singleTable(Class<C> polymorphicType, String discriminatorColumnName) {
    		return new SingleTablePolymorphism<>(discriminatorColumnName, String.class);
    	}

    	Set<SubEntityMappingConfiguration<? extends C>> getSubClasses();

    	class TablePerClassPolymorphism<C> implements PolymorphismPolicy<C> {

    		// we use a KeepOrderSet for stability order (overall for test assertions), not a strong expectation
    		private final Set<Duo<SubEntityMappingConfiguration<? extends C>, Table /* Nullable */>> subClasses = new KeepOrderSet<>();

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfigurationProvider the sub-entity type mapping to register
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public TablePerClassPolymorphism<C> addSubClass(SubEntityMappingConfigurationProvider<? extends C> entityMappingConfigurationProvider) {
    			addSubClass(entityMappingConfigurationProvider, (Table) null);
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public TablePerClassPolymorphism<C> addSubClass(SubEntityMappingConfiguration<? extends C> entityMappingConfiguration) {
    			addSubClass(entityMappingConfiguration, (Table) null);
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfigurationProvider the sub-entity type mapping to register
    		 * @param tableName the table name to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public TablePerClassPolymorphism<C> addSubClass(SubEntityMappingConfigurationProvider<? extends C> entityMappingConfigurationProvider, @Nullable String tableName) {
    			addSubClass(entityMappingConfigurationProvider.getConfiguration(), tableName);
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @param tableName the table name to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public TablePerClassPolymorphism<C> addSubClass(SubEntityMappingConfiguration<? extends C> entityMappingConfiguration, @Nullable String tableName) {
    			addSubClass(entityMappingConfiguration, nullable(tableName).map(Table::new).get());
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfigurationProvider the sub-entity type mapping to register
    		 * @param table the table to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public TablePerClassPolymorphism<C> addSubClass(SubEntityMappingConfigurationProvider<? extends C> entityMappingConfigurationProvider, @Nullable Table table) {
    			addSubClass(entityMappingConfigurationProvider.getConfiguration(), table);
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @param table the table to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public TablePerClassPolymorphism<C> addSubClass(SubEntityMappingConfiguration<? extends C> entityMappingConfiguration, @Nullable Table table) {
    			subClasses.add(new Duo<>(entityMappingConfiguration, table));
    			return this;
    		}

    		@Override
    		public Set<SubEntityMappingConfiguration<? extends C>> getSubClasses() {
    			// we use a KeepOrderSet for stability order (overall for test assertions), not a strong expectation
    			return Iterables.collect(subClasses, Duo::getLeft, KeepOrderSet::new);
    		}

    		@Nullable
    		public Table giveTable(SubEntityMappingConfiguration<? extends C> key) {
    			return Iterables.find(subClasses, duo -> duo.getLeft().equals(key)).getRight();
    		}
    	}

    	class JoinTablePolymorphism<C> implements PolymorphismPolicy<C> {

    		// we use a KeepOrderSet for stability order (overall for test assertions), not a strong expectation
    		private final Set<Duo<SubEntityMappingConfiguration<? extends C>, Table /* Nullable */>> subClasses = new KeepOrderSet<>();

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfigurationProvider the sub-entity type mapping to register
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public JoinTablePolymorphism<C> addSubClass(SubEntityMappingConfigurationProvider<? extends C> entityMappingConfigurationProvider) {
    			addSubClass(entityMappingConfigurationProvider, (Table) null);
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public JoinTablePolymorphism<C> addSubClass(SubEntityMappingConfiguration<? extends C> entityMappingConfiguration) {
    			addSubClass(entityMappingConfiguration, (Table) null);
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfigurationProvider the sub-entity type mapping to register
    		 * @param tableName the table to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public JoinTablePolymorphism<C> addSubClass(SubEntityMappingConfigurationProvider<? extends C> entityMappingConfigurationProvider, @Nullable String tableName) {
    			return addSubClass(entityMappingConfigurationProvider.getConfiguration(), tableName);
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @param tableName the table to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public JoinTablePolymorphism<C> addSubClass(SubEntityMappingConfiguration<? extends C> entityMappingConfiguration, @Nullable String tableName) {
    			addSubClass(entityMappingConfiguration, nullable(tableName).map(Table::new).get());
    			return this;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfigurationProvider the sub-entity type mapping to register
    		 * @param table the table to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public JoinTablePolymorphism<C> addSubClass(SubEntityMappingConfigurationProvider<? extends C> entityMappingConfigurationProvider, @Nullable Table table) {
    			return addSubClass(entityMappingConfigurationProvider.getConfiguration(), table);
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @param table the table to store the sub-entity type
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public JoinTablePolymorphism<C> addSubClass(SubEntityMappingConfiguration<? extends C> entityMappingConfiguration, @Nullable Table table) {
    			subClasses.add(new Duo<>(entityMappingConfiguration, table));
    			return this;
    		}

    		@Override
    		public Set<SubEntityMappingConfiguration<? extends C>> getSubClasses() {
    			// we use a KeepOrderSet for stability order (overall for test assertions), not a strong expectation
    			return Iterables.collect(subClasses, Duo::getLeft, KeepOrderSet::new);
    		}

    		@Nullable
    		public Table giveTable(SubEntityMappingConfiguration key) {
    			return Iterables.find(subClasses, duo -> duo.getLeft().equals(key)).getRight();
    		}
    	}

    	class SingleTablePolymorphism<C, D> implements PolymorphismPolicy<C> {

    		private final String discriminatorColumn;

    		private final Class<D> discriminatorType;

    		// we use a KeepOrderMap for stability order (overall for test assertions), not a strong expectation
    		private final Map<D, SubEntityMappingConfiguration<? extends C>> subClasses = new KeepOrderMap<>();

    		public SingleTablePolymorphism(String discriminatorColumn, Class<D> discriminatorType) {
    			this.discriminatorColumn = discriminatorColumn;
    			this.discriminatorType = discriminatorType;
    		}

    		public String getDiscriminatorColumn() {
    			return discriminatorColumn;
    		}

    		public Class<D> getDiscrimintorType() {
    			return discriminatorType;
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @param discriminatorValue sub-entity discriminator value (will be used to distinguish entity type in while loading them from database)
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public SingleTablePolymorphism<C, D> addSubClass(SubEntityMappingConfigurationProvider<? extends C> entityMappingConfiguration, D discriminatorValue) {
    			return addSubClass(entityMappingConfiguration.getConfiguration(), discriminatorValue);
    		}

    		/**
    		 * Registers a sub-entity type mapping to current polymorphic type
    		 *
    		 * @param entityMappingConfiguration the sub-entity type mapping to register
    		 * @param discriminatorValue sub-entity discriminator value (will be used to distinguish entity type in while loading them from database)
    		 * @return this
    		 * @see FluentMappings#subentityBuilder(Class)
    		 */
    		public SingleTablePolymorphism<C, D> addSubClass(SubEntityMappingConfiguration<? extends C> entityMappingConfiguration, D discriminatorValue) {
    			subClasses.put(discriminatorValue, entityMappingConfiguration);
    			return this;
    		}

    		public Class<? extends C> getClass(D discriminatorValue) {
    			return subClasses.get(discriminatorValue).getEntityType();
    		}

    		public D getDiscriminatorValue(Class<? extends C> instanceType) {
    			return Iterables.find(subClasses.entrySet(), e -> e.getValue().getEntityType().isAssignableFrom(instanceType)).getKey();
    		}

    		@Override
    		public Set<SubEntityMappingConfiguration<? extends C>> getSubClasses() {
    			// we use a KeepOrderSet for stability order (overall for test assertions), not a strong expectation
    			return new KeepOrderSet<>(this.subClasses.values());
    		}
    	}
    }