/******************************************************************************* * Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://robocode.sourceforge.net/license/epl-v10.html * * Contributors: * Pavel Savara * - Initial implementation * Flemming N. Larsen * - The closeJarURLConnection(URL) method was made public * - Extended the gc() method to always remove temporary jar_cache entries *******************************************************************************/ package net.sf.robocode.io; import java.net.URLConnection; import java.net.URL; import java.net.URLDecoder; import java.lang.reflect.Field; import java.util.*; import java.util.jar.JarFile; import java.io.File; import java.io.IOException; /** * This ugly class is helping with closing of robot .jar files when used with URL, URLConnection and useCaches=true * It is designed to close JarFiles opened and cached in SUN's JarFileFactory. * If we are not on SUN's JVM, we fallback to useCaches=false, to not lock the files. * Collection is now called after reposiotry refresh and after battle ended. * Collection is disabled/posponed during running battle. * * @author Pavel Savara (original) * @author Flemming N. Larsen (contributor) */ public class URLJarCollector { static Object factory; static HashMap<?, ?> fileCache; static HashMap<?, ?> urlCache; static Field jarFileURL; static boolean sunJVM; static boolean enabled; static Set<URL> urlsToClean = new HashSet<URL>(); static { try { final Class<?> jarConn = ClassLoader.getSystemClassLoader().loadClass( "sun.net.www.protocol.jar.JarURLConnection"); final Field factoryF = jarConn.getDeclaredField("factory"); factoryF.setAccessible(true); factory = factoryF.get(null); final Class<?> jarFactory = ClassLoader.getSystemClassLoader().loadClass( "sun.net.www.protocol.jar.JarFileFactory"); final Field fileCacheF = jarFactory.getDeclaredField("fileCache"); fileCacheF.setAccessible(true); fileCache = (HashMap<?, ?>) fileCacheF.get(null); final Field urlCacheF = jarFactory.getDeclaredField("urlCache"); urlCacheF.setAccessible(true); urlCache = (HashMap<?, ?>) urlCacheF.get(null); final Class<?> jarURLConnection = ClassLoader.getSystemClassLoader().loadClass( "sun.net.www.protocol.jar.JarURLConnection"); jarFileURL = jarURLConnection.getDeclaredField("jarFileURL"); jarFileURL.setAccessible(true); sunJVM = true; } catch (ClassNotFoundException ignore) { Logger.logError(ignore); } catch (NoSuchFieldException ignore) { Logger.logError(ignore); } catch (IllegalAccessException ignore) { Logger.logError(ignore); } } public static synchronized URLConnection openConnection(URL url) throws IOException { // Logger.logMessage("Open connection to URL: " + url); final URLConnection urlConnection = url.openConnection(); if (sunJVM) { registerConnection(urlConnection); urlConnection.setUseCaches(true); } else { urlConnection.setUseCaches(false); } return urlConnection; } public static synchronized void enableGc(boolean enabled) { URLJarCollector.enabled = enabled; } public static synchronized void gc() { if (sunJVM) { // Close all JarURLConnections if garbage collection is enabled if (enabled) { for (URL url : urlsToClean) { closeJarURLConnection(url); } urlsToClean.clear(); } // Bug fix [2867326] - Lockup on start if too many bots in robots dir (cont'd). // Remove all cache entries to temporary jar cache files created // for connections using the jarjar protocol that get stuck up. for (Iterator<?> it = fileCache.keySet().iterator(); it.hasNext();) { Object urlJarFile = it.next(); final JarFile jarFile = (JarFile) fileCache.get(urlJarFile); String filename = jarFile.getName(); filename = filename.substring(filename.lastIndexOf(File.separatorChar) + 1).toLowerCase(); if (filename.startsWith("jar_cache")) { it.remove(); urlCache.remove(jarFile); } } } } private static void registerConnection(URLConnection conn) { if (conn != null) { final String cl = conn.getClass().getName(); if (cl.equals("sun.net.www.protocol.jar.JarURLConnection")) { try { final URL url = (URL) jarFileURL.get(conn); if (!urlsToClean.contains(url)) { urlsToClean.add(url); } } catch (IllegalAccessException ignore) {} } } } // Added due to bug fix [2867326] - Lockup on start if too many bots in robots dir (cont'd). public synchronized static void closeJarURLConnection(URL url) { if (url != null) { for (Iterator<?> it = fileCache.keySet().iterator(); it.hasNext();) { Object urlJarFile = it.next(); final JarFile jarFile = (JarFile) fileCache.get(urlJarFile); String urlPath = url.getPath(); try { urlPath = URLDecoder.decode(urlPath, "UTF-8"); } catch (java.io.UnsupportedEncodingException ignore) {} File urlFile = new File(urlPath); String jarFileName = jarFile.getName(); String urlFileName = urlFile.getPath(); if (urlFileName.equals(jarFileName)) { it.remove(); urlCache.remove(jarFile); try { jarFile.close(); } catch (IOException e) { Logger.logError(e); } } } } } public static void dumpSunFileCache() { if (sunJVM) { Logger.logMessage("Dumping fileCache..."); for (Object url : fileCache.keySet()) { final JarFile jarFile = (JarFile) fileCache.get(url); Logger.logMessage("fileCache dump: url=" + url + ", jarFile.getName()=" + jarFile.getName()); } Logger.logMessage("fileCache size: " + fileCache.size()); } } public static void dumpSunUrlCache() { if (sunJVM) { Logger.logMessage("Dumping urlCache..."); for (Object urlJarFile : urlCache.keySet()) { final URL url = (URL) urlCache.get(urlJarFile); final JarFile jarFile = (JarFile) urlJarFile; Logger.logMessage("urlCache dump: url=" + url + ", jarFile.getName()=" + jarFile.getName()); } Logger.logMessage("urlCache size: " + urlCache.size()); } } }