KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > startup > ModuleList


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.core.startup;
21
22 import java.beans.PropertyChangeEvent JavaDoc;
23 import java.beans.PropertyChangeListener JavaDoc;
24 import java.io.BufferedInputStream JavaDoc;
25 import java.io.ByteArrayInputStream JavaDoc;
26 import java.io.ByteArrayOutputStream JavaDoc;
27 import java.io.CharArrayWriter JavaDoc;
28 import java.io.File JavaDoc;
29 import java.io.FileNotFoundException JavaDoc;
30 import java.io.IOException JavaDoc;
31 import java.io.InputStream JavaDoc;
32 import java.io.ObjectOutput JavaDoc;
33 import java.io.OutputStream JavaDoc;
34 import java.io.OutputStreamWriter JavaDoc;
35 import java.io.Writer JavaDoc;
36 import java.util.Arrays JavaDoc;
37 import java.util.Collections JavaDoc;
38 import java.util.HashMap JavaDoc;
39 import java.util.HashSet JavaDoc;
40 import java.util.Iterator JavaDoc;
41 import java.util.List JavaDoc;
42 import java.util.Map JavaDoc;
43 import java.util.Set JavaDoc;
44 import java.util.TreeMap JavaDoc;
45 import java.util.logging.Level JavaDoc;
46 import org.netbeans.DuplicateException;
47 import org.netbeans.Events;
48 import org.netbeans.InvalidException;
49 import org.netbeans.Module;
50 import org.netbeans.ModuleManager;
51 import org.netbeans.Util;
52 import org.openide.filesystems.FileAttributeEvent;
53 import org.openide.filesystems.FileChangeListener;
54 import org.openide.filesystems.FileEvent;
55 import org.openide.filesystems.FileLock;
56 import org.openide.filesystems.FileObject;
57 import org.openide.filesystems.FileRenameEvent;
58 import org.openide.filesystems.FileSystem;
59 import org.openide.filesystems.FileUtil;
60 import org.openide.modules.Dependency;
61 import org.openide.modules.InstalledFileLocator;
62 import org.openide.modules.ModuleInstall;
63 import org.openide.modules.SpecificationVersion;
64 import org.openide.util.RequestProcessor;
65 import org.openide.util.Utilities;
66 import org.openide.util.WeakSet;
67 import org.openide.util.io.NbObjectInputStream;
68 import org.openide.util.io.NbObjectOutputStream;
69 import org.openide.xml.EntityCatalog;
70 import org.openide.xml.XMLUtil;
71 import org.xml.sax.Attributes JavaDoc;
72 import org.xml.sax.EntityResolver JavaDoc;
73 import org.xml.sax.ErrorHandler JavaDoc;
74 import org.xml.sax.InputSource JavaDoc;
75 import org.xml.sax.SAXException JavaDoc;
76 import org.xml.sax.SAXParseException JavaDoc;
77 import org.xml.sax.XMLReader JavaDoc;
78 import org.xml.sax.helpers.DefaultHandler JavaDoc;
79
80 /** Class responsible for maintaining the list of modules in the IDE persistently.
81  * This class understands the "module status" XML format, and the list of modules
82  * present in the Modules/ folder. And it can keep track of module histories.
83  * Methods must be called from within appropriate mutex access.
84  * @author Jesse Glick
85  */

86 final class ModuleList {
87     
88     /** The DTD for a module status. */
89     public static final String JavaDoc PUBLIC_ID = "-//NetBeans//DTD Module Status 1.0//EN"; // NOI18N
90
public static final String JavaDoc SYSTEM_ID = "http://www.netbeans.org/dtds/module-status-1_0.dtd"; // NOI18N
91

92     /** Whether to validate module XML files.
93      * Safer; only slows down startup in case quickie parse of XML statuses fails for some reason.
94      */

95     private static final boolean VALIDATE_XML = true;
96     
97     /** associated module manager */
98     private final ModuleManager mgr;
99     /** Modules/ folder containing XML data */
100     private final FileObject folder;
101     /** to fire events with */
102     private final Events ev;
103     /** map from code name (base)s to statuses of modules on disk */
104     private final Map JavaDoc<String JavaDoc,DiskStatus> statuses = new HashMap JavaDoc<String JavaDoc,DiskStatus>(100);
105     /** whether the initial round has been triggered or not */
106     private boolean triggered = false;
107     /** listener for changes in modules, etc.; see comment on class Listener */
108     private final Listener JavaDoc listener = new Listener JavaDoc();
109     /** any module install sers from externalizedModules.ser, from class name to data */
110     private final Map JavaDoc<String JavaDoc,byte[]> compatibilitySers = new HashMap JavaDoc<String JavaDoc,byte[]>(100);
111     /** atomic actions I have used to change Modules/*.xml */
112     private final Set JavaDoc<FileSystem.AtomicAction> myAtomicActions = Collections.<FileSystem.AtomicAction>synchronizedSet(new WeakSet<FileSystem.AtomicAction>(100));
113     
114     /** Create the list manager.
115      * @param mgr the module manager which will actually control the modules at runtime
116      * @param folder the Modules/ folder on the system file system to scan/write
117      * @param ev the event logger
118      */

119     public ModuleList(ModuleManager mgr, FileObject folder, Events ev) {
120         this.mgr = mgr;
121         this.folder = folder;
122         this.ev = ev;
123         Util.err.fine("ModuleList created, storage in " + folder);
124     }
125     
126     /** Read an initial list of modules from disk according to their stored settings.
127      * Just reads the XML files in the Modules/ directory, and adds those to
128      * the manager's list of modules. Errors are handled internally.
129      * Note that the modules encountered are not turned on at this point even if
130      * the XML says they should be; but they are added to the list of modules to
131      * enable as needed. All discovered modules are returned.
132      * Write mutex only.
133      */

134     public Set JavaDoc readInitial() {
135         ev.log(Events.START_READ);
136         final Set JavaDoc<Module> read = new HashSet JavaDoc<Module>();
137         try {
138             folder.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
139                 public void run() throws IOException JavaDoc {
140         FileObject[] children = folder.getChildren();
141         ev.log( Events.MODULES_FILE_SCANNED, children.length );
142
143     XMLReader JavaDoc reader = null;
144     
145         for (int i = 0; i < children.length; i++) {
146             if (children[i].hasExt("ser")) { // NOI18N
147
// Fine, skip over.
148
} else if (children[i].hasExt("xml")) { // NOI18N
149
// Assume this is one of ours. Note fixed naming scheme.
150
try {
151                     String JavaDoc nameDashes = children[i].getName(); // NOI18N
152
char[] badChars = {'.', '/', '>', '='};
153                     for (int j = 0; j < 4; j++) {
154                         if (nameDashes.indexOf(badChars[j]) != -1) {
155                             throw new IllegalArgumentException JavaDoc("Bad name: " + nameDashes); // NOI18N
156
}
157                     }
158                     String JavaDoc name = nameDashes.replace('-', '.').intern(); // NOI18N
159
// Now name is the code name base of the module we expect to find.
160
// Check its format (throws IllegalArgumentException if bad):
161
Dependency.create(Dependency.TYPE_MODULE, name);
162             
163                     // OK, read it from disk.
164
Map JavaDoc<String JavaDoc,Object JavaDoc> props;
165                     InputStream JavaDoc is = children[i].getInputStream();
166                     try {
167                         props = readStatus(new BufferedInputStream JavaDoc(is));
168                         if (props == null) {
169                             Util.err.warning("Note - failed to parse " + children[i] + " the quick way, falling back on XMLReader");
170                             is.close();
171                             is = children[i].getInputStream();
172                             InputSource JavaDoc src = new InputSource JavaDoc(is);
173                             // Make sure any includes etc. are handled properly:
174
src.setSystemId(children[i].getURL().toExternalForm());
175                             if (reader == null) {
176                                 try {
177                                     reader = XMLUtil.createXMLReader();
178                                 } catch(SAXException JavaDoc e) {
179                                     throw (IllegalStateException JavaDoc) new IllegalStateException JavaDoc(e.toString()).initCause(e);
180                                 }
181                                 reader.setEntityResolver(listener);
182                                 reader.setErrorHandler(listener);
183                             }
184                             props = readStatus(src,reader);
185                         }
186                     } finally {
187                         is.close();
188                     }
189                     if (! name.equals(props.get("name"))) throw new IOException JavaDoc("Code name mismatch: " /* #25011 */ + name + " vs. " + props.get("name")); // NOI18N
190
String JavaDoc jar = (String JavaDoc)props.get("jar"); // NOI18N
191
File JavaDoc jarFile;
192                     try {
193                         jarFile = findJarByName(jar, name);
194                     } catch (FileNotFoundException JavaDoc fnfe) {
195                         //Util.err.fine("Cannot find: " + fnfe.getMessage());
196
ev.log(Events.MISSING_JAR_FILE, new File JavaDoc(fnfe.getMessage()));
197                         try {
198                             children[i].delete();
199                         } catch (IOException JavaDoc ioe) {
200                             Util.err.log(Level.WARNING, null, ioe);
201                         }
202                         continue;
203                     }
204
205                     ModuleHistory history = new ModuleHistory(jar); // NOI18N
206
Integer JavaDoc prevReleaseI = (Integer JavaDoc)props.get("release"); // NOI18N
207
int prevRelease = (prevReleaseI == null ? -1 : prevReleaseI.intValue());
208                     SpecificationVersion prevSpec = (SpecificationVersion)props.get("specversion"); // NOI18N
209
history.upgrade(prevRelease, prevSpec);
210                     Boolean JavaDoc reloadableB = (Boolean JavaDoc)props.get("reloadable"); // NOI18N
211
boolean reloadable = (reloadableB != null ? reloadableB.booleanValue() : false);
212                     Boolean JavaDoc enabledB = (Boolean JavaDoc)props.get("enabled"); // NOI18N
213
boolean enabled = (enabledB != null ? enabledB.booleanValue() : false);
214                     Boolean JavaDoc autoloadB = (Boolean JavaDoc)props.get("autoload"); // NOI18N
215
boolean autoload = (autoloadB != null ? autoloadB.booleanValue() : false);
216                     Boolean JavaDoc eagerB = (Boolean JavaDoc)props.get("eager"); // NOI18N
217
boolean eager = (eagerB != null ? eagerB.booleanValue() : false);
218                     String JavaDoc installer = (String JavaDoc)props.get("installer"); // NOI18N
219
if (installer != null) {
220                         if (! installer.equals(nameDashes + ".ser")) throw new IOException JavaDoc("Incorrect installer ser name: " + installer); // NOI18N
221
// Load from disk in mentioned file.
222
FileObject installerSer = folder.getFileObject(nameDashes, "ser"); // NOI18N
223
if (installerSer == null) throw new IOException JavaDoc("No such install ser: " + installer + "; I see only: " + Arrays.asList(children)); // NOI18N
224
// Hope the stored state is not >Integer.MAX_INT! :-)
225
byte[] buf = new byte[(int)installerSer.getSize()];
226                         InputStream JavaDoc is2 = installerSer.getInputStream();
227                         try {
228                             is2.read(buf);
229                         } finally {
230                             is2.close();
231                         }
232                         history.setInstallerState(buf);
233                         // Quasi-prop which is stored separately.
234
props.put("installerState", buf); // NOI18N
235
}
236                     Module m = mgr.create(jarFile, history, reloadable, autoload, eager);
237                     read.add(m);
238                     DiskStatus status = new DiskStatus();
239                     status.module = m;
240                     status.file = children[i];
241                     //status.lastApprovedChange = children[i].lastModified().getTime();
242
status.pendingInstall = enabled;
243                     // Will only really be flushed if mgr props != disk props, i.e
244
// if version changed or could not be enabled.
245
//status.pendingFlush = true;
246
status.diskProps = props;
247                     statuses.put(name, status);
248                 } catch (Exception JavaDoc e) {
249                     Util.err.log(Level.WARNING, "Error encountered while reading " + children[i], e);
250                 }
251             } else {
252                 Util.err.fine("Strange file encountered in modules folder: " + children[i]);
253             }
254             ev.log( Events.MODULES_FILE_PROCESSED, children[i] );
255         }
256         if (Util.err.isLoggable(Level.FINE)) {
257             Util.err.fine("read initial XML files: statuses=" + statuses);
258         }
259         ev.log(Events.FINISH_READ, read);
260         // Handle changes in the Modules/ folder on disk by parsing & applying them.
261
folder.addFileChangeListener(FileUtil.weakFileChangeListener (listener, folder));
262                 }});
263         } catch (IOException JavaDoc ioe) {
264             Util.err.log(Level.WARNING, null, ioe);
265         }
266         return read;
267     }
268     
269     /**
270      * Try to find a module JAR by an XML-supplied name.
271      * @param jar the JAR name (relative to an install dir, or a full path)
272      * @param name code name base of the module JAR
273      * @return an actual JAR file
274      * @throws FileNotFoundException if no such JAR file could be found on disk
275      * @throws IOException if something else was wrong
276      */

