/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sun.jini.norm;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.lang.reflect.Method;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.util.LinkedList;
import java.util.List;

import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.id.Uuid;
import net.jini.io.MarshalledInstance;
import net.jini.lease.LeaseRenewalSet;
import net.jini.lease.LeaseUnmarshalException;
import net.jini.security.proxytrust.ProxyTrustIterator;
import net.jini.security.proxytrust.SingletonProxyTrustIterator;

import com.sun.jini.landlord.ConstrainableLandlordLease;
import com.sun.jini.proxy.ConstrainableProxyUtil;
import com.sun.jini.proxy.ThrowThis;

/**
 * Client side proxy for Norm's lease renewal sets.  Uses an object of
 * type NormServer to communicate back to server.
 *
 * 
 */
class SetProxy extends AbstractProxy implements LeaseRenewalSet {
    private static final long serialVersionUID = 2;

    /** 
     * Lease for this set.
     * @serial
     */
    final Lease ourLease;
     
    /**
     * Creates a lease set proxy, returning an instance that implements
     * RemoteMethodControl if the server does.
     *
     * @param server the server proxy
     * @param id the ID of the lease set
     * @param lease the lease set's lease
     */
    static SetProxy create(NormServer server, Uuid id, Lease lease) {
	if (server instanceof RemoteMethodControl) {
	    return new ConstrainableSetProxy(server, id, lease, null);
	} else {
	    return new SetProxy(server, id, lease);
	}
    }

    /** Simple constructor. */
    private SetProxy(NormServer server, Uuid id, Lease lease) {
	super(server, id);
	if (lease == null) {
	    throw new NullPointerException("lease cannot be null");
	}
	ourLease = lease;
    }

    /** Require fields to be non-null. */
    private void readObjectNoData() throws InvalidObjectException {
	throw new InvalidObjectException(
	    "server, uuid, and ourLease must be non-null");
    }

    /** Require lease to be non-null. */
    private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundException
    {
	in.defaultReadObject();
	if (ourLease == null) {
	    throw new InvalidObjectException("ourLease cannot be null");
	}
    }

    /* -- Implement LeaseRenewalSet -- */

    // Inherit java doc from super type
    public void renewFor(Lease leaseToRenew, long membershipDuration)
	throws RemoteException 
    {
	try {
	    server2().renewFor(uuid, leaseToRenew, membershipDuration,
			       Lease.FOREVER);
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	}
    }

    // Inherit java doc from super type
    public void renewFor(Lease leaseToRenew, long membershipDuration,
			 long  renewDuration)
	throws RemoteException 
    {
	try {
	    server.renewFor(uuid, leaseToRenew, membershipDuration, 
			    renewDuration);
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	}
    }

    // Inherit java doc from super type
    public Lease remove(Lease leaseToRemove) throws RemoteException {
	try {
	    return server.remove(uuid, leaseToRemove);
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	    // Need this because compiler does not know that
	    // throwRemoteException is not going to return
	    return null;
	}
    }

    // Inherit java doc from super type
    public Lease[] getLeases() throws LeaseUnmarshalException, RemoteException
    {
	try {
	    GetLeasesResult result = server.getLeases(uuid);
	    MarshalledInstance mls[] = result.marshalledLeases;

	    if (mls == null || mls.length == 0) 
		return new Lease[0];
	    
	    final List leases = new LinkedList();
	    final List problems = new LinkedList();
	    final List exceptions = new LinkedList();
	    for (int i=0; i<mls.length; i++) {
		Lease l;
		final MarshalledInstance ml = mls[i];
		try {
		    l = (Lease) ml.get(result.verifyCodebaseIntegrity());
		    leases.add(l);
		} catch (Throwable t) {
		    problems.add(ml.convertToMarshalledObject());
		    exceptions.add(t);
		}		
	    }

	    final Lease[] rslt =
		(Lease[]) leases.toArray(new Lease[leases.size()]);

	    if (problems.isEmpty())
		return rslt;
	    
	    final MarshalledObject sml[] =
		(MarshalledObject[]) problems.toArray(
                    new MarshalledObject[problems.size()]);
	    final Throwable es[] =
		(Throwable[]) exceptions.toArray(
		    new Throwable[exceptions.size()]);

	    throw new LeaseUnmarshalException("Problem unmarshalling lease(s)",
		rslt, sml, es);
	    
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	    // Need this because compiler does not know that
	    // throwRemoteException is not going to return
	    return null;
	}
    }

