/*
 * 
 * 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.*;
import net.jini.id.Uuid;
import net.jini.id.ReferentUuid;
import com.sun.jini.lease.AbstractLeaseMap;

import java.rmi.RemoteException;
import java.util.Map;
import java.util.Iterator;
import java.util.HashMap;
import java.util.ConcurrentModificationException;

/**
 * Implementation of <code>LeaseMap</code> for <code>LandlordLease</code>.
 *
 * @author Sun Microsystems, Inc.
 *
 * @see LandlordLease
 * @see net.jini.core.lease.LeaseMap
 * @since 2.0
 */
public class LandlordLeaseMap extends AbstractLeaseMap {
    /** The landlord which this map will talk to. */
    private final Landlord landlord;

    /**
     * The <code>Uuid</code> of the {@link #landlord}. Used
     * to determine if a lease can be placed in this map.
     */
    private final Uuid landlordUuid;

    /**
     * Create a new <code>LandlordLeaseMap</code>.
     * @param landlord Owner of the resource <code>lease</code> is for.
     * @param landlordUuid a universally unique id that has been
     *                 assigned to the server granting of the lease.
     *                 Ideally the <code>Uuid</code> {@link
     *                 ReferentUuid.getUuid landlord.getUuid} would
     *                 return if <code>landlord</code> implemented
     *                 {@link ReferentUuid}. Used to determine when
     *                 two leases can be batched together. 
     * @param lease    first lease to be placed in the map.  It is
     *                 assumed that <code>canContainKey(lease)</code>
     *                 would be <code>true</code>.  Must work with the 
     *                 landlord protocol.
     * @param duration the duration the lease should be renewed for if 
     *                 <code>renewAll</code> is called
     * @throws NullPointerException if <code>landlord</code> or 
     *                 <code>landlordUuid</code> is <code>null</code>.
     */
    LandlordLeaseMap(Landlord landlord, Uuid landlordUuid, Lease lease,
		     long duration) 
    {
	super(lease, duration);
	if (landlord == null)
	    throw new NullPointerException("Landlord must be non-null");

	if (landlordUuid == null)
	    throw new NullPointerException("landlordUuid must be non-null");

	this.landlord = landlord;
	this.landlordUuid = landlordUuid;
    }

    // inherit doc comment
    public boolean canContainKey(Object key) {
	if (key instanceof LandlordLease) {
  	    return landlordUuid.equals(((LandlordLease)key).landlordUuid());
	}

	return false;
    }

    // inherit doc comment
    public void cancelAll() throws LeaseMapException, RemoteException {
	Uuid[] cookies = new Uuid[size()];
	LandlordLease[] leases = new LandlordLease[cookies.length];
	Iterator it = keySet().iterator();
	for (int i = 0; it.hasNext(); i++) {
	    LandlordLease lease = (LandlordLease) it.next();
	    leases[i] = lease;
	    cookies[i] = lease.cookie();
	}

	final Map rslt = landlord.cancelAll(cookies);

	if (rslt == null) {
	    // Everything worked out, normal return
	    return;
	} else {
	    // Some the leases could not be canceled, generate a
	    // LeaseMapException
	    
	    // translate the cookie->exception map into a
	    // lease->exception map

	    int origSize = rslt.size();
	    for (int i = 0; i < cookies.length; i++) {
		Object exception = rslt.remove(cookies[i]); 
		// remove harmless if not in map 

		if (exception != null) {	     // if it was in map
		    rslt.put(leases[i], exception);  // put back as lease
		    remove(leases[i]);		     // remove from this map
		}
	    }

	    if (origSize != rslt.size())	// some cookie wasn't found
		throw new ConcurrentModificationException();
	    
	    throw new LeaseMapException(
	        "Failure canceling one or more leases", rslt);
	}
    }


    // inherit doc comment
    public void renewAll() throws LeaseMapException, RemoteException {
	Uuid[] cookies = new Uuid[size()];
	long[] extensions = new long[cookies.length];
	LandlordLease[] leases = new LandlordLease[cookies.length];
	Iterator it = keySet().iterator();
	for (int i = 0; it.hasNext(); i++) {
	    LandlordLease lease = (LandlordLease) it.next();
	    leases[i] = lease;
	    cookies[i] = lease.cookie();
	    extensions[i] = ((Long) get(lease)).longValue();
	}

	long now = System.currentTimeMillis();
	Landlord.RenewResults results = 
	    landlord.renewAll(cookies, extensions);

	Map bad = null;
	int d = 0;
	for (int i = 0; i < cookies.length; i++) {
	    if (results.granted[i] != -1) {
		long newExp = now + results.granted[i];
		if (newExp < 0) // Overflow, set to Long.MAX_VALUE
		    newExp = Long.MAX_VALUE;
		    
		leases[i].setExpiration(newExp);
	    } else {
		if (bad == null) {
		    bad = new HashMap(results.denied.length +
				      results.denied.length / 2);
		}
		Object badTime = remove(leases[i]);   // remove from this map
		if (badTime == null)		      // better be in there
		    throw new ConcurrentModificationException();
		bad.put(leases[i], results.denied[d++]);// add to "bad" map
	    }
	}

	if (bad != null)
	    throw new LeaseMapException("renewing", bad);
    }

    /** Return the landlord. */
    Landlord landlord() {
	return landlord;
    }
}

