/*
 * Copyright (C) 2007 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.base;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.common.annotations.VisibleForTesting;

/**
 * A reference queue with an associated background thread that dequeues
 * references and invokes {@link FinalizableReference#finalizeReferent()} on
 * them.
 *
 * <p>
 * Keep a strong reference to this object until all of the associated referents
 * have been finalized. If this object is garbage collected earlier, the backing
 * thread will not invoke {@code
 * finalizeReferent()} on the remaining references.
 *
 * <p>
 * As an example of how this is used, imagine you have a class {@code MyServer}
 * that creates a a {@link java.net.ServerSocket ServerSocket}, and you would
 * like to ensure that the {@code ServerSocket} is closed even if the
 * {@code MyServer} object is garbage-collected without calling its
 * {@code close} method. You <em>could</em> use a finalizer to accomplish this,
 * but that has a number of well-known problems. Here is how you might use this
 * class instead:
 *
 * <pre>
 * public class MyServer implements Closeable {
 *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
 *   // You might also share this between several objects.
 *
 *   private static final Set&lt;Reference&lt;?>> references = Sets.newConcurrentHashSet();
 *   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
 *
 *   private final ServerSocket serverSocket;
 *
 *   private MyServer(...) {
 *     ...
 *     this.serverSocket = new ServerSocket(...);
 *     ...
 *   }
 *
 *   public static MyServer create(...) {
 *     MyServer myServer = new MyServer(...);
 *     final ServerSocket serverSocket = myServer.serverSocket;
 *     Reference&lt;?> reference = new FinalizablePhantomReference&lt;MyServer>(myServer, frq) {
 *       &#64;Override public void finalizeReferent() {
 *         references.remove(this):
 *         if (!serverSocket.isClosed()) {
 *           ...log a message about how nobody called close()...
 *           try {
 *             serverSocket.close();
 *           } catch (IOException e) {
 *             ...
 *           }
 *         }
 *       }
 *     };
 *     references.add(reference);
 *     return myServer;
 *   }
 *
 *   &#64;Override public void close() {
 *     serverSocket.close();
 *   }
 * }
 * </pre>
 *
 * @author Bob Lee
 * @since 2.0 (imported from Google Collections Library)
 */
public class FinalizableReferenceQueue implements Closeable {
	/*
	 * The Finalizer thread keeps a phantom reference to this object. When the
	 * client (for example, a map built by MapMaker) no longer has a strong
	 * reference to this object, the garbage collector will reclaim it and enqueue
	 * the phantom reference. The enqueued reference will trigger the Finalizer to
	 * stop.
	 *
	 * If this library is loaded in the system class loader,
	 * FinalizableReferenceQueue can load Finalizer directly with no problems.
	 *
	 * If this library is loaded in an application class loader, it's important that
	 * Finalizer not have a strong reference back to the class loader. Otherwise,
	 * you could have a graph like this:
	 *
	 * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application
	 * class loader which loaded -> ReferenceMap.class which has a static ->
	 * FinalizableReferenceQueue instance
	 *
	 * Even if no other references to classes from the application class loader
	 * remain, the Finalizer thread keeps an indirect strong reference to the queue
	 * in ReferenceMap, which keeps the Finalizer running, and as a result, the
	 * application class loader can never be reclaimed.
	 *
	 * This means that dynamically loaded web applications and OSGi bundles can't be
	 * unloaded.
	 *
	 * If the library is loaded in an application class loader, we try to break the
	 * cycle by loading Finalizer in its own independent class loader:
	 *
	 * System class loader -> Application class loader -> ReferenceMap ->
	 * FinalizableReferenceQueue -> etc. -> Decoupled class loader -> Finalizer
	 *
	 * Now, Finalizer no longer keeps an indirect strong reference to the static
	 * FinalizableReferenceQueue field in ReferenceMap. The application class loader
	 * can be reclaimed at which point the Finalizer thread will stop and its
	 * decoupled class loader can also be reclaimed.
	 *
	 * If any of this fails along the way, we fall back to loading Finalizer
	 * directly in the application class loader.
	 */

	/**
	 * Try to load Finalizer in its own class loader. If Finalizer's thread had a
	 * direct reference to our class loader (which could be that of a dynamically
	 * loaded web application or OSGi bundle), it would prevent our class loader
	 * from getting garbage collected.
	 */
	static class DecoupledLoader implements FinalizerLoader {
		private static final String LOADING_ERROR = "Could not load Finalizer in its own class loader."
				+ "Loading Finalizer in the current class loader instead. As a result, you will not be able"
				+ "to garbage collect this class loader. To support reclaiming this class loader, either"
				+ "resolve the underlying issue, or move Google Collections to your system class path.";

		/**
		 * Gets URL for base of path containing Finalizer.class.
		 */
		URL getBaseUrl() throws IOException {
			// Find URL pointing to Finalizer.class file.
			String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
			URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
			if (finalizerUrl == null) {
				throw new FileNotFoundException(finalizerPath);
			}

			// Find URL pointing to base of class path.
			String urlString = finalizerUrl.toString();
			if (!urlString.endsWith(finalizerPath)) {
				throw new IOException("Unsupported path style: " + urlString);
			}
			urlString = urlString.substring(0, urlString.length() - finalizerPath.length());
			return new URL(finalizerUrl, urlString);
		}

