/*******************************************************************************
 * Copyright (c) 2009 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributor:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.hibernate.console;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;
import java.util.jar.JarFile;

import org.hibernate.util.xpl.ReflectHelper;

/**
 * Workaround for jdk disgrace with open jar files & native libs,
 * which is a reason of locked, "undelete" files.
 * 
 * @author Vitali Yemialyanchyk
 */
public class ConsoleConfigClassLoader extends URLClassLoader {

	protected HashSet<String> setJarFileNames2Close = new HashSet<String>();

	public ConsoleConfigClassLoader(URL[] urls, ClassLoader parent) {
		super(urls, parent);
	}

	public void close() {
		setJarFileNames2Close.clear();
		closeClassLoader(this);
		finalizeNativeLibs(this);
		cleanupJarFileFactory();
	}

	/**
	 * cleanup jar file factory cache
	 */
	@SuppressWarnings({ "nls", "unchecked" })
	public boolean cleanupJarFileFactory()
	{
		boolean res = false;
		Class classJarURLConnection = null;
		try {
			classJarURLConnection = ReflectHelper.classForName("sun.net.www.protocol.jar.JarURLConnection");
		} catch (ClassNotFoundException e) {
			//ignore
		}
		if (classJarURLConnection == null) {
			return res;
		}
		Field f = null;
		try {
			f = classJarURLConnection.getDeclaredField("factory");
		} catch (NoSuchFieldException e) {
			//ignore
		}
		if (f == null) {
			return res;
		}
		f.setAccessible(true);
		Object obj = null;
		try {
			obj = f.get(null);
		} catch (IllegalAccessException e) {
			//ignore
		}
		if (obj == null) {
			return res;
		}
		Class classJarFileFactory = obj.getClass();
		//
		HashMap fileCache = null;
		try {
			f = classJarFileFactory.getDeclaredField("fileCache");
			f.setAccessible(true);
			obj = f.get(null);
			if (obj instanceof HashMap) {
				fileCache = (HashMap)obj;
			}
		} catch (NoSuchFieldException e) {
		} catch (IllegalAccessException e) {
			//ignore
		}
		HashMap urlCache = null;
		try {
			f = classJarFileFactory.getDeclaredField("urlCache");
			f.setAccessible(true);
			obj = f.get(null);
			if (obj instanceof HashMap) {
				urlCache = (HashMap)obj;
			}
		} catch (NoSuchFieldException e) {
		} catch (IllegalAccessException e) {
			//ignore
		}
		if (urlCache != null) {
			HashMap urlCacheTmp = (HashMap)urlCache.clone();
			Iterator it = urlCacheTmp.keySet().iterator();
			while (it.hasNext()) {
				obj = it.next();
				if (!(obj instanceof JarFile)) {
					continue;
				}
				JarFile jarFile = (JarFile)obj;
				if (setJarFileNames2Close.contains(jarFile.getName())) {
					try {
						jarFile.close();
					} catch (IOException e) {
						//ignore
					}
					if (fileCache != null) {
						fileCache.remove(urlCache.get(jarFile));
					}
					urlCache.remove(jarFile);
				}
			}
			res = true;
		} else if (fileCache != null) {
			// urlCache := null
			HashMap fileCacheTmp = (HashMap)fileCache.clone();
			Iterator it = fileCacheTmp.keySet().iterator();
			while (it.hasNext()) {
				Object key = it.next();
				obj = fileCache.get(key);
				if (!(obj instanceof JarFile)) {
					continue;
				}
				JarFile jarFile = (JarFile)obj;
				if (setJarFileNames2Close.contains(jarFile.getName())) {
					try {
						jarFile.close();
					} catch (IOException e) {
						//ignore
					}
					fileCache.remove(key);
				}
			}
			res = true;
		}
		setJarFileNames2Close.clear();
		return res;
	}

	/**
	 * close jar files of cl
	 * @param cl
	 * @return
	 */
	@SuppressWarnings( { "nls", "unchecked" })
	public boolean closeClassLoader(ClassLoader cl) {
		boolean res = false;
		if (cl == null) {
			return res;
		}
		Class classURLClassLoader = URLClassLoader.class;
		Field f = null;
		try {
			f = classURLClassLoader.getDeclaredField("ucp");
		} catch (NoSuchFieldException e1) {
			//ignore
		}
		if (f != null) {
			f.setAccessible(true);
			Object obj = null;
			try {
				obj = f.get(cl);
			} catch (IllegalAccessException e1) {
				//ignore
			}
			if (obj != null) {
				final Object ucp = obj;
				f = null;
				try {
					f = ucp.getClass().getDeclaredField("loaders");
				} catch (NoSuchFieldException e1) {
					//ignore
				}
				if (f != null) {
					f.setAccessible(true);
					ArrayList loaders = null;
					try {
						loaders = (ArrayList) f.get(ucp);
						res = true;
					} catch (IllegalAccessException e1) {
						//ignore
					}
					for (int i = 0; loaders != null && i < loaders.size(); i++) {
						obj = loaders.get(i);
						f = null;
						try {
							f = obj.getClass().getDeclaredField("jar");
						} catch (NoSuchFieldException e) {
							//ignore
						}
						if (f != null) {
							f.setAccessible(true);
							try {
								obj = f.get(obj);
							} catch (IllegalAccessException e1) {
								// ignore
							}
							if (obj instanceof JarFile) {
								final JarFile jarFile = (JarFile)obj;
								setJarFileNames2Close.add(jarFile.getName());
								//try {
								//	jarFile.getManifest().clear();
								//} catch (IOException e) {
								//	// ignore
								//}
								try {
									jarFile.close();
								} catch (IOException e) {
									// ignore
								}
							}
						}
					}
				}
			}
		}
		return res;
	}
	
	/**
	 * finalize native libraries
	 * @param cl
	 * @return
	 */
	@SuppressWarnings({ "nls", "unchecked" })
	public boolean finalizeNativeLibs(ClassLoader cl) {
		boolean res = false;
		Class classClassLoader = ClassLoader.class;
		java.lang.reflect.Field nativeLibraries = null;
		try {
			nativeLibraries = classClassLoader.getDeclaredField("nativeLibraries");
		} catch (NoSuchFieldException e1) {
			//ignore
		}
		if (nativeLibraries == null) {
			return res;
		}
		nativeLibraries.setAccessible(true);
		Object obj = null;
		try {
			obj = nativeLibraries.get(cl);
		} catch (IllegalAccessException e1) {
			//ignore
		}
		if (!(obj instanceof Vector)) {
			return res;
		}
		res = true;
		Vector java_lang_ClassLoader_NativeLibrary = (Vector)obj;
		for (Object lib : java_lang_ClassLoader_NativeLibrary) {
			java.lang.reflect.Method finalize = null;
			try {
				finalize = lib.getClass().getDeclaredMethod("finalize", new Class[0]);
			} catch (NoSuchMethodException e) {
				//ignore
			}
			if (finalize != null) {
				finalize.setAccessible(true);
				try {
					finalize.invoke(lib, new Object[0]);
				} catch (IllegalAccessException e) {
				} catch (InvocationTargetException e) {
					//ignore
				}
			}
		}
		return res;
	}
}