277     private File JavaDoc findJarByName(String JavaDoc jar, String JavaDoc name) throws IOException JavaDoc {
278         File JavaDoc f = new File JavaDoc(jar);
279         if (f.isAbsolute()) {
280             if (!f.isFile()) throw new FileNotFoundException JavaDoc(f.getAbsolutePath());
281             return f;
282         } else {
283             f = InstalledFileLocator.getDefault().locate(jar, name, false);
284             if (f != null) {
285                 return f;
286             } else {
287                 throw new FileNotFoundException JavaDoc(jar);
288             }
289         }
290     }
291     
292     /** Actually go ahead and enable modules which were queued up by
293      * reading methods. Should be done after as many modules
294      * are collected as possible, in case they have odd mutual
295      * dependencies. Also begins listening to further changes.
296      * Pass in a list of boot modules which you would
297      * like to also try to enable now.
298      */

299     public void trigger(Set JavaDoc<Module> boot) {
300         ev.log(Events.PERF_START, "ModuleList.trigger"); // NOI18N
301
if (triggered) throw new IllegalStateException JavaDoc("Duplicate call to trigger()"); // NOI18N
302
Set JavaDoc<Module> maybeEnable = new HashSet JavaDoc<Module>(boot);
303         for (DiskStatus status: statuses.values()) {
304             if (status.pendingInstall) {
305                 // We are going to try to turn it on...
306
status.pendingInstall = false;
307                 Module m = status.module;
308                 if (m.isEnabled() || m.isAutoload() || m.isEager()) throw new IllegalStateException JavaDoc();
309                 maybeEnable.add(m);
310             }
311         }
312         ev.log(Events.PERF_TICK, "modules to enable prepared"); // NOI18N
313

314         if (! maybeEnable.isEmpty()) {
315             ev.log(Events.START_AUTO_RESTORE, maybeEnable);
316             installNew(maybeEnable);
317             ev.log(Events.FINISH_AUTO_RESTORE, maybeEnable);
318         }
319         Util.err.fine("ModuleList.trigger: enabled new modules, flushing changes...");
320         triggered = true;
321         flushInitial();
322         ev.log(Events.PERF_END, "ModuleList.trigger"); // NOI18N
323
}
324     // XXX is this method still needed? rethink...
325
private void installNew(Set JavaDoc<Module> modules) {
326         if (modules.isEmpty()) {
327             return;
328         }
329         ev.log(Events.PERF_START, "ModuleList.installNew"); // NOI18N
330
// First suppress all autoloads.
331
Iterator JavaDoc<Module> it = modules.iterator();
332         while (it.hasNext()) {
333             Module m = it.next();
334             if (m.isAutoload() || m.isEager()) {
335                 it.remove();
336             } else if (m.isEnabled()) {
337                 // Can happen in obscure circumstances: old module A
338
// now exists again but with dependency on new module B,
339
// and a complete build was not done for A+B, so they have
340
// no existing Modules/ *.xml. In such a case B will already
341
// have been turned on when restoring A; harmless to remove
342
// it from the list here.
343
Util.err.fine("#17295 fix active for " + m.getCodeNameBase());
344                 it.remove();
345             } else if (!m.isValid()) {
346                 // Again can also happen if the user upgrades from one version
347
// of a module to another. In this case ModuleList correctly removed
348
// the old dead module from the manager's list, however it is still
349
// in the set of modules to restore.
350
Util.err.fine("#17471 fix active for " + m.getCodeNameBase());
351                 it.remove();
352             }
353         }
354         List JavaDoc<Module> toEnable = mgr.simulateEnable(modules);
355     for (Module m: toEnable) {
356             if (m.isAutoload() || m.isEager()) {
357                 continue;
358             }
359             // Quietly turn on others as well:
360
if (! modules.contains(m)) {
361                 modules.add(m);
362             }
363         }
364         Set JavaDoc<Module> missing = new HashSet JavaDoc<Module>(modules);
365         missing.removeAll(toEnable);
366         if (! missing.isEmpty()) {
367             // Include also problematic autoloads and so on needed by these modules.
368
Util.transitiveClosureModuleDependencies(mgr, missing);
369             it = missing.iterator();
370             while (it.hasNext()) {
371                 Module m = it.next();
372                 if (m.getProblems().isEmpty()) {
373                     it.remove();
374                 }
375             }
376             ev.log(Events.FAILED_INSTALL_NEW, missing);
377             modules.removeAll(missing);
378         }
379         try {
380             mgr.enable(modules);
381         } catch (InvalidException ie) {
382             Util.err.log(Level.WARNING, null, ie);
383             Module bad = ie.getModule();
384             if (bad == null) throw new IllegalStateException JavaDoc();
385             ev.log(Events.FAILED_INSTALL_NEW_UNEXPECTED, bad, ie);
386             modules.remove(bad);
387             // Try again without it. Note that some other dependent modules might
388
// then be in the missing list for the second round.
389
installNew(modules);
390         }
391         ev.log(Events.PERF_END, "ModuleList.installNew"); // NOI18N
392
}
393     
394     /** Record initial condition of a module installer.
395      * First, if any stored state of the installer was found on disk,
396      * that is if it is kept in the ModuleHistory, then deserialize
397      * it (to the found installer). If there are deserialization errors,
398      * this step is simply skipped. Or, if there was no prerecorded state,
399      * and the ModuleInstall class overrides writeExternal or a similar
400      * serialization-related method, thus indicating that it wishes to
401      * serialize state, then this initial object is serialized to the
402      * history's state for comparison with the later value. If there are
403      * problems with this serialization, the history receives a dummy state
404      * (empty bytestream) to indicate that something should be saved later.
405      * If there is no prerecorded state and no writeExternal method, it is
406      * assumed that serialization is irrelevant to this installer and so the
407      * state is left null and nothing will be done here or in the postpare method.
408      * If this installer was mentioned in externalizedModules.ser, also try to
409      * handle the situation (determine if the state was useful or not etc.).
410      * Access from NbInstaller.
411      */

