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

import com.sun.jini.fiddler.Fiddler;
import com.sun.jini.fiddler.FiddlerLease;
import com.sun.jini.fiddler.FiddlerRenewResults;
import com.sun.jini.fiddler.ProxyUtil;

import com.sun.jini.lease.AbstractLeaseMap;
import com.sun.jini.proxy.ConstrainableProxyUtil;

import net.jini.id.Uuid;

import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseMap;
import net.jini.core.lease.LeaseMapException;

import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;

/**
 * When clients request a registration with the Fiddler implementation of
 * the lookup discovery service, leases of type FiddlerLease are granted
 * on those registrations. Under certain circumstances it may be desirable
 * to collect multiple granted leases in a set which implements the
 * <code>net.jini.core.lease.LeaseMap</code> interface. 
 * <p>
 * This class is the implementation class of the <code>LeaseMap</code>
 * interface that is employed by the Fiddler implementation of the lookup
 * discovery service. When a client wishes to "batch" leases granted by
 * that service, the are placed in an instance of this class.
 * <p>
 * Clients only see instances of this class via the  <code>LeaseMap</code>
 * interface.
 *
 * @author Sun Microsystems, Inc.
 *
 */
class FiddlerLeaseMap extends AbstractLeaseMap {

    /**
     * The reference to the back-end server of the lookup discovery service
     *
     * @serial
     */
    final Fiddler server;
    /**
     * The unique ID associated with the server referenced in this class
     * (used to compare server references).
     *
     * @serial
     */
    final Uuid serverID;

    /**
     * Static factory method that creates and returns an instance of 
     * <code>FiddlerLeaseMap</code>. If the lease input to this method
     * is and instance of <code>ConstrainableFiddlerLease</code>, then
     * the object returned by this method will be an instance of
     * <code>ConstrainableFiddlerLeaseMap</code>.
     *
     * Note that because of the way the <code>Lease</code> class for the
     * associated service is implemented, together with the way the
     * <code>canBatch</code> method of that class is implemented, if the
     * <code>lease</code> object input to this method implements
     * <code>ConstrainableFiddlerLease</code>, then the <code>server</code>
     * object referenced in this class will implement 
     * <code>RemoteMethodControl</code> as well.
     * 
     * @param lease    reference to a lease to add to the map as a key value
     * @param duration the duration of the corresponding lease. This value
     *                 is the "mapped" value corresponding to the lease key.
     * 
     * @return an instance of <code>FiddlerLeaseMap</code>, or an instance
     *         of <code>ConstrainableFiddlerLeaseMap</code> if the given 
     *         <code>lease</code> is an instance of
     *         <code>ConstrainableFiddlerLease</code>.
     */
    static FiddlerLeaseMap createLeaseMap(FiddlerLease lease, long duration) {
        if(lease instanceof FiddlerLease.ConstrainableFiddlerLease) {
            MethodConstraints leaseConstraints =
        ((FiddlerLease.ConstrainableFiddlerLease)lease).getConstraints();

            return new ConstrainableFiddlerLeaseMap(lease.getServer(),
                                                    lease,
                                                    duration,
                                                    leaseConstraints);
        } else {
            return new FiddlerLeaseMap(lease.getServer(), lease, duration);
        }//endif
    }//end createLeaseMap

    /**
     * Constructs a new instance of FiddlerLeaseMap.
     *
     * @param server   reference to the server object through which 
     *                 communication occurs between the client-side and
     *                 server-side of the associated service.
     * @param lease    reference to a lease to add to the map as a key value
     * @param duration the duration of the corresponding lease. This value
     *                 is the "mapped" value corresponding to the lease key.
     */
    private FiddlerLeaseMap(Fiddler server, FiddlerLease lease, long duration){
        super(lease, duration);
        this.server = server;
        this.serverID = lease.getServerID();
    }//end constructor

    /**
     * Examines the input parameter to determine if that parameter will be
     * accepted or rejected by this map as a "legal" key value.
     * <p>
     * This method will return true if the <code>key</code> parameter is 
     * the type of lease which can be "batch-wise" renewed and cancelled 
     * along with all of the other leases in the map.
     * <p>
     * For the Fiddler implementation of the lookup discovery service, two
     * leases can be batched (placed in the same </code>FiddlerLeaseMap</code>)
     * if they are both instances of <code>FiddlerLease</code> and they
     * were both granted by the same Fiddler implementation of the lookup
     * discovery service. That is, they are the same type, and they were
     * granted by the same server.
     *                      
     * @param key reference to the object that this method examines to
     *        determine if this map will accept or reject it as a key
     *                      
     * @return true if this map will accept the <code>key</code> parameter,
     *         false otherwise
     *
     * @see net.jini.core.lease.Lease#canBatch
     */
    public boolean canContainKey(Object key) {
        return (    (key instanceof FiddlerLease)
                 && (serverID.equals(((FiddlerLease)key).getServerID()))  );
    }