		@Override
		public Class<?> loadFinalizer() {
			try {
				/*
				 * We use URLClassLoader because it's the only concrete class loader
				 * implementation in the JDK. If we used our own ClassLoader subclass, Finalizer
				 * would indirectly reference this class loader:
				 *
				 * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class
				 * loader
				 *
				 * System class loader will (and must) be the parent.
				 */
				ClassLoader finalizerLoader = newLoader(getBaseUrl());
				return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
			} catch (Exception e) {
				logger.log(Level.WARNING, LOADING_ERROR, e);
				return null;
			}
		}

		/** Creates a class loader with the given base URL as its classpath. */
		URLClassLoader newLoader(URL base) {
			// We use the bootstrap class loader as the parent because Finalizer by design
			// uses
			// only standard Java classes. That also means that
			// FinalizableReferenceQueueTest
			// doesn't pick up the wrong version of the Finalizer class.
			return new URLClassLoader(new URL[] { base }, null);
		}
	}

	/**
	 * Loads Finalizer directly using the current class loader. We won't be able to
	 * garbage collect this class loader, but at least the world doesn't end.
	 */
	static class DirectLoader implements FinalizerLoader {
		@Override
		public Class<?> loadFinalizer() {
			try {
				return Class.forName(FINALIZER_CLASS_NAME);
			} catch (ClassNotFoundException e) {
				throw new AssertionError(e);
			}
		}
	}

	/**
	 * Loads Finalizer.class.
	 */
	interface FinalizerLoader {

		/**
		 * Returns Finalizer.class or null if this loader shouldn't or can't load it.
		 *
		 * @throws SecurityException if we don't have the appropriate privileges
		 */
		Class<?> loadFinalizer();
	}

	/**
	 * Tries to load Finalizer from the system class loader. If Finalizer is in the
	 * system class path, we needn't create a separate loader.
	 */
	static class SystemLoader implements FinalizerLoader {
		// This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to
		// disable
		// finding Finalizer on the system class path even if it is there.
		@VisibleForTesting
		static boolean disabled;

		@Override
		public Class<?> loadFinalizer() {
			if (disabled) {
				return null;
			}
			ClassLoader systemLoader;
			try {
				systemLoader = ClassLoader.getSystemClassLoader();
			} catch (SecurityException e) {
				logger.info("Not allowed to access system class loader.");
				return null;
			}
			if (systemLoader != null) {
				try {
					return systemLoader.loadClass(FINALIZER_CLASS_NAME);
				} catch (ClassNotFoundException e) {
					// Ignore. Finalizer is simply in a child class loader.
					return null;
				}
			} else {
				return null;
			}
		}
	}

	private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName());

	private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";

	/** Reference to Finalizer.startFinalizer(). */
	private static final Method startFinalizer;

	static {
		Class<?> finalizer = loadFinalizer(new SystemLoader(), new DecoupledLoader(), new DirectLoader());
		startFinalizer = getStartFinalizer(finalizer);
	}

	/**
	 * Looks up Finalizer.startFinalizer().
	 */
	static Method getStartFinalizer(Class<?> finalizer) {
		try {
			return finalizer.getMethod("startFinalizer", Class.class, ReferenceQueue.class, PhantomReference.class);
		} catch (NoSuchMethodException e) {
			throw new AssertionError(e);
		}
	}

	/**
	 * Iterates through the given loaders until it finds one that can load
	 * Finalizer.
	 *
	 * @return Finalizer.class
	 */
	private static Class<?> loadFinalizer(FinalizerLoader... loaders) {
		for (FinalizerLoader loader : loaders) {
			Class<?> finalizer = loader.loadFinalizer();
			if (finalizer != null) {
				return finalizer;
			}
		}

		throw new AssertionError();
	}

	/**
	 * The actual reference queue that our background thread will poll.
	 */
	final ReferenceQueue<Object> queue;

	final PhantomReference<Object> frqRef;

	/**
	 * Whether or not the background thread started successfully.
	 */
	final boolean threadStarted;

	/**
	 * Constructs a new queue.
	 */
	public FinalizableReferenceQueue() {
		// We could start the finalizer lazily, but I'd rather it blow up early.
		queue = new ReferenceQueue<Object>();
		frqRef = new PhantomReference<Object>(this, queue);
		boolean threadStarted = false;
		try {
			startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef);
			threadStarted = true;
		} catch (IllegalAccessException impossible) {
			throw new AssertionError(impossible); // startFinalizer() is public
		} catch (Throwable t) {
			logger.log(Level.INFO, "Failed to start reference finalizer thread."
					+ " Reference cleanup will only occur when new references are created.", t);
		}

		this.threadStarted = threadStarted;
	}

	/**
	 * Repeatedly dequeues references from the queue and invokes
	 * {@link FinalizableReference#finalizeReferent()} on them until the queue is
	 * empty. This method is a no-op if the background thread was created
	 * successfully.
	 */
	void cleanUp() {
		if (threadStarted) {
			return;
		}

		Reference<?> reference;
		while ((reference = queue.poll()) != null) {
			/*
			 * This is for the benefit of phantom references. Weak and soft references will
			 * have already been cleared by this point.
			 */
			reference.clear();
			try {
				((FinalizableReference) reference).finalizeReferent();
			} catch (Throwable t) {
				logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
			}
		}
	}

	@Override
	public void close() {
		frqRef.enqueue();
		cleanUp();
	}
}
