/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.trace;

import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.function.Consumer;

/**
 * This class is used to print information about all locks and monitors inside thread.
 *
 * Because obtaining this information have high cost, we defined few static conditional methods.
 * You can use them to avoid unnecessary dump creation if, for example, logging is disabled.
 *
 * It is built on top of {@link ThreadInfo}
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class ThreadDump {

	private final ThreadInfo info;

	private ThreadDump(long cid) {
        info = Li.THREAD_INFO.getThreadInfo(new long[]{ cid }, true, true)[0];
	}

	/**
	 * Creates dump for current thread.
	 */
	public ThreadDump() {
		this(Thread.currentThread().getId());
	}

	/**
	 * Creates dump for specified thread.
	 *
	 * @param thread thread
	 */
	public ThreadDump(Thread thread) {
		this(thread.getId());
	}

	/**
	 * Static conditional method: if condition is true,
	 * it creates dump of current thread and prints it.
	 *
	 * @param condition condition
	 * @param out out
	 */
	public static void printIf(boolean condition, Consumer<String> out) {
		if(condition) {
			ThreadDump dump = new ThreadDump();
			if(dump.locks() > 0) {
				dump.print(out);
			}
		}
	}

	/**
	 * Static conditional method: if condition is true,
	 * it creates dump of specified thread and prints it.
	 *
	 * @param condition condition
	 * @param thread thread
	 * @param out out
	 */
	public static void printIf(boolean condition, Thread thread, Consumer<String> out) {
		if(condition) {
			new ThreadDump(thread).print(out);
		}
	}

	/**
	 * Prints information about this dump:
	 * thread name, id, locked monitors and locked synchronizers
	 *
	 * @param out out
	 */
	public void print(Consumer<String> out) {
		out.accept(String.format("THREAD: %s 0x%08X (monitors=%d, locks=%d)",
            info.getThreadName(), 
            info.getThreadId(),
			info.getLockedMonitors().length,
			info.getLockedSynchronizers().length
        ));
        for(MonitorInfo item : info.getLockedMonitors()) {
            out.accept(String.format(" - MONITOR : %s 0x%08X @ %s",
                item.getClassName(),
                item.getIdentityHashCode(),
                item.getLockedStackFrame()
            ));
        }
        for(LockInfo item : info.getLockedSynchronizers()) {
            out.accept(String.format(" - LOCK : %s 0x%08X", 
                item.getClassName(), 
                item.getIdentityHashCode()
            ));
        }
	}

	/**
	 * Returns number of locked objects (monitors and synchronizers)
	 *
	 * @return int
	 */
    public int locks() {
        return info.getLockedMonitors().length + info.getLockedSynchronizers().length;
    }

    private static final class Li { // NOPMD lazy init idiom 
        
        public static final ThreadMXBean THREAD_INFO = ManagementFactory.getThreadMXBean();
		
    }
    
}
