Index: core/src/org/netbeans/core/modules/JarClassLoader.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/modules/JarClassLoader.java,v retrieving revision 1.6 diff -u -r1.6 JarClassLoader.java --- core/src/org/netbeans/core/modules/JarClassLoader.java 18 Feb 2002 13:43:51 -0000 1.6 +++ core/src/org/netbeans/core/modules/JarClassLoader.java 4 Mar 2002 18:11:15 -0000 @@ -23,10 +23,9 @@ import java.net.MalformedURLException; import java.security.*; import java.security.cert.Certificate; -import java.util.Enumeration; -import java.util.Vector; -import java.util.List; -import java.util.Iterator; +import java.util.*; + +import org.openide.ErrorManager; /** * A ProxyClassLoader capable of loading classes from a set of jar files @@ -39,6 +38,8 @@ private URL[] urls; private ProtectionDomain[] pds; private int count; + /** temp copy JARs which ought to be deleted */ + private Set deadJars = null; // Set /** Creates new JarClassLoader */ public JarClassLoader (List files, ClassLoader[] parents) { @@ -245,5 +246,76 @@ return v.elements(); } + /** Try to release any JAR locks held by this classloader. + * @see #21114 + */ + void releaseLocks() { + if (deadJars != null) throw new IllegalStateException(); + deadJars = new HashSet(); // Set + try { + for (int i = 0; i < sources.length; i++) { + if (sources[i] instanceof JarFile) { + JarFile origJar = (JarFile)sources[i]; + File orig = new File(origJar.getName()); + String name = orig.getName(); + String prefix, suffix; + int idx = name.lastIndexOf('.'); + if (idx == -1) { + prefix = name; + suffix = null; + } else { + prefix = name.substring(0, idx); + suffix = name.substring(idx); + } + while (prefix.length() < 3) prefix += "x"; // NOI18N + File temp = File.createTempFile(prefix, suffix); + temp.deleteOnExit(); + InputStream is = new FileInputStream(orig); + try { + OutputStream os = new FileOutputStream(temp); + try { + byte[] buf = new byte[4096]; + int j; + while ((j = is.read(buf)) != -1) { + os.write(buf, 0, j); + } + } finally { + os.close(); + } + } finally { + is.close(); + } + JarFile tempJar = new JarFile(temp); + origJar.close(); + deadJars.add(tempJar); + sources[i] = tempJar; + Util.err.log("#21114: replacing " + orig + " with " + temp); + } + } + } catch (IOException ioe) { + Util.err.notify(ErrorManager.INFORMATIONAL, ioe); + } + } + + /** Delete any temporary JARs we were holding on to. + * Also close any other JARs in our list. + */ + protected void finalize() throws Throwable { + super.finalize(); + if (deadJars != null) { + Iterator it = deadJars.iterator(); + while (it.hasNext()) { + JarFile jar = (JarFile)it.next(); + jar.close(); + new File(jar.getName()).delete(); + Util.err.log("#21114: closing and deleting temporary " + jar.getName()); + } + } + for (int i = 0; i < sources.length; i++) { + if (sources[i] instanceof JarFile) { + ((JarFile)sources[i]).close(); + } + } + } } Index: core/src/org/netbeans/core/modules/Module.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/modules/Module.java,v retrieving revision 1.22 diff -u -r1.22 Module.java --- core/src/org/netbeans/core/modules/Module.java 1 Mar 2002 11:00:16 -0000 1.22 +++ core/src/org/netbeans/core/modules/Module.java 4 Mar 2002 18:11:17 -0000 @@ -29,6 +29,7 @@ import java.util.zip.ZipEntry; import org.openide.modules.SpecificationVersion; import org.openide.modules.Dependency; +import org.openide.util.WeakSet; /** Object representing one module, possibly installed. * Responsible for opening of module JAR file; reading @@ -84,6 +85,8 @@ private SpecificationVersion specVers; /** currently active module classloader */ private ClassLoader classloader = null; + /** module classloaders that might have been created before */ + private final Set oldClassLoaders = new WeakSet(5); // Set /** localized properties, only non-null if requested from disabled module */ private Properties localizedProps; @@ -772,6 +775,7 @@ Util.err.annotate(ioe, iae); throw ioe; } + oldClassLoaders.add(classloader); } /** Turn off the classloader and release all resources. */ void classLoaderDown() { @@ -796,6 +800,17 @@ Util.err.log ("All resources associated with module " + jar + " were successfully released."); } destroyPhysicalJar(); + } + + /** Notify the module that it is being deleted. */ + void destroy() { + // #21114: try to release all JAR locks + Iterator it = oldClassLoaders.iterator(); + while (it.hasNext()) { + OneModuleClassLoader l = (OneModuleClassLoader)it.next(); + if (!Boolean.getBoolean("no21114"))//XXX + l.releaseLocks(); + } } /** Get the JAR manifest. Index: core/src/org/netbeans/core/modules/ModuleManager.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/modules/ModuleManager.java,v retrieving revision 1.24 diff -u -r1.24 ModuleManager.java --- core/src/org/netbeans/core/modules/ModuleManager.java 1 Mar 2002 10:56:54 -0000 1.24 +++ core/src/org/netbeans/core/modules/ModuleManager.java 4 Mar 2002 18:11:19 -0000 @@ -384,6 +384,7 @@ firer.change(new ChangeFirer.Change(m, Module.PROP_VALID, Boolean.TRUE, Boolean.FALSE)); // #14561: some other module might now be uninstallable as a result. clearProblemCache(); + m.destroy(); } private void possibleProviderRemoved(Module m) { String[] provides = m.getProvides(); Index: core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java =================================================================== RCS file: /cvs/core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java,v retrieving revision 1.14 diff -u -r1.14 ModuleManagerTest.java --- core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java 1 Mar 2002 11:00:16 -0000 1.14 +++ core/test/unit/src/org/netbeans/core/modules/ModuleManagerTest.java 4 Mar 2002 18:11:20 -0000 @@ -21,8 +21,7 @@ import java.lang.reflect.*; import org.openide.util.*; import org.openide.modules.*; -import java.net.URLClassLoader; -import java.net.URL; +import java.net.*; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -51,6 +50,11 @@ public static void main(String[] args) { // Turn on verbose logging while developing tests: System.setProperty("org.netbeans.core.modules", "0"); + // In case run standalone, need a work dir. + if (System.getProperty("nbjunit.workdir") == null) { + // Hope java.io.tmpdir is set... + System.setProperty("nbjunit.workdir", System.getProperty("java.io.tmpdir")); + } TestRunner.run(new NbTestSuite(ModuleManagerTest.class)); } @@ -1195,6 +1199,37 @@ // 8 - too late // 9 - must give some rel vers, else ~ -1 assertEquals(ok, new HashSet(mgr.simulateEnable(all))); + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + } + + /** Test #21114: after deleting a module, its JARs are released. + * Would probably always pass on Unix, but on Windows it matters. + */ + public void testModuleDeletion() throws Exception { + FakeModuleInstaller installer = new FakeModuleInstaller(); + FakeEvents ev = new FakeEvents(); + ModuleManager mgr = new ModuleManager(installer, ev); + mgr.mutexPrivileged().enterWriteAccess(); + try { + File jar = new File(getWorkDir(), "copy-of-simple-module.jar"); + copy(new File(jars, "simple-module.jar"), jar); + Module m = mgr.create(jar, null, false, false, false); + mgr.enable(m); + Class c = m.getClassLoader().loadClass("org.foo.Something"); + URL u = m.getClassLoader().getResource("org/foo/Something.class"); + URLConnection uc = u.openConnection(); + assertTrue("using JarURLConnection", uc instanceof JarURLConnection); + uc.connect(); + mgr.disable(m); + mgr.delete(m); + assertTrue("could delete JAR file", jar.delete()); + c = null; + u = null; + uc = null; + System.gc(); + System.runFinalization(); } finally { mgr.mutexPrivileged().exitWriteAccess(); } Index: xtest/src/org/netbeans/core/modules/Module.java =================================================================== RCS file: /cvs/xtest/src/org/netbeans/core/modules/Module.java,v retrieving revision 1.5 diff -u -r1.5 Module.java --- xtest/src/org/netbeans/core/modules/Module.java 13 Feb 2002 13:39:35 -0000 1.5 +++ xtest/src/org/netbeans/core/modules/Module.java 4 Mar 2002 18:11:22 -0000 @@ -810,6 +810,7 @@ } /** For compat only! */ void cleanup() {} + void destroy() {} /** Get the JAR manifest. * Should never be null, even if disabled.