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

import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseDeniedException;

/**
 * Simple implementation of <code>LeasePeriodPolicy</code> that grants
 * lease times based on a fixed default and maximum lease. Will grant
 * renewals longer than the maximum if the current lease and the
 * request are both longer than the maximum.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 */
public class FixedLeasePeriodPolicy implements LeasePeriodPolicy {
    /** Under normal circumstances, the maximum lease or renewal to grant */
    final private long maximum;

    /** The default lease length */
    final private long defaultLength;

    /**
     * Create a new <code>FixedLeasePeriodPolicy</code> with 
     * the specified values for the maxium and default lease
     * lengths.
     * @param maximum the length in milliseconds of the longest lease
     *                this object should normally grant
     * @param defaultLength the length in milliseconds of the
     *                default lease grants
     * @throws IllegalArgumentException if either argument
     *         is not positive.
     */
    public FixedLeasePeriodPolicy(long maximum, long defaultLength) {
	if (maximum <= 0)
	    throw new IllegalArgumentException("FixedLeasePeriodPolicy:" +
	        "maximum lease time must be larger than 0, " +
		 "passed:" + maximum);

	if (defaultLength <= 0)
	    throw new IllegalArgumentException("FixedLeasePeriodPolicy:" +
	        "default lease time must be larger than 0, " + 
		 "passed:" + defaultLength);

	this.maximum = maximum;
	this.defaultLength = defaultLength;
    }


    /**
     * Returns the duration this policy is willing to grant for the
     * passed resource at this time.  The duration actually granted
     * will be shorter if the duration extends pass the end of the
     * epoch.  Must return a positive number.
     * <p>
     * Note the duration returned by this method will be shorter than
     * the final duration granted if the requested duration extends
     * past the current expiration, and duration return by this 
     * method is before the current expiration.
     *     
     * @param resource          the resource having a lease granted
     *                          or renewed
     * @param requestedDuration the duration the client wants 
     * @exception IllegalArgumentException thrown if requestedDuration
     *            is less than 0 and not equal to
     *            <code>Lease.ANYLENGTH</code> or
     *            <code>Lease.FOREVER</code>.  
     */
    protected long calculateDuration(LeasedResource resource, 
        long requestedDuration) 
    {
	if (requestedDuration == Lease.FOREVER)
	    requestedDuration = Long.MAX_VALUE;
	else if (requestedDuration == Lease.ANY)
	    requestedDuration = defaultLength;
	else if (requestedDuration < 0)
	    throw new 
		IllegalArgumentException("Negative lease duration " +
		    "requested");

	return Math.min(requestedDuration, maximum);
    }

    /**
     * Method that provides some notion of the current time in milliseconds
     * since the beginning of the epoch. Default implementation
     * calls System.currentTimeMillis()
     */
    protected long currentTime() {
	return System.currentTimeMillis();
    }

    /**
     * Calculates an expiration based on the passed time and a requested
     * duration. Will clip the expiration to the end of the epoch
     * @param preferredDuration the duration the policy wants to grant
     * @param now               the current time in milliseconds since
     *                          the beginning of the epoch
     * @return the new expiration time for the lease in milliseconds since 
     *         the beginning of the epoch
     */
    private long calcExpiration(long preferredDuration, long now) {
	long expiration = now + preferredDuration;
	// Any addition of two positive longs is guaranteed to be
	// negative if it overflowed
	if (expiration < 0) {
	    // Not enough time for the duration we want, set to end of
	    // epoch 
	    return Long.MAX_VALUE;
	}

	return expiration;
    }


    public Result grant(LeasedResource resource, long requestedDuration) 
        throws LeaseDeniedException
    {
	final long now = currentTime();
	final long expiration =
	    calcExpiration(calculateDuration(resource, requestedDuration), 
			   now);
	final long duration = expiration - now;
	return new Result(expiration, duration);
    }

    public Result renew(LeasedResource resource, long requestedDuration)
        throws LeaseDeniedException
    {
	final long now = currentTime();

	// The new expiration we would grant independent of the
	// current expiration time 
	long newExpiration =
	    calcExpiration(calculateDuration(resource, requestedDuration), 
			   now);	

	// Do not change the expiration time to be sooner that it is
	// now unless the client explicitly asked for it.  If they did
	// ask for a sooner expiration give them exactly what they
	// asked for.

	// The current expiration of the lease
	final long oldExpiration = resource.getExpiration();

	if (oldExpiration > newExpiration) {
	    // need to make sure they asked for it

	    if (requestedDuration == Lease.ANY) {
		// they did not explicitly ask for a sooner
		// expiration, give them at least what they had
		newExpiration = oldExpiration; 
	    } else {
		// The new expiration time they are requesting
		// (possibly clipped to the end of the epoch) this is
		// not affected by what we are willing to grant.
		// [Note we know at this point that requestedDuration
		// is >= 0]
		final long requestedExpiration = calcExpiration(
		    requestedDuration, now);
		
		if (requestedExpiration > oldExpiration) {
		    // they made a request for something longer, give
		    // them at least what they had
		    newExpiration = oldExpiration; 
		} else {
		    // They made a request for something shorter,
		    // give them what they at least what they want
		    newExpiration = requestedExpiration;
		}
	    }
	}

	// Now that we finally have decided on a new expiration,
	// get the real duration and return the result.
	return new Result(newExpiration, newExpiration - now);
    }
}