412     void installPrepare(Module m, ModuleInstall inst) {
413         if (! (m.getHistory() instanceof ModuleHistory)) {
414             Util.err.fine(m + " had strange history " + m.getHistory() + ", ignoring...");
415             return;
416         }
417         ModuleHistory hist = (ModuleHistory)m.getHistory();
418         // We might have loaded something from externalizedModules.ser before.
419
byte[] compatSer = compatibilitySers.get(inst.getClass().getName());
420         if (compatSer != null) {
421             Util.err.fine("Had some old-style state for " + m);
422             if (isReallyExternalizable(inst.getClass())) {
423                 // OK, maybe it was not useless, let's see...
424
// Compare virgin state to what we had; if different, load
425
// the old state and record that we want to track state.
426
try {
427                     ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc(1000);
428                     new NbObjectOutputStream(baos).writeObject(inst);
429                     baos.close();
430                     if (Utilities.compareObjects(compatSer, baos.toByteArray())) {
431                         Util.err.fine("Old-style state for " + m + " was gratuitous");
432                         // leave hist.installerState null
433
} else {
434                         Util.err.fine("Old-style state for " + m + " was useful, loading it...");
435                         // Make sure it is recorded as "changed" in history by writing something
436
// fake now. In installPostpare, we will load the new installer state
437
// and call setInstallerState again, so the result will be written to disk.
438
hist.setInstallerState(new byte[0]);
439                         // And also load it into the actual installer.
440
InputStream JavaDoc is = new ByteArrayInputStream JavaDoc(compatSer);
441                         Object JavaDoc o = new NbObjectInputStream(is).readObject();
442                         if (o != inst) throw new ClassCastException JavaDoc("Stored " + o + " but expecting " + inst); // NOI18N
443
}
444                 } catch (Exception JavaDoc e) {
445                     Util.err.log(Level.WARNING, null, e);
446                     // Try later to continue.
447
hist.setInstallerState(new byte[0]);
448                 } catch (LinkageError JavaDoc le) {
449                     Util.err.log(Level.WARNING, null, le);
450                     // Try later to continue.
451
hist.setInstallerState(new byte[0]);
452                 }
453             } else {
454                 Util.err.fine(m + " did not want to store install state");
455                 // leave hist.installerState null
456
}
457         } else if (hist.getInstallerState() != null) {
458             // We already have some state, load it now.
459
Util.err.fine("Loading install state for " + m);
460             try {
461                 InputStream JavaDoc is = new ByteArrayInputStream JavaDoc(hist.getInstallerState());
462                 // Note: NBOOS requires the system class loader to be in order.
463
// Technically we have not yet fired any changes in it. However,
464
// assuming that we are not in the first block of modules to be
465
// loaded (that is, core = bootstraps) this will work because the
466
// available systemClassLoader is just appended to. Better would
467
// probably be to use a special ObjectInputStream resolving
468
// specifically to the module's classloader, as this is far more
469
// direct and possibly more reliable.
470
Object JavaDoc o = new NbObjectInputStream(is).readObject();
471                 // The joys of SharedClassObject. The deserialization itself actually
472
// is assumed to overwrite the state of the install singleton. We
473
// can only confirm that we actually deserialized the same thing we
474
// were expecting too (it is too late to revert anything of course).
475
if (o != inst) throw new ClassCastException JavaDoc("Stored " + o + " but expecting " + inst); // NOI18N
476
} catch (Exception JavaDoc e) {
477                 // IOException, ClassNotFoundException, and maybe unchecked stuff
478
Util.err.log(Level.WARNING, null, e);
479                 // Nothing else to do, hope that the install object was not corrupted
480
// by the failed deserialization! If it was, it cannot be saved now.
481
} catch (LinkageError JavaDoc le) {
482                 Util.err.log(Level.WARNING, null, le);
483             }
484         } else {
485             // Virgin installer. First we check if it really cares about serialization
486
// at all, because it not we do not want to waste time forcing it.
487
if (isReallyExternalizable(inst.getClass())) {
488                 Util.err.fine("Checking pre-install state of " + m);
489                 Util.err.warning("Warning: use of writeExternal (or writeReplace) in " + inst.getClass().getName() + " is deprecated; use normal settings instead");
490                 try {
491                     ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc(1000);
492                     new NbObjectOutputStream(baos).writeObject(inst);
493                     baos.close();
494                     // Keep track of the installer's state before we installed it.
495
// This will be compared to its state afterwards so we can
496
// avoid writing anything if nothing changed, thus avoid
497
// polluting the disk.
498
hist.setInstallerState(baos.toByteArray());
499                 } catch (Exception JavaDoc e) {
500                     Util.err.log(Level.WARNING, null, e);
501                     // Remember that it is *supposed* to be serializable to something.
502
hist.setInstallerState(new byte[0]);
503                 } catch (LinkageError JavaDoc le) {
504                     Util.err.log(Level.WARNING, null, le);
505                     hist.setInstallerState(new byte[0]);
506                 }
507             } else {
508                 // It does not want to store anything. Leave the installer state null
509
// and continue.
510
Util.err.fine(m + " did not want to store install state");
511             }
512         }
513     }
514     /** Check if a class (extends ModuleInstall) is truly externalizable,
515      * e.g. overrides writeExternal.
516      */

517     private static boolean isReallyExternalizable(Class JavaDoc clazz) {
518         Class JavaDoc<?> c;
519         for (c = clazz; c != ModuleInstall.class && c != Object JavaDoc.class; c = c.getSuperclass()) {
520             try {
521                 c.getDeclaredMethod("writeExternal", ObjectOutput JavaDoc.class); // NOI18N
522
// [PENDING] check that m is public, nonstatic, returns Void.TYPE and includes at most
523
// IOException and unchecked exceptions in its clauses, else die
524
// OK, it does something nontrivial.
525
return true;
526             } catch (NoSuchMethodException JavaDoc nsme) {
527                 // Didn't find it at this level, continue.
528
}
529             try {
530                 c.getDeclaredMethod("writeReplace"); // NOI18N
531
// [PENDING] check that m is nonstatic, returns Object, throws ObjectStreamException
532
// Designates a serializable replacer, this is special.
533
return true;
534             } catch (NoSuchMethodException JavaDoc nsme) {
535                 // Again keep on looking.
536
}
537         }
538         // Hit a superclass.
539
if (c == Object JavaDoc.class) throw new IllegalArgumentException JavaDoc("Class " + clazz + " was not a ModuleInstall"); // NOI18N
540
// Hit ModuleInstall. Did not find anything. Assumed to not do anything during externalization.
541
return false;
542     }
543     
544     /** Acknowledge later conditions of a module installer.
545      * If the module history indicates a nonnull installer state, then we
546      * try to serialize the current state to a temporary buffer and compare
547      * to the previous state. If the serialization succeeds, and they differ,
548      * then the new state is recorded in the history and will later be written to disk.
549      * Access from NbInstaller.
550      */

551     void installPostpare(Module m, ModuleInstall inst) {
552         if (! (m.getHistory() instanceof ModuleHistory)) {
553             Util.err.fine(m + " had strange history " + m.getHistory() + ", ignoring...");
554             return;
555         }
556         ModuleHistory hist = (ModuleHistory)m.getHistory();
557         if (hist.getInstallerState() != null) {
558             try {
559                 ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc(1000);
560                 new NbObjectOutputStream(baos).writeObject(inst);
561                 baos.close();
562                 byte[] old = hist.getInstallerState();
563                 byte[] nue = baos.toByteArray();
564                 if (Utilities.compareObjects(old, nue)) {
565                     // State has not changed.
566
Util.err.fine(m + " did not change installer state (" + old.length + " bytes), not writing anything");
567                 } else {
568                     // It did change. Store new version.
569
Util.err.fine(m + " changed installer state after loading");
570                     hist.setInstallerState(nue);
571                 }
572             } catch (Exception JavaDoc e) {
573                 Util.err.log(Level.WARNING, null, e);
574                 // We could not compare, so don't bother writing out any old state.
575
//hist.setInstallerState(null);
576
} catch (LinkageError JavaDoc le) {
577                 Util.err.log(Level.WARNING, null, le);
578             }
579         } else {
580             // Nothing stored (does not writeExternal), do nothing.
581
Util.err.fine(m + " has no saved state");
582         }
583     }
584     
585     /** Read an XML file using an XMLReader and parse into a map of properties.
586      * One distinguished property 'name' is the code name base
587      * and is taken from the root element. Others are taken
588      * from the param elements.
589      * Properties are of type String, Boolean, Integer, or SpecificationVersion
590      * according to the property name.
591      * @param is the input stream
592      * @param reader the XML reader to use to parse; may be null
593      * @return a map of named properties to values of various types
594      */

595     private Map JavaDoc<String JavaDoc,Object JavaDoc> readStatus(InputSource JavaDoc is, XMLReader JavaDoc reader) throws IOException JavaDoc, SAXException JavaDoc {
596         if (reader == null) {
597             reader = XMLUtil.createXMLReader(VALIDATE_XML);
598             reader.setEntityResolver(listener);
599             reader.setErrorHandler(listener);
600         }
601         final Map JavaDoc<String JavaDoc,Object JavaDoc> m = new HashMap JavaDoc<String JavaDoc,Object JavaDoc>();
602
603         DefaultHandler JavaDoc handler = new DefaultHandler JavaDoc() {
604             private String JavaDoc modName;
605             private String JavaDoc paramName;
606             private StringBuffer JavaDoc data = new StringBuffer JavaDoc();
607         
608             public void startElement(String JavaDoc uri,
609                                      String JavaDoc localname,
610                                      String JavaDoc qname,
611                                      Attributes JavaDoc attrs) throws SAXException JavaDoc {
612                 if ("module".equals(qname) ) { // NOI18N
613
modName = attrs.getValue("name"); // NOI18N
614
if( modName == null )
615                         throw new SAXException JavaDoc("No module name"); // NOI18N
616
m.put("name", modName.intern()); // NOI18N
617
}
618                 else if (modName != null && "param".equals(qname)) { // NOI18N
619
paramName = attrs.getValue("name");
620                     if( paramName == null ) {
621                         throw new SAXException JavaDoc("No param name"); // NOI18N
622
}
623                     paramName = paramName.intern();
624                     data.setLength(0);
625                 }
626             }
627         
628             public void characters(char[] ch, int start, int len) {
629                 if(modName != null && paramName != null)
630                     data.append( ch, start, len );
631             }
632             
633             public void endElement (String JavaDoc uri, String JavaDoc localname, String JavaDoc qname)
634                 throws SAXException JavaDoc
635             {
636                 if ("param".equals(qname)) { // NOI18N
637
if (modName != null && paramName != null) {
638                         if (data.length() == 0)
639                             throw new SAXException JavaDoc("No text contents in " + paramName + " of " + modName); // NOI18N
640

641                         try {
642                             m.put(paramName, processStatusParam(paramName, data.toString()));
643                         } catch (NumberFormatException JavaDoc nfe) {
644                             // From either Integer or SpecificationVersion constructors.
645
throw (SAXException JavaDoc) new SAXException JavaDoc(nfe.toString()).initCause(nfe);
646                         }
647
648                         data.setLength(0);
649                         paramName = null;
650                     }
651                 }
652                 else if ("module".equals(qname)) { // NOI18N
653
modName = null;
654                 }
655             }
656         };
657         
658         reader.setContentHandler(handler);
659         reader.parse(is);
660
661         sanityCheckStatus(m);
662
663         return m;
664     }
665     
666     /** Parse a param value according to a natural type.
667      * @param k the param name (must be interned!)
668      * @param v the raw string value from XML
669      * @return some parsed value suitable for the status map
670      */

