package com.hammurapi.common;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Maintains weak references to objects. Removes cleared entries in
 * getIdentity() method.
 * @author Pavel Vlasov
 *
 */
public class WeakIdentityManager implements IdentityManager<Object> {
	
	private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();

	private static long[] counter = {0};
	
	private static class Entry extends WeakReference<Object> {
		String identity;
		
		Entry(Object obj) {
			super(obj, rq);
			identity="wim_"+Long.toString(counter[0]++, Character.MAX_RADIX);
		}
	}
	
	private static List<IdentityExtractor<?>> extractors = new ArrayList<IdentityExtractor<?>>();
	
	static {
		Thread reaper = new Thread("Removes cleared references") {
			
			@Override
			public void run() {
				while (true) {
					try {
						Reference<? extends Object> removed = rq.remove();
						if (removed instanceof Entry) {
							synchronized (counter) {
								Iterator<Entry> it = entries.iterator();
								while (it.hasNext()) {
									Entry entry = it.next();
									if (removed==entry) {
										it.remove();
										break;
									}
								}
							}							
						}
					} catch (InterruptedException e) {
						return; // Time to exit.
					}
				}
			}
		};
		
		reaper.setDaemon(true);
		reaper.start();
	}
	
	private static LinkedList<Entry> entries = new LinkedList<Entry>(); 

	@SuppressWarnings("unchecked")
	public Object getIdentity(Object obj) {
		if (obj==null) {
			return null;
		}
		if (obj instanceof Identifiable) {
			return ((Identifiable<Object>) obj).getIdentity();
		}
		for (IdentityExtractor<?> ie: extractors) {
			Object identity = ie.getIdentity(obj);
			if (identity!=null) {
				return identity;
			}
		}
		synchronized (counter) {
			Iterator<Entry> it = entries.iterator();
			while (it.hasNext()) {
				Entry entry = it.next();
				Object eObj = entry.get();
				if (eObj==null) {
					it.remove();
				} else if (eObj==obj) {
					return entry.identity;
				}
			}
			Entry entry = new Entry(obj);
			entries.add(entry);
			return entry.identity;
		}
	}

}
