package org.skife.release;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

public class ReleaseManager
{
    private static final ReleaseManager instance = new ReleaseManager();
    private HashMap listeners = new HashMap();
    private ReferenceQueue queue = new ReferenceQueue();
    private HashMap refToReal = new HashMap();

    public static ReleaseManager instance()
    {
        return instance;
    }

    private ReleaseManager()
    {
        Thread thread = new Thread(new Runnable()
        {
            public void run()
            {
                while (true)
                {
                    Reference ref = queue.poll();
                    while (ref != null)
                    {
                        Object real = refToReal.get(ref);
                        release(real);
                        ref = queue.poll();
                    }
                    Thread.yield();
                }
            }
        });
        thread.start();
    }

    public void addListener(Class type, ReleaseListener listener)
    {
        ArrayList list = (ArrayList) listeners.get(type);
        if (list == null)
        {
            list = new ArrayList();
            listeners.put(type, list);
        }
        list.add(listener);
    }

    public Object register(Object real)
    {
        MyProxyHandler handler = new MyProxyHandler(real);
        Object proxy = Proxy.newProxyInstance(real.getClass().getClassLoader(),
                                              real.getClass().getInterfaces(),
                                              handler);
        Reference ref = new WeakReference(proxy, queue);
        refToReal.put(ref, real);
        return proxy;
    }

    private void release(Object real)
    {
        synchronized (listeners)
        {
            for (Iterator iterator = listeners.keySet().iterator(); iterator.hasNext();)
            {
                Class clz = (Class) iterator.next();
                if (clz.isAssignableFrom(real.getClass()))
                {
                    List list = (List) listeners.get(clz);
                    for (Iterator iterator1 = list.iterator(); iterator1.hasNext();)
                    {
                        ReleaseListener listener = (ReleaseListener) iterator1.next();
                        listener.released(real);
                    }
                }
            }
        }
    }


    private class MyProxyHandler implements InvocationHandler
    {
        private Object real;

        public MyProxyHandler(Object real)
        {
            this.real = real;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            return method.invoke(real, args);
        }
    }
}

