/*
* 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 com.sun.jini.jeri.internal.http;
import com.sun.jini.thread.Executor;
import java.util.HashMap;
import java.util.Map;
/**
* Simple hash map which evicts entries after a fixed timeout. All operations
* which modify a TimedMap synchronize on the TimedMap instance itself,
* including the thread which evicts expired entries.
*
* @author Sun Microsystems, Inc.
*
*/
class TimedMap {
private final Executor executor;
private final long timeout;
private final Map map = new HashMap();
private final Queue evictQueue = new Queue();
private boolean evictorActive = false;
/**
* Creates empty TimedMap which uses threads from the given Executor to
* evict entries after the specified timeout.
*/
TimedMap(Executor executor, long timeout) {
if (executor == null) {
throw new NullPointerException();
}
if (timeout < 0) {
throw new IllegalArgumentException();
}
this.executor = executor;
this.timeout = timeout;
}
/**
* Associates the given key with the given value, resetting the key's
* timeout. Returns value (if any) previously associated with key.
*/
synchronized Object put(Object key, Object value) {
if (!evictorActive) {
executor.execute(new Evictor(), "TimedMap evictor");
evictorActive = true;
}
long now = System.currentTimeMillis();
Mapping mapping = new Mapping(key, value, now + timeout);
evictQueue.append(mapping);
if ((mapping = (Mapping) map.put(key, mapping)) == null) {
return null;
}
if (mapping.expiry <= now) {
mapping.remove = false;
return null;
}
evictQueue.remove(mapping);
return mapping.value;
}
/**
* Returns value associated with given key, or null if no mapping for key
* is found. Resets timeout for key if it is present in map.
*/
synchronized Object get(Object key) {
Mapping mapping = (Mapping) map.get(key);
if (mapping == null) {
return null;
}
long now = System.currentTimeMillis();
if (mapping.expiry <= now) {
return null;
}
evictQueue.remove(mapping);
mapping.expiry = now + timeout;
evictQueue.append(mapping);
return mapping.value;
}
/**
* Removes mapping for key from map, returning the value associated with
* the key (or null if no mapping present).
*/
synchronized Object remove(Object key) {
Mapping mapping = (Mapping) map.remove(key);
if (mapping == null) {
return null;
}
if (mapping.expiry <= System.currentTimeMillis()) {
mapping.remove = false;
return null;
}
evictQueue.remove(mapping);
return mapping.value;
}
/**
* Upcall invoked after key's timeout has expired and key has been removed
* from the map.
*/
void evicted(Object key, Object value) {
}
/**
* Key/value mapping.
*/
private static class Mapping extends Queue.Node {
final Object key;
final Object value;
long expiry;
boolean remove = true;
Mapping(Object key, Object value, long expiry) {
this.key = key;
this.value = value;
this.expiry = expiry;
}
}
/**
* Expired mapping eviction thread.
*/
private class Evictor implements Runnable {
public void run() {
Mapping mapping;
while ((mapping = nextEvicted()) != null) {
evicted(mapping.key, mapping.value);
}
}
private Mapping nextEvicted() {
synchronized (TimedMap.this) {
Mapping mapping;
while ((mapping = (Mapping) evictQueue.getHead()) != null) {
long now = System.currentTimeMillis();
if (mapping.expiry <= now) {
evictQueue.remove(mapping);
if (mapping.remove) {
map.remove(mapping.key);
}
return mapping;
} else {
try {
TimedMap.this.wait(mapping.expiry - now);
} catch (InterruptedException ex) {
}
}
}
evictorActive = false;
return null;
}
}
}
/**
* Lightweight doubly-linked queue supporting constant-time manipulation.
*/
private static class Queue {
static class Node {
private Queue owner;
private Node prev;
private Node next;
}
private Node head;
private Node tail;
Queue() {
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
}
Node getHead() {
return (head.next != tail) ? head.next : null;
}
void remove(Node node) {
if (node.owner != this) {
throw new IllegalArgumentException();
}
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = node.next = null;
node.owner = null;
}
void append(Node node) {
if (node.owner != null) {
throw new IllegalArgumentException();
}
node.owner = this;
node.prev = tail.prev;
node.next = tail;
tail.prev.next = node;
tail.prev = node;
}
}
}