| /* |
| * 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.jasper.util; |
| |
| /** |
| * The FastRemovalDequeue is a Dequeue that supports constant time removal of entries. This is achieved by using a |
| * doubly linked list and wrapping any object added to the collection with an Entry type, that is returned to the |
| * consumer. When removing an object from the list, the consumer provides this Entry object. |
| * <p> |
| * The Entry type is nearly opaque to the consumer of the queue. The only public member is the getter for any object |
| * displaced when adding a new object to the queue. This can be used to destroy that object. |
| * <p> |
| * The Entry object contains the links pointing to the neighbours in the doubly linked list, so that removal of an Entry |
| * does not need to search for it but instead can be done in constant time. |
| * <p> |
| * The implementation is fully thread-safe. |
| * <p> |
| * Invalidation of Entry objects during removal from the list is done by setting their "valid" field to false. All |
| * public methods which take Entry objects as arguments are NOP if the entry is no longer valid. |
| * <p> |
| * A typical use of the FastRemovalDequeue is a list of entries in sorted order, where the sort position of an object |
| * will only switch to first or last. |
| * <p> |
| * Whenever the sort position needs to change, the consumer can remove the object and reinsert it in front or at the end |
| * in constant time. So keeping the list sorted is very cheap. |
| * |
| * @param <T> The type of elements in the queue |
| */ |
| public class FastRemovalDequeue<T> { |
| |
| /** Maximum size of the queue */ |
| private final int maxSize; |
| /** First element of the queue. */ |
| protected Entry first; |
| /** Last element of the queue. */ |
| protected Entry last; |
| /** Size of the queue */ |
| private int size; |
| |
| /** |
| * Initialize empty queue. |
| * |
| * @param maxSize The maximum size to which the queue will be allowed to grow |
| */ |
| public FastRemovalDequeue(int maxSize) { |
| if (maxSize <= 1) { |
| maxSize = 2; |
| } |
| this.maxSize = maxSize; |
| first = null; |
| last = null; |
| size = 0; |
| } |
| |
| /** |
| * Retrieve the size of the list. This method also needs to be externally synchronized to ensure correct publication |
| * of changes. |
| * |
| * @return the size of the list. |
| */ |
| public synchronized int getSize() { |
| return size; |
| } |
| |
| /** |
| * Adds an object to the start of the list and returns the entry created for said object. The entry can later be |
| * reused for moving the entry. |
| * |
| * @param object the object to prepend to the start of the list. |
| * |
| * @return an entry for use when the object should be moved. |
| */ |
| public synchronized Entry push(final T object) { |
| Entry entry = new Entry(object); |
| if (size >= maxSize) { |
| entry.setReplaced(pop()); |
| } |
| if (first == null) { |
| first = last = entry; |
| } else { |
| first.setPrevious(entry); |
| entry.setNext(first); |
| first = entry; |
| } |
| size++; |
| |
| return entry; |
| } |
| |
| /** |
| * Adds an object to the end of the list and returns the entry created for said object. The entry can later be |
| * reused for moving the entry. |
| * |
| * @param object the object to append to the end of the list. |
| * |
| * @return an entry for use when the object should be moved. |
| */ |
| public synchronized Entry unpop(final T object) { |
| Entry entry = new Entry(object); |
| if (size >= maxSize) { |
| entry.setReplaced(unpush()); |
| } |
| if (first == null) { |
| first = last = entry; |
| } else { |
| last.setNext(entry); |
| entry.setPrevious(last); |
| last = entry; |
| } |
| size++; |
| |
| return entry; |
| } |
| |
| /** |
| * Removes the first element of the list and returns its content. |
| * |
| * @return the content of the first element of the list. |
| **/ |
| public synchronized T unpush() { |
| T content = null; |
| if (first != null) { |
| Entry element = first; |
| first = first.getNext(); |
| content = element.getContent(); |
| if (first == null) { |
| last = null; |
| } else { |
| first.setPrevious(null); |
| } |
| size--; |
| element.invalidate(); |
| } |
| return content; |
| } |
| |
| /** |
| * Removes the last element of the list and returns its content. |
| * |
| * @return the content of the last element of the list. |
| **/ |
| public synchronized T pop() { |
| T content = null; |
| if (last != null) { |
| Entry element = last; |
| last = last.getPrevious(); |
| content = element.getContent(); |
| if (last == null) { |
| first = null; |
| } else { |
| last.setNext(null); |
| } |
| size--; |
| element.invalidate(); |
| } |
| return content; |
| } |
| |
| /** |
| * Removes any element of the list and returns its content. |
| * |
| * @param element The element to remove |
| */ |
| public synchronized void remove(final Entry element) { |
| if (element == null || !element.getValid()) { |
| return; |
| } |
| Entry next = element.getNext(); |
| Entry prev = element.getPrevious(); |
| if (next != null) { |
| next.setPrevious(prev); |
| } else { |
| last = prev; |
| } |
| if (prev != null) { |
| prev.setNext(next); |
| } else { |
| first = next; |
| } |
| size--; |
| element.invalidate(); |
| } |
| |
| /** |
| * Moves the element in front. Could also be implemented as remove() and push(), but explicitly coding might be a |
| * bit faster. |
| * |
| * @param element the entry to move in front. |
| */ |
| public synchronized void moveFirst(final Entry element) { |
| if (element.getValid() && element.getPrevious() != null) { |
| Entry prev = element.getPrevious(); |
| Entry next = element.getNext(); |
| prev.setNext(next); |
| if (next != null) { |
| next.setPrevious(prev); |
| } else { |
| last = prev; |
| } |
| first.setPrevious(element); |
| element.setNext(first); |
| element.setPrevious(null); |
| first = element; |
| } |
| } |
| |
| /** |
| * Moves the element to the back. Could also be implemented as remove() and unpop(), but explicitly coding might be |
| * a bit faster. |
| * |
| * @param element the entry to move to the back. |
| */ |
| public synchronized void moveLast(final Entry element) { |
| if (element.getValid() && element.getNext() != null) { |
| Entry next = element.getNext(); |
| Entry prev = element.getPrevious(); |
| next.setPrevious(prev); |
| if (prev != null) { |
| prev.setNext(next); |
| } else { |
| first = next; |
| } |
| last.setNext(element); |
| element.setPrevious(last); |
| element.setNext(null); |
| last = element; |
| } |
| } |
| |
| /** |
| * Implementation of a doubly linked list entry. All implementation details are private. For the consumer of the |
| * above collection, this is simply garbage in, garbage out. |
| */ |
| public class Entry { |
| |
| /** Is this entry still valid? */ |
| private boolean valid = true; |
| /** The content this entry is valid for. */ |
| private final T content; |
| /** Optional content that was displaced by this entry */ |
| private T replaced = null; |
| /** Pointer to next element in queue. */ |
| private Entry next = null; |
| /** Pointer to previous element in queue. */ |
| private Entry previous = null; |
| |
| private Entry(T object) { |
| content = object; |
| } |
| |
| private boolean getValid() { |
| return valid; |
| } |
| |
| private void invalidate() { |
| this.valid = false; |
| this.previous = null; |
| this.next = null; |
| } |
| |
| /** |
| * Returns the content stored in this entry. |
| * |
| * @return the content object |
| */ |
| public final T getContent() { |
| return content; |
| } |
| |
| /** |
| * Returns the content that was displaced when this entry was added to a full queue. |
| * |
| * @return the displaced content, or {@code null} if no content was displaced |
| */ |
| public final T getReplaced() { |
| return replaced; |
| } |
| |
| private void setReplaced(final T replaced) { |
| this.replaced = replaced; |
| } |
| |
| /** |
| * Clears the displaced content reference. |
| */ |
| public final void clearReplaced() { |
| this.replaced = null; |
| } |
| |
| private Entry getNext() { |
| return next; |
| } |
| |
| private void setNext(final Entry next) { |
| this.next = next; |
| } |
| |
| private Entry getPrevious() { |
| return previous; |
| } |
| |
| private void setPrevious(final Entry previous) { |
| this.previous = previous; |
| } |
| |
| @Override |
| public String toString() { |
| return "Entry-" + content.toString(); |
| } |
| } |
| |
| } |