

package org.jgroups.stack;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jgroups.Event;
import org.jgroups.util.ThreadFactory;
import org.jgroups.protocols.TP;
import org.jgroups.annotations.DeprecatedProperty;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.Property;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * The Protocol class provides a set of common services for protocol layers. Each layer has to
 * be a subclass of Protocol and override a number of methods (typically just <code>up()</code>,
 * <code>down()</code> and <code>getName()</code>. Layers are stacked in a certain order to form
 * a protocol stack. <a href=org.jgroups.Event.html>Events</a> are passed from lower
 * layers to upper ones and vice versa. E.g. a Message received by the UDP layer at the bottom
 * will be passed to its higher layer as an Event. That layer will in turn pass the Event to
 * its layer and so on, until a layer handles the Message and sends a response or discards it,
 * the former resulting in another Event being passed down the stack.
 * <p/>
 * The important thing to bear in mind is that Events have to passed on between layers in FIFO
 * order which is guaranteed by the Protocol implementation and must be guranteed by subclasses
 * implementing their on Event queuing.<p>
 * <b>Note that each class implementing interface Protocol MUST provide an empty, public
 * constructor !</b>
 *
 * @author Bela Ban
 * @version $Id: Protocol.java,v 1.65 2008/10/21 08:14:47 vlada Exp $
 */
@DeprecatedProperty(names={"down_thread","down_thread_prio","up_thread","up_thread_prio"})
public abstract class Protocol {
    protected Protocol         up_prot=null, down_prot=null;
    protected ProtocolStack    stack=null;
    
    @Property(description="Determines whether to collect statistics (and expose them via JMX). Default is true")
    protected boolean          stats=true;
    protected final Log        log=LogFactory.getLog(this.getClass());


    /**
     * Configures the protocol initially. A configuration string consists of name=value
     * items, separated by a ';' (semicolon), e.g.:<pre>
     * "loopback=false;unicast_inport=4444"
     * </pre>
     * @deprecated The properties are now set through the @Property annotation on the attribute or setter
     */
    protected boolean setProperties(Properties props) {
        throw new UnsupportedOperationException("deprecated; use a setter instead");
    }


    /**
     * Sets a property
     * @param key
     * @param val
     * @deprecated Use the corresponding setter instead
     */
    public void setProperty(String key, String val) {
        throw new UnsupportedOperationException("deprecated; use a setter instead");
    }


    /** Called by Configurator. Removes 2 properties which are used by the Protocol directly and then
     *	calls setProperties(), which might invoke the setProperties() method of the actual protocol instance.
     * @deprecated Use a setter instead
     */
    public boolean setPropertiesInternal(Properties props) {
        throw new UnsupportedOperationException("use a setter instead");
    }

    /**
     * @return
     * @deprecated Use a getter to get the actual instance variable
     */
    public Properties getProperties() {
        if(log.isWarnEnabled())
            log.warn("deprecated feature: please use a setter instead");
        return new Properties();
    }
    
    public ProtocolStack getProtocolStack(){
        return stack;
    }

    /**
     * After configuring the protocol itself from the properties defined in the XML config, a protocol might have
     * additional objects which need to be configured. This callback allows a protocol developer to configure those
     * other objects. This call is guaranteed to be invoked <em>after</em> the protocol itself has
     * been configured. See AUTH for an example.
     * @return
     */
    protected List<Object> getConfigurableObjects() {
        return null;
    }

    protected TP getTransport() {
        Protocol retval=this;
        while(retval != null && retval.down_prot != null) {
            retval=retval.down_prot;
        }
        return (TP)retval;
    }

    /** Supposed to be overwritten by subclasses. Usually the transport returns a valid non-null thread factory, but
     * thread factories can also be created by individual protocols
     * @return
     */
    public ThreadFactory getThreadFactory() {
        return down_prot != null? down_prot.getThreadFactory(): null;
    }


    /** @deprecated up_thread was removed
     * @return false by default
     */
    public boolean upThreadEnabled() {
        return false;
    }

    /**
     * @deprecated down thread was removed
     * @return boolean False by default
     */
    public boolean downThreadEnabled() {
        return false;
    }

    public boolean statsEnabled() {
        return stats;
    }

    public void enableStats(boolean flag) {
        stats=flag;
    }

    public void resetStats() {
        ;
    }

    public String printStats() {
        return null;
    }

    public Map<String,Object> dumpStats() {
        HashMap<String,Object> map=new HashMap<String,Object>();
        for(Class<?> clazz=this.getClass();clazz != null;clazz=clazz.getSuperclass()) {
            Field[] fields=clazz.getDeclaredFields();
            for(Field field:fields) {
                if(field.isAnnotationPresent(ManagedAttribute.class)) {
                    String attributeName=field.getName();
                    Object value;
                    try {
                        field.setAccessible(true);
                        value=field.get(this);
                        if(value != null) {
                            map.put(attributeName, value.toString());
                        }
                        else {
                            map.put(attributeName, null);
                        }
                    }
                    catch(Exception e) {
                        log.warn("Could not retrieve value of attribute (field) " + attributeName,e);
                    }
                }
            }

            Method[] methods=this.getClass().getMethods();
            for(Method method:methods) {
                if(method.isAnnotationPresent(ManagedAttribute.class)) {
                    ManagedAttribute annotation=method.getAnnotation(ManagedAttribute.class);
                    if(!annotation.writable() && (method.getName().startsWith("is")
                       || method.getName().startsWith("get"))) {
                        Object value=null;
                        try {
                            value=method.invoke(this, new Object[0]);
                            String attributeName=methodNameToAttributeName(method.getName());
                            if(value != null) {
                                map.put(attributeName, value.toString());
                            }
                            else {
                                map.put(attributeName, null);
                            }
                        }
                        catch(Exception e) {
                            log.warn("Could not retrieve value of attribute (method) " + method.getName(),e);
                        }
                    }
                }
            }
        }
        return map;
    }
    
    private String methodNameToAttributeName(String methodName) {
        methodName=methodName.startsWith("get") || methodName.startsWith("set")? methodName.substring(3): methodName;
        methodName=methodName.startsWith("is")? methodName.substring(2) : methodName;
        Pattern p=Pattern.compile("[A-Z]");
        Matcher m=p.matcher(methodName.substring(1));
        StringBuffer sb=new StringBuffer();
        while(m.find()) {
            m.appendReplacement(sb, "_" + methodName.substring(m.end(), m.end() + 1).toLowerCase());
        }
        m.appendTail(sb);
        sb.insert(0, methodName.substring(0, 1).toLowerCase());
        return sb.toString();
    }
    
    /**
     * Called after instance has been created (null constructor) and before protocol is started.
     * Properties are already set. Other protocols are not yet connected and events cannot yet be sent.
     * @exception Exception Thrown if protocol cannot be initialized successfully. This will cause the
     *                      ProtocolStack to fail, so the channel constructor will throw an exception
     */
    public void init() throws Exception {
    }

    /**
     * This method is called on a {@link org.jgroups.Channel#connect(String)}. Starts work.
     * Protocols are connected and queues are ready to receive events.
     * Will be called <em>from bottom to top</em>. This call will replace
     * the <b>START</b> and <b>START_OK</b> events.
     * @exception Exception Thrown if protocol cannot be started successfully. This will cause the ProtocolStack
     *                      to fail, so {@link org.jgroups.Channel#connect(String)} will throw an exception
     */
    public void start() throws Exception {
    }

    /**
     * This method is called on a {@link org.jgroups.Channel#disconnect()}. Stops work (e.g. by closing multicast socket).
     * Will be called <em>from top to bottom</em>. This means that at the time of the method invocation the
     * neighbor protocol below is still working. This method will replace the
     * <b>STOP</b>, <b>STOP_OK</b>, <b>CLEANUP</b> and <b>CLEANUP_OK</b> events. The ProtocolStack guarantees that
     * when this method is called all messages in the down queue will have been flushed
     */
    public void stop() {
    }


    /**
     * This method is called on a {@link org.jgroups.Channel#close()}.
     * Does some cleanup; after the call the VM will terminate
     */
    public void destroy() {
    }


    /** List of events that are required to be answered by some layer above.
     @return Vector (of Integers) */
    public Vector<Integer> requiredUpServices() {
        return null;
    }

    /** List of events that are required to be answered by some layer below.
     @return Vector (of Integers) */
    public Vector<Integer> requiredDownServices() {
        return null;
    }

    /** List of events that are provided to layers above (they will be handled when sent down from
     above).
     @return Vector (of Integers) */
    public Vector<Integer> providedUpServices() {
        return null;
    }

    /** List of events that are provided to layers below (they will be handled when sent down from
     below).
     @return Vector<Integer (of Integers) */
    public Vector<Integer> providedDownServices() {
        return null;
    }


    /** All protocol names have to be unique ! */
    public abstract String getName();

    public Protocol getUpProtocol() {
        return up_prot;
    }

    public Protocol getDownProtocol() {
        return down_prot;
    }

    public void setUpProtocol(Protocol up_prot) {
        this.up_prot=up_prot;
    }

    public void setDownProtocol(Protocol down_prot) {
        this.down_prot=down_prot;
    }

    public void setProtocolStack(ProtocolStack stack) {
        this.stack=stack;
    }


    /**
     * An event was received from the layer below. Usually the current layer will want to examine
     * the event type and - depending on its type - perform some computation
     * (e.g. removing headers from a MSG event type, or updating the internal membership list
     * when receiving a VIEW_CHANGE event).
     * Finally the event is either a) discarded, or b) an event is sent down
     * the stack using <code>down_prot.down()</code> or c) the event (or another event) is sent up
     * the stack using <code>up_prot.up()</code>.
     */
    public Object up(Event evt) {
        return up_prot.up(evt);
    }


    /**
     * An event is to be sent down the stack. The layer may want to examine its type and perform
     * some action on it, depending on the event's type. If the event is a message MSG, then
     * the layer may need to add a header to it (or do nothing at all) before sending it down
     * the stack using <code>down_prot.down()</code>. In case of a GET_ADDRESS event (which tries to
     * retrieve the stack's address from one of the bottom layers), the layer may need to send
     * a new response event back up the stack using <code>up_prot.up()</code>.
     */
    public Object down(Event evt) {
        return down_prot.down(evt);
    }




}
