| /* |
| * 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.activemq.artemis.utils.collections; |
| |
| import java.lang.reflect.Array; |
| import java.util.Comparator; |
| import java.util.NoSuchElementException; |
| import java.util.Objects; |
| |
| /** |
| * A linked list implementation which allows multiple iterators to exist at the same time on the queue, and which see any |
| * elements added or removed from the queue either directly or via iterators. |
| * <p> |
| * This class is not thread safe. |
| */ |
| public class LinkedListImpl<E> implements LinkedList<E> { |
| |
| private static final int INITIAL_ITERATOR_ARRAY_SIZE = 10; |
| |
| private final Node<E> head = new NodeHolder<>(null); |
| private final Comparator<E> comparator; |
| private Node<E> tail = null; |
| private int size; |
| // We store in an array rather than a Map for the best performance |
| private volatile Iterator[] iters; |
| private int numIters; |
| private int nextIndex; |
| private NodeStore<E> nodeStore; |
| |
| public LinkedListImpl() { |
| this(null, null); |
| } |
| |
| public LinkedListImpl(Comparator<E> comparator) { |
| this(comparator, null); |
| } |
| |
| public LinkedListImpl(Comparator<E> comparator, NodeStore<E> supplier) { |
| iters = createIteratorArray(INITIAL_ITERATOR_ARRAY_SIZE); |
| this.comparator = comparator; |
| this.nodeStore = supplier; |
| } |
| |
| @Override |
| public void clearID() { |
| if (nodeStore != null) { |
| nodeStore.clear(); |
| } |
| nodeStore = null; |
| } |
| |
| @Override |
| public void setNodeStore(NodeStore<E> supplier) { |
| this.nodeStore = supplier; |
| |
| try (Iterator iterator = (Iterator) iterator()) { |
| while (iterator.hasNext()) { |
| E value = iterator.next(); |
| Node<E> position = iterator.last; |
| putID(value, position); |
| } |
| } |
| } |
| |
| private void putID(E element, Node<E> node) { |
| nodeStore.storeNode(element, node); |
| } |
| |
| @Override |
| public void addHead(E e) { |
| Node<E> node = Node.with(e); |
| |
| node.next = head.next; |
| |
| node.prev = head; |
| |
| head.next = node; |
| |
| if (size == 0) { |
| tail = node; |
| } else { |
| // Need to set the previous element on the former head |
| node.next.prev = node; |
| } |
| |
| itemAdded(node, e); |
| |
| size++; |
| } |
| |
| @Override |
| public E removeWithID(String listID, long id) { |
| assert nodeStore != null; // it is assumed the code will call setNodeStore before callin removeWithID |
| |
| Node<E> node = nodeStore.getNode(listID, id); |
| |
| if (node == null) { |
| return null; |
| } |
| |
| // the node will always have a prev element |
| removeAfter(node.prev); |
| return node.val(); |
| } |
| |
| private void itemAdded(Node<E> node, E item) { |
| if (nodeStore != null) { |
| putID(item, node); |
| } |
| } |
| |
| private void itemRemoved(Node<E> node) { |
| if (nodeStore != null) { |
| nodeStore.removeNode(node.val(), node); |
| } |
| } |
| |
| @Override |
| public void addTail(E e) { |
| if (size == 0) { |
| addHead(e); |
| } else { |
| Node<E> node = Node.with(e); |
| |
| node.prev = tail; |
| |
| tail.next = node; |
| |
| tail = node; |
| |
| itemAdded(node, e); |
| |
| size++; |
| } |
| } |
| |
| public void addSorted(E e) { |
| if (comparator == null) { |
| throw new NullPointerException("comparator=null"); |
| } |
| if (size == 0) { |
| addHead(e); |
| } else { |
| if (comparator.compare(head.next.val(), e) < 0) { |
| addHead(e); |
| return; |
| } |
| |
| // in our usage, most of the times we will just add to the end |
| // as the QueueImpl cancellations in AMQP will return the buffer back to the queue, in the order they were consumed. |
| // There is an exception to that case, when there are more messages on the queue. |
| // This would be an optimization for our usage. |
| // avoiding scanning the entire List just to add at the end, so we compare the end first. |
| if (comparator.compare(tail.val(), e) >= 0) { |
| addTail(e); |
| return; |
| } |
| |
| Node<E> fetching = head.next; |
| while (fetching.next != null) { |
| int compareNext = comparator.compare(fetching.next.val(), e); |
| if (compareNext <= 0) { |
| addAfter(fetching, e); |
| return; |
| } |
| fetching = fetching.next; |
| } |
| |
| // this shouldn't happen as the tail was compared before iterating |
| // the only possibilities for this to happen are: |
| // - there is a bug on the comparator |
| // - This method is buggy |
| // - The list wasn't properly synchronized as this list does't support concurrent access |
| // |
| // Also I'm not bothering about creating a Logger ID for this, because the only reason for this code to exist |
| // is because my OCD level is not letting this out. |
| throw new IllegalStateException("Cannot find a suitable place for your element, There's a mismatch in the comparator or there was concurrent adccess on the queue"); |
| } |
| } |
| |
| private void addAfter(Node<E> node, E e) { |
| Node<E> newNode = Node.with(e); |
| Node<E> nextNode = node.next; |
| node.next = newNode; |
| newNode.prev = node; |
| newNode.next = nextNode; |
| nextNode.prev = newNode; |
| itemAdded(node, e); |
| size++; |
| } |
| |
| @Override |
| public E poll() { |
| Node<E> ret = head.next; |
| |
| if (ret != null) { |
| removeAfter(head); |
| |
| return ret.val(); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public void clear() { |
| // Clearing all of the links between nodes is "unnecessary", but: |
| // - helps a generational GC if the discarded nodes inhabit |
| // more than one generation |
| // - is sure to free memory even if there is a reachable Iterator |
| while (poll() != null) { |
| |
| } |
| } |
| |
| @Override |
| public int size() { |
| return size; |
| } |
| |
| @Override |
| public LinkedListIterator<E> iterator() { |
| return new Iterator(); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder str = new StringBuilder("LinkedListImpl [ "); |
| |
| Node<E> node = head; |
| |
| while (node != null) { |
| str.append(node.toString()); |
| |
| if (node.next != null) { |
| str.append(", "); |
| } |
| |
| node = node.next; |
| } |
| |
| return str.toString(); |
| } |
| |
| public int numIters() { |
| return numIters; |
| } |
| |
| private Iterator[] createIteratorArray(int size) { |
| return (Iterator[]) Array.newInstance(Iterator.class, size); |
| } |
| |
| private void removeAfter(Node<E> node) { |
| Node<E> toRemove = node.next; |
| |
| node.next = toRemove.next; |
| |
| if (toRemove.next != null) { |
| toRemove.next.prev = node; |
| } |
| |
| itemRemoved(toRemove); |
| |
| if (toRemove == tail) { |
| tail = node; |
| } |
| |
| size--; |
| |
| if (toRemove.iterCount != 0) { |
| LinkedListImpl.this.nudgeIterators(toRemove); |
| } |
| |
| //Help GC - otherwise GC potentially has to traverse a very long list to see if elements are reachable, this can result in OOM |
| //https://jira.jboss.org/browse/HORNETQ-469 |
| toRemove.next = toRemove.prev = null; |
| } |
| |
| private synchronized void nudgeIterators(Node<E> node) { |
| for (int i = 0; i < numIters; i++) { |
| Iterator iter = iters[i]; |
| if (iter != null) { |
| iter.nudged(node); |
| } |
| } |
| } |
| |
| private synchronized void addIter(Iterator iter) { |
| if (numIters == iters.length) { |
| resize(2 * numIters); |
| } |
| |
| iters[nextIndex++] = iter; |
| |
| numIters++; |
| } |
| |
| private synchronized void resize(int newSize) { |
| Iterator[] newIters = createIteratorArray(newSize); |
| |
| System.arraycopy(iters, 0, newIters, 0, numIters); |
| |
| iters = newIters; |
| } |
| |
| private synchronized void removeIter(Iterator iter) { |
| for (int i = 0; i < numIters; i++) { |
| if (iter == iters[i]) { |
| iters[i] = null; |
| |
| if (i != numIters - 1) { |
| // Fill in the hole |
| |
| System.arraycopy(iters, i + 1, iters, i, numIters - i - 1); |
| } |
| |
| numIters--; |
| |
| if (numIters >= INITIAL_ITERATOR_ARRAY_SIZE && numIters == iters.length / 2) { |
| resize(numIters); |
| } |
| |
| nextIndex--; |
| |
| return; |
| } |
| } |
| |
| throw new IllegalStateException("Cannot find iter to remove"); |
| } |
| |
| private static final class NodeHolder<E> extends Node<E> { |
| |
| private final E val; |
| |
| //only the head is allowed to hold a null |
| private NodeHolder(E e) { |
| val = e; |
| } |
| |
| @Override |
| protected E val() { |
| return val; |
| } |
| } |
| |
| public static class Node<E> { |
| |
| private Node<E> next; |
| |
| private Node<E> prev; |
| |
| private int iterCount; |
| |
| private static <E> Node<E> with(final E o) { |
| Objects.requireNonNull(o, "Only HEAD nodes are allowed to hold null values"); |
| if (o instanceof Node) { |
| final Node node = (Node) o; |
| //only a node that not belong already to a list is allowed to be reused |
| if (node.prev == null && node.next == null) { |
| //reset the iterCount |
| node.iterCount = 0; |
| return node; |
| } |
| } |
| return new NodeHolder<>(o); |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected E val() { |
| return (E) this; |
| } |
| |
| protected final LinkedListImpl.Node<E> next() { |
| return next; |
| } |
| |
| protected final LinkedListImpl.Node<E> prev() { |
| return prev; |
| } |
| |
| @Override |
| public String toString() { |
| return val() == this ? "Intrusive Node" : "Node, value = " + val(); |
| } |
| } |
| |
| private class Iterator implements LinkedListIterator<E> { |
| |
| Node<E> last; |
| |
| Node<E> current = head.next; |
| |
| boolean repeat; |
| |
| Iterator() { |
| if (current != null) { |
| current.iterCount++; |
| } |
| |
| addIter(this); |
| } |
| |
| @Override |
| public void repeat() { |
| repeat = true; |
| } |
| |
| @Override |
| public boolean hasNext() { |
| Node<E> e = getNode(); |
| |
| if (e != null && (e != last || repeat)) { |
| return true; |
| } |
| |
| return canAdvance(); |
| } |
| |
| @Override |
| public E next() { |
| Node<E> e = getNode(); |
| |
| if (repeat) { |
| repeat = false; |
| |
| if (e != null) { |
| return e.val(); |
| } else { |
| if (canAdvance()) { |
| advance(); |
| |
| e = getNode(); |
| |
| return e.val(); |
| } else { |
| throw new NoSuchElementException(); |
| } |
| } |
| } |
| |
| if (e == null || e == last) { |
| if (canAdvance()) { |
| advance(); |
| |
| e = getNode(); |
| } else { |
| throw new NoSuchElementException(); |
| } |
| } |
| |
| last = e; |
| |
| repeat = false; |
| |
| return e.val(); |
| } |
| |
| @Override |
| public void remove() { |
| if (last == null) { |
| throw new NoSuchElementException(); |
| } |
| |
| if (current == null) { |
| throw new NoSuchElementException(); |
| } |
| |
| LinkedListImpl.this.removeAfter(current.prev); |
| |
| last = null; |
| } |
| |
| @Override |
| public void close() { |
| removeIter(this); |
| } |
| |
| public void nudged(Node<E> node) { |
| if (current == node) { |
| if (canAdvance()) { |
| advance(); |
| } else { |
| if (current.prev != head) { |
| current.iterCount--; |
| |
| current = current.prev; |
| |
| current.iterCount++; |
| } else { |
| current = null; |
| } |
| } |
| } |
| } |
| |
| private Node<E> getNode() { |
| if (current == null) { |
| current = head.next; |
| |
| if (current != null) { |
| current.iterCount++; |
| } |
| } |
| |
| if (current != null) { |
| return current; |
| } else { |
| return null; |
| } |
| } |
| |
| private boolean canAdvance() { |
| if (current == null) { |
| current = head.next; |
| |
| if (current != null) { |
| current.iterCount++; |
| } |
| } |
| |
| return current != null && current.next != null; |
| } |
| |
| private void advance() { |
| if (current == null || current.next == null) { |
| throw new NoSuchElementException(); |
| } |
| |
| current.iterCount--; |
| |
| current = current.next; |
| |
| current.iterCount++; |
| } |
| |
| } |
| } |