671     private Object JavaDoc processStatusParam(String JavaDoc k, String JavaDoc v) throws NumberFormatException JavaDoc {
672         if (k == "release") { // NOI18N
673
return new Integer JavaDoc(v);
674         } else if (k == "enabled" // NOI18N
675
|| k == "autoload" // NOI18N
676
|| k == "eager" // NOI18N
677
|| k == "reloadable" // NOI18N
678
) {
679             return Boolean.valueOf(v);
680         } else if (k == "specversion") { // NOI18N
681
return new SpecificationVersion(v);
682         } else {
683             // Other properties are of type String.
684
// Intern the smaller ones which are likely to be repeated somewhere.
685
if (v.length() < 100) v = v.intern();
686             return v;
687         }
688     }
689     
690     /** Just checks that all the right stuff is there.
691      */

692     private void sanityCheckStatus(Map JavaDoc<String JavaDoc,Object JavaDoc> m) throws IOException JavaDoc {
693         if (m.get("jar") == null) // NOI18N
694
throw new IOException JavaDoc("Must define jar param"); // NOI18N
695
if (m.get("autoload") != null // NOI18N
696
&& ((Boolean JavaDoc)m.get("autoload")).booleanValue() // NOI18N
697
&& m.get("enabled") != null) // NOI18N
698
throw new IOException JavaDoc("Autoloads cannot specify enablement"); // NOI18N
699
if (m.get("eager") != null // NOI18N
700
&& ((Boolean JavaDoc)m.get("eager")).booleanValue() // NOI18N
701
&& m.get("enabled") != null) // NOI18N
702
throw new IOException JavaDoc("Eager modules cannot specify enablement"); // NOI18N
703
// Compatibility:
704
String JavaDoc origin = (String JavaDoc)m.remove("origin"); // NOI18N
705
if (origin != null) {
706             String JavaDoc jar = (String JavaDoc)m.get("jar"); // NOI18N
707
String JavaDoc newjar;
708             if (origin.equals("user") || origin.equals("installation")) { // NOI18N
709
newjar = "modules/" + jar; // NOI18N
710
} else if (origin.equals("user/autoload") || origin.equals("installation/autoload")) { // NOI18N
711
newjar = "modules/autoload/" + jar; // NOI18N
712
} else if (origin.equals("user/eager") || origin.equals("installation/eager")) { // NOI18N
713
newjar = "modules/eager/" + jar; // NOI18N
714
} else if (origin.equals("adhoc")) { // NOI18N
715
newjar = jar;
716             } else {
717                 throw new IOException JavaDoc("Unrecognized origin " + origin + " for " + jar); // NOI18N
718
}
719             Util.err.warning("Upgrading 'jar' param from " + jar + " to " + newjar + " and removing 'origin' " + origin);
720             m.put("jar", newjar); // NOI18N
721
}
722     }
723
724     // Encoding irrelevant for these getBytes() calls: all are ASCII...
725
// (unless someone has their system encoding set to UCS-16!)
726
private static final byte[] MODULE_XML_INTRO = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE module PUBLIC \"-//NetBeans//DTD Module Status 1.0//EN\"\n \"http://www.netbeans.org/dtds/module-status-1_0.dtd\">\n<module name=\"".getBytes(); // NOI18N
727
// private static final byte[] MODULE_XML_DIV1 = ">\n <param name=\"".getBytes(); // NOI18N
728
private static final byte[] MODULE_XML_INTRO_END = ">\n".getBytes(); // NOI18N
729
private static final byte[] MODULE_XML_DIV2 = " <param name=\"".getBytes(); // NOI18N
730
private static final byte[] MODULE_XML_DIV3 = "/param>\n".getBytes(); // NOI18N
731
private static final byte[] MODULE_XML_END = "/module>\n".getBytes(); // NOI18N
732
/** Just like {@link #readStatus(InputSource,XMLReader} but avoids using an XML parser.
733      * If it does not manage to parse it this way, it returns null, in which case
734      * you have to use a real parser.
735      * @see "#26786"
736      */

737     private Map JavaDoc<String JavaDoc, Object JavaDoc> readStatus(InputStream JavaDoc is) throws IOException JavaDoc {
738         Map JavaDoc<String JavaDoc,Object JavaDoc> m = new HashMap JavaDoc<String JavaDoc,Object JavaDoc>(15);
739         if (!expect(is, MODULE_XML_INTRO)) {
740             Util.err.fine("Could not read intro");
741             return null;
742         }
743         String JavaDoc name = readTo(is, '"');
744         if (name == null) {
745             Util.err.fine("Could not read code name base");
746             return null;
747         }
748         m.put("name", name.intern()); // NOI18N
749
if (!expect(is, MODULE_XML_INTRO_END)) {
750             Util.err.fine("Could not read stuff after cnb");
751             return null;
752         }
753         // Now we have <param>s some number of times, finally </module>.
754
PARSE:
755         while (true) {
756             int c = is.read();
757             switch (c) {
758             case ' ':
759                 // <param>
760
if (!expect(is, MODULE_XML_DIV2)) {
761                     Util.err.fine("Could not read up to param");
762                     return null;
763                 }
764                 String JavaDoc k = readTo(is, '"');
765                 if (k == null) {
766                     Util.err.fine("Could not read param");
767                     return null;
768                 }
769                 k = k.intern();
770                 if (is.read() != '>') {
771                     Util.err.fine("No > at end of <param> " + k);
772                     return null;
773                 }
774                 String JavaDoc v = readTo(is, '<');
775                 if (v == null) {
776                     Util.err.fine("Could not read value of " + k);
777                     return null;
778                 }
779                 if (!expect(is, MODULE_XML_DIV3)) {
780                     Util.err.fine("Could not read end of param " + k);
781                     return null;
782                 }
783                 try {
784                     m.put(k, processStatusParam(k, v));
785                 } catch (NumberFormatException JavaDoc nfe) {
786                     Util.err.fine("Number misparse: " + nfe);
787                     return null;
788                 }
789                 break;
790             case '<':
791                 // </module>
792
if (!expect(is, MODULE_XML_END)) {
793                     Util.err.fine("Strange ending");
794                     return null;
795                 }
796                 if (is.read() != -1) {
797                     Util.err.fine("Trailing garbage");
798                     return null;
799                 }
800                 // Success!
801
break PARSE;
802             default:
803                 Util.err.fine("Strange stuff after <param>s: " + c);
804                 return null;
805             }
806         }
807         sanityCheckStatus(m);
808         return m;
809     }
810     
811     /** Read some stuff from a stream and skip over it.
812      * Newline conventions are normalized to Unix \n.
813      * @return true upon success, false if stream contained something else
814      */

815     private boolean expect(InputStream JavaDoc is, byte[] stuff) throws IOException JavaDoc {
816         int len = stuff.length;
817         boolean inNewline = false;
818         for (int i = 0; i < len; ) {
819             int c = is.read();
820             if (c == 10 || c == 13) {
821                 // Normalize: s/[\r\n]+/\n/g
822
if (inNewline) {
823                     continue;
824                 } else {
825                     inNewline = true;
826                     c = 10;
827                 }
828             } else {
829                 inNewline = false;
830             }
831             if (c != stuff[i++]) {
832                 return false;
833             }
834         }
835         if (stuff[len - 1] == 10) {
836             // Expecting something ending in a \n - so we have to
837
// read any further \r or \n and discard.
838
if (!is.markSupported()) throw new IOException JavaDoc("Mark not supported"); // NOI18N
839
is.mark(1);
840             int c = is.read();
841             if (c != -1 && c != 10 && c != 13) {
842                 // Got some non-newline character, push it back!
843
is.reset();
844             }
845         }
846         return true;
847     }
848     /** Read a maximal string until delim is encountered (which will be removed from stream).
849      * This impl reads only ASCII, for speed.
850      * Newline conventions are normalized to Unix \n.
851      * @return the read string, or null if the delim is not encountered before EOF.
852      */

853     private String JavaDoc readTo(InputStream JavaDoc is, char delim) throws IOException JavaDoc {
854         if (delim == 10) {
855             // Not implemented - stream might have "foo\r\n" and we would
856
// return "foo" and leave "\n" in the stream.
857
throw new IOException JavaDoc("Not implemented"); // NOI18N
858
}
859         CharArrayWriter JavaDoc caw = new CharArrayWriter JavaDoc(100);
860         boolean inNewline = false;
861         while (true) {
862             int c = is.read();
863             if (c == -1) return null;
864             if (c > 126) return null;
865             if (c == 10 || c == 13) {
866                 // Normalize: s/[\r\n]+/\n/g
867
if (inNewline) {
868                     continue;
869                 } else {
870                     inNewline = true;
871                     c = 10;
872                 }
873             } else if (c < 32 && c != 9) {
874                 // Random control character!
875
return null;
876             } else {
877                 inNewline = false;
878             }
879             if (c == delim) {
880                 return caw.toString();
881             } else {
882                 caw.write(c);
883             }
884         }
885     }
886     
887     /** Write a module's status to disk in the form of an XML file.
888      * The map of parameters must contain one named 'name' with the code
889      * name base of the module.
890      */

