| /* |
| * 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; |
| } |
| } |
| } |