/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * Licensed 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 com.analog.lyric.collect; import java.io.Serializable; import java.lang.reflect.Array; import java.util.AbstractQueue; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.PriorityQueue; import net.jcip.annotations.Immutable; import net.jcip.annotations.NotThreadSafe; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.google.common.collect.Ordering; /** * Abstract base class for priority queue with unique keys which can be computed * from the elements themselves. * * @since 0.05 * @author Christopher Barber */ @NotThreadSafe @NonNullByDefault(false) public abstract class AbstractKeyedPriorityQueue<K,E> extends AbstractQueue<E> implements Serializable { /*------- * State */ private static final long serialVersionUID = 1L; protected final PriorityQueue<Entry<E>> _queue; protected final Map<K,Entry<E>> _keyToEntry; protected final @Nullable Comparator<? super E> _priorityComparator; protected int _insertOrder = 0; /*-------------- * Construction */ public AbstractKeyedPriorityQueue(int initialCapacity, @Nullable Comparator<? super E> comparator) { _priorityComparator = comparator; if (comparator == null) { @SuppressWarnings("unchecked") final Comparator<E> natural = (Comparator<E>)Ordering.natural(); comparator = natural; } _queue = new PriorityQueue<Entry<E>>(initialCapacity, new EntryComparator<E>(comparator)); _keyToEntry = new HashMap<K,Entry<E>>(initialCapacity); } public AbstractKeyedPriorityQueue(@NonNull Collection<? extends E> collection) { this(collection.size(), Comparators.fromCollection(collection)); addAll(collection); } /*-------------------- * Collection methods */ @Override public boolean add(E element) { return offer(element); } @Override public void clear() { _queue.clear(); _keyToEntry.clear(); _insertOrder = 0; } @Override public Iterator<E> iterator() { return new IteratorImpl(); } @Override public boolean removeAll(Collection<?> collection) { boolean changed = false; for (Object object : collection) { changed |= remove(object); } return changed; } @Override public int size() { return _queue.size(); } @Override public Object[] toArray() { return toArray(new Object[size()]); } @SuppressWarnings("unchecked") @Override public <T> T[] toArray(T[] array) { if (array.length < size()) { array = (T[]) Array.newInstance(array.getClass().getComponentType(), size()); } int i = 0; for (Entry<E> entry : _queue) { array[i++] = (T) entry._element; } return array; } /*--------------- * Queue methods */ @Override public E element() { return _queue.element()._element; } /** * Adds element to the queue if it does not already contain an element with the same key. * Unlike {@link #add} this will return false instead of throwing an exception if the * element was not added because its key was found. * <p> * If the element {@link #comparator()} is null, indicating that natural ordering should be * used, then this method will attempt to cast the element to {@link Comparable}. * <p> * @throws ClassCastException if queue has no explicit comparator and element is not an instance of * {@link Comparable} * @throws NullPointerException if element is null */ @Override public boolean offer(E element) { K key = getKeyFromElement(element); if (!_keyToEntry.containsKey(key)) { if (_priorityComparator == null) { // If there is no explicit comparator, then the natural ordering will be used, which // requires that the elements must implement Comparable. Comparable.class.cast(element); } Entry<E> entry = new Entry<E>(element, _insertOrder++); _keyToEntry.put(key, entry); _queue.add(entry); return true; } return false; } @Override public @Nullable E peek() { Entry<E> entry = _queue.peek(); return entry != null ? entry._element : null; } @Override public @Nullable E poll() { Entry<E> entry = _queue.poll(); if (entry == null) { _insertOrder = 0; return null; } else { E element = entry._element; _keyToEntry.remove(getKeyFromElement(element)); return element; } } /*------------------------------------ * AbstractKeyedPriorityQueue methods */ /** * The comparator used to determine priority order among elements in the queue. * If null, then natural ordering of elements will be used and methods that * add elements to the queue will only accept {@link Comparable} objects. */ public final @Nullable Comparator<? super E> comparator() { return _priorityComparator; } /** * Returns the key for a given element. */ protected abstract @Nullable K getKeyFromElement(@NonNull E element); /** * Indicates whether queue contains element with the given key. */ protected boolean containsKey(@Nullable Object key) { return _keyToEntry.containsKey(key); } /** * Returns element in queue with given key or null. */ protected @Nullable E get(@Nullable Object key) { Entry<E> entry = _keyToEntry.get(key); return entry != null ? entry._element : null; } /** * Remove entry with given key from the queue. * @return false if queue did not contain element for the key. */ protected boolean removeKey(@Nullable Object key) { Entry<E> entry = _keyToEntry.remove(key); if (entry != null) { _queue.remove(entry); if (_queue.isEmpty()) { _insertOrder = 0; } return true; } return false; } /*--------------- * Inner classes */ @Immutable protected static class Entry<E> implements Serializable { private static final long serialVersionUID = 1L; protected final E _element; protected final long _insertOrder; protected Entry(E element, long insertOrder) { _element = element; _insertOrder = insertOrder; } } @Immutable private static class EntryComparator<E> implements Comparator<Entry<E>>, Serializable { private static final long serialVersionUID = 1L; private final Comparator<? super E> _comparator; private EntryComparator(Comparator<? super E> comparator) { _comparator = comparator; } @Override public int compare(Entry<E> entry1, Entry<E> entry2) { int diff = _comparator.compare(entry1._element, entry2._element); if (diff == 0) { diff = Long.signum(entry1._insertOrder - entry2._insertOrder); } return diff; } } /*--------------- * Inner classes */ @NotThreadSafe private class IteratorImpl implements Iterator<E> { private @NonNull final Iterator<Entry<E>> _iterator; private @Nullable E _last = null; private IteratorImpl() { _iterator = _queue.iterator(); } @Override public boolean hasNext() { return _iterator.hasNext(); } @Override public @Nullable E next() { return _last = _iterator.next()._element; } @Override public void remove() { final E last = _last; if (last == null) { throw new IllegalStateException("Iterator.remove() invoked not called after next()"); } _iterator.remove(); // If that didn't throw an exception, remove from the map as well. _keyToEntry.remove(getKeyFromElement(last)); _last = null; } } }