891     private void writeStatus(Map JavaDoc<String JavaDoc, Object JavaDoc> m, OutputStream JavaDoc os) throws IOException JavaDoc {
892         String JavaDoc codeName = (String JavaDoc)m.get("name"); // NOI18N
893
if (codeName == null)
894             throw new IllegalArgumentException JavaDoc("no code name present"); // NOI18N
895

896         Writer JavaDoc w = new OutputStreamWriter JavaDoc(os, "UTF-8"); // NOI18N
897
w.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); // NOI18N
898
w.write("<!DOCTYPE module PUBLIC \""); // NOI18N
899
w.write(PUBLIC_ID);
900         w.write("\"\n \""); // NOI18N
901
w.write(SYSTEM_ID);
902         w.write("\">\n"); // NOI18N
903
w.write("<module name=\""); // NOI18N
904
w.write(XMLUtil.toAttributeValue(codeName)); // NOI18N
905
w.write("\">\n"); // NOI18N
906

907         // Use TreeMap to sort the keys by name; since the module status files might
908
// be version-controlled we want to avoid gratuitous format changes.
909
for (Map.Entry JavaDoc<String JavaDoc, Object JavaDoc> entry: new TreeMap JavaDoc<String JavaDoc, Object JavaDoc>(m).entrySet()) {
910             String JavaDoc name = entry.getKey();
911             if (name.equals("installerState") || name.equals("name")) { // NOI18N
912
// Skip this one, it is a pseudo-param.
913
continue;
914             }
915             
916             Object JavaDoc val = entry.getValue();
917
918             w.write(" <param name=\""); // NOI18N
919
w.write(XMLUtil.toAttributeValue(name)); // NOI18N
920
w.write("\">"); // NOI18N
921
w.write(XMLUtil.toElementContent(val.toString()));
922             w.write("</param>\n"); // NOI18N
923
}
924
925         w.write("</module>\n"); // NOI18N
926
w.flush();
927     }
928     
929     /** Write information about a module out to disk.
930      * If the old status is given as null, this is a newly
931      * added module; create an appropriate status and return it.
932      * Else update the existing status and return it (it is
933      * assumed properties are already updated).
934      * Should write the XML and create/rewrite/delete the serialized
935      * installer file as needed.
936      */

937     private DiskStatus writeOut(Module m, DiskStatus old) throws IOException JavaDoc {
938         final DiskStatus nue;
939         if (old == null) {
940             nue = new DiskStatus();
941             nue.module = m;
942             nue.diskProps = computeProperties(m);
943         } else {
944             nue = old;
945         }
946         FileSystem.AtomicAction aa = new FileSystem.AtomicAction() {
947             public void run() throws IOException JavaDoc {
948                 if (nue.file == null) {
949                     nue.file = folder.createData(((String JavaDoc)nue.diskProps.get("name")).replace('.', '-'), "xml"); // NOI18N
950
} else {
951                     // Just verify that no one else touched it since we last did.
952
if (/*nue.lastApprovedChange != nue.file.lastModified().getTime()*/nue.dirty) {
953                         // Oops, something is wrong.
954
// XXX should this only warn instead?
955
throw new IOException JavaDoc("Will not clobber external changes in " + nue.file); // NOI18N
956
}
957                 }
958                 Util.err.fine("ModuleList: (re)writing " + nue.file);
959                 FileLock lock = nue.file.lock();
960                 try {
961                     OutputStream JavaDoc os = nue.file.getOutputStream(lock);
962                     try {
963                         writeStatus(nue.diskProps, os);
964                     } finally {
965                         os.close();
966                     }
967                 } finally {
968                     lock.releaseLock();
969                 }
970                 //nue.lastApprovedChange = nue.file.lastModified().getTime();
971
// Now check up on the installer ser.
972
byte[] data = (byte[])nue.diskProps.get("installerState"); // NOI18N
973
if (data != null) {
974                     String JavaDoc installerName = (String JavaDoc)nue.diskProps.get("installer"); // NOI18N
975
FileObject ser = folder.getFileObject(installerName);
976                     if (ser == null) {
977                         // Need to make it.
978
int idx = installerName.lastIndexOf('.'); // NOI18N
979
ser = folder.createData(installerName.substring(0, idx), installerName.substring(idx + 1));
980                     }
981                     // Now write it.
982
lock = ser.lock();
983                     try {
984                         OutputStream JavaDoc os = ser.getOutputStream(lock);
985                         try {
986                             os.write(data);
987                         } finally {
988                             os.close();
989                         }
990                     } finally {
991                         lock.releaseLock();
992                     }
993                 } else {
994                     /* Probably not right:
995                     // Delete any existing one.
996                     if (ser != null) {
997                         ser.delete();
998                     }
999                      */

1000                }
1001            }
1002        };
1003        myAtomicActions.add(aa);
1004        folder.getFileSystem().runAtomicAction(aa);
1005        return nue;
1006    }
1007    
1008    /** Delete a module from disk.
1009     */

1010    private void deleteFromDisk(final Module m, final DiskStatus status) throws IOException JavaDoc {
1011        final String JavaDoc nameDashes = m.getCodeNameBase().replace('.', '-'); // NOI18N
1012
//final long expectedTime = status.lastApprovedChange;
1013
FileSystem.AtomicAction aa = new FileSystem.AtomicAction() {
1014            public void run() throws IOException JavaDoc {
1015                FileObject xml = folder.getFileObject(nameDashes, "xml"); // NOI18N
1016
if (xml == null) {
1017                    // Could be that the XML was already deleted externally, etc.
1018
Util.err.fine("ModuleList: " + m + "'s XML already gone from disk");
1019                    return;
1020                }
1021                //if (xml == null) throw new IOException("No such XML file: " + nameDashes + ".xml"); // NOI18N
1022
if (status.dirty) {
1023                    // Someone wrote to the file since we did. Don't delete it blindly!
1024
// XXX should this throw an exception, or just warn??
1025
throw new IOException JavaDoc("Unapproved external change to " + xml); // NOI18N
1026
}
1027                Util.err.fine("ModuleList: deleting " + xml);
1028                /*
1029                if (xml.lastModified().getTime() != expectedTime) {
1030                    // Someone wrote to the file since we did. Don't delete it blindly!
1031                    throw new IOException("Unapproved external change to " + xml); // NOI18N
1032                }
1033                 */

1034                xml.delete();
1035                FileObject ser = folder.getFileObject(nameDashes, "ser"); // NOI18N
1036
if (ser != null) {
1037                    Util.err.fine("(and also " + ser + ")");
1038                    ser.delete();
1039                }
1040            }
1041        };
1042        myAtomicActions.add(aa);
1043        folder.getFileSystem().runAtomicAction(aa);
1044    }
1045    
1046    /** Flush the initial state of the module installer after startup to disk.
1047     * This means:
1048     * 1. Find all modules in the manager.
1049     * 2. Anything for which we have no status, write out its XML now
1050     * and create a status object for it.
1051     * 3. Anything for which we have a status, compare the status we
1052     * have to its current state (don't forget the installer
1053     * serialization state--if this is nonnull, that counts as an
1054     * automatic change because it means the module was loaded and
1055     * needed to store something).
1056     * 4. For any changes found in 3., write out new XML (and if
1057     * there is any installer state, a new installer ser).
1058     * 5. Attach listeners to the manager and all modules to catch further
1059     * changes in the system so they may be flushed.
1060     * We could in principle start listening right after readInitial()
1061     * but it should be more efficient to wait and see what has really
1062     * changed. Also, some XML may say that a module is enabled, and in
1063     * fact trigger() was not able to turn it on. In that case, this will
1064     * show up as a change in step 3. and we will rewrite it as disabled.
1065     * Called within write mutex by trigger().
1066     */

1067    private void flushInitial() {
1068        Util.err.fine("Flushing initial module list...");
1069        // Find all modules for which we have status already. Treat
1070
// them as possibly changed, and attach listeners.
1071
Iterator JavaDoc it = mgr.getModules().iterator();
1072        while (it.hasNext()) {
1073            Module m = (Module)it.next();
1074            DiskStatus status = statuses.get(m.getCodeNameBase());
1075            if (status != null) {
1076                moduleChanged(m, status);
1077                m.addPropertyChangeListener(listener);
1078            }
1079        }
1080        // Now find all new and deleted modules.
1081
moduleListChanged();
1082        // And listener for new or deleted modules.
1083
mgr.addPropertyChangeListener(listener);
1084    }
1085    
1086    /** Does the real work when the list of modules changes.
1087     * Finds newly added modules, creates XML and status for
1088     * them and begins listening for changes; finds deleted
1089     * modules, removes their listener, XML, and status.
1090     * May be called within read or write mutex; since it
1091     * could be in the read mutex, synchronize (on statuses).
1092     */

1093    private void moduleListChanged() {
1094        synchronized (statuses) {
1095            if (Util.err.isLoggable(Level.FINE)) {
1096                Util.err.fine("ModuleList: moduleListChanged; statuses=" + statuses);
1097            }
1098            // Newly added modules first.
1099
for (Module m : mgr.getModules()) {
1100                if (m.isFixed() || m.getAllJars().isEmpty()) {
1101                    // No way, we don't manage these.
1102
continue;
1103                }
1104                final String JavaDoc name = m.getCodeNameBase();
1105                if (statuses.get(name) == null) {
1106                    // Yup, it's new. Write it out.
1107
Util.err.fine("moduleListChanged: added: " + m);
1108                    try {
1109                        statuses.put(name, writeOut(m, null));
1110                        m.addPropertyChangeListener(listener);
1111                    } catch (IOException JavaDoc ioe) {
1112                        Util.err.log(Level.WARNING, null, ioe);
1113                        // XXX Now what? Keep it in our list or what??
1114
}
1115                }
1116            }
1117            // Now deleted & recreated modules.
1118
Iterator JavaDoc<DiskStatus> it = statuses.values().iterator();
1119            while (it.hasNext()) {
1120                DiskStatus status = it.next();
1121                if (! status.module.isValid()) {
1122                    status.module.removePropertyChangeListener(listener);
1123                    Module nue = mgr.get(status.module.getCodeNameBase());
1124                    if (nue != null) {
1125                        // Deleted, but a new module with the same code name base
1126
// was created (#5922 e.g.). So change the module reference
1127
// in the status and write out any changes to disk.
1128
Util.err.fine("moduleListChanged: recreated: " + nue);
1129                        nue.addPropertyChangeListener(listener);
1130                        status.module = nue;
1131                        moduleChanged(nue, status);
1132                    } else {
1133                        // Newly deleted.
1134
Util.err.fine("moduleListChanged: deleted: " + status.module);
1135                        it.remove();
1136                        try {
1137                            deleteFromDisk(status.module, status);
1138                        } catch (IOException JavaDoc ioe) {
1139                            Util.err.log(Level.WARNING, null, ioe);
1140                        }
1141                    }
1142                }
1143            }
1144        }
1145    }
1146    
1147    /** Does the real work when one module changes.
1148     * Compares old and new state and writes XML
1149     * (and perhaps serialized installer state) as needed.
1150     * May be called within read or write mutex; since it
1151     * could be in the read mutex, synchronize (on status).
1152     */

