/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

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;
	}
    }
}
