/*
 * ident	"@(#)ServiceURL.java	1.2	99/05/10 SMI"
 *
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 *
 */

//  SCCS Status:      @(#)ServiceURL.java	1.11	06/17/97
//  *M% :  The service URL.
//  Author:           James Kempf, Erik Guttman
//

package com.sun.slp;

import java.util.*;
import java.io.*;
import java.net.*;

/**
 * The ServiceURL object models the SLP service URL. Both service: URLs
 * and regular URLs are handled by this class.
 *
 * @version 1.3 98/10/14
 * @author James Kempf, Erik Guttman
 */

public class ServiceURL extends Object implements Serializable   {

    // Recognized transports.

    private final static String IPX = "ipx";
    private final static String AT = "at";

    /**
     * Indicates that no port information is required or was returned
     * for this service URL.
     */

    public static final int NO_PORT = 0;

    /**
     * No life time parameter is given.
     */

    public static final int LIFETIME_NONE    =  0;

    /**
     * Default lifetime, 3 hours.
     */

    public static final int LIFETIME_DEFAULT = 10800;

    /**
     * Maximum lifetime, approximately 18 hours.
     */

    public static final int LIFETIME_MAXIMUM = 0xFFFF;

    /**
     * Reregister periodically.
     */

    public static final int LIFETIME_PERMANENT = -1;

    // Maximum port size.

    static final int PORT_MAXIMUM = 0xFFFF;


    //
    // data fields
    //

    private ServiceType serviceType = null;
    private ServiceType originalServiceType = null;
    private String transport = "";
    private String host = "";
    private int port = NO_PORT;
    private String URLPath = "";
    private int lifetime = LIFETIME_DEFAULT;
    private boolean isPermanent = false;
    private boolean noDoubleSlash = false;

    /**
     * Construct a service URL object.
     *
     * @param URL		The service URL as a string.
     * @param iLifetime		The service advertisement lifetime.
     * @exception IllegalArgumentException Thrown if parse
     *				          errors occur in the
     *					  parameter.
     */

    public ServiceURL(String URL, int iLifetime)
	throws IllegalArgumentException {

	Assert.nonNullParameter(URL, "URL");

	if ((iLifetime > LIFETIME_MAXIMUM) ||
	   (iLifetime < LIFETIME_PERMANENT)) {
	    throw
		new IllegalArgumentException(
		SLPConfig.getSLPConfig().formatMessage("lifetime_error",
						       new Object[0]));
	}

	checkURLString(URL);
	parseURL(URL);

	if (iLifetime == LIFETIME_PERMANENT) {
	    isPermanent = true;
	    iLifetime = LIFETIME_MAXIMUM;

	}

	lifetime = iLifetime;
    }

    //
    // ------------------------------------------------------------------
    // Accessors
    // ------------------------------------------------------------------
    //

    /**
     * @return The service type name.
     */

    public ServiceType getServiceType() {
	return serviceType;

    }

    /**
     * Set service type and naming authority if this is not a service: URL.
     *
     * @param type The new ServiceType object.
     * @exception IllegalArgumentException If the service type name or
     *					 naming authority name is invalid.
     */

    public void setServiceType(ServiceType type) {
	if (!serviceType.isServiceURL()) {
	    serviceType = type;

	}

    }

    /**
     * @return The machine name or IP address.
     */

    public String getHost() {
	return host;

    }

    /**
     * @return The port number, if any.
     */

    public int getPort() {
	return port;

    }

    /**
     * @return The URL path description, if any.
     */

    public String getURLPath() {
	return URLPath;

    }

    /**
     * @return The service advertisement lifetime.
     */

    public int getLifetime() {
	return lifetime;

    }

    /**
     * Formats the service URL into standard URL form.
     *
     * @return Formatted string with the service URL.
     */

    public String toString() {  // Overrides Object.toString();

	return
	    originalServiceType.toString() +
	    ":/" + transport + (noDoubleSlash == false ? "/":"") +
	    host + (port != NO_PORT ? (":" + port) : "") +
	    URLPath;

    }

    public int hashCode() {
	return
	    serviceType.hashCode() +
	    transport.hashCode() +
	    host.hashCode() +
	    port +
	    URLPath.hashCode();
    }

    public boolean equals(Object obj) {

	if (obj == this) {
	    return true;

	}

	if (!(obj instanceof ServiceURL)) {
	    return false;
	}

	ServiceURL surl = (ServiceURL)obj;

	return
	    serviceType.equals(surl.serviceType) &&
	    transport.equals(surl.transport) &&
	    host.equals(surl.host) &&
	    (port == surl.port) &&
	    (noDoubleSlash == surl.noDoubleSlash) &&
	    URLPath.equals(surl.URLPath);

    }

    // Return permanent status.

    boolean getIsPermanent() {
	return isPermanent;

    }

    // Check URL characters for correctness.