    // Inherit java doc from super type
    public EventRegistration setExpirationWarningListener(
	                         RemoteEventListener listener, 
				 long                minWarning, 
				 MarshalledObject    handback)
	throws RemoteException    
    {
	// Do some client side error checking
	if (listener == null) {
	    throw new NullPointerException(
	        "LeaseRenewalSet.setExpirationWarningListener:Must " +
		"pass a non-null listener");
	}

	try {
	    return server.setExpirationWarningListener(uuid,
	        listener, minWarning, handback);
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	    // Need this because compiler does not know that
	    // throwRemoteException is not going to return
	    return null;
	}
    }

    // Inherit java doc from super type
    public void clearExpirationWarningListener() throws RemoteException {
	try {
	    server2().setExpirationWarningListener(
		uuid, null, NormServer.NO_LISTENER, null);
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	}
    }

    // Inherit java doc from super type
    public EventRegistration setRenewalFailureListener(
				 RemoteEventListener listener, 
				 MarshalledObject    handback)
	throws RemoteException
    {
	// Do some client side error checking
	if (listener == null) {
	    throw new NullPointerException(
	        "LeaseRenewalSet.setRenewalFailureListener:Must " +
		"pass a non-null listener");
	}

	try {
	    return server.setRenewalFailureListener(uuid, listener, handback);
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	    // Need this because compiler does not know that
	    // throwRemoteException is not going to return
	    return null;
	}
    }


    // Inherit java doc from super type
    public void clearRenewalFailureListener() throws RemoteException {
	try {
	    server2().setRenewalFailureListener(uuid, null, null);
	} catch (ThrowThis e) {
	    // The server wants to throw some remote exception
	    e.throwRemoteException();
	}
    }

    /**
     * Returns a second server proxy for use by methods that represent a second
     * client-side method for a single server-side method.  The distinction
     * only matters for the constrainable subclass, where the different server
     * proxies need different client constraints.
     */
    NormServer server2() {
	return server;
    }

    // Inherit java doc from super type
    public Lease getRenewalSetLease() {
	return ourLease;
    }

