AbstractJoinNode.java
package org.codefilarete.stalactite.engine.runtime.load;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
import org.codefilarete.stalactite.query.model.Fromable;
import org.codefilarete.stalactite.query.model.JoinLink;
import org.codefilarete.stalactite.query.model.Selectable;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.tool.collection.ReadOnlyList;
/**
* Abstraction of relation, merge and passive joins.
*
* @author Guillaume Mary
*/
public abstract class AbstractJoinNode<C, T1 extends Fromable, T2 extends Fromable, JOINTYPE> implements JoinNode<C, T2> {
/** Join column with previous strategy table */
private final Key<T1, JOINTYPE> leftJoinLink;
/** Join column with next strategy table */
private final Key<T2, JOINTYPE> rightJoinLink;
/** Indicates if the join must be an inner or (left) outer join */
private final JoinType joinType;
private final Set<Selectable<?>> columnsToSelect;
private final JoinNode<?, T1> parent;
/** Joins */
private final List<AbstractJoinNode<?, ?, ?, ?>> joins = new ArrayList<>();
@Nullable
protected String tableAlias;
@Nullable
private EntityTreeJoinNodeConsumptionListener<C> consumptionListener;
private final IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> originalColumnsToLocalOnes;
protected AbstractJoinNode(JoinNode<?, T1> parent,
JoinLink<T1, JOINTYPE> leftJoinLink,
JoinLink<T2, JOINTYPE> rightJoinLink,
JoinType joinType,
Set<? extends Selectable<?>> columnsToSelect, // From T2
@Nullable String tableAlias) {
this(parent, Key.ofSingleColumn(leftJoinLink), Key.ofSingleColumn(rightJoinLink), joinType, columnsToSelect, tableAlias);
}
protected AbstractJoinNode(JoinNode<?, T1> parent,
Key<T1, JOINTYPE> leftJoinLink,
Key<T2, JOINTYPE> rightJoinLink,
JoinType joinType,
Set<? extends Selectable<?>> columnsToSelect, // From T2
@Nullable String tableAlias) {
this.parent = parent;
this.leftJoinLink = leftJoinLink;
this.rightJoinLink = rightJoinLink;
this.joinType = joinType;
this.columnsToSelect = (Set) columnsToSelect;
this.tableAlias = tableAlias;
parent.add(this);
this.originalColumnsToLocalOnes = new IdentityHashMap<>();
rightJoinLink.getTable().getColumns().forEach(column -> {
// we clone columns to avoid side effects on the original query
this.originalColumnsToLocalOnes.put((JoinLink<?, ?>) column, (JoinLink<?, ?>) column);
});
}
/**
* Constructor dedicated for node cloning
*
* @param parent the node to add the created instance to
* @param leftJoinLink the left joining columns of this node, expected to be taken on parent node table
* @param rightJoinLink the right joining columns of this node, expected to be taken on this node table
* @param joinType inner or outer join type
* @param columnsToSelect additional columns to select from right table (T2), out of the joining columns which are automatically selected
* @param tableAlias optional table alias to use in the query, if null then no alias is used
* @param originalColumnsToLocalOnes a map of original columns to local ones, used to simplify and allow selecting original columns by user
*/
protected AbstractJoinNode(JoinNode<?, T1> parent,
Key<T1, JOINTYPE> leftJoinLink,
Key<T2, JOINTYPE> rightJoinLink,
JoinType joinType,
Set<? extends Selectable<?>> columnsToSelect, // From T2
@Nullable String tableAlias,
IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> originalColumnsToLocalOnes) {
this.parent = parent;
this.leftJoinLink = leftJoinLink;
this.rightJoinLink = rightJoinLink;
this.joinType = joinType;
this.columnsToSelect = (Set) columnsToSelect;
this.tableAlias = tableAlias;
parent.add(this);
this.originalColumnsToLocalOnes = originalColumnsToLocalOnes;
}
@Override
public <ROOT, ID> EntityJoinTree<ROOT, ID> getTree() {
// going up to the root to get tree from it because JoinRoot owns the information
JoinNodeHierarchyIterator joinNodeHierarchyIterator = new JoinNodeHierarchyIterator(this);
AbstractJoinNode<?, ?, ?, ?> currentNode = this;
while (joinNodeHierarchyIterator.hasNext()) {
currentNode = joinNodeHierarchyIterator.next();
}
// currentNode is the last before root
return currentNode.getParent().getTree();
}
public JoinNode<?, T1> getParent() {
return parent;
}
@Override
public T2 getTable() {
return getRightTable();
}
public Key<T1, JOINTYPE> getLeftJoinLink() {
return leftJoinLink;
}
public Key<T2, JOINTYPE> getRightJoinLink() {
return rightJoinLink;
}
public JoinType getJoinType() {
return joinType;
}
@Override
public Set<Selectable<?>> getColumnsToSelect() {
return columnsToSelect;
}
@Override
public IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> getOriginalColumnsToLocalOnes() {
return originalColumnsToLocalOnes;
}
@Override
public ReadOnlyList<AbstractJoinNode<?, ?, ?, ?>> getJoins() {
return new ReadOnlyList<>(joins);
}
@Nullable
EntityTreeJoinNodeConsumptionListener<C> getConsumptionListener() {
return consumptionListener;
}
@Override
public AbstractJoinNode<C, T1, T2, JOINTYPE> setConsumptionListener(@Nullable EntityTreeJoinNodeConsumptionListener<C> consumptionListener) {
this.consumptionListener = consumptionListener;
return this;
}
@Override
public void add(AbstractJoinNode node) {
// safeguard
if (node.getParent() != this) {
throw new IllegalArgumentException("Node is not added as child of right node : parent differs from target owner");
}
this.joins.add(node);
}
@Nullable
@Override
public String getTableAlias() {
return tableAlias;
}
public T2 getRightTable() {
return this.rightJoinLink.getTable();
}
/**
* Iterator over {@link AbstractJoinNode} from given node over parents, except root (because it's not a {@link AbstractJoinNode} so can't be returned)
*/
static class JoinNodeHierarchyIterator implements Iterator<AbstractJoinNode> {
private AbstractJoinNode currentNode;
JoinNodeHierarchyIterator(AbstractJoinNode currentNode) {
this.currentNode = currentNode;
}
@Override
public boolean hasNext() {
return currentNode != null;
}
@Override
public AbstractJoinNode next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
AbstractJoinNode toReturn = currentNode;
prepareNextIteration();
return toReturn;
}
private void prepareNextIteration() {
JoinNode parent = currentNode.getParent();
if (parent instanceof AbstractJoinNode) {
currentNode = (AbstractJoinNode) parent;
} else {
currentNode = null;
}
}
}
}