    private void checkURLString(String s)
	throws IllegalArgumentException {
	for (int i = 0; i < s.length(); i++) {
	    char c = s.charAt(i);
	    // allowed by RFC1738
	    if (c == '/' || c == ':' || c == '-' || c == ':' ||
		c == '.' || c == '%' || c == '_' || c == '\'' ||
		c == '*' || c == '(' || c == ')' || c == '$' ||
		c == '!' || c == ',' || c == '+' || c == '\\') {
							// defer to Windows
		continue;

	    }

	    // reserved by RFC1738, and thus allowed, pg. 20
	    if (c == ';' || c == '@' || c == '?' || c == '&' || c == '=') {
		continue;

	    }

	    if (Character.isLetterOrDigit(c)) {
		continue;
	    }

	    SLPConfig conf = SLPConfig.getSLPConfig();

	    throw
		new IllegalArgumentException(
				conf.formatMessage("url_char_error",
						   new Object[] {
				    new Character(c)}));
	}
    }

    // Parse the incoming service URL specification.

    private void parseURL(String sURL)
	throws IllegalArgumentException {

	StringTokenizer st = new StringTokenizer(sURL, "/", true);

	try {

	    // This loop is a kludgy way to break out of the parse so
	    //  we only throw at one location in the code.

	    do {
		String typeName = st.nextToken();

		// First token must be service type name.

		if (typeName.equals("/")) {
		    break; // error!

		}

		// Check for colon terminator, not part of service
		// type name.

		if (!typeName.endsWith(":")) {
		    break; // error!

		}

		// Create service type, remove trailing colon.

		serviceType =
		    new ServiceType(typeName.substring(0,
						       typeName.length() - 1));
		originalServiceType = serviceType;

		// Separator between service type name and transport.

		String slash1 = st.nextToken();

		if (!slash1.equals("/")) {
		    break; // error!

		}

		String slash2 = st.nextToken();

		String sAddr = "";  // address...

		// Check for abstract type or alternate transport.

		if (!slash2.equals("/")) {

		    // If this is an abstract type, then we could have
		    //  something like: service:file-printer:file:/foo/bar.
		    //  This is OK. Also, if this is a non-service: URL,
		    //  something like file:/foo/bar is OK.

		    if (!serviceType.isServiceURL()) {
			sAddr = slash2;

			noDoubleSlash = true;

		    } else {

			// We only recognize IPX and Appletalk at this point.

			if (!slash2.equalsIgnoreCase(IPX) &&
			    !slash2.equalsIgnoreCase(AT)) {

			    // Abstract type is OK. We must check here because
			    //  something like
			    //  service:printing:lpr:/ipx/foo/bar
			    //  is allowed.

			    if (serviceType.isAbstractType()) {
				sAddr = slash2;

				noDoubleSlash = true;

			    } else {

				break;  // error!

			    }
			} else {

			    transport = slash2.toLowerCase();

			    // Check for separator between transport and host.

			    if (!st.nextToken().equals("/")) {
				break; // error!
	
			    }

			    sAddr = st.nextToken();
			}
		    }
		} else {

		    // Not abstract type, no alternate transport. Get host.

		    sAddr = st.nextToken();

		}

		if (sAddr.equals("/")) {// no host part
		    URLPath = "/" + st.nextToken("");
		    return; // we're done!

		}

		host = sAddr;

		// Need to check for port number if this is an IP transport.

		if (transport.equals("")) {
		    StringTokenizer tk = new StringTokenizer(host, ":");

		    host = tk.nextToken();

		    // Get port if any.

		    if (tk.hasMoreTokens()) {
			String p = tk.nextToken();

			if (tk.hasMoreTokens()) {
			    break; // error!

			}

			try {

			    port = Integer.parseInt(p);

			} catch (NumberFormatException ex) {
			    break; // error!

			}

			if (port <= 0 || port > PORT_MAXIMUM) {
			    break; // error!

			}
		    }
		}

		//
		// after this point we have to check if there is a token
		// remaining before we read it: It is legal to stop at any
		// point now.  Before all the tokens were required, so
		// missing any was an error.
		//
		if (st.hasMoreTokens() == false) {
					//  minimal url service:t:// a
		    return; // we're done!

		}

		String sSep  = st.nextToken();

		if (!sSep.equals("/")) {
		    break; // error!

		}
	
		// there is a URL path
		// URLPath is all remaining tokens
		URLPath = sSep;

		if (st.hasMoreTokens()) {
		    URLPath += st.nextToken("");

		}

		URLPath = URLPath.trim();

		return; // done!

	    } while (false); // done with parse.

	} catch (NoSuchElementException ex) {
	    throw
		new IllegalArgumentException(
		SLPConfig.getSLPConfig().formatMessage("url_syntax_error",
						       new Object[] {sURL}));

	}

	// The only way to get here is if there was an error in the
	//  parse.

	throw
	    new IllegalArgumentException(
		SLPConfig.getSLPConfig().formatMessage("url_syntax_error",
						       new Object[] {sURL}));

    }

}