    /** Defines a subclass of SetProxy that implements RemoteMethodControl. */
    static final class ConstrainableSetProxy extends SetProxy
	implements RemoteMethodControl
    {
	private static final long serialVersionUID = 1;

	/**
	 * Mappings from client to server methods, using the client method with
	 * more arguments for each server method when more than one client
	 * method maps to a single server method.
	 */
	private static final Method[] methodMap1;
	static {
	    try {
		methodMap1 = new Method[] {
		    LeaseRenewalSet.class.getMethod(
			"renewFor",
			new Class[] { Lease.class, long.class, long.class }),
		    NormServer.class.getMethod(
			"renewFor",
			new Class[] { Uuid.class, Lease.class, long.class,
				      long.class }),
		    LeaseRenewalSet.class.getMethod(
			"remove", new Class[] { Lease.class }),
		    NormServer.class.getMethod(
			"remove",
			new Class[] { Uuid.class, Lease.class }),
		    LeaseRenewalSet.class.getMethod(
			"getLeases", new Class[] { }),
		    NormServer.class.getMethod(
			"getLeases", new Class[] { Uuid.class }),
		    LeaseRenewalSet.class.getMethod(
			"setExpirationWarningListener",
			new Class[] { RemoteEventListener.class,
				      long.class, MarshalledObject.class }),
		    NormServer.class.getMethod(
			"setExpirationWarningListener",
			new Class[] { Uuid.class, RemoteEventListener.class,
				      long.class, MarshalledObject.class }),
		    LeaseRenewalSet.class.getMethod(
			"setRenewalFailureListener",
			new Class[] { RemoteEventListener.class,
				      MarshalledObject.class }),
		    NormServer.class.getMethod(
			"setRenewalFailureListener",
			new Class[] { Uuid.class, RemoteEventListener.class,
				      MarshalledObject.class })
		};
	    } catch (NoSuchMethodException e) {
		throw new NoSuchMethodError(e.getMessage());
	    }
	}

	/**
	 * Second set of mappings from client to server method names, for
	 * server methods with a second associated client method.
	 */
	private static final Method[] methodMap2;
	static {
	    try {
		methodMap2 = new Method[] {
		    LeaseRenewalSet.class.getMethod(
			"renewFor",
			new Class[] { Lease.class, long.class }),
		    NormServer.class.getMethod(
			"renewFor",
			new Class[] { Uuid.class, Lease.class, long.class,
				      long.class } ),
		    LeaseRenewalSet.class.getMethod(
			"clearExpirationWarningListener", new Class[] { }),
		    NormServer.class.getMethod(
			"setExpirationWarningListener",
			new Class[] { Uuid.class, RemoteEventListener.class,
				      long.class, MarshalledObject.class }),
		    LeaseRenewalSet.class.getMethod(
			"clearRenewalFailureListener", new Class[] { }),
		    NormServer.class.getMethod(
			"setRenewalFailureListener",
			new Class[] { Uuid.class, RemoteEventListener.class,
				      MarshalledObject.class })
		};
	    } catch (NoSuchMethodException e) {
		throw new NoSuchMethodError(e.getMessage());
	    }
	}

	/**
	 * The client constraints placed on this proxy or <code>null</code>.
	 *
	 * @serial
	 */
	private MethodConstraints methodConstraints;

	/**
	 * A second inner proxy to use when the client constraints for
	 * different smart proxy methods implemented by the same inner proxy
	 * methods have different constraints.  This proxy is used for the
	 * renewFor(Lease, long), clearExpirationWarningListener, and
	 * clearRenewalFailureListener methods.
	 */
	private transient NormServer server2;

	/** Creates an instance of this class. */
	ConstrainableSetProxy(NormServer server,
			      Uuid id,
			      Lease lease,
			      MethodConstraints methodConstraints)
	{
	    super(constrainServer(server, methodConstraints, methodMap1),
		  id, lease);
	    if (!(lease instanceof ConstrainableLandlordLease)) {
		throw new IllegalArgumentException(
		    "lease must be a ConstrainableLandlordLease");
	    }
	    this.methodConstraints = methodConstraints;
	    server2 = constrainServer(server, methodConstraints, methodMap2);
	}

	/**
	 * Verifies that ourLease is a ConstrainableLandlordLease, and that
	 * server implements RemoteMethodControl and has the appropriate method
	 * constraints.  Also sets the server2 field.
	 */
	private void readObject(ObjectInputStream s)
	    throws IOException, ClassNotFoundException
	{
	    s.defaultReadObject();
	    if (!(server instanceof RemoteMethodControl)) {
		throw new InvalidObjectException(
		    "server does not implement RemoteMethodControl");
	    } else if (!(ourLease instanceof ConstrainableLandlordLease)) {
		throw new InvalidObjectException(
		    "ourLease is not a ConstrainableLandlordLease");
	    }
	    ConstrainableProxyUtil.verifyConsistentConstraints(
		methodConstraints, server, methodMap1);
	    server2 = constrainServer(server, methodConstraints, methodMap2);
	}


	/**
	 * Returns a copy of the server proxy with the specified client
	 * constraints and methods mapping.
	 */
	private static NormServer constrainServer(
	    NormServer server,
	    MethodConstraints methodConstraints,
	    Method[] mappings)
	{
	    return (NormServer) ((RemoteMethodControl) server).setConstraints(
		ConstrainableProxyUtil.translateConstraints(
		    methodConstraints, mappings));
	}

	/** inherit javadoc */
	public RemoteMethodControl setConstraints(
	    MethodConstraints constraints)
	{
	    return new ConstrainableSetProxy(
		server, uuid, ourLease, constraints);
	}

	/** inherit javadoc */
	public MethodConstraints getConstraints() {
	    return methodConstraints;
	}

	/** Returns the second server proxy. */
	NormServer server2() {
	    return server2;
	}

	/**
	 * Returns a proxy trust iterator that supplies the server, for use by
	 * ProxyTrustVerifier.
	 */
	private ProxyTrustIterator getProxyTrustIterator() {
	    return new SingletonProxyTrustIterator(server);
	}
    }
}
