package hiro.yoshioka.util.ref;

import hiro.yoshioka.util.StringUtil;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//class MyObject {
//	protected void finalize() throws Throwable {
//		System.out.println("In finalize method for this object: " + this);
//	}
//}
class ReferenceInfo {
	public static String PHANTOM = "PHANTOM";

	public static String WEAK = "WEAK";

	private long fTime;

	int fHashCode;

	String fText;

	Class fClazz;

	List<StackTraceElement> fStackTraceElementList = new ArrayList<StackTraceElement>();

	private boolean fRemoved;

	private String type;

	protected ReferenceInfo(String type, Object obj) {
		this(type, obj, false);
	}

	protected ReferenceInfo(String type, Object obj, boolean recordMe) {
		fTime = System.currentTimeMillis();
		this.type = type;
		if (obj != null) {
			fHashCode = obj.hashCode();
			fText = obj.toString();
			fClazz = obj.getClass();
		}
		if (recordMe) {
			inferCaller();
		}
	}

	private void inferCaller() {
		// Get the stack trace.
		StackTraceElement stack[] = (new Throwable()).getStackTrace();
		// First, search back to a method in the Logger class.
		for (int ix = 0; ix < stack.length; ix++) {
			StackTraceElement stackTraceElement = stack[ix];
			String cname = stackTraceElement.getClassName();
			if (cname.indexOf("hiro.yoshioka.util.ref.Reference") >= 0) {
				continue;
			}
			fStackTraceElementList.add(stackTraceElement);
		}
	}

	public void removed() {
		fTime = System.currentTimeMillis() - fTime;
		fRemoved = true;
	}

	public String toString() {
		StringBuilder buf = new StringBuilder();
		if (type == PHANTOM) {
			buf.append("[PHANTOM]Call finalizer ");
		} else if (type == WEAK) {
			buf.append("[WEAK]lost referent ");
		}
		buf.append(" HASH-CD/ALIVED-TIME/INFO[").append(
				Integer.toHexString(fHashCode));
		buf.append("/");
		if (fRemoved) {
			buf.append(fTime);
		} else {
			buf.append(System.currentTimeMillis() - fTime);
		}
		buf.append("/").append(fClazz);
		for (int i = 0; i < fStackTraceElementList.size() && i < 3; i++) {
			buf.append(StringUtil.LINE_SEPARATOR).append("  stack : ")
					.append(i).append(" [").append(
							fStackTraceElementList.get(i)).append("]");
		}
		return buf.toString();
	}
}

public class ReferenceTracker {
	private static Log log = LogFactory.getLog(ReferenceTracker.class);
	private static ReferenceQueue queue = new ReferenceQueue();

	static Map<Reference, ReferenceInfo> fTraceReferenceMap = Collections
			.synchronizedMap(new HashMap<Reference, ReferenceInfo>());

	static {
		Thread handler = new ReferenceHandler();
		handler.setPriority(Thread.MAX_PRIORITY);
		handler.setDaemon(true);
		handler.start();
	}

	public static void main(String[] arg) {
		// new ReferenceTracker().doTest();
	}

	// private void doTest() {
	// Object target = new MyObject();
	//
	// Reference r = ReferenceTracker.phantomTrace(target);
	// Object target2 = new MyObject();
	// // ReferenceTracker.weakTrace(target2);
	// target = null;
	// target2 = null;
	// for (int j = 0; j < 5; j++) {
	// System.gc();
	// System.out.println("--------gc-----");
	//
	// }
	// System.gc();
	//
	// System.out.println("end.............");
	// }

	private static class ReferenceHandler extends Thread {
		public void run() {
			try {
				log.info("ReferenceHandler started...");
				for (;;) {
					Reference reference = queue.remove();
					ReferenceInfo info = (ReferenceInfo) fTraceReferenceMap
							.remove(reference);
					info.removed();
					Object target = reference.get();
					log.info("object finalized:" + info);
					if (target != null) {
						reference.clear();
					}
				}
			} catch (Exception e) {
				log.fatal(StringUtil.EMPTY_STRING, e);
			}
		}
	}

	public static PhantomReference phantomTrace(Object obj, boolean recordMe) {

		PhantomReference ref = new PhantomReference(obj, queue);
		fTraceReferenceMap.put(ref, new ReferenceInfo(ReferenceInfo.PHANTOM,
				obj, recordMe));
		return ref;
	}

	public static PhantomReference phantomTrace(Object obj) {
		return phantomTrace(obj, false);
	}

	public static WeakReference weakTrace(Object obj) {
		WeakReference ref = new WeakReference(obj, queue);
		fTraceReferenceMap.put(ref, new ReferenceInfo(ReferenceInfo.WEAK, obj));
		return ref;
	}

	public static void dump() {
		System.out.println("-- ReferenceTracking Object list -----["
				+ fTraceReferenceMap.size() + "]");
		for (Iterator ite = fTraceReferenceMap.entrySet().iterator(); ite
				.hasNext();) {
			Map.Entry entry = (Entry) ite.next();
			System.out.println(entry.getValue());
		}
		System.out.println("------------------------------------------");
	}
}