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

//  SCCS Status:      @(#)ServiceType.java	1.2	05/10/99
//  ServiceType.java: Model a service type.
//  Author:           James Kempf
//  Created On:       Thu Apr  9 09:23:18 1998
//  Last Modified By: James Kempf
//  Last Modified On: Mon Oct 19 15:43:18 1998
//  Update Count:     33
//

package com.sun.slp;

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


/**
 * The ServiceType class conceals the complex details of whether a
 * service type name is a simple URL scheme identifier, a service:
 * type, an abstract type or protocol type.
 *
 * @version 1.2 00/10/14
 * @author James Kempf
 */

public class ServiceType extends Object implements Serializable {

    boolean isServiceURL = true;	// was it originally a service: type?
    private String type1 = "";	// scheme, abstract, or protocol.
    private String type2 = "";	// concrete, if type was abstract.
    private String na = "";	// naming authority.

    // For removing IANA.

    static final String IANA = "iana";

    /**
     * Create a service type object from the type name. The name may
     * take the form of any valid SLP service type name.
     *
     * @param t The type name.
     * @return The ServiceType object.
     * @exception IllegalArgumentException If the name is syntactically
     *					 incorrect.
     */

    public ServiceType(String t) throws IllegalArgumentException {

	parse(t);

    }

    /**
     * Return true if type name was from a service: URL.
     *
     * @return True if type name came from service: URL.
     */

    public boolean isServiceURL() {
	return isServiceURL;

    }

    /**
     * Return true if type name is for an abstract type.
     *
     * @return True if type name is for an abstract type.
     */

    public boolean isAbstractType() {
	return (type2.length() > 0);

    }

    /**
     * Return true if naming authority is default.
     *
     * @return True if naming authority is default.
     */

    public boolean isNADefault() {
	return (na.length() <= 0);

    }

    /**
     * Return the concrete service type name without naming authority.
     *
     * @return concrete type name.
     */

    public String getConcreteTypeName() {
	return type2;

    }

    /**
     * Return the principle type name, which is either the abstract
     * type name or the protocol name, without naming authority.
     *
     * @return Principle type name.
     */

    public String getPrincipleTypeName() {
	return type1;

    }

    /**
     * Return the fully formatted abstract type name, if it is an abstract
     * type, otherwise the empty string.
     */

    public String getAbstractTypeName() {
	if (isAbstractType()) {
	    return "service:" + type1 + (na.length() > 0 ? ("." + na):"");

	}
	return "";

    }

    /**
     * Return the naming authority name.
     *
     * @return Naming authority name.
     */

    public String getNamingAuthority() {
	return na;

    }

    /**
     *Validate a naming authority name.
     */

    static void validateTypeComponent(String name)
	throws ServiceLocationException {

	validateTypeComponentInternal(name, false);
    }

    // Validate, allowing '.' if allowDot is true.

    static private void
	validateTypeComponentInternal(String name, boolean allowDot)
	throws ServiceLocationException {
	int i, n = name.length();

	for (i = 0; i < n; i++) {
	    char c = name.charAt(i);

	    if ((Character.isLetterOrDigit(c) == false) &&
		(c != '+') && (c != '-')) {
		boolean throwIt = true;

		// If dot is allowed, don't throw it.

		if (allowDot && (c == '.')) {
		    throwIt = false;

		}

		if (throwIt) {
		    throw
			new IllegalArgumentException(
	SLPConfig.getSLPConfig().formatMessage("service_type_syntax",
					       new Object[] {name}));
		}
	    }
	}
    }

    // Two service type names are equal if they have the same
    //  types, naming authority, and same service: flag.

    public boolean equals(Object o) {

	if (o == this) {
	    return true;

	}

	if (!(o instanceof ServiceType)) {
	    return false;

	}

	ServiceType type = (ServiceType)o;

	return
	    (isServiceURL == type.isServiceURL) &&
	    type1.equals(type.type1) &&
	    type2.equals(type.type2) &&
	    na.equals(type.na);

    }

    // Format the service type name for output.

    public String toString() {

	return
	    (isServiceURL ? "service:" : "") +
	    type1 +
	    (na.length() > 0 ? ("." + na) : "") +
	    (type2.length() > 0 ? (":" + type2) : "");

    }

    // Return a hash code for the type.

    public int hashCode() {

	return type1.hashCode() +
	    na.hashCode() +
	    type2.hashCode() +
	    (isServiceURL ? Defaults.SERVICE_PREFIX.hashCode():0);

    }

    // Parse a service type name with optional naming authority.

    private void parse(String t) {
	StringTokenizer st = new StringTokenizer(t, ":.", 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 tok = st.nextToken();

		if (tok.equals(":") || tok.equals(".")) {
		    break;  // error!

		}

		// Look for a nonservice: URL.

		if (!tok.equalsIgnoreCase(Defaults.SERVICE_PREFIX)) {
		    isServiceURL = false;

		    // Need to eat through all dots.

		    do {
			type1 = type1 + tok.toLowerCase();

			// Break when we run out of tokens.

			if (!st.hasMoreTokens()) {
			    break;

			}

			tok = st.nextToken();

		    } while (true);

		    // Check for disallowed characters. Allow '.'.

		    validateTypeComponentInternal(type1, true);

		    // There should be no more tokens.

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

		    }

		    return; // done!

		}

		tok = st.nextToken();

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

		}

		// Get the protocol or abstract type name.

		type1 = st.nextToken().toLowerCase();

		validateTypeComponent(type1);

		// From here on in, everything is optional, so be sure
		//  to check for no remaining tokens.

		if (!st.hasMoreTokens()) {
		    return;
		// done! It's a simple protocol type w.o. naming authority.

		}

		// It's either got a naming authority or is an abstract
		//  type (or both).

		tok = st.nextToken();

		// Check for naming authorithy.

		if (tok.equals(".")) {
		    tok = st.nextToken();

		    validateTypeComponent(tok);

		    if (!tok.equalsIgnoreCase(IANA)) {

			na = tok.toLowerCase();

		    } else {

			// Error to have IANA.

			break;

		    }

		    if (!st.hasMoreTokens()) {
			return;
		// done! It's a simple protocol type w. naming authority.

		    }

		    tok = st.nextToken();

		}

		// Should be at the separator to concrete type.

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

		}

		tok = st.nextToken();

		// This is the concrete type name.

		validateTypeComponent(tok);

		type2 = tok.toLowerCase();

		// Should be no more tokens.

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

		}

		return; // done!

	    } while (false);

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

	} catch (ServiceLocationException ex) {
	    throw
		new IllegalArgumentException(ex.getMessage());

	}

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

    }

}
