diff -r 381c94f289dc o.n.bootstrap/manifest.mf --- a/o.n.bootstrap/manifest.mf Thu Jul 09 16:58:04 2009 +0200 +++ b/o.n.bootstrap/manifest.mf Fri Jul 10 15:26:35 2009 +0200 @@ -1,4 +1,4 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.bootstrap/1 -OpenIDE-Module-Specification-Version: 2.19 +OpenIDE-Module-Specification-Version: 2.20 OpenIDE-Module-Localizing-Bundle: org/netbeans/Bundle.properties diff -r 381c94f289dc o.n.bootstrap/nbproject/project.xml --- a/o.n.bootstrap/nbproject/project.xml Thu Jul 09 16:58:04 2009 +0200 +++ b/o.n.bootstrap/nbproject/project.xml Fri Jul 10 15:26:35 2009 +0200 @@ -84,6 +84,7 @@ org.netbeans.core org.netbeans.core.execution + org.netbeans.core.netigso org.netbeans.core.startup org.netbeans.modules.autoupdate.services org.netbeans.modules.modulemanager diff -r 381c94f289dc o.n.bootstrap/src/org/netbeans/InvalidException.java --- a/o.n.bootstrap/src/org/netbeans/InvalidException.java Thu Jul 09 16:58:04 2009 +0200 +++ b/o.n.bootstrap/src/org/netbeans/InvalidException.java Fri Jul 10 15:26:35 2009 +0200 @@ -42,6 +42,7 @@ package org.netbeans; import java.io.IOException; +import java.util.jar.Manifest; /** Exception thrown indicating that a module's contents are ill-formed. * This could be a parse error in the manifest, or an inability to load @@ -53,16 +54,25 @@ public final class InvalidException extends IOException { private final Module m; + private final Manifest man; private String localizedMessage; public InvalidException(String detailMessage) { super(detailMessage); m = null; + man = null; } public InvalidException(Module m, String detailMessage) { super(m + ": " + detailMessage); // NOI18N this.m = m; + this.man = null; + } + + InvalidException(String msg, Manifest manifest) { + super(msg); + this.m = null; + this.man = manifest; } public InvalidException(Module m, String detailMessage, String localizedMessage) { @@ -78,6 +88,21 @@ return m; } + /** The manifest that caused this exception. Can be null, if the + * manifest cannot be obtained. + * @return manifest that contains error + * @since 2.20 + */ + public Manifest getManifest() { + if (man != null) { + return man; + } + if (m != null) { + return m.getManifest(); + } + return null; + } + @Override public String getLocalizedMessage() { if (localizedMessage != null) { diff -r 381c94f289dc o.n.bootstrap/src/org/netbeans/Module.java --- a/o.n.bootstrap/src/org/netbeans/Module.java Thu Jul 09 16:58:04 2009 +0200 +++ b/o.n.bootstrap/src/org/netbeans/Module.java Fri Jul 10 15:26:35 2009 +0200 @@ -236,12 +236,10 @@ } public Set getDependencies() { - return new HashSet(Arrays.asList(dependenciesA)); + return new HashSet(Arrays.asList(getDependenciesArray())); } - // Faster to loop over: - // @since JST-PENDING called from NbInstaller public final Dependency[] getDependenciesArray() { - return dependenciesA; + return dependenciesA == null ? new Dependency[0] : dependenciesA; } public SpecificationVersion getSpecificationVersion() { @@ -310,7 +308,7 @@ // Code name codeName = attr.getValue("OpenIDE-Module"); // NOI18N if (codeName == null) { - InvalidException e = new InvalidException("Not a module: no OpenIDE-Module tag in manifest of " + /* #17629: important! */this); // NOI18N + InvalidException e = new InvalidException("Not a module: no OpenIDE-Module tag in manifest of " + /* #17629: important! */this, getManifest()); // NOI18N // #29393: plausible user mistake, deal with it politely. Exceptions.attachLocalizedMessage(e, NbBundle.getMessage(Module.class, diff -r 381c94f289dc o.n.bootstrap/src/org/netbeans/ModuleManager.java --- a/o.n.bootstrap/src/org/netbeans/ModuleManager.java Thu Jul 09 16:58:04 2009 +0200 +++ b/o.n.bootstrap/src/org/netbeans/ModuleManager.java Fri Jul 10 15:26:35 2009 +0200 @@ -146,7 +146,7 @@ // the following call will set it to the same value. classLoader.setSystemClassLoader( moduleFactory.getClasspathDelegateClassLoader(this, - ClassLoader.getSystemClassLoader())); + ModuleManager.class.getClassLoader())); } } diff -r 381c94f289dc o.n.bootstrap/src/org/netbeans/ProxyClassLoader.java --- a/o.n.bootstrap/src/org/netbeans/ProxyClassLoader.java Thu Jul 09 16:58:04 2009 +0200 +++ b/o.n.bootstrap/src/org/netbeans/ProxyClassLoader.java Fri Jul 10 15:26:35 2009 +0200 @@ -227,28 +227,7 @@ if (shouldDelegateResource(path, null)) cls = systemCL.loadClass(name); }*/ } else { - // multicovered package, search in order - for (ProxyClassLoader pcl : parents) { // all our accessible parents - if (del.contains(pcl) && shouldDelegateResource(path, pcl)) { // that cover given package - Class _cls = pcl.selfLoadClass(pkg, name); - if (_cls != null) { - if (cls == null) { - cls = _cls; - } else if (cls != _cls) { - String message = "Will not load class " + name + " arbitrarily from one of " + - cls.getClassLoader() + " and " + pcl + " starting from " + this + - "; see http://wiki.netbeans.org/DevFaqModuleCCE"; - ClassNotFoundException cnfe = new ClassNotFoundException(message); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, null, cnfe); - } else { - LOGGER.warning(message); - } - throw cnfe; - } - } - } - } + cls = doLoadClassFromParents(del, path, pkg, name, cls); if (cls == null && del.contains(this)) cls = selfLoadClass(pkg, name); if (cls != null) sclPackages.put(pkg, false); } @@ -256,7 +235,13 @@ try { cls = systemCL.loadClass(name); } catch (ClassNotFoundException e) { - throw new ClassNotFoundException(diagnosticCNFEMessage(e.getMessage(), del), e); + cls = doLoadClassFromParents(null, path, pkg, name, cls); + if (cls == null) { + cls = selfLoadClass(pkg, name); + if (cls == null) { + throw new ClassNotFoundException(diagnosticCNFEMessage(e.getMessage(), del), e); + } + } } } if (cls == null) { @@ -284,6 +269,33 @@ return base + " starting from " + this + " with possible defining loaders " + del + " and declared parents " + parentSetS; + } + + private Class doLoadClassFromParents(Set del, final String path, String pkg, String name, Class cls) throws ClassNotFoundException { + // multicovered package, search in order + for (ProxyClassLoader pcl : parents) { // all our accessible parents + if (del == null || (del.contains(pcl) && shouldDelegateResource(path, pcl))) { + // that cover given package + Class _cls = pcl.selfLoadClass(pkg, name); + if (_cls != null) { + if (cls == null) { + cls = _cls; + } else if (cls != _cls) { + String message = "Will not load class " + name + " arbitrarily from one of " + + cls.getClassLoader() + " and " + pcl + " starting from " + this + + "; see http://wiki.netbeans.org/DevFaqModuleCCE"; + ClassNotFoundException cnfe = new ClassNotFoundException(message); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, null, cnfe); + } else { + LOGGER.warning(message); + } + throw cnfe; + } + } + } + } + return cls; } /** May return null */ @@ -381,6 +393,9 @@ // uncovered package, go directly to SCL if (url == null && shouldDelegateResource(path, null)) url = systemCL.getResource(name); + if (url == null) { + url = findResource(name); + } return url; } diff -r 381c94f289dc o.n.bootstrap/test/unit/src/org/netbeans/ModuleFactoryAlienTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/o.n.bootstrap/test/unit/src/org/netbeans/ModuleFactoryAlienTest.java Fri Jul 10 15:26:35 2009 +0200 @@ -0,0 +1,413 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Nokia. Portions Copyright 2005 Nokia. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ + +package org.netbeans; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import org.fakepkg.FakeIfceHidden; +import org.openide.modules.SpecificationVersion; +import org.openide.util.Enumerations; +import org.openide.util.test.MockLookup; + +/** Verify contracts needed by Netigso. + */ +public class ModuleFactoryAlienTest extends SetupHid { + + static { + MockLookup.setInstances(new Factory()); + } + + public ModuleFactoryAlienTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + + + public void testFactoryCreatesAlienModules() throws Exception { + MockModuleInstaller installer = new MockModuleInstaller(); + MockEvents ev = new MockEvents(); + ModuleManager mgr = new ModuleManager(installer, ev); + mgr.mutexPrivileged().enterWriteAccess(); + + Module m2, m3; + try { + String mf = "AlienName: test-name.m2\n" + + "AlienVersion: 1.1.0\n" + + "AlienExport: org.fakepkg\n\n"; + File j2 = changeManifest(new File(jars, "simple-module.jar"), mf); + m2 = mgr.create(j2, null, false, false, false); + mf = "AlienName: test-name\n" + + "AlienVersion: 1.1.0\n" + + "AlienExport: org.fakepkg\n\n"; + File j3 = changeManifest(new File(jars, "depends-on-simple-module.jar"), mf); + m3 = mgr.create(j3, null, false, false, false); + mgr.enable(new HashSet(Arrays.asList(m2, m3))); + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + ClassLoader l; + URL u; + Class clazz; + + + l = Thread.currentThread().getContextClassLoader(); + u = l.getResource("org/fakepkg/Something.txt"); + assertNotNull("Resource found", u); + + assertEquals("No dependencies", 0, m3.getDependencies().size()); + + clazz = l.loadClass("org.fakepkg.FakeIfce"); + assertNotNull("Class loaded", clazz); + assertEquals("it is our fake class", FakeIfceHidden.class, clazz); + + + l = m3.getClassLoader(); + + assertNotNull("Classloader found", l); + assertEquals("My classloader", Loader.class, l.getClass()); + + u = l.getResource("org/fakepkg/Something.txt"); + assertNotNull("Resource found", u); + + clazz = l.loadClass("org.fakepkg.FakeIfce"); + assertNotNull("Class loaded", clazz); + assertEquals("it is our fake class", FakeIfceHidden.class, clazz); + + assertEquals("No dependencies", 0, m3.getDependencies().size()); + } + + public void testAlienCanDependOnNetBeans() throws Exception { + MockModuleInstaller installer = new MockModuleInstaller(); + MockEvents ev = new MockEvents(); + ModuleManager mgr = new ModuleManager(installer, ev); + mgr.mutexPrivileged().enterWriteAccess(); + HashSet both = null; + try { + String mfBar = "AlienName: org.bar\n" + + "AlienExport: org.bar\n" + + "AlienImport: org.foo\n" + + "\n\n"; + + File j1 = new File(jars, "simple-module.jar"); + File j2 = changeManifest(new File(jars, "depends-on-simple-module.jar"), mfBar); + Module m1 = mgr.create(j1, null, false, false, false); + Module m2 = mgr.create(j2, null, false, false, false); + HashSet b = new HashSet(Arrays.asList(m1, m2)); + mgr.enable(b); + both = b; + + AlienModule am = (AlienModule)m2; + am.loader.l = new URLClassLoader(new URL[] { am.jar.toURI().toURL() }, m1.getClassLoader()); + + + Class clazz = m2.getClassLoader().loadClass("org.bar.SomethingElse"); + Class sprclass = m2.getClassLoader().loadClass("org.foo.Something"); + + assertEquals("Correct parent is used", sprclass, clazz.getSuperclass()); + } finally { + if (both != null) { + mgr.disable(both); + } + mgr.mutexPrivileged().exitWriteAccess(); + } + } + + public static class Factory extends ModuleFactory { + private static Set registered; + + static void clear() { + } + + public Factory() { + } + + static void registerBundle(Module m) throws IOException { + + } + + @Override + public Module createFixed(Manifest mani, Object history, ClassLoader loader, boolean autoload, boolean eager, ModuleManager mgr, Events ev) throws InvalidException { + Module m = super.createFixed(mani, history, loader, autoload, eager, mgr, ev); + try { + registerBundle(m); + } catch (IOException ex) { + throw (InvalidException)new InvalidException(m, ex.getMessage()).initCause(ex); + } + return m; + } + + @Override + public Module create( + File jar, Object history, + boolean reloadable, boolean autoload, boolean eager, + ModuleManager mgr, Events ev + ) throws IOException { + try { + Module m = super.create(jar, history, reloadable, autoload, eager, mgr, ev); + registerBundle(m); + return m; + } catch (InvalidException ex) { + Manifest mani = ex.getManifest(); + if (mani != null) { + String name = mani.getMainAttributes().getValue("AlienName"); // NOI18N + if (name == null) { + throw ex; + } + return new AlienModule(mani, jar, mgr, ev, history, reloadable, autoload, eager); + } + throw ex; + } + } + + } + + static final class AlienModule extends Module { + private Manifest manifest; + private Loader loader; + private String name; + private File jar; + + public AlienModule(Manifest m, File jar, ModuleManager mgr, Events ev, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException { + super(mgr, ev, history, reloadable, autoload, eager); + + this.manifest = m; + this.name = manifest.getMainAttributes().getValue("AlienName"); + this.jar = jar; + } + + @Override + public String[] getProvides() { + return new String[0]; + } + + @Override + public String getCodeName() { + return name; + } + + @Override + public String getCodeNameBase() { + return getCodeName(); + } + + @Override + public int getCodeNameRelease() { + return -1; + } + + @Override + public SpecificationVersion getSpecificationVersion() { + return new SpecificationVersion("1.0"); + } + + @Override + public String getImplementationVersion() { + return "testimpl"; // NOI18N + } + + @Override + protected void parseManifest() throws InvalidException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getAllJars() { + return Collections.emptyList(); + } + + @Override + public void setReloadable(boolean r) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void reload() throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected void classLoaderUp(Set parents) throws IOException { + loader = new Loader(this); + } + + @Override + protected void classLoaderDown() { + loader = null; + } + + @Override + public ClassLoader getClassLoader() throws IllegalArgumentException { + if (loader == null) { + throw new IllegalArgumentException("No classloader for " + getCodeNameBase()); // NOI18N + } + return loader; + } + + @Override + protected void cleanup() { + } + + @Override + protected void destroy() { + } + + @Override + public boolean isFixed() { + return false; + } + + @Override + public Manifest getManifest() { + return manifest; + } + + @Override + public Object getLocalizedAttribute(String attr) { + // TBD; + return null; + } + + @Override + public String toString() { + return "Alien: " + getCodeName(); + } + } + + static final class Loader extends ProxyClassLoader { + final AlienModule am; + ClassLoader l; + + public Loader(AlienModule mf) throws MalformedURLException { + super(new ClassLoader[0], true); + Set pkgs = new HashSet(); + pkgs.add(mf.getManifest().getMainAttributes().getValue("AlienExport")); + addCoveredPackages(pkgs); + this.am = mf; + } + + @Override + public URL findResource(String name) { + if ("org/fakepkg/Something.txt".equals(name)) { + URL u = ModuleFactoryAlienTest.class.getResource("/org/fakepkg/resource1.txt"); + assertNotNull("text found", u); + return u; + } + return null; + } + + @Override + @SuppressWarnings(value = "unchecked") + public Enumeration findResources(String name) { + return Enumerations.empty(); + } + + @Override + protected Class doLoadClass(String pkg, String name) { + if (name.equals("org.fakepkg.FakeIfce")) { + return FakeIfceHidden.class; + } + try { + return l == null ? null : l.loadClass(name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + @Override + public String toString() { + return "Alien[test]"; + } + } + + private File changeManifest(File orig, String manifest) throws IOException { + File f = new File(getWorkDir(), orig.getName()); + Manifest mf = new Manifest(new ByteArrayInputStream(manifest.getBytes("utf-8"))); + mf.getMainAttributes().putValue("Manifest-Version", "1.0"); + JarOutputStream os = new JarOutputStream(new FileOutputStream(f), mf); + JarFile jf = new JarFile(orig); + Enumeration en = jf.entries(); + InputStream is; + while (en.hasMoreElements()) { + JarEntry e = en.nextElement(); + if (e.getName().equals("META-INF/MANIFEST.MF")) { + continue; + } + os.putNextEntry(e); + is = jf.getInputStream(e); + byte[] arr = new byte[4096]; + for (;;) { + int len = is.read(arr); + if (len == -1) { + break; + } + os.write(arr, 0, len); + } + is.close(); + os.closeEntry(); + } + os.close(); + + return f; + } + +} diff -r 381c94f289dc o.n.bootstrap/test/unit/src/org/netbeans/ProxyClassLoaderTest.java --- a/o.n.bootstrap/test/unit/src/org/netbeans/ProxyClassLoaderTest.java Thu Jul 09 16:58:04 2009 +0200 +++ b/o.n.bootstrap/test/unit/src/org/netbeans/ProxyClassLoaderTest.java Fri Jul 10 15:26:35 2009 +0200 @@ -40,18 +40,22 @@ package org.netbeans; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLClassLoader; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; -import junit.framework.TestCase; +import java.util.HashSet; +import java.util.Set; +import org.fakepkg.FakeIfceHidden; import org.openide.util.Enumerations; import org.openide.util.Exceptions; -public class ProxyClassLoaderTest extends TestCase { +public class ProxyClassLoaderTest extends SetupHid { public ProxyClassLoaderTest(String name) { super(name); @@ -185,4 +189,74 @@ assertEquals(Arrays.asList(new URL(b, "3a/p/3"), new URL(b, "3b/p/3")), Collections.list(cl4.getResources("p/3"))); } + public void testAlienClassloader() throws Exception { + URL u; + + final class Loader extends ProxyClassLoader { + + ClassLoader l; + + public Loader(String publicPackage) throws MalformedURLException { + super(new ClassLoader[0], true); + Set pkgs = new HashSet(); + pkgs.add(publicPackage); + addCoveredPackages(pkgs); + } + + @Override + public URL findResource(String name) { + if ("org/fakepkg/Something.txt".equals(name)) { + URL u = ModuleFactoryAlienTest.class.getResource("/org/fakepkg/resource1.txt"); + assertNotNull("text found", u); + return u; + } + return null; + } + + @Override + @SuppressWarnings(value = "unchecked") + public Enumeration findResources(String name) { + return Enumerations.empty(); + } + + @Override + protected Class doLoadClass(String pkg, String name) { + if (name.equals("org.fakepkg.FakeIfce")) { + return FakeIfceHidden.class; + } + try { + return l == null ? null : l.loadClass(name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + @Override + public String toString() { + return "Alien[test]"; + } + } + + File j1 = new File(jars, "simple-module.jar"); + ClassLoader l1 = new URLClassLoader(new URL[] { j1.toURI().toURL() }); + + Loader loader = new Loader("org.bar"); + File jar = new File(jars, "depends-on-simple-module.jar"); + loader.l = new URLClassLoader(new URL[] { jar.toURI().toURL() }, l1); + + + Class clazz = loader.loadClass("org.bar.SomethingElse"); + Class sprclass = loader.loadClass("org.foo.Something"); + + assertEquals("Correct parent is used", sprclass, clazz.getSuperclass()); + + u = loader.getResource("org/fakepkg/Something.txt"); + assertNotNull("Resource found", u); + + clazz = loader.loadClass("org.fakepkg.FakeIfce"); + assertNotNull("Class loaded", clazz); + assertEquals("it is our fake class", FakeIfceHidden.class, clazz); + + } }