package org.jcon.util.service;

import org.jcon.util.GenLib;
import org.jcon.util.msg.Message;
import org.jcon.util.msg.MessageService;
import java.util.Hashtable;

/**
 * This class provides services to container members. Its
 * goal is to allow Application/Services architecture to be
 * easily implemented in a reasonably mature manner. The
 * following features are supported: <p> <pre>
 * - Services are named and can be any Object. They do not
 *      have to implement or extend anything.
 * - Container services are hierarchially organized. This
 *      allows systems to be composed of subsystems and
 *      avoids a simplistic global services design.
 * - Add or remove public or local services from a container.
 * - "Upper" public services are inherited.
 * - A service can be local to only that container.
 * - Public services can be shadowed.
 * - Local services shadow public services.
 * - getService(name) is completely automatic and transparent.
 * - Messages may be used instead of object lookups. This
 *     was a late modification but is preferred. We may
 *     modify this class and the system to use only Messages.
 *     All new uses of this class should use Messages.
 * </pre> <p>
 * Later we may add methods such as getServicesList(),
 * findService(criteria) or addContainerServicesListener().
 * NOTES - We have disabled local services since they are
 * not used and are potentially confusing. Messaging does
 * not have any local notion. We need to design a way to
 * determine what Message structure is needed to use
 * MessageListener services, such as MessageProcessor with
 * processMessage(Message) and getMessageDefs() for the
 * Messages it processes.
 *
 * @author Jack Harich
 */
public class ContainerServices implements java.io.Serializable {

//---------- Private Fields ------------------------------
//----- Properties
private ContainerServices parent; // null if root

//----- Internal
// Key = name, Object = Object
private Hashtable publicServices = new Hashtable();
//private Hashtable localServices  = new Hashtable();

private ContainerMessaging messaging = new ContainerMessaging();

//---------- Initialization ------------------------------
public static void main(String args[]) {
    new ContainerServices().runUnitTest();
}
//---------- Properties ----------------------------------
/**
 * Sets the parent of this container. This MUST be done
 * before using the container. We have chosen to not set
 * the parent in the constructor, to allow the container
 * to be moved to a different parent. The root container
 * has a null parent.
 */
public void setParent(ContainerServices parent) {
    this.parent = parent;
    if (parent == null) {
        messaging.setParent(null);
    } else {
        messaging.setParent(parent.messaging);
    }
}
//---------- Public Methods ------------------------------
/**
 * Sets the named service as a public service. Displays
 * problem and throws an IllegalArgumentException if the
 * name already exists publically in this container. Note
 * the name may exist upward.
 */
public void setPublicService(String name, Object service) {
    if (publicServices.containsKey(name)) {
        IllegalArgumentException ex = new IllegalArgumentException(
            "Service name '" + name + "' already exists in this container. Overwrite not allowed.");
        GenLib.exception("ContainerServices.addPublicService()",
            "Cannot add service '" + name + "'.", ex);
        throw ex;
    } else {
        publicServices.put(name, service);
    }
}
/**
 * Sets the named service as a local service. Displays
 * problem and throws an IllegalArgumentException if the
 * name already exists locally.
 */
/** public void setLocalService(String name, Object service) {
    if (localServices.containsKey(name)) {
        IllegalArgumentException ex = new IllegalArgumentException(
            "Service name '" + name + "' already exists. Overwrite not allowed.");
        GenLib.exception("ContainerServices.addLocalService()",
            "Cannot add service '" + name + "'.", ex);
        throw ex;
    } else {
        localServices.put(name, service);
    }
} */
/**
 * Removes the named service from this container's public
 * services. Returns the service removed or null if it did
 * not exist.
 */
public Object removePublicService(String name) {
    return publicServices.remove(name);
}
/**
 * Removes the named service from this container's local
 * services. Returns the service removed or null if it did
 * not exist.
 */
/** public Object removeLocalService(String name) {
    return localServices.remove(name);
} */
/**
 * Returns the named service or displays problem and throws
 * an IllegalArgumentException if not found, since this is
 * usually a programming error.
 * <p> <pre>
 * The service search algorithm is as follows:
 * - Check local services first.
 * - If not found check public services.
 * - If not found and has parent check parent's public services.
 * - If found return it. If not found throw exception.
 * </pre> <p>
 * Note this allows local services to shadow public services
 * and a container's public services to shadow its parent's
 * public services. This allows great design flexibility.
 */
public Object getService(String name) {
    Object service = getServiceDefaultNull(name);
    if (service == null) {
        IllegalArgumentException ex = new IllegalArgumentException(
            "Service name '" + name + "' not found.");
        GenLib.exception("ContainerServices.getService()",
            "Cannot find service '" + name + "'.", ex);
        throw ex;

    } else {
        return service;
    }
}
/**
* Same as getService() except returns null if not found.
* This does not complain if not found.
*/
public Object getServiceDefaultNull(String name) {

    Object service = publicServices.get(name);

    if (service == null && parent != null) {
        service = parent.getPublicService(name);
    }
    return service;
}    
/**
 * Returns true if the named service exists, false if not.
 */
public boolean hasService(String name) {
    return (getServiceDefaultNull(name) != null ? true : false);
}
/**
* Removes all public and local services in this container.
* This method is designed for same instance reuse.
*/
public void removeAllServices() {
    publicServices.clear();
    //localServices.clear();
}
public void runUnitTest() {
    ContainerServices cs = new ContainerServices();

    cs.setPublicService("Service1", "DoService1");
    cs.setPublicService("Service2", "DoService2Public");
    //cs.addLocalService("Service2", "DoService2Local");

    // Should print "DoService1"
    print(" - Service1 is " + cs.getService("Service1"));

    // Should print "DoService2Local" due to shadow
    print(" - Service2 is " + cs.getService("Service2"));
}
//========== Messaging Related ===========================
/**
* For messaging related documentation see ContainerMessaging.
* These methods are delegated to it.
*/
// This is setMessageService in Basket
public void setMessageHandler(String messageName,
                                MessageService handler) {
    messaging.setMessageHandler(messageName, handler); 
}                                    
public void removeMessageHandler(String messageName) {
    messaging.removeMessageHandler(messageName);                                   
}
public void removeAllMessageHandlers() {
    messaging.removeAllMessageHandlers();
}    
public boolean sendMessage(Message message) {
    return messaging.sendMessage(message);
}    
//---------- Private Methods -----------------------------
/**
 * Returns the named public service or null if not found.
 * If not found and it has a parent, the parent service is
 * returned. Thus this is a RECURSIVE method. Local
 * services are ignored.
 */
private Object getPublicService(String name) {
    Object service = publicServices.get(name);
    if (service == null && parent != null) {
        service = parent.getPublicService(name);
    }
    return service;
}
//--- Std
private static void print(String text) {
    System.out.println("ContainerServices" + text);
}

} // End class