1153    private void moduleChanged(Module m, DiskStatus status) {
1154        synchronized (status) {
1155            if (Util.err.isLoggable(Level.FINE)) {
1156                Util.err.fine("ModuleList: moduleChanged: " + m);
1157            }
1158            Map JavaDoc<String JavaDoc,Object JavaDoc> newProps = computeProperties(m);
1159            if (! Utilities.compareObjects(status.diskProps, newProps)) {
1160                if (Util.err.isLoggable(Level.FINE)) {
1161                    Set JavaDoc<Map.Entry JavaDoc<String JavaDoc,Object JavaDoc>> changes = new HashSet JavaDoc<Map.Entry JavaDoc<String JavaDoc,Object JavaDoc>>(newProps.entrySet());
1162                    changes.removeAll(status.diskProps.entrySet());
1163                    Util.err.fine("ModuleList: changes are " + changes);
1164                }
1165                // We need to write changes.
1166
status.diskProps = newProps;
1167                try {
1168                    writeOut(m, status);
1169                } catch (IOException JavaDoc ioe) {
1170                    Util.err.log(Level.WARNING, null, ioe);
1171                    // XXX now what? continue to manage it anyway?
1172
}
1173            }
1174        }
1175    }
1176    
1177    /** Compute what properties we would want to store in XML
1178     * for this module. I.e. 'name', 'reloadable', etc.
1179     * The special property 'installerState' may be set (only
1180     * if the normal property 'installer' is also set) and
1181     * will be a byte[] rather than a string, which means that
1182     * the indicated installer state should be written out.
1183     */

1184    private Map JavaDoc<String JavaDoc,Object JavaDoc> computeProperties(Module m) {
1185        if (m.isFixed() || ! m.isValid()) throw new IllegalArgumentException JavaDoc("fixed or invalid: " + m); // NOI18N
1186
if (! (m.getHistory() instanceof ModuleHistory)) throw new IllegalArgumentException JavaDoc("weird history: " + m); // NOI18N
1187
Map JavaDoc<String JavaDoc,Object JavaDoc> p = new HashMap JavaDoc<String JavaDoc,Object JavaDoc>();
1188        p.put("name", m.getCodeNameBase()); // NOI18N
1189
int rel = m.getCodeNameRelease();
1190        if (rel >= 0) {
1191            p.put("release", new Integer JavaDoc(rel)); // NOI18N
1192
}
1193        SpecificationVersion spec = m.getSpecificationVersion();
1194        if (spec != null) {
1195            p.put("specversion", spec); // NOI18N
1196
}
1197        if (!m.isAutoload() && !m.isEager()) {
1198            p.put("enabled", m.isEnabled() ? Boolean.TRUE : Boolean.FALSE); // NOI18N
1199
}
1200        p.put("autoload", m.isAutoload() ? Boolean.TRUE : Boolean.FALSE); // NOI18N
1201
p.put("eager", m.isEager() ? Boolean.TRUE : Boolean.FALSE); // NOI18N
1202
p.put("reloadable", m.isReloadable() ? Boolean.TRUE : Boolean.FALSE); // NOI18N
1203
ModuleHistory hist = (ModuleHistory)m.getHistory();
1204        p.put("jar", hist.getJar()); // NOI18N
1205
if (hist.getInstallerStateChanged()) {
1206            p.put("installer", m.getCodeNameBase().replace('.', '-') + ".ser"); // NOI18N
1207
p.put("installerState", hist.getInstallerState()); // NOI18N
1208
}
1209        return p;
1210    }
1211    
1212    private static RequestProcessor rpListener = null;
1213    /** Listener for changes in set of modules and various properties of individual modules.
1214     * Also serves as a strict error handler for XML parsing.
1215     * Also listens to changes in the Modules/ folder and processes them in req proc.
1216     */

