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

import com.sun.jini.proxy.MarshalledWrapper;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.util.StringTokenizer;
import net.jini.loader.ClassLoading;

/**
 * A ServiceType is a descriptor for a class, packaged up for
 * transmission between client-side proxies and the registrar server.
 * Instances are never visible to clients, they are private to the
 * communication between the proxies and the server.
 * <p>
 * This class only has a bare minimum of methods, to minimize
 * the amount of code downloaded into clients.
 * <p>
 * Equality is based on object identity, on the assumption that
 * ClassResolver is always used and that equality is only interesting
 * in the registrar.
 *
 * @author Sun Microsystems, Inc.
 *
 * @see ClassMapper
 * @see ClassResolver
 */
class ServiceType implements Serializable {

    private static final long serialVersionUID = 2L;

    /**
     * Class name. If the class is generated by java.lang.reflect.Proxy,
     * then the name is of the form ";iface1;iface2;...;ifaceN".
     *
     * @serial
     */
    protected String name;
    /**
     * Descriptor for the superclass.
     *
     * @serial
     */
    protected ServiceType superclass;
    /**
     * Descriptor for the interfaces.  As a special case, interfaces is
     * null for the descriptor for java.lang.Object, and non-null otherwise.
     * This avoids carrying a boolean isInterface around.
     *
     * @serial
     */
    protected ServiceType[] interfaces;
    /**
     * An instance containing only name, no supertype info.
     * This is only used on the registrar side, to minimize the amount
     * of info transmitted back to clients.
     */
    protected transient ServiceType replacement;
    /** The concrete classes. */
    protected transient ServiceType[] concreteClasses;
    /** 
     * Flag set to true if this instance was unmarshalled from an
     * integrity-protected stream, or false otherwise
     */
    private transient boolean integrity = false;

    private static final ServiceType[] empty = {};

    /** Should only be called by ClassMapper */
    public ServiceType(Class clazz,
		       ServiceType superclass,
		       ServiceType[] interfaces)
    {
	if (!Proxy.isProxyClass(clazz)) {
	    name = clazz.getName();
	} else if (interfaces.length == 0) {
	    name = ";";
	} else {
	    StringBuffer buf = new StringBuffer();
	    for (int i = 0; i < interfaces.length; i++) {
		buf.append(';');
		buf.append(interfaces[i].getName());
	    }
	    name = buf.toString();
	}
	this.superclass = superclass;
	if (clazz != Object.class)
	    this.interfaces = interfaces;
    }

    /**
     * Constructor used for creating replacement instances,
     * containing only name.
     */
    private ServiceType(ServiceType stype) {
	name = stype.name;
    }

    /** Return the class name */
    public String getName() {
	return name;
    }

    /** Return the superclass descriptor */
    public ServiceType getSuperclass() {
	return superclass;
    }

    /** Return the interfaces.  The array is not a copy; do not modify it. */
    public ServiceType[] getInterfaces() {
	if (interfaces != null)
	    return interfaces;
	return empty;
    }

    /** Return the replacement, if any, containing only name and rep. */
    public ServiceType getReplacement() {
	return replacement;
    }

    /**
     * This is really only needed in the registrar, but it's very
     * convenient to have here.
     * @see Class#isAssignableFrom
     */
    public boolean isAssignableFrom(ServiceType cls) {
	if (this == cls)
	    return true;
	if (isInterface()) {
	    if (isAssignableFrom(cls.interfaces))
		return true;
	    for (ServiceType sup = cls.superclass;
		 sup != null && sup.interfaces != null;
		 sup = sup.superclass)
	    {
		if (isAssignableFrom(sup.interfaces))
		    return true;
	    }
	} else {
	    for (ServiceType sup = cls.superclass;
		 sup != null;
		 sup = sup.superclass)
	    {
		if (this == sup)
		    return true;
	    }
	}
	return false;
    }

    /** Test if this isAssignableFrom any of the given interface types. */
    private boolean isAssignableFrom(ServiceType[] ifaces)
    {
	for (int i = ifaces.length; --i >= 0; ) {
	    if (this == ifaces[i] || isAssignableFrom(ifaces[i].interfaces))
		return true;
	}
	return false;
    }

    /** @see Class#isInterface */
    public boolean isInterface() {
	return (superclass == null && interfaces != null);
    }

    /** Converts the descriptor to a Class instance, loading from codebase */
    public Class toClass(String codebase)
	throws IOException, ClassNotFoundException
    {
	if (name.charAt(0) != ';') {
	    return ClassLoading.loadClass(
		codebase, name, null, integrity, null);
	}
	StringTokenizer st = new StringTokenizer(name, ";");
	String[] ifs = new String[st.countTokens()];
	for (int i = 0; i < ifs.length; i++) {
	    ifs[i] = st.nextToken();
	}
	return ClassLoading.loadProxyClass(
	    codebase, ifs, null, integrity, null);
    }

    /**
     * Sets this descriptor to be the canonical one.  Replaces the
     * superclass with the canonical superclass, and constructs the
     * replacement object.
     *
     * This should only be called by ClassResolver.
     */
    public void canonical(ServiceType superclass) {
	this.superclass = superclass;
	replacement = new ServiceType(this);
	concreteClasses = empty;
    }

    /**
     * Returns the set of all ServiceTypes T such that there is at least one
     * registered service with serviceType equal to T and for which
     * this.isAssignableFrom(T).  This is only used on the registrar side.
     */
    public ServiceType[] getConcreteClasses() {
	return concreteClasses;
    }

    /** Sets the concrete classes. */
    public void setConcreteClasses(ServiceType[] concreteClasses) {
	if (concreteClasses.length == 0)
	    concreteClasses = empty;
	this.concreteClasses = concreteClasses;
    }

    /**
     * Samples integrity protection setting (if any) of the stream from which
     * this instance is being deserialized.
     */
    private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundException
    {
	in.defaultReadObject();
	integrity = MarshalledWrapper.integrityEnforced(in);
    }
}
