/*
 * 
 * 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.mahalo;

import java.util.Map;
import java.rmi.RemoteException;
import java.lang.ref.WeakReference;

import net.jini.core.lease.UnknownLeaseException;
import net.jini.id.Uuid;
import com.sun.jini.thread.WakeupManager;
import com.sun.jini.collection.WeakTable;
import com.sun.jini.landlord.LeasedResource;


/**
 * Lease Mgr implementation that aggressively expires leases as they
 * expiration times occur.  Synchronizes on resource before canceling it.
 *
 * @author Sun Microsystems, Inc.
 *
 * @see com.sun.jini.mahalo.LeaseManager
 */
class LeaseExpirationMgr implements LeaseManager, WeakTable.KeyGCHandler {
    /**
     * Interface that allows LeaseExpirationMgr to expire resources.
     * This is the same as the
     * <code>com.sun.jini.landlord.Landlord.cancel()<code> method less
     * the <code>RemoteException</code> in the throws clause. Mixing
     * this interface into a <code>Landlord</code> implementation
     * allows the <code>LeaseExpirationMgr</code> to cancel leases
     * without having to deal with <code>RemoteException</code>.
     */
    static interface Expirer {
	/**
	 * Called by a LeaseExpirationMgr when it needs to expire a
	 * resource. The value of the <code>cookie</code> parameter is
	 * obtained from <code>getCookie()</code> method of the
	 * <code>LeasedResource</code> being expired.
         */
	public void cancel(Uuid cookie) throws UnknownLeaseException;
    }


    // Map of resources to tickets
    private WeakTable		ticketMap = new WeakTable(this); 
    private Expirer		landlord;
    private WakeupManager expirationQueue
        = new WakeupManager(new WakeupManager.ThreadDesc(null, true));

    /**
     * Create a <code>LeaseExpirationMgr</code> to aggressively expire
     * the leases of the passed landlord (implementing
     * <code>Expirer</code> is trivial for a <code>Landlord</code>.
     */
    LeaseExpirationMgr(Expirer landlord) {
	this.landlord = landlord;
    }

    /**
     * Terminate the <code>LeaseExpirationMgr</code>, killing any 
     * threads it has started
     */
    void terminate() {
        expirationQueue.stop();
        expirationQueue.cancelAll();
    }

    
    // purposefully inherit doc comment from supertype
    public void register(LeasedResource resource) {
	schedule(resource);
    }

    // purposefully inherit doc comment from supertype
    public void renewed(LeasedResource resource) {
	// Remove the old event
	expirationQueue.cancel(
	    (WakeupManager.Ticket)ticketMap.remove(resource)
	);
	// Schedule the new event
	schedule(resource);
    }

    /** 
     * Schedule a leased resource to be reaped in the future. Called
     * when a resource gets a lease, or a lease is renewed.
     */
    private void schedule(LeasedResource resource) {
	final WakeupManager.Ticket ticket =
	    expirationQueue.schedule(resource.getExpiration(),
				     new Canceler(resource));
	ticketMap.getOrAdd(resource, ticket);
    }

    // purposefully inherit doc comment from supertype
    // Called when LeaseResource we are tracking is garbage collected
    public void keyGC(Object value) {
	final WakeupManager.Ticket ticket = (WakeupManager.Ticket)value;
	expirationQueue.cancel(ticket);
    }

    /**
     * Objects that do the actually cancel the resource in question, stuck
     * in <code>WakeupManager</code>
     */
    private class Canceler implements Runnable {
	private final WeakReference resourceRef;
	
	/**
	 * Create a <code>Canceler</code> for the passed resource
	 */
	Canceler(LeasedResource resource) {
	    resourceRef = new WeakReference(resource);
	}

	/**
	 * Check the associated resource's expiration against the
 	 * current time, canceling the resource if its time has
	 * passed.  Synchronize on the resource before checking the
	 * expiration time.
	 */
	public void run() {
	    final LeasedResource resource = (LeasedResource)resourceRef.get();
	    if (resource == null)
		// Already gone
		return;

	    synchronized (resource) {
		if (resource.getExpiration() <= System.currentTimeMillis()) {
		    try {
			ticketMap.remove(resource);
			landlord.cancel(resource.getCookie());
		    } catch (UnknownLeaseException e) {
		        // Don't care, probably already gone
		    }
		}
		// else Someone must have just renewed the resource,
		// don't need to re-register since that will be done
		// by the renewer
	    }
	}
    }
}
