# TablePerClassPolymorphicRelationJoinNode.java

    package org.codefilarete.stalactite.engine.runtime.load;

    import javax.annotation.Nullable;
    import java.util.HashSet;
    import java.util.IdentityHashMap;
    import java.util.Set;

    import org.codefilarete.reflection.Accessor;
    import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
    import org.codefilarete.stalactite.engine.runtime.load.EntityTreeInflater.RelationIdentifier;
    import org.codefilarete.stalactite.engine.runtime.load.EntityTreeInflater.TreeInflationContext;
    import org.codefilarete.stalactite.engine.runtime.load.JoinRowConsumer.ForkJoinRowConsumer;
    import org.codefilarete.stalactite.query.model.JoinLink;
    import org.codefilarete.stalactite.query.model.QueryStatement;
    import org.codefilarete.stalactite.query.model.QueryStatement.PseudoTable;
    import org.codefilarete.stalactite.query.model.Selectable;
    import org.codefilarete.stalactite.query.model.Union;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Key;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.BeanRelationFixer;
    import org.codefilarete.stalactite.sql.result.ColumnedRow;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.collection.Iterables;

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

    /**
     * Particular {@link JoinNode} made to handle relation from an entity to a collection of some another polymorphic one. Actually relation doesn't
     * make the complexity of that class: polymorphic entity instantiation is the core focus of it. Here are the hot spots:
     * identifier is given by the subclass which find its id in the row (see {@link TablePerClassPolymorphicRelationJoinRowConsumer#giveIdentifier}),
     * and while doing it, it remembers which consumer made it. Then while instantiating entity we invoke it to get right entity type (parent mapping
     * would only give parent entity, which, out of being a wrong approach, can be an abstract type). Then instance is filled up with parent properties
     * by calling merging method (see line 98).
     * Finally, {@link TablePerClassPolymorphicRelationJoinRowConsumer} must extend {@link ForkJoinRowConsumer} to give next branch to be consumed by
     * {@link EntityTreeInflater} to avoid "dead" branch to be read : we give it according to the consumer which found the identifier.
     *
     * @author Guillaume Mary
     */
    public class TablePerClassPolymorphicRelationJoinNode<C, T1 extends Table<T1>, JOINCOLTYPE, I> extends RelationJoinNode<C, T1, PseudoTable, JOINCOLTYPE, I> {

    	private static IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> collectColumnClones(Key<?, ?> rightJoinLink, Set<? extends JoinLink<?, ?>> columnsToSelect) {
    		IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> result = new IdentityHashMap<>();
    		rightJoinLink.getTable().getColumns().forEach(column -> {
    			result.put((JoinLink<?, ?>) column, (JoinLink<?, ?>) column);
    		});
    		columnsToSelect.forEach(column -> {
    			result.put(column, column);
    		});
    		return result;
    	}

    	private final Set<SubPersisterAndDiscriminator<? extends C>> subPersisters = new HashSet<>();

    	private final PseudoTable pseudoTable;
    	private final Union.PseudoColumn<Integer> discriminatorColumn;
    	private final Key<PseudoTable, JOINCOLTYPE> pseudoRightJoinLink;

    	public TablePerClassPolymorphicRelationJoinNode(JoinNode<?, T1> parent,
    													Union subPersistersUnion,
    													Accessor<?, ?> propertyAccessor,
    													Key<T1, JOINCOLTYPE> leftJoinColumn,
    													Key<?, JOINCOLTYPE> rightJoinColumn,
    													JoinType joinType,
    													Set<? extends JoinLink<?, ?>> columnsToSelect,
    													@Nullable String tableAlias,
    													EntityInflater<C, I> entityInflater,
    													BeanRelationFixer<Object, C> beanRelationFixer,
    													Union.PseudoColumn<Integer> discriminatorColumn) {
    		super(parent, propertyAccessor, leftJoinColumn, (Key) rightJoinColumn, joinType, columnsToSelect, tableAlias, entityInflater, beanRelationFixer, null,
    				collectColumnClones(rightJoinColumn, columnsToSelect));
    		this.pseudoTable = subPersistersUnion.asPseudoTable(getTableAlias());
    		super.getOriginalColumnsToLocalOnes().clear();
    		super.getOriginalColumnsToLocalOnes().putAll(collectColumnClones(rightJoinColumn, pseudoTable.getColumns()));
    		this.discriminatorColumn = discriminatorColumn;
    		// rebuilding the right join link to make it match the union table, because the given one has columns from the abstract (parent) entity
    		// which doesn't have a matching / existing table
    		Key.KeyBuilder<PseudoTable, JOINCOLTYPE> pseudoRightJoinLinkBuilder = Key.from(pseudoTable);
    		rightJoinColumn.getColumns()
    				.forEach(column -> {
    					QueryStatement.PseudoColumn pseudoColumn1 = Iterables.find(pseudoTable.getColumns(), pseudoColumn -> pseudoColumn.getExpression().equals(column.getExpression()));
    					pseudoRightJoinLinkBuilder.addColumn(pseudoColumn1);
    				});
    		this.pseudoRightJoinLink = pseudoRightJoinLinkBuilder.build();
    	}

    	@Override
    	public PseudoTable getRightTable() {
    		return pseudoTable;
    	}

    	@Override
    	public Key<PseudoTable, JOINCOLTYPE> getRightJoinLink() {
    		return pseudoRightJoinLink;
    	}

    	/**
    	 * Overridden, else it returns the ones given at construction time which are from Union, meaning getOwner() is not
    	 * {@link PseudoTable} which, out of throwing a {@link ClassCastException}, avoid to give access to correct
    	 * {@link Column}
    	 */
    	@Override
    	public Set<Selectable<?>> getColumnsToSelect() {
    		return (Set) pseudoTable.getColumns();
    	}

    	@Override
    	public TablePerClassPolymorphicRelationJoinRowConsumer toConsumer(JoinNode<C, PseudoTable> joinNode) {
    		return new TablePerClassPolymorphicRelationJoinRowConsumer(joinNode, discriminatorColumn, getConsumptionListener());
    	}

    	public <D extends C> void addSubPersisterJoin(PolymorphicMergeJoinRowConsumer<D, I> subPersisterJoin, int discriminatorValue) {
    		this.subPersisters.add(new SubPersisterAndDiscriminator<>(subPersisterJoin, discriminatorValue));
    	}

    	private class SubPersisterAndDiscriminator<D extends C> {

    		private final PolymorphicMergeJoinRowConsumer<D, I> subPersisterJoin;
    		private final int discriminatorValue;

    		public SubPersisterAndDiscriminator(PolymorphicMergeJoinRowConsumer<D, I> subPersisterJoin, int discriminatorValue) {
    			this.subPersisterJoin = subPersisterJoin;
    			this.discriminatorValue = discriminatorValue;
    		}
    	}

    	public class TablePerClassPolymorphicRelationJoinRowConsumer implements RelationJoinRowConsumer<C, I>, ForkJoinRowConsumer {

    		private final ThreadLocal<RowIdentifier<? extends C>> currentlyFoundConsumer = new ThreadLocal<>();

    		private final JoinNode<C, PseudoTable> joinNode;

    		/** Optional listener of ResultSet decoding */
    		@Nullable
    		private final EntityTreeJoinNodeConsumptionListener<C> consumptionListener;
    		private final Union.PseudoColumn<Integer> discriminatorColumn;

    		private TablePerClassPolymorphicRelationJoinRowConsumer(JoinNode<C, PseudoTable> joinNode,
    																Union.PseudoColumn<Integer> discriminatorColumn,
    																@Nullable EntityTreeJoinNodeConsumptionListener<C> consumptionListener) {
    			this.joinNode = joinNode;
    			this.consumptionListener = consumptionListener;
    			this.discriminatorColumn = discriminatorColumn;
    		}

    		@Override
    		public JoinNode<C, ?> getNode() {
    			return joinNode;
    		}

    		@Override
    		public C applyRelatedEntity(Object parentJoinEntity, ColumnedRow row, TreeInflationContext context) {
    			RowIdentifier<C> rowIdentifier = giveIdentifier(row);
    			currentlyFoundConsumer.set(rowIdentifier);
    			if (rowIdentifier == null) {
    				return null;
    			} else {
    				I rightIdentifier = rowIdentifier.entityIdentifier;
    				// we avoid treating twice same relation, overall to avoid adding twice same instance to a collection (one-to-many list cases)
    				// in case of multiple collections in ResultSet because it creates similar data (through cross join) which are treated as many as
    				// collections cross with each other. This also works for one-to-one relations but produces no bugs. It can also be seen as a performance
    				// enhancement even if it hasn't been measured.
    				RelationIdentifier eventuallyApplied = new RelationIdentifier(
    						parentJoinEntity,
    						getEntityType(),
    						rightIdentifier,
    						this);
    				// primary key null means no entity => nothing to do
    				if (rightIdentifier != null && context.isTreatedOrAppend(eventuallyApplied)) {
    					C rightEntity = context.giveEntityFromCache(getEntityType(), rightIdentifier, () -> rowIdentifier.rowConsumer.transform(row));
    					getBeanRelationFixer().apply(parentJoinEntity, rightEntity);
    					if (this.consumptionListener != null) {
    						this.consumptionListener.onNodeConsumption(rightEntity, row);
    					}
    					return rightEntity;
    				} else {
    					return null;
    				}
    			}
    		}

    		@Nullable
    		/* Optimized, from 530 000 nanos to 65 000 nanos at 1st exec, from 40 000 nanos to 12 000 nanos on usual run */
    		private <D extends C> RowIdentifier<D> giveIdentifier(ColumnedRow row) {
    			// @Optimized : use for & return instead of stream().map().filter(notNull).findFirst()
    			Integer discriminatorValue = row.get(discriminatorColumn);
    			if (discriminatorValue != null) {
    				SubPersisterAndDiscriminator<D> discriminatorConsumer = (SubPersisterAndDiscriminator) Iterables.find(subPersisters, o -> o.discriminatorValue == discriminatorValue);
    				if (discriminatorConsumer != null) {
    					I identifier = discriminatorConsumer.subPersisterJoin.giveIdentifier(row);
    					if (identifier != null) {
    						return new RowIdentifier<>(identifier, discriminatorConsumer.subPersisterJoin);
    					}
    				}
    			}
    			return null;
    		}

    		@Override
    		public JoinRowConsumer giveNextConsumer() {
    			return nullable(currentlyFoundConsumer.get()).map(rowIdentifier -> rowIdentifier.rowConsumer).get();
    		}

    		@Override
    		public void afterRowConsumption(TreeInflationContext context) {
    			currentlyFoundConsumer.remove();
    		}

    		private class RowIdentifier<D extends C> {

    			private final I entityIdentifier;
    			private final PolymorphicMergeJoinRowConsumer<D, I> rowConsumer;

    			private RowIdentifier(I entityIdentifier, PolymorphicMergeJoinRowConsumer<D, I> rowConsumer) {
    				this.entityIdentifier = entityIdentifier;
    				this.rowConsumer = rowConsumer;
    			}
    		}

    		/**
    		 * Implemented for debug. DO NOT RELY ON IT for anything else.
    		 */
    		@Override
    		public String toString() {
    			return Reflections.toString(this.getClass());
    		}
    	}
    }