package edu.vandy.common;

import java.util.HashMap;
import java.util.Map;

import android.util.Log;

/**
 * Defines a generic singleton class whose instance() method ensures
 * only one object of type Class<T> is created.  We need this class
 * since Java Generic don't support proper singletons (a la the
 * "Gang-of-Four" book).  More information about this approach appears
 * at http://neutrofoton.com/generic-singleton-pattern-in-java.
 */
public class GenericSingleton {
    /**
     * Debugging tag used by the Android logger.
     */
    protected final static String TAG =
        GenericSingleton.class.getCanonicalName();

    /**
     * The singleton field.
     */
    private static final GenericSingleton sInstance =
        new GenericSingleton();
	
    /**
     * This HashMap ensures only one object of type Class<T> is
     * created.
     */
    @SuppressWarnings("rawtypes")
    private Map<Class, Object> mMap =
        new HashMap<>();
	
    /**
     * Return the one and only instance of Class<T>, which is created
     * on-demand if it doesn't exist.
     */
    @SuppressWarnings("unchecked")
    public static <T> T instance(Class<T> classOf) { 
        // Ensure thread-safety.
        synchronized(sInstance) {
            // Try to get the one and only instance of Class<T> that's
            // stored in the map.
            T t = (T) sInstance.mMap.get(classOf);          

            // Check to see if this is the first time a request for an
            // instance of Class<T> has been occurred.
            if (t == null) {
                try {
                    // If this is the first time in then create a new
                    // instance of Class<T>.
                    t = classOf.newInstance();
                } catch (Exception e) {
                    Log.d(TAG,
                          "GenericSingleton.instance() "
                          + e);
                    t = null;
                }
                // Store the new instance of Class<T> in the map so
                // it'll be available next time instance() is called.
                sInstance.mMap.put(classOf, t);
            }
            // Return the one and only instance of Class<T>.
            return t;
        }
    } 

    /**
     * If @a classOf is in the singleton map then set it to null so
     * it's cleaned up properly by the garbage collector.
     *
     * @return True if @a classOf is found/removed, else false.
     */
    public static <T> boolean remove(Class<T> classOf) {
        synchronized(sInstance) {
            // Try to get the one and only instance of Class<T> that's
            // stored in the map and if it's found, set it to null so
            // it will be garbage collected.
            if (sInstance.mMap.get(classOf) != null) {
                sInstance.mMap.put(classOf, 
                                   null);
                return true;
            } else
                return false;
        }
    }

    /**
     * Disallow instantiation.
     */
    private GenericSingleton() {}
	
    /**
     * Disallow cloning.
     */
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    } 
}
