Index: src/org/netbeans/core/modules/Module.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/modules/Module.java,v retrieving revision 1.55 diff -u -r1.55 Module.java --- src/org/netbeans/core/modules/Module.java 11 Aug 2004 10:07:40 -0000 1.55 +++ src/org/netbeans/core/modules/Module.java 24 Jan 2005 15:08:30 -0000 @@ -140,6 +140,9 @@ * Files are assumed to be JARs; directories are themselves. */ private Set patches = null; // Set + /** additional classpath elements from module.xml file + */ + private Set extraExtensions = null; /** Use ModuleManager.create as a factory. */ Module(ModuleManager mgr, Events ev, File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException { @@ -197,6 +200,30 @@ this.enabled = enabled; } + /** @param arr full path to array + */ + final void assignExtraClassPath (String[] arr) { + HashSet set = new HashSet (); + for (int i = 0; i < arr.length; i++) { + File f = new File (arr[i]); + if (f.exists ()) { + set.add (f); + } + } + extraExtensions = set; + } + + /** Getter for the extensions - returns the list. + * @return null or array of files that describe our extensions + */ + final File[] getExtraClassPath () { + if (extraExtensions == null) { + return null; + } + return (File[])extraExtensions.toArray (new File[0]); + } + + /** Normally a module once created and managed is valid * (that is, either installed or not, but at least managed). * If it is deleted any remaining references to it become @@ -843,6 +870,7 @@ if (plainExtensions != null) l.addAll (plainExtensions); if (localeVariants != null) l.addAll (localeVariants); if (localeExtensions != null) l.addAll (localeExtensions); + if (extraExtensions != null) l.addAll (extraExtensions); return l; } @@ -984,6 +1012,12 @@ } if (plainExtensions != null) { for( it = plainExtensions.iterator(); it.hasNext(); ) { + File act = (File)it.next(); + classp.add(act.isDirectory() ? (Object)act : new JarFile(act, false)); + } + } + if (extraExtensions != null) { + for( it = extraExtensions.iterator(); it.hasNext(); ) { File act = (File)it.next(); classp.add(act.isDirectory() ? (Object)act : new JarFile(act, false)); } Index: src/org/netbeans/core/modules/ModuleList.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/modules/ModuleList.java,v retrieving revision 1.51 diff -u -r1.51 ModuleList.java --- src/org/netbeans/core/modules/ModuleList.java 29 Mar 2004 23:40:14 -0000 1.51 +++ src/org/netbeans/core/modules/ModuleList.java 24 Jan 2005 15:08:31 -0000 @@ -199,6 +199,12 @@ props.put("installerState", buf); // NOI18N } Module m = mgr.create(jarFile, history, reloadable, autoload, eager); + + List l = (List)props.get ("extra-cp"); // NOI18N + if (l != null) { + m.assignExtraClassPath ((String[])l.toArray (new String[0])); + } + read.add(m); DiskStatus status = new DiskStatus(); status.module = m; @@ -573,6 +579,7 @@ private String modName; private String paramName; private StringBuffer data = new StringBuffer(); + private List extraClassPath; public void startElement(String uri, String localname, @@ -592,6 +599,23 @@ paramName = paramName.intern(); data.setLength(0); } + else if ("extra-cp".equals (qname)) { // NOI18N + if (extraClassPath != null) { + throw new SAXException("extra-cp can be there just once"); // NOI18N + } + extraClassPath = new ArrayList (); + m.put ("extra-cp", extraClassPath); // NOI18N + } + else if ("path".equals (qname)) { // NOI18N + if (! (extraClassPath instanceof ArrayList)) { + throw new SAXException ("path can be just inside extra-cp"); // NOI18N + } + String location = attrs.getValue ("location"); // NOI18N + if (location == null) { + throw new SAXException ("attribute location must be present when specifying path"); // NOI18N + } + extraClassPath.add (location); + } } public void characters(char[] ch, int start, int len) { @@ -623,6 +647,9 @@ else if ("module".equals(qname)) { // NOI18N modName = null; } + else if ("extra-cp".equals (qname)) { // NOI18N + extraClassPath = java.util.Collections.unmodifiableList (extraClassPath); + } } }; @@ -698,6 +725,11 @@ // private static final byte[] MODULE_XML_DIV1 = ">\n s some number of times, finally . PARSE: while (true) { @@ -729,6 +764,42 @@ case ' ': // if (!expect(is, MODULE_XML_DIV2)) { + if (expect (is, MODULE_EXTRA_CP)) { + // do extra cp loading, if possible + for (;;) { + if (!expect (is, MODULE_SPACES)) { + return null; + } + int x = is.read (); + if (x == ' ') { + // path element + if (!expect (is, MODULE_PATH)) { + Util.err.log("No path"); + return null; + } + String k = readTo(is, '"'); + if (k == null) { + Util.err.log("Could not read path"); + return null; + } + extraClassPath.add (k.intern ()); + if (!expect (is, MODULE_PATH_END)) { + Util.err.log("Could not read path end"); + return null; + } + } else if (x == '<') { + // + if (!expect (is, MODULE_EXTRA_CP_END)) { + return null; + } else { + break; + } + } else { + return null; + } + } + continue; + } Util.err.log("Could not read up to param"); return null; } @@ -775,6 +846,10 @@ return null; } } + + if (!extraClassPath.isEmpty ()) { + m.put ("extra-cp", extraClassPath); // NOI18N + } sanityCheckStatus(m); return m; } @@ -786,6 +861,8 @@ private boolean expect(InputStream is, byte[] stuff) throws IOException { int len = stuff.length; boolean inNewline = false; + // mark the possition so we can reset on failure + is.mark (stuff.length); for (int i = 0; i < len; ) { int c = is.read(); if (c == 10 || c == 13) { @@ -800,6 +877,7 @@ inNewline = false; } if (c != stuff[i++]) { + is.reset (); return false; } } @@ -874,6 +952,8 @@ w.write("\n"); // NOI18N + + File[] extraClassPath = null; // Use TreeMap to sort the keys by name; since the module status files might // be version-controlled we want to avoid gratuitous format changes. @@ -887,6 +967,11 @@ } Object val = entry.getValue(); + + if (name.equals ("extra-cp")) { // NOI18N + extraClassPath = (File[])val; + continue; + } w.write(" \n"); // NOI18N } + if (extraClassPath != null) { + w.write (" \n"); + for (int i = 0; i < extraClassPath.length; i++) { + w.write (" \n"); + } + w.write (" \n"); + } + w.write("\n"); // NOI18N w.flush(); } @@ -1184,6 +1279,7 @@ p.put("installer", m.getCodeNameBase().replace('.', '-') + ".ser"); // NOI18N p.put("installerState", hist.getInstallerState()); // NOI18N } + p.put("extra-cp", m.getExtraClassPath ()); // NOI18N return p; } Index: src/org/netbeans/core/modules/module-status-1_0.dtd =================================================================== RCS file: /cvs/core/src/org/netbeans/core/modules/module-status-1_0.dtd,v retrieving revision 1.3 diff -u -r1.3 module-status-1_0.dtd --- src/org/netbeans/core/modules/module-status-1_0.dtd 25 Jul 2001 10:37:10 -0000 1.3 +++ src/org/netbeans/core/modules/module-status-1_0.dtd 24 Jan 2005 15:08:31 -0000 @@ -29,13 +29,20 @@ 1 false 1.6 + + + + Such a module status would canonically be stored in the system file system with the name Modules/org-netbeans-modules-foo.xml. --> - + + + + Index: test/unit/src/org/netbeans/core/modules/ModuleListTest.java =================================================================== RCS file: /cvs/core/test/unit/src/org/netbeans/core/modules/ModuleListTest.java,v retrieving revision 1.11 diff -u -r1.11 ModuleListTest.java --- test/unit/src/org/netbeans/core/modules/ModuleListTest.java 12 Nov 2004 23:13:50 -0000 1.11 +++ test/unit/src/org/netbeans/core/modules/ModuleListTest.java 24 Jan 2005 15:08:38 -0000 @@ -38,7 +38,7 @@ public static final class L extends ProxyLookup { public L() { super(new Lookup[] { - Lookups.singleton(new IFL()), + Lookups.singleton(new IFL()), Lookups.singleton (new ErrManager ()), }); } } @@ -58,6 +58,37 @@ return null; } } + private static final class ErrManager extends org.openide.ErrorManager { + public static final StringBuffer messages = new StringBuffer (); + + public Throwable annotate (Throwable t, int severity, String message, String localizedMessage, Throwable stackTrace, java.util.Date date) { + return t; + } + + public Throwable attachAnnotations (Throwable t, org.openide.ErrorManager.Annotation[] arr) { + return t; + } + + public org.openide.ErrorManager.Annotation[] findAnnotations (Throwable t) { + return null; + } + + public org.openide.ErrorManager getInstance (String name) { + return this; + } + + public void log (int severity, String s) { + messages.append (s); + messages.append ('\n'); + } + + public void notify (int severity, Throwable t) { + junit.framework.AssertionFailedError err = new junit.framework.AssertionFailedError (t.getMessage ()); + err.initCause (t); + throw err; + } + + } public ModuleListTest(String name) { super(name); @@ -80,6 +111,8 @@ modulesfolder = fs.findResource("Modules"); assertNotNull(modulesfolder); list = new ModuleList(mgr, modulesfolder, ev); + + ErrManager.messages.setLength (0); } private Module makeModule(String jarName) throws Exception { @@ -186,6 +219,122 @@ } finally { mgr.mutexPrivileged().exitReadAccess(); } + } + + /** Checks that extra-cp element is honored. + */ + public void testExtraClassPathElementWorks () throws Exception { + FileObject jar = modulesfolder.getFileSystem ().getRoot ().createData ("External.jar"); + File f = FileUtil.toFile (jar); + assertNotNull ("File created on disk", f); + + { + FileLock l = jar.lock (); + java.util.jar.JarOutputStream os = new java.util.jar.JarOutputStream (jar.getOutputStream (l)); + os.putNextEntry (new java.util.jar.JarEntry ("external/file.txt")); + os.closeEntry (); + os.close (); + l.releaseLock (); + } + + FileObject jar2 = modulesfolder.getFileSystem ().getRoot ().createData ("External2.jar"); + File f2 = FileUtil.toFile (jar2); + assertNotNull ("File created on disk", f2); + + { + FileLock l = jar2.lock (); + java.util.jar.JarOutputStream os = new java.util.jar.JarOutputStream (jar2.getOutputStream (l)); + os.putNextEntry (new java.util.jar.JarEntry ("external/file2.txt")); + os.closeEntry (); + os.close (); + l.releaseLock (); + } + + + + FileObject fooxml = modulesfolder.createData("org-foo.xml"); + { + FileLock l = fooxml.lock (); + PrintStream os = new PrintStream (fooxml.getOutputStream (l)); + os.println (""); + os.println (""); + os.println (""); + os.println ("false"); + os.println ("false"); + os.println ("true"); + os.println ("wherever/simple-module.jar"); + os.println ("1"); + os.println ("false"); + os.println ("1.1"); + os.println (""); + os.println (" "); + os.println (" "); + os.println (""); + os.println (""); + os.close (); + l.releaseLock (); + } + + doCheckForTestExtraClassPathElementWorks (mgr, list, false); + + assertTrue ("Shutdowning the manager, we will use another one", mgr.shutDown ()); + + if (ErrManager.messages.indexOf ("Note - failed to parse") < 0) { + fail ("We should have fallback to parsing by SAX parser as this text is not well formated: " + ErrManager.messages); + } + + // initialize new module + FakeModuleInstaller installer = new FakeModuleInstaller(); + FakeEvents ev = new FakeEvents(); + ModuleManager m = new ModuleManager(installer, ev); + ErrManager.messages.setLength (0); + + doCheckForTestExtraClassPathElementWorks ( + m, + new ModuleList(m, modulesfolder, ev), + true + ); + if (ErrManager.messages.indexOf ("Note - failed to parse") >= 0) { + fail ("Parsing by faster method should be performed now, as the test was saved by us: " + ErrManager.messages); + } + + } + + private static void doCheckForTestExtraClassPathElementWorks (ModuleManager mgr, ModuleList list, boolean enable) + throws Exception { + Module m; + mgr.mutexPrivileged().enterWriteAccess(); + try { + Set modules = list.readInitial(); + assertEquals("One module", 1, modules.size ()); + + m = (Module)modules.iterator ().next (); + assertEquals ("Name is foo", "org.foo", m.getCodeNameBase ()); + + assertFalse ("Is not yet enabled", m.isEnabled ()); + + list.trigger(Collections.EMPTY_SET); + + assertTrue ("Is enabled", m.isEnabled ()); + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + + ClassLoader c = m.getClassLoader (); + assertNotNull ("external.jar is in classpath", c.getResource ("external/file.txt")); + assertNotNull ("external2.jar is in classpath", c.getResource ("external/file2.txt")); + + mgr.mutexPrivileged().enterWriteAccess(); + try { + // and now disable the module + mgr.disable (m); + mgr.enable (m); + } finally { + mgr.mutexPrivileged().exitWriteAccess(); + } + + assertTrue ("It is ok to finish", mgr.shutDown ()); } // XXX try to read a nonempty initial list