blob: 7bd4184514fcd8688889c3f737b01bbb63d345ee [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jdbc.sql;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
* Set type that recognizes that inner joins should take precedence
* over otherwise equal outer joins.
*
* @author Abe White
*/
class JoinSet {
// the joins are stored as an adjacency list graph. it's a reasonably
// efficient representation with O(1) lookup, add, remove operations for
// typical sets of joins, and it means we'd have to create a graph anyway
// when joinIterator() is called
private final List _graph = new ArrayList();
private int _size = 0;
private List _sorted = null;
public JoinSet() {
}
public JoinSet(JoinSet copy) {
for (int i = 0; i < copy._graph.size(); i++) {
if (copy._graph.get(i) == null)
_graph.add(null);
else
_graph.add(((Node) copy._graph.get(i)).clone());
}
_size = copy._size;
_sorted = copy._sorted;
}
/**
* Return the recorded join matching the given join's aliases.
*/
public Join getRecordedJoin(Join join) {
if (join == null)
return null;
Node node = getNode(join, join.getIndex1());
return (node == null) ? null : node.join;
}
/**
* Return the node for the specified join and index.
*/
private Node getNode(Join join, int idx) {
if (_graph.size() <= idx)
return null;
Node node = (Node) _graph.get(idx);
for (; node != null; node = node.next)
if (node.join.equals(join))
return node;
return null;
}
/**
* Return the logical last join.
*/
public Join last() {
if (_size == 0)
return null;
Node node = (Node) _graph.get(_graph.size() - 1);
while (node.next != null)
node = node.next;
return node.join;
}
/**
* Iterator over joins that prepares them for SQL translation.
*/
public Iterator joinIterator() {
if (_size < 2)
return iterator();
if (_sorted != null)
return _sorted.iterator();
List sorted = new ArrayList(_size);
LinkedList queue = new LinkedList();
BitSet seen = new BitSet(_graph.size() * _graph.size()
+ _graph.size());
// traverse graph
Node n;
int idx, sidx;
for (Object o : _graph) {
// seed queue with next set of disconnected joins
for (n = (Node) o; n != null; n = n.next) {
sidx = getSeenIndex(n.join);
if (!seen.get(sidx)) {
seen.set(sidx);
queue.add(n);
}
}
if (queue.isEmpty())
continue;
// traverse from those joins to reachables
while (!queue.isEmpty()) {
n = (Node) queue.removeFirst();
// don't repeat a join to a table we've already joined, but
// do traverse through it in the graph (the first indexes of
// the seeen bitset are reserved for joined-to tables)
idx = (n.forward) ? n.join.getIndex2() : n.join.getIndex1();
if (!seen.get(idx)) {
sorted.add((n.forward) ? n.join : n.join.reverse());
seen.set(idx);
}
for (n = (Node) _graph.get(idx); n != null; n = n.next) {
sidx = getSeenIndex(n.join);
if (!seen.get(sidx)) {
seen.set(sidx);
queue.add(n);
}
}
}
}
_sorted = sorted;
return _sorted.iterator();
}
/**
* We create a unique index for recording seen nodes by
* treating the joined indexes as a base (max-index) number.
*/
private int getSeenIndex(Join join) {
// we reserve indexes 0..._graph.size() for joined tables
return join.getIndex1() * _graph.size() + join.getIndex2()
+ _graph.size();
}
public boolean add(Join join) {
if (join.getType() == Join.TYPE_OUTER) {
// outer shouldn't override any other join type
if (!contains(join)) {
addNode(join);
return true;
}
return false;
}
// replace any existing join with this one
Node node = getNode(join, join.getIndex1());
if (node != null) {
node.join = join;
getNode(join, join.getIndex2()).join = join;
_sorted = null;
} else
addNode(join);
return true;
}
public boolean addAll(JoinSet js) {
if (js.isEmpty())
return false;
boolean added = false;
for (Iterator itr = js.iterator(); itr.hasNext();)
added = add((Join) itr.next()) || added;
return added;
}
/**
* Add the give join to our graph.
*/
private void addNode(Join join) {
_sorted = null;
int size = Math.max(join.getIndex1(), join.getIndex2()) + 1;
while (_graph.size() < size)
_graph.add(null);
Node node = (Node) _graph.get(join.getIndex1());
if (node == null)
_graph.set(join.getIndex1(), new Node(join, true));
else {
while (node.next != null)
node = node.next;
node.next = new Node(join, true);
}
node = (Node) _graph.get(join.getIndex2());
if (node == null)
_graph.set(join.getIndex2(), new Node(join, false));
else {
while (node.next != null)
node = node.next;
node.next = new Node(join, false);
}
_size++;
}
public Iterator iterator() {
return new Iterator() {
private Node _next = null;
private int _idx = -1;
@Override
public boolean hasNext() {
if (_next != null)
return true;
while (++_idx < _graph.size()) {
_next = (Node) _graph.get(_idx);
while (_next != null && !_next.forward)
_next = _next.next;
if (_next != null)
return true;
}
return false;
}
@Override
public Object next() {
if (!hasNext())
throw new NoSuchElementException();
Join j = _next.join;
do {
_next = _next.next;
} while (_next != null && !_next.forward);
return j;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public boolean remove(Join join) {
if (join == null || _graph.size() <= join.getIndex1())
return false;
if (remove(join, join.getIndex1())) {
_size--;
return remove(join, join.getIndex2());
}
return false;
}
/**
* Each join is recorded one at its first index and once at its second;
* remove the join at one of its indexes.
*/
private boolean remove(Join join, int idx) {
Node node = (Node) _graph.get(idx);
for (Node prev = null; node != null; prev = node, node = node.next) {
if (!node.join.equals(join))
continue;
if (prev != null)
prev.next = node.next;
else {
_graph.set(idx, node.next);
// trim to size
while (!_graph.isEmpty() && _graph.get(idx) == null
&& idx == _graph.size() - 1)
_graph.remove(idx--);
}
return true;
}
return false;
}
public boolean removeAll(JoinSet js) {
boolean remd = false;
for (Iterator itr = js.iterator(); itr.hasNext();)
remd = remove((Join) itr.next()) || remd;
return remd;
}
public boolean retainAll(JoinSet js) {
boolean remd = false;
Join join;
for (Iterator itr = iterator(); itr.hasNext();) {
join = (Join) itr.next();
if (!js.contains(join))
remd = remove(join);
}
return remd;
}
public void clear() {
_graph.clear();
_sorted = null;
_size = 0;
}
public boolean contains(Join join) {
return getRecordedJoin(join) != null;
}
public boolean containsAll(JoinSet js) {
if (js._size > _size || js._graph.size() > _graph.size())
return false;
for (Iterator itr = js.iterator(); itr.hasNext();)
if (!contains((Join) itr.next()))
return false;
return true;
}
public boolean isEmpty() {
return _size == 0;
}
public int size() {
return _size;
}
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if (!(other instanceof JoinSet))
return false;
return _graph.equals(((JoinSet) other)._graph);
}
@Override
public int hashCode() {
return _graph.hashCode();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("[");
for (Iterator itr = iterator(); itr.hasNext();) {
buf.append("<").append(itr.next()).append(">");
if (itr.hasNext())
buf.append(", ");
}
return buf.append("]").toString();
}
/**
* A graph node.
*/
private static class Node
implements Cloneable {
public Join join;
public Node next;
public boolean forward;
public Node(Join join, boolean forward) {
this.join = join;
this.forward = forward;
}
@Override
public int hashCode() {
int rs = 17;
rs = 37 * rs + join.hashCode();
if (next != null)
rs = 37 * rs + next.hashCode();
return rs;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Node))
return false;
Node node = (Node) other;
return Objects.equals(join, node.join)
&& Objects.equals(next, node.next);
}
@Override
public Object clone() {
try {
Node node = (Node) super.clone();
if (node.next != null)
node.next = (Node) node.next.clone();
return node;
} catch (CloneNotSupportedException cnse) {
// can't happen
return null;
}
}
@Override
public String toString() {
return join + "(" + ((forward) ? "forward" : "backward") + ")"
+ "; next: " + next;
}
}
}