/*
 * Written by Dawid Kurzyniec and released to the public domain, as explained
 * at http://creativecommons.org/licenses/publicdomain
 */

package edu.emory.mathcs.util.gc;

import java.lang.ref.*;

import edu.emory.mathcs.backport.java.util.concurrent.*;
import java.util.logging.*;

public class ReferenceCleaner {

    private static final Logger logger =
        Logger.getLogger("edu.emory.mathcs.util.gc.ReferenceCleaner");

    private static final String logID = "ReferenceCleaner: ";

    private final ReferenceQueue queue;
    private final Executor engineExecutor;

    CleanupTask currentCleaner;

    public ReferenceCleaner() {
        this(Executors.privilegedThreadFactory());
    }

    public ReferenceCleaner(ThreadFactory engineThreadFactory) {
        this(new ReferenceQueue(), engineThreadFactory);
    }

    public ReferenceCleaner(Executor engineExecutor) {
        this(new ReferenceQueue(), engineExecutor);
    }

    public ReferenceCleaner(ReferenceQueue queue, ThreadFactory engineThreadFactory) {
        this(queue, createSingleTaskExecutor(engineThreadFactory));
    }

    public ReferenceCleaner(ReferenceQueue queue, Executor engineExecutor) {
        this.queue = queue;
        this.engineExecutor = engineExecutor;
    }

    public ReferenceQueue getQueue() {
        return queue;
    }

    public void cleanNow() {
        while (true) {
            Reference ref = queue.poll();
            if (ref == null) break;
            if (ref instanceof CleanableReference) {
                ((CleanableReference)ref).cleanReference();
            }
        }
    }

    public void start() {
        CleanupTask newCleaner;
        synchronized (this) {
            if (currentCleaner != null) throw new IllegalStateException();
            currentCleaner = newCleaner = new CleanupTask();
        }
        if (logger.isLoggable(Level.FINE)) {
            logger.fine(logID + "starting cleaner");
        }
        engineExecutor.execute(newCleaner);
    }

    public void stop() {
        synchronized (this) {
            if (this.currentCleaner != null) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine(logID + "stopping cleaner");
                }
                this.currentCleaner.stop();
                this.currentCleaner = null;
            }
        }
    }

    private class CleanupTask implements Runnable {
        volatile boolean stopped;
        Thread runner;
        synchronized void stop() {
            stopped = true;
            if (runner != null) runner.interrupt();
        }
        public void run() {
            synchronized (this) {
                if (stopped) return;
                this.runner = Thread.currentThread();
            }
            runLoop(this);
        };
    }

    private void runLoop(CleanupTask task) {
        try {
            if (logger.isLoggable(Level.FINER)) {
                logger.finer(logID + "cleaner started");
            }
            while (!task.stopped) {
                Reference ref = queue.remove();
                if (logger.isLoggable(Level.FINER)) {
                    logger.finer(logID + "dequeued reference: " + ref);
                }
                if (ref instanceof CleanableReference) {
                    ((CleanableReference)ref).cleanReference();
                }
            }
        }
        catch (InterruptedException e) {
            if (!task.stopped) {
                e.printStackTrace();
            }
            else {
            }
        }
        if (logger.isLoggable(Level.FINER)) {
            logger.finer(logID + "cleaner stopped");
        }
    }

    private static Executor createSingleTaskExecutor(ThreadFactory threadFactory) {
        ThreadPoolExecutor executor =
            new ThreadPoolExecutor(1, 1, 1, TimeUnit.NANOSECONDS,
                                   new LinkedBlockingQueue(),
                                   threadFactory);
        executor.allowCoreThreadTimeOut(true);
        return executor;
    }

    public static interface CleanableReference {
        public void cleanReference();
    }
}
