// Copyright (c) 1996-2002 Brian D. Carlstrom

package bdc.util;

import java.util.IdentityHashMap;
import java.util.Map;

/**
    This class maintains debug state for the current thread. The class
    is intended to allow callers to add and remove arbitrary debug
    information as processing of a transation continues. This way when
    there is an error or a hang this information can be retrieved. It
    is very cheap to add and remove items as there is no
    synchronization. All static methods are safe to call.

    <p>

    No non static calls should be made without extreme caution - as
    that code will have to be able to handle all types of exceptions
    due to possible synchronization issues.

    <p>

    The only non static methods are for reading data exclusivly. This
    way a thread trying to look at the debug state of another thread
    will not be able to, under any circumstances, damage the thread it
    is looking at. Although it certainly may trigger exceptions in its
    own thread if race conditions are hit.
*/
public class ThreadDebugState
{

    public static boolean threadStateEnabled = true;
    private static final ThreadDebugKey ThreadNameKey =
        new ThreadDebugKey("ThreadName");

    /**
        The key specified must be pointer equal to the key asked
        for. This is to allow operations to be significantly
        faster. Callers of this method should not do much computation
        or memory allocation to create the value object. It is ok if
        the value object's toString() is expensive as that will be
        called rarely.

        The Key may not be null, but the value may be null.
    */
    public static void set (ThreadDebugKey key, Object value)
    {
        if (!threadStateEnabled) {
            return;
        }
            //updateStats(key);
        Map map = getThisThreadMap();
            // make work for the caller be as little as possible and
            // handle null values - since those are determined at
            // runtime unlike the key...
        if (value == null) {
            value = NullObject;
        }
        map.put(key, value);
    }

    /**
        Retrieve a value for a specified key. The key must be pointer
        equal to the key used to set the value.
    */
    public static Object get (ThreadDebugKey key)
    {
        if (!threadStateEnabled) {
            return null;
        }
            //updateStats(key);
        Map map = getThisThreadMap();
        return internalGet(key, map);
    }

    /**
        Remove a value for a specified key. The key must be pointer
        equal to the key used to set the value. The old value will be
        returned.
    */
    public static Object remove (ThreadDebugKey key)
    {
        if (!threadStateEnabled) {
            return null;
        }
            //updateStats(key);
        Map map = getThisThreadMap();
        Object oldValue = map.remove(key);
        if (oldValue == NullObject) {
            oldValue = null;
        }
        return oldValue;
    }

    /**
        Remove all values that are currently stored.
    */
    public static void clear ()
    {
        if (!threadStateEnabled) {
            return;
        }
        Map map = getThisThreadMap();
        Object threadName = map.get(ThreadNameKey);
        map.clear();
        map.put(ThreadNameKey, threadName);
    }

    /**
        Return a string representing the contents of the state.
    */
    public static String makeString ()
    {
        if (!threadStateEnabled) {
            return "";
        }
        return protectedToString(getThisThreadMap());
    }

    /**
        This is the method to call to print state from a different
        thread. To print your own thread's state, call the static
        makeString() method instead. This method will catch any
        exceptions that are thrown during processing and return them
        as the string.
    */
    public String toString ()
    {
        return protectedToString(someRandomThreadState);
    }
    
    /**
        This is an unsafe method. The caller is required to protect
        against exceptions that occur as a result of synchronization
        issues. It will attempt to perform an equivilent of a get.
    */
    public Object unsafeGet (ThreadDebugKey key)
    {
        if (!threadStateEnabled) {
            return null;
        }

        Assert.that(someRandomThreadState != null,
                    "Do not call unsafe methods unless you know what " +
                    "you are doing and deeply understand this code");
        return internalGet(key, someRandomThreadState);
    }
    private static final ThreadDebugState EmptyThreadDebugState =
        new ThreadDebugState(new IdentityHashMap());
    /**
        This method is thread safe, but is the first step towards
        getting non safe data. It will return a new ThreadDebugState
        object that will allow unsafe calls on it from a different
        thread. That object will, from then on, be able to see state
        that is set on this thread - even state set after this call.
    */
    public static ThreadDebugState getUnsafeThreadDebugState ()
    {
        if (!threadStateEnabled) {
            return EmptyThreadDebugState;
        }
        return new ThreadDebugState(getThisThreadMap());
    }
    
    private static Object internalGet (ThreadDebugKey key, Map map)
    {
        Object value = map.get(key);
        if (value == NullObject) {
            value = null;
        }
        return value;
    }

    private static String protectedToString (Map someRandomThreadState)
    {
        try {
            return unprotectedToString(someRandomThreadState);
        }
            // don't want debugging code to bring down the system.
        catch (RuntimeException ex) {
            return Fmt.S("Unable to print state: %s", SystemUtil.stackTrace(ex));
        }
    }

    private static String unprotectedToString (Map stateInfo)
    {
        if (!threadStateEnabled) {
            return "";
        }
        return stateInfo.toString();
    }

    private static final State state = new ThreadLocalState();
    private static final Object NullObject = new String("NullValue"); // OK
    private final Map someRandomThreadState;

    private ThreadDebugState (Map someRandomThreadState)
    {
        this.someRandomThreadState = someRandomThreadState;
    }

    private static Map getThisThreadMap ()
    {
        Map map = (Map)state.get();
        if (map == null) {
            map = new IdentityHashMap();
            state.set(map);
                // make sure this is after the state is set so there
                // are no recursion problems.
            set(ThreadNameKey, Thread.currentThread().toString());
        }
        /*
        threadLocalAccessCount++;
        if (threadLocalAccessCount % 100 == 0) {
            SystemUtil.out().println("At " + new java.util.Date() + " there are " +
                               threadLocalAccessCount +
                               " calls to thread local");
            SystemUtil.out().println("access pattern: " + statAggrigation);
        }
    */
        return map;
    }

    /**
        This is just a convinence for callers who wish to create
        Maps or Lists that contain objects that may be
        null. ThreadDebugState itself can handle null values
    */
    public static Object nullSafeObject (Object o)
    {
        if (o == null) {
            return "null";
        }
        return o;
    }
}