1217    private final class Listener implements PropertyChangeListener JavaDoc, ErrorHandler JavaDoc, EntityResolver JavaDoc, FileChangeListener, Runnable JavaDoc {
1218        
1219        Listener() {}
1220        
1221        // Property change coming from ModuleManager or some known Module.
1222

1223        private boolean listening = true;
1224        public void propertyChange(PropertyChangeEvent JavaDoc evt) {
1225            if (! triggered) throw new IllegalStateException JavaDoc("Property change before trigger()"); // NOI18N
1226
// REMEMBER this is inside *read* mutex, it is forbidden to even attempt
1227
// to get write access synchronously here!
1228
String JavaDoc prop = evt.getPropertyName();
1229            Object JavaDoc src = evt.getSource();
1230            if (!listening) {
1231                // #27106: do not react to our own changes while we are making them
1232
if (Util.err.isLoggable(Level.FINE)) {
1233                    Util.err.fine("ModuleList: ignoring own change " + prop + " from " + src);
1234                }
1235                return;
1236            }
1237            if (ModuleManager.PROP_CLASS_LOADER.equals(prop) ||
1238                    ModuleManager.PROP_ENABLED_MODULES.equals(prop) ||
1239                    Module.PROP_CLASS_LOADER.equals(prop) ||
1240                    Module.PROP_PROBLEMS.equals(prop) ||
1241                    Module.PROP_VALID.equals(prop)) {
1242                // Properties we are not directly interested in, ignore.
1243
// Note that rather than paying attention to PROP_VALID
1244
// we simply deal with deletions when PROP_MODULES is fired.
1245
return;
1246            } else if (ModuleManager.PROP_MODULES.equals(prop)) {
1247                moduleListChanged();
1248            } else if (src instanceof Module) {
1249                // enabled, manifest, reloadable, possibly other stuff in the future
1250
Module m = (Module)src;
1251                if (! m.isValid()) {
1252                    // Skip it. We will get PROP_MODULES sometime anyway.
1253
return;
1254                }
1255                DiskStatus status = statuses.get(m.getCodeNameBase());
1256                if (status == null) {
1257                    throw new IllegalStateException JavaDoc("Unknown module " + m + "; statuses=" + statuses); // NOI18N
1258
}
1259                if (status.pendingInstall && Module.PROP_ENABLED.equals(prop)) {
1260                    throw new IllegalStateException JavaDoc("Got PROP_ENABLED on " + m + " before trigger()"); // NOI18N
1261
}
1262                moduleChanged(m, status);
1263            } else {
1264                Util.err.fine("Unexpected property change: " + evt + " prop=" + prop + " SRC=" + src);
1265            }
1266        }
1267        
1268        // SAX stuff.
1269

1270        public void warning(SAXParseException JavaDoc e) throws SAXException JavaDoc {
1271            Util.err.log(Level.WARNING, null, e);
1272        }
1273        public void error(SAXParseException JavaDoc e) throws SAXException JavaDoc {
1274            throw e;
1275        }
1276        public void fatalError(SAXParseException JavaDoc e) throws SAXException JavaDoc {
1277            throw e;
1278        }
1279        public InputSource JavaDoc resolveEntity(String JavaDoc pubid, String JavaDoc sysid) throws SAXException JavaDoc, IOException JavaDoc {
1280            if (pubid.equals(PUBLIC_ID)) {
1281                if (VALIDATE_XML) {
1282                    // We certainly know where to get this from.
1283
return new InputSource JavaDoc(ModuleList.class.getResource("module-status-1_0.dtd").toExternalForm()); // NOI18N
1284
} else {
1285                    // Not validating, don't load any DTD! Significantly faster.
1286
return new InputSource JavaDoc(new ByteArrayInputStream JavaDoc(new byte[0]));
1287                }
1288            } else {
1289                // Otherwise try the standard places.
1290
return EntityCatalog.getDefault().resolveEntity(pubid, sysid);
1291            }
1292        }
1293        
1294        // Changes in Modules/ folder.
1295

1296        public void fileDeleted(FileEvent ev) {
1297            if (isOurs(ev)) {
1298                if (Util.err.isLoggable(Level.FINE)) {
1299                    Util.err.fine("ModuleList: got expected deletion " + ev);
1300                }
1301                return;
1302            }
1303            FileObject fo = ev.getFile();
1304            fileDeleted0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
1305        }
1306        
1307        public void fileDataCreated(FileEvent ev) {
1308            if (isOurs(ev)) {
1309                if (Util.err.isLoggable(Level.FINE)) {
1310                    Util.err.fine("ModuleList: got expected creation " + ev);
1311                }
1312                return;
1313            }
1314            FileObject fo = ev.getFile();
1315            fileCreated0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
1316        }
1317        
1318        public void fileRenamed(FileRenameEvent ev) {
1319            if (isOurs(ev)) {
1320                throw new IllegalStateException JavaDoc("I don't rename anything! " + ev); // NOI18N
1321
}
1322            FileObject fo = ev.getFile();
1323            fileDeleted0(ev.getName(), ev.getExt()/*, ev.getTime()*/);
1324            fileCreated0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
1325        }
1326        
1327        private void fileCreated0(String JavaDoc name, String JavaDoc ext/*, long time*/) {
1328            if ("xml".equals(ext)) { // NOI18N
1329
String JavaDoc codenamebase = name.replace('-', '.');
1330                DiskStatus status = statuses.get(codenamebase);
1331                Util.err.fine("ModuleList: outside file creation event for " + codenamebase);
1332                if (status != null) {
1333                    // XXX should this really happen??
1334
status.dirty = true;
1335                }
1336                runme();
1337            } else if ("ser".equals(ext)) { // NOI18N
1338
// XXX handle newly added installers?? or not
1339
} // else ignore
1340
}
1341        
1342        private void fileDeleted0(String JavaDoc name, String JavaDoc ext/*, long time*/) {
1343            if ("xml".equals(ext)) { // NOI18N
1344
// Removed module.
1345
String JavaDoc codenamebase = name.replace('-', '.');
1346                DiskStatus status = statuses.get(codenamebase);
1347                Util.err.fine("ModuleList: outside file deletion event for " + codenamebase);
1348                if (status != null) {
1349                    // XXX should this ever happen?
1350
status.dirty = true;
1351                }
1352                runme();
1353            } else if ("ser".equals(ext)) { // NOI18N
1354
// XXX handle newly deleted installers?? or not
1355
} // else ignore
1356
}
1357        
1358        public void fileChanged(FileEvent ev) {
1359            if (isOurs(ev)) {
1360                if (Util.err.isLoggable(Level.FINE)) {
1361                    Util.err.fine("ModuleList: got expected modification " + ev);
1362                }
1363                return;
1364            }
1365            FileObject fo = ev.getFile();
1366            String JavaDoc name = fo.getName();
1367            String JavaDoc ext = fo.getExt();
1368            if ("xml".equals(ext)) { // NOI18N
1369
// Changed module.
1370
String JavaDoc codenamebase = name.replace('-', '.');
1371                DiskStatus status = statuses.get(codenamebase);
1372                Util.err.fine("ModuleList: outside file modification event for " + codenamebase + ": " + ev);
1373                if (status != null) {
1374                    status.dirty = true;
1375                } else {
1376                    // XXX should this ever happen?
1377
}
1378                runme();
1379            } else if ("ser".equals(ext)) { // NOI18N
1380
// XXX handle changes of installers?? or not
1381
} // else ignore
1382
}
1383        
1384        public void fileFolderCreated(FileEvent ev) {
1385            // ignore
1386
}
1387        public void fileAttributeChanged(FileAttributeEvent ev) {
1388            // ignore
1389
}
1390        
1391        /** Check if a given file event in the Modules/ folder was a result
1392         * of our own manipulations, as opposed to some other code (or polled
1393         * refresh) manipulating one of these XML files. See #15573.
1394         */

1395        private boolean isOurs(FileEvent ev) {
1396            Iterator JavaDoc it = myAtomicActions.iterator();
1397            while (it.hasNext()) {
1398                if (ev.firedFrom((FileSystem.AtomicAction)it.next())) {
1399                    return true;
1400                }
1401            }
1402            return false;
1403        }
1404        
1405        // Dealing with changes in Modules/ folder and processing them.
1406

1407        private boolean pendingRun = false;
1408        private synchronized void runme() {
1409            if (! pendingRun) {
1410                pendingRun = true;
1411                if (rpListener == null) {
1412                    rpListener = new RequestProcessor("org.netbeans.core.modules.ModuleList.Listener"); // NOI18N
1413
}
1414                rpListener.post(this);
1415            }
1416        }
1417        public void run() {
1418            synchronized (this) {
1419                pendingRun = false;
1420            }
1421            Util.err.fine("ModuleList: will process outstanding external XML changes");
1422            mgr.mutexPrivileged().enterWriteAccess();
1423            try {
1424                folder.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
1425                    public void run() throws IOException JavaDoc {
1426                        // 1. For any dirty XML for which status exists but reloadable differs from XML: change.
1427
// 2. For any XML for which we have no status: create & create status, as disabled.
1428
// 3. For all dirty XML which says enabled but status says disabled: batch-enable as possible.
1429
// (Where not possible, mark disabled in XML??)
1430
// 4. For all dirty XML which says disabled but status says enabled: batch-disable plus others.
1431
// 5. For all status for which no XML exists: batch-disable plus others, then delete.
1432
// 6. For any dirty XML for which jar/autoload/eager/release/specversion differs from
1433
// actual state of module: warn but do nothing.
1434
// 7. For now, ignore any changes in *.ser.
1435
// 8. For any dirty XML for which status now exists: replace diskProps with contents of XML.
1436
// 9. Mark all statuses clean.
1437
// Code name to module XMLs found on disk:
1438
Map JavaDoc<String JavaDoc,FileObject> xmlfiles = prepareXMLFiles();
1439                        // Code name to properties for dirty XML or XML sans status only.
1440
Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> dirtyprops = prepareDirtyProps(xmlfiles);
1441                        // #27106: do not listen to changes we ourselves produce.
1442
// It only matters if statuses has not been updated before
1443
// the changes are fired.
1444
listening = false;
1445                        try {
1446                            stepCheckReloadable(dirtyprops);
1447                            stepCreate(xmlfiles, dirtyprops);
1448                            stepEnable(dirtyprops);
1449                            stepDisable(dirtyprops);
1450                            stepDelete(xmlfiles);
1451                            stepCheckMisc(dirtyprops);
1452                            stepCheckSer(xmlfiles, dirtyprops);
1453                        } finally {
1454                            listening = true;
1455                            stepUpdateProps(dirtyprops);
1456                            stepMarkClean();
1457                        }
1458                    }
1459                });
1460                Util.err.fine("ModuleList: finished processing outstanding external XML changes");
1461            } catch (IOException JavaDoc ioe) {
1462                Util.err.log(Level.WARNING, null, ioe);
1463            } finally {
1464                mgr.mutexPrivileged().exitWriteAccess();
1465            }
1466        }
1467        // All the steps called from the run() method to handle disk changes:
1468
private Map JavaDoc<String JavaDoc,FileObject> prepareXMLFiles() {
1469            Util.err.fine("ModuleList: prepareXMLFiles");
1470            Map JavaDoc<String JavaDoc,FileObject> xmlfiles = new HashMap JavaDoc<String JavaDoc,FileObject>(100);
1471            FileObject[] kids = folder.getChildren();
1472            for (int i = 0; i < kids.length; i++) {
1473                if (kids[i].hasExt("xml")) { // NOI18N
1474
xmlfiles.put(kids[i].getName().replace('-', '.'), kids[i]);
1475                }
1476            }
1477            return xmlfiles;
1478        }
1479        private Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> prepareDirtyProps(Map JavaDoc<String JavaDoc,FileObject> xmlfiles) throws IOException JavaDoc {
1480            Util.err.fine("ModuleList: prepareDirtyProps");
1481            Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> dirtyprops = new HashMap JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>>(100);
1482            Iterator JavaDoc<Map.Entry JavaDoc<String JavaDoc,FileObject>> it = xmlfiles.entrySet().iterator();
1483            while (it.hasNext()) {
1484                Map.Entry JavaDoc<String JavaDoc,FileObject> entry = it.next();
1485                String JavaDoc cnb = entry.getKey();
1486                DiskStatus status = statuses.get(cnb);
1487                if (status == null || status.dirty) {
1488                    FileObject xmlfile = entry.getValue();
1489                    if (xmlfile == null || ! xmlfile.canRead ()) {
1490                        continue;
1491                    }
1492                    InputStream JavaDoc is = xmlfile.getInputStream();
1493                    try {
1494                        InputSource JavaDoc src = new InputSource JavaDoc(is);
1495                        src.setSystemId(xmlfile.getURL().toString());
1496                        try {
1497                            dirtyprops.put(cnb, readStatus(src, null));
1498                        } catch (SAXException JavaDoc saxe) {
1499                            throw (IOException JavaDoc) new IOException JavaDoc(saxe.toString()).initCause(saxe);
1500                        }
1501                    } finally {
1502                        is.close();
1503                    }
1504                }
1505            }
1506            return dirtyprops;
1507        }
1508        private void stepCheckReloadable(Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> dirtyprops) {
1509            Util.err.fine("ModuleList: stepCheckReloadable");
1510            Iterator JavaDoc<Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>>> it = dirtyprops.entrySet().iterator();
1511            while (it.hasNext()) {
1512                Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> entry = it.next();
1513                String JavaDoc cnb = entry.getKey();
1514                DiskStatus status = statuses.get(cnb);
1515                if (status != null) {
1516                    Map JavaDoc<String JavaDoc,Object JavaDoc> props = entry.getValue();
1517                    Boolean JavaDoc diskReloadableB = (Boolean JavaDoc)props.get("reloadable"); // NOI18N
1518
boolean diskReloadable = (diskReloadableB != null ? diskReloadableB.booleanValue() : false);
1519                    boolean memReloadable = status.module.isReloadable();
1520                    if (memReloadable != diskReloadable) {
1521                        Util.err.fine("Disk change in reloadable for " + cnb + " from " + memReloadable + " to " + diskReloadable);
1522                        status.module.setReloadable(diskReloadable);
1523                    }
1524                }
1525            }
1526        }
1527        private void stepCreate(Map JavaDoc<String JavaDoc,FileObject> xmlfiles, Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> dirtyprops) throws IOException JavaDoc {
1528            Util.err.fine("ModuleList: stepCreate");
1529            Iterator JavaDoc<Map.Entry JavaDoc<String JavaDoc,FileObject>> it = xmlfiles.entrySet().iterator();
1530            while (it.hasNext()) {
1531                Map.Entry JavaDoc<String JavaDoc,FileObject> entry = it.next();
1532                String JavaDoc cnb = entry.getKey();
1533                if (! statuses.containsKey(cnb)) {
1534                    FileObject xmlfile = entry.getValue();
1535                    Map JavaDoc<String JavaDoc, Object JavaDoc> props = dirtyprops.get(cnb);
1536                    if (! cnb.equals(props.get("name"))) throw new IOException JavaDoc("Code name mismatch"); // NOI18N
1537
String JavaDoc jar = (String JavaDoc)props.get("jar"); // NOI18N
1538
File JavaDoc jarFile = findJarByName(jar, cnb);
1539                    Boolean JavaDoc reloadableB = (Boolean JavaDoc)props.get("reloadable"); // NOI18N
1540
boolean reloadable = (reloadableB != null ? reloadableB.booleanValue() : false);
1541                    Boolean JavaDoc autoloadB = (Boolean JavaDoc)props.get("autoload"); // NOI18N
1542
boolean autoload = (autoloadB != null ? autoloadB.booleanValue() : false);
1543                    Boolean JavaDoc eagerB = (Boolean JavaDoc)props.get("eager"); // NOI18N
1544
boolean eager = (eagerB != null ? eagerB.booleanValue() : false);
1545                    Module m;
1546                    try {
1547                        m = mgr.create(jarFile, new ModuleHistory(jar), reloadable, autoload, eager);
1548                    } catch (DuplicateException dupe) {
1549                        // XXX should this be tolerated somehow? In case the original is
1550
// in fact scheduled for deletion anyway?
1551
throw (IOException JavaDoc) new IOException JavaDoc(dupe.toString()).initCause(dupe);
1552                    }
1553                    m.addPropertyChangeListener(this);
1554                    // Mark the status as disabled for the moment, so in step 3 it will be turned on
1555
// if in dirtyprops it was marked enabled.
1556
Map JavaDoc<String JavaDoc, Object JavaDoc> statusProps;
1557                    if (props.get("enabled") != null && ((Boolean JavaDoc)props.get("enabled")).booleanValue()) { // NOI18N
1558
statusProps = new HashMap JavaDoc<String JavaDoc, Object JavaDoc>(props);
1559                        statusProps.put("enabled", Boolean.FALSE); // NOI18N
1560
} else {
1561                        statusProps = props;
1562                    }
1563                    DiskStatus status = new DiskStatus();
1564                    status.module = m;
1565                    status.file = xmlfile;
1566                    status.diskProps = statusProps;
1567                    statuses.put(cnb, status);
1568                }
1569            }
1570        }
1571        private void stepEnable(Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> dirtyprops) throws IOException JavaDoc {
1572            Util.err.fine("ModuleList: stepEnable");
1573            Set JavaDoc<Module> toenable = new HashSet JavaDoc<Module>();
1574            Iterator JavaDoc<Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>>> it = dirtyprops.entrySet().iterator();
1575            while (it.hasNext()) {
1576                Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> entry = it.next();
1577                String JavaDoc cnb = entry.getKey();
1578                Map JavaDoc<String JavaDoc, Object JavaDoc> props = entry.getValue();
1579                if (props.get("enabled") != null && ((Boolean JavaDoc)props.get("enabled")).booleanValue()) { // NOI18N
1580
DiskStatus status = statuses.get(cnb);
1581                    if (status.diskProps.get("enabled") == null || ! ((Boolean JavaDoc)status.diskProps.get("enabled")).booleanValue()) { // NOI18N
1582
if (status.module.isEnabled()) throw new IllegalStateException JavaDoc("Already enabled: " + status.module); // NOI18N
1583
toenable.add(status.module);
1584                    }
1585                }
1586            }
1587            installNew(toenable);
1588        }
1589        private void stepDisable(Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> dirtyprops) throws IOException JavaDoc {
1590            Util.err.fine("ModuleList: stepDisable");
1591            Set JavaDoc<Module> todisable = new HashSet JavaDoc<Module>();
1592            for (Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> entry: dirtyprops.entrySet()) {
1593                String JavaDoc cnb = entry.getKey();
1594                Map JavaDoc<String JavaDoc, Object JavaDoc> props = entry.getValue();
1595                if (props.get("enabled") == null || ! ((Boolean JavaDoc)props.get("enabled")).booleanValue()) { // NOI18N
1596
DiskStatus status = statuses.get(cnb);
1597                    if (status.diskProps.get("enabled") != null && ((Boolean JavaDoc)status.diskProps.get("enabled")).booleanValue()) { // NOI18N
1598
if (! status.module.isEnabled()) throw new IllegalStateException JavaDoc("Already disabled: " + status.module); // NOI18N
1599
todisable.add(status.module);
1600                    }
1601                }
1602            }
1603            if (todisable.isEmpty()) {
1604                return;
1605            }
1606            List JavaDoc<Module> reallydisable = mgr.simulateDisable(todisable);
1607        for (Module m: reallydisable) {
1608                if (!m.isAutoload() && !m.isEager() && !todisable.contains(m)) {
1609                    todisable.add(m);
1610                }
1611            }
1612            mgr.disable(todisable);
1613        }
1614        private void stepDelete(Map JavaDoc<String JavaDoc,FileObject> xmlfiles) throws IOException JavaDoc {
1615            Util.err.fine("ModuleList: stepDelete");
1616            Set JavaDoc<Module> todelete = new HashSet JavaDoc<Module>();
1617            Iterator JavaDoc<Map.Entry JavaDoc<String JavaDoc,DiskStatus>> it = statuses.entrySet().iterator();
1618            while (it.hasNext()) {
1619                Map.Entry JavaDoc<String JavaDoc,DiskStatus> entry = it.next();
1620                String JavaDoc cnb = entry.getKey();
1621                DiskStatus status = entry.getValue();
1622                if (! xmlfiles.containsKey(cnb)) {
1623                    Module m = status.module;
1624                    todelete.add(m);
1625                    it.remove();
1626                }
1627            }
1628            if (todelete.isEmpty()) {
1629                return;
1630            }
1631            Set JavaDoc<Module> todisable = new HashSet JavaDoc<Module>();
1632        for (Module m: todelete) {
1633                if (m.isEnabled() && !m.isAutoload() && !m.isEager()) {
1634                    todisable.add(m);
1635                }
1636            }
1637            List JavaDoc<Module> reallydisable = mgr.simulateDisable(todisable);
1638        for (Module m: reallydisable) {
1639                if (!m.isAutoload() && !m.isEager() && !todisable.contains(m)) {
1640                    todisable.add(m);
1641                }
1642            }
1643            mgr.disable(todisable);
1644            // In case someone tried to delete an enabled autoload/eager module:
1645
Iterator JavaDoc<Module> delIt = todelete.iterator();
1646            while (delIt.hasNext()) {
1647                Module m = delIt.next();
1648                if (m.isEnabled()) {
1649                    if (!m.isAutoload() && !m.isEager()) throw new IllegalStateException JavaDoc("Module " + m + " scheduled for deletion could not be disabled yet was not an autoload nor eager"); // NOI18N
1650
// XXX is it better to find all regular module using it and turn all of those off?
1651
ev.log(Events.CANT_DELETE_ENABLED_AUTOLOAD, m);
1652                    delIt.remove();
1653                } else {
1654                    mgr.delete(m);
1655                }
1656            }
1657        }
1658        private void stepCheckMisc(Map JavaDoc/*<String,Map<String,Object>>*/ dirtyprops) {
1659            Util.err.fine("ModuleList: stepCheckMisc");
1660            String JavaDoc[] toCheck = {"jar", "autoload", "eager", "release", "specversion"}; // NOI18N
1661
Iterator JavaDoc it = dirtyprops.entrySet().iterator();
1662            while (it.hasNext()) {
1663                Map.Entry JavaDoc entry = (Map.Entry JavaDoc)it.next();
1664                String JavaDoc cnb = (String JavaDoc)entry.getKey();
1665                Map JavaDoc props = (Map JavaDoc)entry.getValue();
1666                DiskStatus status = statuses.get(cnb);
1667                Map JavaDoc diskProps = status.diskProps;
1668                for (int i = 0; i < toCheck.length; i++) {
1669                    String JavaDoc prop = toCheck[i];
1670                    Object JavaDoc onDisk = props.get(prop);
1671                    Object JavaDoc inMem = diskProps.get(prop);
1672                    if (! Utilities.compareObjects(onDisk, inMem)) {
1673                        ev.log(Events.MISC_PROP_MISMATCH, status.module, prop, onDisk, inMem);
1674                    }
1675                }
1676            }
1677        }
1678        private void stepCheckSer(Map JavaDoc/*<String,FileObject>*/ xmlfiles, Map JavaDoc/*<String,Map<String,Object>>*/ dirtyprops) {
1679            // There is NO step 7!
1680
}
1681        private void stepUpdateProps(Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> dirtyprops) {
1682            Util.err.fine("ModuleList: stepUpdateProps");
1683        for (Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Object JavaDoc>> entry: dirtyprops.entrySet()) {
1684                String JavaDoc cnb = entry.getKey();
1685                DiskStatus status = statuses.get(cnb);
1686                if (status != null) {
1687                    Map JavaDoc<String JavaDoc,Object JavaDoc> props = entry.getValue();
1688                    status.diskProps = props;
1689                }
1690            }
1691        }
1692        private void stepMarkClean() {
1693            Util.err.fine("ModuleList: stepMarkClean");
1694            Iterator JavaDoc it = statuses.values().iterator();
1695            while (it.hasNext()) {
1696                DiskStatus status = (DiskStatus)it.next();
1697                status.dirty = false;
1698            }
1699        }
1700        
1701    }
1702    
1703    /** Representation of the status of a module on disk and so on. */
1704    private static final class DiskStatus {
1705        /** Initialize as a struct, i.e. member by member: */
1706        public DiskStatus() {}
1707        /** actual module object */
1708        public Module module;
1709        /** XML file holding its status */
1710        public FileObject file;
1711        /** timestamp of last modification to XML file that this class did */
1712        //public long lastApprovedChange;
1713
/** if true, this module was scanned and should be enabled but we are waiting for trigger */
1714        public boolean pendingInstall = false;
1715        /** properties of the module on disk */
1716        public Map JavaDoc<String JavaDoc,Object JavaDoc /*String|Integer|Boolean|SpecificationVersion*/> diskProps;
1717        /** if true, the XML was changed on disk by someone else */
1718        public boolean dirty = false;
1719        /** for debugging: */
1720        public String JavaDoc toString() {
1721            return "DiskStatus[module=" + module + // NOI18N
1722
",valid=" + module.isValid() + // NOI18N
1723
",file=" + file + /*",lastApprovedChange=" + new Date(lastApprovedChange) +*/ // NOI18N
1724
",dirty=" + dirty + // NOI18N
1725
",pendingInstall=" + pendingInstall + // NOI18N
1726
",diskProps=" + diskProps + "]"; // NOI18N
1727
}
1728    }
1729    
1730}
1731
Popular Tags