/***************************************************************************
 * Copyright 2014 Kieker Project (http://kieker-monitoring.net)
 *
 * 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 kieker.webgui.persistence.impl.utility;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.jar.JarFile;

/**
 * This is a class loader which enriches the {@link URLClassLoader} with a close-method. The close method is implemented using a hack, which will probably only work
 * for a Sun VM.
 * 
 * @author Nils Christian Ehmke
 */
public final class CloseableURLClassLoader extends URLClassLoader implements Closeable {

	private boolean closed;

	/**
	 * Creates a new class loader with the given URLs and without a parent class loader.
	 * 
	 * @param urls
	 *            The URLs to be used by the class loader.
	 */
	public CloseableURLClassLoader(final URL[] urls) {
		this(urls, null);
	}

	/**
	 * Creates a new class loader with the given URLs and the given parent class loader.
	 * 
	 * @param urls
	 *            The URLs to be used by the class loader.
	 * @param parent
	 *            The parent of this class loader.
	 */
	public CloseableURLClassLoader(final URL[] urls, final ClassLoader parent) {
		super(urls, parent);
	}

	@Override
	public synchronized void close() throws IOException {
		// Make sure that the class loader can only be closed once.
		if (!this.closed) {
			this.closed = true;

			try {
				final Field ucp = URLClassLoader.class.getDeclaredField("ucp");
				ucp.setAccessible(true);

				final Object sunMiscURLClassPath = ucp.get(this);
				final Field loaders = sunMiscURLClassPath.getClass().getDeclaredField("loaders");
				loaders.setAccessible(true);

				// Run through all available loaders and try to close them
				final Object javaUtilCollection = loaders.get(sunMiscURLClassPath);
				for (final Object sunMiscURLClassPathJarLoader : ((Collection<?>) javaUtilCollection).toArray()) {
					try {
						final Field loader = sunMiscURLClassPathJarLoader.getClass().getDeclaredField("jar");
						loader.setAccessible(true);

						final Object javaUtilJarJarFile = loader.get(sunMiscURLClassPathJarLoader);
						((JarFile) javaUtilJarJarFile).close();
					} catch (final Throwable t) { // NOCS, NOPMD (Catch of Throwable)
						// If we got this far, this is probably not a JAR loader so skip it
					}
				}
			} catch (final Throwable ex) { // NOCS, NOPMD (Catch of Throwable)
				// We work probably not on a SUN VM
				throw new IOException("Could not close class loader", ex);
			}
		}
	}

}