    /**
     * Renews all leases in this map. For each lease (key) in the map, the
     * duration used to renew the lease is the lease's corresponding map
     * value. If all renewal attempts are successful, this method returns
     * normally; otherwise, this method removes from this map all leases
     * that could not be renewed, and throws a <code>LeaseMapException</code>.
     *
     * @throws net.jini.core.lease.LeaseMapException this exception is thrown
     *         when one or more leases in the map could not be renewed.
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         server. When this exception does occur, the leases in the map
     *         may or may not have been renewed successfully.
     */
    public void renewAll() throws LeaseMapException, RemoteException {
        int size = map.size();
        if (size == 0) {
            return;
        }
        Uuid[] registrationIDs = new Uuid[size];
        Uuid[] leaseIDs = new Uuid[size];
        long[] durations = new long[size];
        int i = 0;
        for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); i++) {
            Map.Entry e = (Map.Entry)iter.next();
            FiddlerLease ls = (FiddlerLease)e.getKey();
            registrationIDs[i] = ls.getRegistrationID();
            leaseIDs[i] = ls.getLeaseID();
            durations[i] = ((Long)e.getValue()).longValue();
        }
        FiddlerRenewResults results = server.renewLeases(registrationIDs,
                                                         leaseIDs,
                                                         durations);
        long now = System.currentTimeMillis();
        HashMap emap = (results.exceptions != null) ?
                         new HashMap(2 * results.exceptions.length + 1) : null;
        i = 0;
        int j = 0;
        for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); i++) {
            Map.Entry e = (Map.Entry)iter.next();
            long duration = results.durations[i];
            if (duration >= 0) {
                ((FiddlerLease)e.getKey()).setExpiration(duration + now);
            } else {
                emap.put(e.getKey(), results.exceptions[j++]);
                iter.remove();
            }
        }
        if (emap != null) {
            throw new LeaseMapException("lease renewal failures", emap);
        }
    }

    /**
     * Cancels all leases in this map. If all cancellation attempts are
     * successful, this method returns normally; otherwise, this method
     * removes from this map all leases that could not be cancelled, and
     * throws a <code>LeaseMapException</code>.
     * <p>
     * Note that a lease is removed from this map only when an attempt to
     * cancel it fails; that is, every lease that is successfully cancelled
     * is left in the map.
     *
     * @throws net.jini.core.lease.LeaseMapException this exception is thrown
     *         when one or more leases in the map could not be cancelled.
     * 
     * @throws java.rmi.RemoteException typically, this exception occurs when
     *         there is a communication failure between the client and the
     *         server. When this exception does occur, the leases in the map
     *         may or may not have been cancelled successfully.
     */
    public void cancelAll() throws LeaseMapException, RemoteException {
        int size = map.size();
        if (size == 0) {
            return;
        }
        Uuid[] registrationIDs = new Uuid[size];
        Uuid[] leaseIDs = new Uuid[size];
        int i = 0;
        for (Iterator iter = map.keySet().iterator(); iter.hasNext(); i++) {
            FiddlerLease ls = (FiddlerLease)iter.next();
            registrationIDs[i] = ls.getRegistrationID();
            leaseIDs[i] = ls.getLeaseID();
        }
        Exception[] exceptions = server.cancelLeases(registrationIDs,leaseIDs);
        if (exceptions == null) {
            return;
        }
        i = 0;
        HashMap emap = new HashMap(13);
        for (Iterator iter = map.keySet().iterator(); iter.hasNext(); i++) {
            FiddlerLease ls = (FiddlerLease)iter.next();
            Exception ex = exceptions[i];
            if (ex != null) {
                emap.put(ls, ex);
                iter.remove();
            }
        }
        throw new LeaseMapException("lease cancellation failures", emap);
    }

    /** The constrainable version of the class <code>FiddlerLeaseMap</code>. 
     *
     * @since 2.0
     */
    static final class ConstrainableFiddlerLeaseMap extends FiddlerLeaseMap {

        /* Convenience fields containing, respectively, the renew and cancel
         * methods defined in the Lease interface. These fields are used in
         * the method mapping array, and when retrieving method constraints
         * for comparison in canContainKey().
         */
        private static final Method renewMethod =
                             ProxyUtil.getMethod(Lease.class,
                                                 "renew",
                                                 new Class[] {long.class} );
        private static final Method cancelMethod =
                             ProxyUtil.getMethod(Lease.class,
                                                 "cancel",
                                                 new Class[] {} );

        /* When a <code>LeaseMap</code> is created, an implicit set of 
         * constraints is generated and associated with that lease map.
         * The constraints that are generated are based on the constraints
         * on the first lease placed in that lease map. As such, the array
         * defined here contains element pairs in which each pair of elements
         * represents a correspondence 'mapping' between two methods having
         * the following characteristics:
         *  - the first element in the pair is one of the public, remote
         *    method(s) that may be invoked by the client through a lease
         *    proxy, and which has method constraints that are intended 
         *    to apply to the corresponding method that is invoked on
         *    server's backend
         *  - the second element in the pair is the method, implemented
         *    in the backend server class, that is intended to be 
         *    executed with the same method constraints as its corresponding
         *    method, specified in the first element of the pair
         */
        private static final Method[] methodMapArray = 
        {
            renewMethod, ProxyUtil.getMethod(Fiddler.class,
                                             "renewLeases", 
                                             new Class[] {Uuid[].class,
                                                          Uuid[].class,
                                                          long[].class}),

            cancelMethod, ProxyUtil.getMethod(Fiddler.class,
                                              "cancelLeases", 
                                              new Class[] {Uuid[].class,
                                                           Uuid[].class})
        };//end methodMapArray

        /** In order to determine if a given lease will be accepted by this
         *  map as a "legal" key value, the method <code>canContainKey</code>
         *  must verify that the corresponding methods of the initial
         *  lease used to create this map and the lease input to
         *  <code>canContainKey</code> have equivalent constraints. The
         *  array defined here contains the set of methods whose constraints
         *  will be compared in <code>canContainKey</code>.
         */
        private static final Method[] canContainKeyMethodMapArray =
                                            { renewMethod,  renewMethod,
                                              cancelMethod, cancelMethod
                                            };//end canContainKeyMethodMapArray

        /** Client constraints placed on this proxy (may be <code>null</code>).
         *
         * @serial
         */
        private MethodConstraints methodConstraints;

        /** Constructs a new <code>ConstrainableFiddlerLeaseMap</code>
         *  instance.
         *  <p>
         *  For a description of all but the <code>methodConstraints</code>
         *  argument (provided below), refer to the description for the
         *  constructor of this class' super class.
         *
         *  @param methodConstraints the client method constraints to place on
         *                           this proxy (may be <code>null</code>).
         */
        private ConstrainableFiddlerLeaseMap
                                        (Fiddler server, 
                                         FiddlerLease lease,
                                         long duration,
                                         MethodConstraints methodConstraints)
        {
            super(constrainServer(server, methodConstraints), lease, duration);
	    this.methodConstraints = methodConstraints;
        }//end constructor

        /**
         * Examines the input parameter to determine if that parameter will
         * be accepted or rejected by this map as a "legal" key value.
         * <p>
         * This method will return true if the <code>key</code> parameter is 
         * the type of lease which can be "batch-wise" renewed and cancelled 
         * along with all of the other leases in the map.
         * <p>
         * For this implementation of the service, two leases can be
         * batched (placed in the same service-specific instance of
         * <code>LeaseMap</code>) if those leases satisfy the following
         * conditions:
         * <p><ul>
         *    <li> the leases are the same type; that is, the leases are 
         *         both instances of the same, constrainable, service-specific
         *         <code>Lease</code> implementation
         *    <li> the leases were granted by the same backend server
         *    <li> the leases have the same constraints
         *                      
         * @param key reference to the object that this method examines to
         *        determine if this map will accept or reject it as a key
         *                      
         * @return <code>true</code> if this map will accept the
         *         <code>key</code> parameter, <code>false</code> otherwise
         *
         * @see net.jini.core.lease.Lease#canBatch
         * @see net.jini.core.lease.LeaseMap#canContainKey
         */
        public boolean canContainKey(Object key) {
            if( !(super.canContainKey(key)) )  return false;
            /* Non-constrainable criteria satisfied, now handle constrainable
             * case.
             */
            if( !(key instanceof FiddlerLease.ConstrainableFiddlerLease) ) {
                return false;
            }//endif
            /* Compare constraints */
            MethodConstraints keyConstraints =
          ((FiddlerLease.ConstrainableFiddlerLease)key).getConstraints();
            return ConstrainableProxyUtil.equivalentConstraints
                                              ( methodConstraints,
                                                keyConstraints,
                                                canContainKeyMethodMapArray );
        }//end ConstrainableFiddlerLeaseMap.canContainKey

        /** Returns a copy of the given server proxy having the client method
         *  constraints that result after the specified method mapping is
         *  applied to the given client method constraints.
         */
        private static Fiddler constrainServer( Fiddler server,
                                                MethodConstraints constraints )
        {
            MethodConstraints newConstraints 
               = ConstrainableProxyUtil.translateConstraints(constraints,
                                                             methodMapArray);
            RemoteMethodControl constrainedServer = 
                ((RemoteMethodControl)server).setConstraints(newConstraints);

            return ((Fiddler)constrainedServer);
        }//end constrainServer

    }//end class ConstrainableFiddlerLeaseMap

}//end class FiddlerLeaseMap
