package org.dynalang.classtoken;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * The class token is used as a surrogate representation for a 
 * {@link java.lang.Class} object in various contexts where it is undesirable 
 * to keep a strong reference to the Class object itself. This class guarantees
 * that at most one ClassToken object exists for every loaded Class object.
 * Holding a reference to a ClassToken does not prevent the Class object from
 * unloading. There is an event listener mechanism to observe when a ClassToken
 * becomes invalid (its associated class becomes unloaded).
 * @author Attila Szegedi
 * @version $Id: $
 */
public class ClassToken {
    private static final ReferenceQueue<Class<?>> queue = new
    	ReferenceQueue<Class<?>>();
    private static final ConcurrentMap<ClassTokenKey, ClassToken> canonicals = 
	new ConcurrentHashMap<ClassTokenKey, ClassToken>();
    private static final List<WeakReference<ClassTokenListener>> listeners = 
	new LinkedList<WeakReference<ClassTokenListener>>();
    
    // This is never read - it is used solely to prevent the reference object
    // itself from getting garbage collected.
    private WeakClassRef weakClassRef;
    
    /**
     * Gets a class token for a given class.
     * @param clazz the class for which the token is requested
     * @return a class token for the given class. Always returns the same 
     * ClassToken object for the same Class object, allowing ClassTokens to be
     * compared using identity.
     */
    public static ClassToken forClass(Class<?> clazz) {
	processClearedReferences();
	ClassTokenKey tokenKey = new ClassTokenKey(clazz);
	ClassToken token = canonicals.get(tokenKey);
	if(token == null) {
	    token = new ClassToken();
	    if(canonicals.putIfAbsent(tokenKey, token) == null) {
		token.weakClassRef = new WeakClassRef(tokenKey, token, clazz);
	    }
	}
	return token;
    }

    /**
     * Adds a {@link ClassTokenListener} that will get notified whenever a 
     * {@link ClassToken} object gets invalidated.
     * @param listener the listener
     */
    public static void addClassRefListener(ClassTokenListener listener) {
	synchronized(listeners) {
	    listeners.add(new WeakReference<ClassTokenListener>(listener));
	}
    }
    
    /**
     * Removes a {@link ClassTokenListener} previously registered with the 
     * {@link #addClassRefListener(ClassTokenListener)} method. It is not 
     * strictly necessary to eagerly remove listeners, as this class references
     * its listeners weakly, and won't prevent a listener from getting garbage
     * collected.
     * @param listener the listener
     */
    public static void removeClassRefListener(ClassTokenListener listener) {
	synchronized(listeners) {
	    for (Iterator<WeakReference<ClassTokenListener>> iterator = 
		listeners.iterator(); iterator.hasNext();) {
		WeakReference<ClassTokenListener> listenerRef = iterator.next();
		ClassTokenListener refListener = listenerRef.get();
		if(refListener == null || refListener == listener) {
		    iterator.remove();
		    if(refListener != null) {
			break;
		    }
		}
	    }
	    listeners.remove(listener);
	}
    }

    private static void processClearedReferences() {
	List<ClassToken> list = null;
	for(;;) {
	    WeakClassRef clearedWeakRef = (WeakClassRef)queue.poll();
	    if(clearedWeakRef == null) {
		break;
	    }
	    ClassToken token = clearedWeakRef.classToken;
	    canonicals.remove(clearedWeakRef.classTokenKey, token);
            if(list == null) {
    	        list = new LinkedList<ClassToken>();
            }
            list.add(token);
	}
	if(list != null) {
	    // Note: we accumulate all invalidated tokens into a single event
	    // as it is anticipated that class unloads happen in batches so 
	    // this will be more effective than firing one event per class 
	    // token.
	    fireClassTokenInvalidatedEvent(list.toArray(
		    new ClassToken[list.size()]));
	}
    }

    private static void fireClassTokenInvalidatedEvent(ClassToken[] tokens) {
	ClassTokenInvalidatedEvent event = new ClassTokenInvalidatedEvent(tokens);
	synchronized(listeners) {
	    for (Iterator<WeakReference<ClassTokenListener>> iterator = 
		listeners.iterator(); iterator.hasNext();) {
		WeakReference<ClassTokenListener> listenerRef = iterator.next();
		ClassTokenListener listener = listenerRef.get();
		if(listener == null) {
		    iterator.remove();
		}
		else {
		    listener.classTokenInvalidated(event);
		}
	    }
	}
    }
    
    private static class WeakClassRef extends WeakReference<Class<?>> {
	private final ClassTokenKey classTokenKey;
	private final ClassToken classToken;
	WeakClassRef(ClassTokenKey classTokenKey, ClassToken classToken, Class<?> clazz) {
	    super(clazz, queue);
	    this.classTokenKey = classTokenKey;
	    this.classToken = classToken;
	}
    }
}