/*
 * 
 * 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.
 * 
 */
package net.jini.core.discovery;

import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.DataOutputStream;
import java.io.Serializable;
import java.rmi.MarshalledObject;
import java.net.MalformedURLException;
import java.net.Socket;
import java.security.AccessController;
import java.security.PrivilegedAction;
import net.jini.core.lookup.ServiceRegistrar;

/**
 * A utility class that performs unicast discovery.
 *
 * @author Sun Microsystems, Inc.
 *
 * @since 1.0
 */
public class LookupLocator implements Serializable {
    private static final long serialVersionUID = 1448769379829432795L;

    /**
     * The port for both unicast and multicast boot requests.
     */
    private static final short discoveryPort = 4160;
    /**
     * The current version of the unicast discovery protocol.
     */
    private static final int protoVersion = 1;

    /**
     * The name of the host at which to perform discovery.
     *
     * @serial
     */
    protected String host;
    /**
     * The port number on the host at which to perform discovery.
     *
     * @serial
     */
    protected int port;
    
    /**
     * The timeout after which we give up waiting for a response from
     * the lookup service.
     */
    static final int defaultTimeout =
	((Integer)AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() {
		int timeout = 60 * 1000;
		try {
		    return Integer.getInteger("net.jini.discovery.timeout",
					      timeout);
		} catch (SecurityException e) {
		    return new Integer(timeout);
		}
	    }
	})).intValue();

    /**
     * Construct a new LookupLocator object, set up to perform
     * discovery to the given URL. <p>
     *
     * The URL in question must be of the form <tt>jini://host/</tt>
     * or <tt>jini://host:port/</tt>.
     *
     * @param url the URL to use
     * @exception MalformedURLException the URL passed in could not be
     * parsed
     */
    public LookupLocator(String url) throws MalformedURLException {
	if (!url.toLowerCase().startsWith("jini://"))
	    throw new MalformedURLException("not a jini-scheme URL");
	int len = url.length();
	char c = '\0';
	int h;
	for (h = 7; h < len && (c = url.charAt(h)) != ':' && c != '/'; h++) {
	    // do nothing
	}
	if (h == 7)
	    throw new MalformedURLException("empty hostname");
	host = url.substring(7, h);
	if (c == ':') {
	    int p;
	    for (p = h + 1; p < len && (c = url.charAt(p)) != '/'; p++) {
		// do nothing
	    }
	    try {
		port = Integer.parseInt(url.substring(h + 1, p));
		if (port <= 0 || port >= 65536)
		    throw new MalformedURLException("port number out of range");
	    } catch (NumberFormatException e) {
		throw new MalformedURLException("malformed port number");
	    }
	} else {
	    port = discoveryPort;
	}
    }

    /**
     * Construct a new LookupLocator object, set to perform unicast
     * discovery to the given host and port.
     *
     * @param host the name of the host to contact
     * @param port the number of the port to connect to
     */
    public LookupLocator(String host, int port) {
	if (host == null)
	    throw new NullPointerException("null host");
	if (port <= 0 || port >= 65536)
	    throw new IllegalArgumentException("port number out of range");
	this.host = host;
	this.port = port;
    }

    /**
     * Return the name of the host this LookupLocator object should
     * contact.
     *
     * @return a String representing the host value
     */
    public String getHost() {
	return host;
    }

    /**
     * Return the number of the port to which this LookupLocator
     * object should connect.
     *
     * @return an int representing the port value
     */
    public int getPort() {
	return port;
    }

    /**
     * Perform unicast discovery and return the ServiceRegistrar
     * object for the given lookup service.  Unicast discovery is
     * performed anew each time this method is called.
     *
     * @return the ServiceRegistrar for the lookup service denoted by
     * this LookupLocator object
     * @exception IOException an error occurred during discovery
     * @exception ClassNotFoundException some discovery-related
     * classes could not be found
     */
    public ServiceRegistrar getRegistrar()
	throws IOException, ClassNotFoundException
    {
	return getRegistrar(defaultTimeout);
    }

    /**
     * Perform unicast discovery and return the ServiceRegistrar
     * object for the given lookup service.  Unicast discovery is
     * performed anew each time this method is called. <p>
     *
     * If a connection can be established to start unicast discovery
     * but the remote end fails to respond within the given time
     * limit, an exception is thrown.
     *
     * @param timeout the maximum time to wait for a response, in
     * milliseconds
     * @return the ServiceRegistrar for the lookup service denoted by
     * this LookupLocator object
     * @exception IOException an error occurred during discovery
     * @exception ClassNotFoundException some discovery-related
     * classes could not be found
     */
    public ServiceRegistrar getRegistrar(int timeout)
	throws IOException, ClassNotFoundException
    {
	Socket sock = new Socket(host, port);
	try {
	    sock.setSoTimeout(timeout);
	    DataOutputStream dstr =
		new DataOutputStream(sock.getOutputStream());
	    dstr.writeInt(protoVersion);
	    dstr.flush();
	    ObjectInputStream istr =
		new ObjectInputStream(sock.getInputStream());
	    ServiceRegistrar registrar =
		(ServiceRegistrar)((MarshalledObject)istr.readObject()).get();
	    for (int grpCount = istr.readInt(); --grpCount >= 0; ) {
		istr.readUTF(); // ensure proper format, then discard
	    }
	    return registrar;
	} finally {
	    try {
		sock.close();
	    } catch (IOException e) {
		// ignore
	    }
	}
    }

    /**
     * Return the string form of this LookupLocator, as a
     * <tt>jini</tt>-scheme URL.
     */
    public String toString() {
	if (port != discoveryPort)
	    return "jini://" + host + ":" + port + "/";
	return "jini://" + host + "/";
    }

    /**
     * Two locators are equal if they have the same host and port.
     */
    public boolean equals(Object o) {
	if (o instanceof LookupLocator) {
	    LookupLocator oo = (LookupLocator) o;
	    return port == oo.port && host.equalsIgnoreCase(oo.host);
	}
	return false;
    }

    public int hashCode() {
	return host.toLowerCase().hashCode() ^ port;
    }
}
