# JoinRoot.java

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

    import javax.annotation.Nullable;
    import java.util.ArrayList;
    import java.util.IdentityHashMap;
    import java.util.List;
    import java.util.Set;
    import java.util.function.Function;

    import org.codefilarete.stalactite.engine.runtime.load.EntityTreeInflater.TreeInflationContext;
    import org.codefilarete.stalactite.engine.runtime.load.JoinRowConsumer.RootJoinRowConsumer;
    import org.codefilarete.stalactite.mapping.RowTransformer;
    import org.codefilarete.stalactite.query.api.Fromable;
    import org.codefilarete.stalactite.query.api.JoinLink;
    import org.codefilarete.stalactite.query.api.Selectable;
    import org.codefilarete.stalactite.sql.result.ColumnedRow;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.collection.ReadOnlyList;

    /**
     * Very first table (and its joins) of a from clause
     *
     * @author Guillaume Mary
     */
    public class JoinRoot<C, I, T extends Fromable> implements JoinNode<C, T> {

    	private final EntityJoinTree<C, I> tree;

    	/** Root entity inflater */
    	private final EntityInflater<C, I> entityInflater;

    	private final T table;

    	/** Joins */
    	private final List<AbstractJoinNode<?, ?, ?, ?>> joins = new ArrayList<>();

    	@Nullable
    	private String tableAlias;

    	@Nullable
    	private EntityTreeJoinNodeConsumptionListener<C> consumptionListener;

    	private final IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> columnClones;

    	public JoinRoot(EntityJoinTree<C, I> tree, EntityInflater<C, I> entityInflater, T table) {
    		this.tree = tree;
    		this.entityInflater = entityInflater;
    		this.table = table;
    		this.columnClones = new IdentityHashMap<>();
    		table.getColumns().forEach(column -> {
    			// we clone columns to avoid side effects on the original query
    			this.columnClones.put((JoinLink<?, ?>) column, (JoinLink<?, ?>) column);
    		});
    	}

    	public JoinRoot(EntityJoinTree<C, I> tree, EntityInflater<C, I> entityInflater, T table, IdentityHashMap<? extends JoinLink<?, ?>, ? extends JoinLink<?, ?>> columnClones) {
    		this.tree = tree;
    		this.entityInflater = entityInflater;
    		this.table = table;
    		this.columnClones = (IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>>) columnClones;
    	}

    	public EntityInflater<C, I> getEntityInflater() {
    		return entityInflater;
    	}

    	@Nullable
    	EntityTreeJoinNodeConsumptionListener<C> getConsumptionListener() {
    		return consumptionListener;
    	}

    	@Override
    	public JoinRoot<C, I, T> setConsumptionListener(@Nullable EntityTreeJoinNodeConsumptionListener<C> consumptionListener) {
    		this.consumptionListener = consumptionListener;
    		return this;
    	}

    	@Override
    	public IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> getOriginalColumnsToLocalOnes() {
    		return columnClones;
    	}

    	@Override
    	public EntityJoinTree<C, I> getTree() {
    		return tree;
    	}

    	@Override
    	public T getTable() {
    		return table;
    	}

    	@Override
    	public Set<Selectable<?>> getColumnsToSelect() {
    		return entityInflater.getSelectableColumns();
    	}

    	@Override
    	public ReadOnlyList<AbstractJoinNode<?, ?, ?, ?>> getJoins() {
    		return new ReadOnlyList<>(joins);
    	}

    	@Override
    	public void add(AbstractJoinNode node) {
    		this.joins.add(node);
    	}

    	@Nullable
    	@Override
    	public String getTableAlias() {
    		return tableAlias;
    	}

    	@Override
    	public RootJoinRowConsumer<C, I> toConsumer(JoinNode<C, T> joinNode) {
    		return new JoinRootRowConsumer(this, entityInflater);
    	}

    	public class JoinRootRowConsumer implements RootJoinRowConsumer<C, I> {

    		private final JoinRoot<C, ?, ?> joinNode;
    		private final Class<C> entityType;

    		/** Root entity identifier decoder */
    		private final Function<ColumnedRow, I> identifierDecoder;

    		private final RowTransformer<C> entityBuilder;

    		public JoinRootRowConsumer(JoinRoot<C, ?, ?> joinNode, EntityInflater<C, I> entityInflater) {
    			this.joinNode = joinNode;
    			this.entityType = entityInflater.getEntityType();
    			this.identifierDecoder = entityInflater::giveIdentifier;
    			this.entityBuilder = entityInflater.getRowTransformer();
    		}

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

    		@Override
    		public EntityReference<C, I> createRootInstance(ColumnedRow row, TreeInflationContext context) {
    			I identifier = identifierDecoder.apply(row);
    			EntityReference<C, I> entityReference = null;
    			if (identifier != null) {
    				C entity = context.giveEntityFromCache(entityType, identifier, () -> entityBuilder.transform(row));
    				entityReference = new EntityReference<>(entity, identifier);
    				if (getConsumptionListener() != null) {
    					getConsumptionListener().onNodeConsumption(entity, row);
    				}
    			}
    			return entityReference;
    		}

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