com.sun.xml.ws.rx.util.TimestampedCollection Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package com.sun.xml.ws.rx.util;
import com.sun.istack.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* TODO javadoc
*
*
* WARNING: This class is a private utility class used by WS-RX implementation. Any usage outside
* the intedned scope is strongly discouraged. The API exposed by this class may be changed, replaced
* or removed without any advance notice.
*
*
*/
public class TimestampedCollection {
public static TimestampedCollection newInstance() {
return new TimestampedCollection<>();
}
//
private static class TimestampedRegistration implements Comparable> {
private final long timestamp;
private final K key;
private final @NotNull V value;
public TimestampedRegistration(long timestamp, K key, @NotNull V value) {
this.timestamp = timestamp;
this.key = key;
this.value = value;
}
@Override
public int compareTo(TimestampedRegistration other) {
return Long.compare(this.timestamp, other.timestamp);
}
}
/**
* Primary registration collection
*/
private final PriorityQueue> timestampedPriorityQueue = new PriorityQueue<>();
/**
* Correlation key to registration mapping, may contain fewer elements than the whole collection.
*/
private final Map> correlationMap = new HashMap<>();
/**
* Data access lock
*/
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
TimestampedCollection() {
// package private constructor - only classes in this package can extend this class
}
/**
* Registers a {@code subject} and maps it to a given {@code correlationId}.
* The newly created registration is timestamped which allows for later removal
* based on the age of registration using {@link #removeOldest()} method.
*
* @param correlationId correlation identifier to be associated with a given {@code subject}
* @param subject a primary registration object
*
* @return old {@code subject} associated with a given {@code correlationId}
* or {@code null} if there's no such {@code subject}
*
* @see #remove(java.lang.Object)
* @see #removeOldest()
*/
public V register(@NotNull K correlationId, @NotNull V subject) {
try {
TimestampedRegistration tr = new TimestampedRegistration<>(System.currentTimeMillis(), correlationId, subject);
rwLock.writeLock().lock();
timestampedPriorityQueue.offer(tr);
TimestampedRegistration oldTr = correlationMap.put(tr.key, tr);
if (oldTr != null) {
removeFromQueue(oldTr);
return oldTr.value;
}
return null;
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Creates a new {@code subject} registration. The newly created registration
* is timestamped which allows for later removal based on the age of registration
* using {@link #removeOldest()} method.
*
* @param subject a primary registration subject
*
* @return {@code true} if the registration was successfull, {@code false} otherwise
*
* @see #removeOldest()
*/
public boolean register(@NotNull V subject) {
return register(System.currentTimeMillis(), subject);
}
/**
* Creates a new {@code subject} registration. The newly created registration
* is timestamped using a value of the {@code timestamp} parameter which allows
* for later removal based on the age of registration using {@link #removeOldest()}
* method.
*
* @param timestamp a timestamp to be used for the registration
* @param subject a primary registration subject
*
* @return {@code true} if the registration was successfull, {@code false} otherwise
*
* @see #removeOldest()
*/
public boolean register(long timestamp, @NotNull V subject) {
try {
TimestampedRegistration tr = new TimestampedRegistration<>(timestamp, null, subject);
rwLock.writeLock().lock();
return timestampedPriorityQueue.offer(tr);
// we don't put anything into correlationMap
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Removes a registration from the collection based on a {@code correlationId} and returns
* the value of the registered {@code subject}. This method may return {@code null}
*
* @param correlationId identifier to be associated with an already registered {@code subject}
*
* @return a registered {@code subject} associated with a given {@code correlationId}
* or {@code null} if there's no such {@code subject}
*
* @see #register(java.lang.Object, java.lang.Object)
*/
public V remove(@NotNull K correlationId) {
try {
rwLock.writeLock().lock();
TimestampedRegistration tr = correlationMap.remove(correlationId);
if (tr == null) {
return null;
}
removeFromQueue(tr);
return tr.value;
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Removes the oldest registration from the collection and returns the value of
* the registered {@code subject}.
*
* @return an oldest registered {@code subject}
*
* @throws NoSuchElementException if the underlying collection is empty.
*
* @see #register(java.lang.Object, java.lang.Object)
* @see #register(java.lang.Object)
* @see #register(long, java.lang.Object)
*/
public V removeOldest() {
try {
rwLock.writeLock().lock();
TimestampedRegistration tr = timestampedPriorityQueue.poll();
try {
if (tr.key != null) {
correlationMap.remove(tr.key);
}
return tr.value;
} catch (NullPointerException cause) {
NoSuchElementException ex = new NoSuchElementException("The underlying collection is empty.");
ex.initCause(cause);
throw ex;
}
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Removes all values from the time-stamped collection and returns them as an ordered FIFO
* list.
*
* @return ordered FIFO list of the removed values. Returns empty list in case there are no
* values stored in the collection.
*/
public List removeAll() {
try {
rwLock.writeLock().lock();
if (timestampedPriorityQueue.isEmpty()) {
return Collections.emptyList();
}
List values = new ArrayList<>(timestampedPriorityQueue.size());
while (!timestampedPriorityQueue.isEmpty()) {
values.add(removeOldest());
}
return values;
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Call this function to determine whether the collection is empty or not.
*
* @return {@code true} if the collection is empty, {@code false} otherwise
*/
public boolean isEmpty() {
try {
rwLock.readLock().lock();
return timestampedPriorityQueue.isEmpty();
} finally {
rwLock.readLock().unlock();
}
}
/**
* Returns the number of elements in this collection. If the collection
* contains more than {@code Integer.MAX_VALUE} elements, returns
* {@code Integer.MAX_VALUE}.
*
* @return the number of elements in this collection.
*/
public int size() {
try {
rwLock.readLock().lock();
return timestampedPriorityQueue.size();
} finally {
rwLock.readLock().unlock();
}
}
/**
* Returns a timestamp of the oldest registered subject.
*
* @return timestamp of the oldest registered subject.
*
* @throws NoSuchElementException if the underlying collection is empty.
*
* @see #removeOldest()
*/
public long getOldestRegistrationTimestamp() {
try {
rwLock.readLock().lock();
try {
return timestampedPriorityQueue.peek().timestamp;
} catch (NullPointerException cause) {
NoSuchElementException ex = new NoSuchElementException("The underlying collection is empty.");
ex.initCause(cause);
throw ex;
}
} finally {
rwLock.readLock().unlock();
}
}
private void removeFromQueue(TimestampedRegistration tr) {
Iterator> it = timestampedPriorityQueue.iterator();
while (it.hasNext()) {
if (it.next() == tr) {
// this must be the same instance
it.remove();
break;
}
}
}
}