0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans;
0043:
0044: import java.beans.PropertyChangeListener;
0045: import java.beans.PropertyChangeSupport;
0046: import java.io.File;
0047: import java.io.IOException;
0048: import java.security.AllPermission;
0049: import java.security.CodeSource;
0050: import java.security.PermissionCollection;
0051: import java.security.Permissions;
0052: import java.util.ArrayList;
0053: import java.util.Collection;
0054: import java.util.Collections;
0055: import java.util.HashMap;
0056: import java.util.HashSet;
0057: import java.util.Iterator;
0058: import java.util.LinkedList;
0059: import java.util.List;
0060: import java.util.Map;
0061: import java.util.Set;
0062: import java.util.StringTokenizer;
0063: import java.util.jar.Manifest;
0064: import java.util.logging.Level;
0065: import org.openide.modules.Dependency;
0066: import org.openide.modules.ModuleInfo;
0067: import org.openide.modules.SpecificationVersion;
0068: import org.openide.util.Lookup;
0069: import org.openide.util.Mutex;
0070: import org.openide.util.TopologicalSortException;
0071: import org.openide.util.Union2;
0072: import org.openide.util.Utilities;
0073:
0074: /** Manages a collection of modules.
0075: * Must use {@link #mutex} to access its important methods.
0076: * @author Jesse Glick
0077: */
0078: public final class ModuleManager {
0079:
0080: public static final String PROP_MODULES = "modules"; // NOI18N
0081: public static final String PROP_ENABLED_MODULES = "enabledModules"; // NOI18N
0082: public static final String PROP_CLASS_LOADER = "classLoader"; // NOI18N
0083:
0084: // JST-PENDING: Document in arch. used in org.netbeans.core.startup tests
0085: // For unit testing only:
0086: static boolean PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES = !Boolean
0087: .getBoolean("suppress.topological.exception"); // NOI18N
0088:
0089: // the modules being managed (not all need be installed)
0090: private final Set<Module> modules = new HashSet<Module>(100);
0091: // the same, indexed by code name base
0092: private final Map<String, Module> modulesByName = new HashMap<String, Module>(
0093: 100);
0094:
0095: // for any module, set of known failed dependencies or problems,
0096: // or null if this has not been computed yet
0097: private final Map<Module, Set<Union2<Dependency, InvalidException>>> moduleProblemsWithoutNeeds = new HashMap<Module, Set<Union2<Dependency, InvalidException>>>(
0098: 100);
0099: private final Map<Module, Set<Union2<Dependency, InvalidException>>> moduleProblemsWithNeeds = new HashMap<Module, Set<Union2<Dependency, InvalidException>>>(
0100: 100);
0101:
0102: // modules providing a given requires token; set may never be empty
0103: private final Map<String, Set<Module>> providersOf = new HashMap<String, Set<Module>>(
0104: 25);
0105:
0106: private final ModuleInstaller installer;
0107: private ModuleFactory moduleFactory;
0108:
0109: private SystemClassLoader classLoader;
0110: private List<File> classLoaderPatches;
0111: private final Object classLoaderLock = new String(
0112: "ModuleManager.classLoaderLock"); // NOI18N
0113:
0114: private final Events ev;
0115:
0116: /** Create a manager, initially with no managed modules.
0117: * The handler for installing modules is given.
0118: * Also the sink for event messages must be given.
0119: */
0120: public ModuleManager(ModuleInstaller installer, Events ev) {
0121: this .installer = installer;
0122: this .ev = ev;
0123: String patches = System
0124: .getProperty("netbeans.systemclassloader.patches");
0125: if (patches != null) {
0126: // Probably temporary helper for XTest. By setting this system property
0127: // to a classpath (list of directories and JARs separated by the normal
0128: // path separator) you may append to the system class loader.
0129: System.err.println("System class loader patches: "
0130: + patches); // NOI18N
0131: classLoaderPatches = new ArrayList<File>();
0132: StringTokenizer tok = new StringTokenizer(patches,
0133: File.pathSeparator);
0134: while (tok.hasMoreTokens()) {
0135: classLoaderPatches.add(new File(tok.nextToken()));
0136: }
0137: } else {
0138: // Normal case.
0139: classLoaderPatches = Collections.emptyList();
0140: }
0141: classLoader = new SystemClassLoader(classLoaderPatches,
0142: new ClassLoader[] { installer.getClass()
0143: .getClassLoader() }, Collections
0144: .<Module> emptySet());
0145: updateContextClassLoaders(classLoader, true);
0146:
0147: moduleFactory = Lookup.getDefault().lookup(ModuleFactory.class);
0148: if (moduleFactory == null) {
0149: moduleFactory = new ModuleFactory();
0150: } else {
0151: // Custom module factory might want to replace
0152: // the system classloader by its own.
0153: // If it does not want to replace it the following
0154: // call should not change anything since the system classloader
0155: // should still be set to ClassLoader.getSystemClassLoader() so
0156: // the following call will set it to the same value.
0157: classLoader.setSystemClassLoader(moduleFactory
0158: .getClasspathDelegateClassLoader(this , ClassLoader
0159: .getSystemClassLoader()));
0160: }
0161: }
0162:
0163: /** Access for ManifestSection.
0164: * @since JST-PENDING needed by ManifestSection
0165: */
0166: public final Events getEvents() {
0167: return ev;
0168: }
0169:
0170: private final Mutex.Privileged MUTEX_PRIVILEGED = new Mutex.Privileged();
0171: private final Mutex MUTEX = new Mutex(MUTEX_PRIVILEGED);
0172:
0173: /** Get a locking mutex for this module installer.
0174: * All calls other than adding or removing property change
0175: * listeners, or getting the module lookup, called on this
0176: * class must be done within the scope of this mutex
0177: * (with read or write access as appropriate). Methods
0178: * on ModuleInfo need not be called within it; methods
0179: * specifically on Module do need to be called within it
0180: * (read access is sufficient). Note that property changes
0181: * are fired with read access already held for convenience.
0182: * Please avoid entering the mutex from "sensitive" threads
0183: * such as the event thread, the folder recognizer/lookup
0184: * thread, etc., or with other locks held (such as the Children
0185: * mutex), especially when entering the mutex as a writer:
0186: * actions such as enabling modules in particular can call
0187: * arbitrary foreign module code which may do a number of
0188: * strange things (including consuming a significant amount of
0189: * time and waiting for other tasks such as lookup or data
0190: * object recognition). Use the request processor or the IDE's
0191: * main startup thread or the execution engine to be safe.
0192: */
0193: public final Mutex mutex() {
0194: return MUTEX;
0195: }
0196:
0197: /** Classes in this package can, if careful, use the privileged form.
0198: * @since JST-PENDING this had to be made public as the package is now split in two
0199: */
0200: public final Mutex.Privileged mutexPrivileged() {
0201: return MUTEX_PRIVILEGED;
0202: }
0203:
0204: // [PENDING] with improved API for Mutex, could throw
0205: // IllegalStateException if any thread attempts to call
0206: // a controlled method without holding the proper mutex lock
0207:
0208: /** Manages changes accumulating in this manager and fires them when ready.
0209: */
0210: private ChangeFirer firer = new ChangeFirer(this );
0211: /** True while firer is firing changes.
0212: */
0213: private boolean readOnly = false;
0214:
0215: /**
0216: * Release storage for all module manifests.
0217: * @see Module#releaseManifest
0218: */
0219: public void releaseModuleManifests() {
0220: for (Module m : modules) {
0221: m.releaseManifest();
0222: }
0223: }
0224:
0225: /** Sets the r/o flag. Access from ChangeFirer.
0226: * @param ro if true, cannot make any changes until set to false again
0227: */
0228: void readOnly(boolean ro) {
0229: readOnly = ro;
0230: }
0231:
0232: /** Assert that the current thread state permits writing.
0233: * Currently does not check that there is a write mutex!
0234: * (Pending #13352.)
0235: * But does check that I am not firing changes.
0236: * @throws IllegalThreadStateException if currently firing changes
0237: */
0238: void assertWritable() throws IllegalThreadStateException {
0239: if (readOnly) {
0240: throw new IllegalThreadStateException(
0241: "You are attempting to make changes to "
0242: + this
0243: + " in a property change callback. This is illegal. You may only make module system changes while holding a write mutex and not inside a change callback. See #16328."); // NOI18N
0244: }
0245: }
0246:
0247: private PropertyChangeSupport changeSupport;
0248:
0249: /** Add a change listener.
0250: * Only the declared properties will be fired, and they are
0251: * not guaranteed to be fired synchronously with the change
0252: * (currently they are not in fact, for safety). The change
0253: * events are not guaranteed to provide an old and new value,
0254: * so you will need to use the proper
0255: * getter methods. When the changes are fired, you are inside
0256: * the mutex with read access.
0257: */
0258: public final void addPropertyChangeListener(PropertyChangeListener l) {
0259: synchronized (this ) {
0260: if (changeSupport == null)
0261: changeSupport = new PropertyChangeSupport(this );
0262: }
0263: changeSupport.addPropertyChangeListener(l);
0264: }
0265:
0266: /** Remove a change listener. */
0267: public final void removePropertyChangeListener(
0268: PropertyChangeListener l) {
0269: if (changeSupport != null)
0270: changeSupport.removePropertyChangeListener(l);
0271: }
0272:
0273: // Access from ChangeFirer:
0274: final void firePropertyChange(String prop, Object old, Object nue) {
0275: if (Util.err.isLoggable(Level.FINE)) {
0276: Util.err.fine("ModuleManager.propertyChange: " + prop
0277: + ": " + old + " -> " + nue);
0278: }
0279: if (changeSupport != null)
0280: changeSupport.firePropertyChange(prop, old, nue);
0281: }
0282:
0283: /** For access from Module. */
0284: final void fireReloadable(Module m) {
0285: firer.change(new ChangeFirer.Change(m, Module.PROP_RELOADABLE,
0286: null, null));
0287: firer.fire();
0288: }
0289:
0290: private final Util.ModuleLookup lookup = new Util.ModuleLookup();
0291:
0292: /** Retrieve set of modules in Lookup form.
0293: * The core top manager should install this into the set of
0294: * available lookups. Will fire lookup events when the
0295: * set of modules changes (not for enabling/disabling/etc.).
0296: * No other subsystem should make any attempt to provide an instance of
0297: * ModuleInfo via lookup, so an optimization could be to jump
0298: * straight to this lookup when ModuleInfo/Module is requested.
0299: */
0300: public Lookup getModuleLookup() {
0301: return lookup;
0302: }
0303:
0304: // Access from ChangeFirer:
0305: final void fireModulesCreatedDeleted(Set created, Set deleted) {
0306: Util.err.fine("lookup created: " + created + " deleted: "
0307: + deleted);
0308: lookup.changed();
0309: }
0310:
0311: /** Get a set of {@link Module}s being managed.
0312: * No two contained modules may at any time share the same code name base.
0313: * @see #PROP_MODULES
0314: */
0315: public Set<Module> getModules() {
0316: return new HashSet<Module>(modules);
0317: }
0318:
0319: /** Get a set of modules managed which are currently enabled.
0320: * Convenience method only.
0321: * @see #PROP_ENABLED_MODULES
0322: */
0323: public final Set<Module> getEnabledModules() {
0324: Set<Module> s = new HashSet<Module>(modules);
0325: Iterator<Module> it = s.iterator();
0326: while (it.hasNext()) {
0327: Module m = it.next();
0328: if (!m.isEnabled()) {
0329: it.remove();
0330: }
0331: }
0332: return s;
0333: }
0334:
0335: /** Convenience method to find a module by name.
0336: * Returns null if there is no such managed module.
0337: */
0338: public final Module get(String codeNameBase) {
0339: return modulesByName.get(codeNameBase);
0340: }
0341:
0342: /**
0343: * Get a set of modules depended upon or depending on this module.
0344: * <p>Note that provide-require/need dependencies are listed alongside direct
0345: * dependencies; a module with a required token is considered to depend on
0346: * <em>all</em> modules providing that token (though in fact only one is needed
0347: * to enable it).
0348: * <p>Illegal cyclic dependencies are omitted.
0349: * @param m a module to start from; may be enabled or not, but must be owned by this manager
0350: * @param reverse if true, find modules depending on this module; if false, find
0351: * modules this module depends upon
0352: * @param transitive if true, these dependencies are considered transitively as well
0353: * @return a set (possibly empty) of modules managed by this manager, never including m
0354: * @since org.netbeans.core/1 > 1.17
0355: */
0356: public Set<Module> getModuleInterdependencies(Module m,
0357: boolean reverse, boolean transitive) {
0358: return Util.moduleInterdependencies(m, reverse, transitive,
0359: modules, modulesByName, providersOf);
0360: }
0361:
0362: /** Get a classloader capable of loading from any
0363: * of the enabled modules or their declared extensions.
0364: * Should be used as the result of TopManager.systemClassLoader.
0365: * Thread-safe.
0366: * @see #PROP_CLASS_LOADER
0367: */
0368: public ClassLoader getClassLoader() {
0369: // #16265: should not require mutex to get at. Many pieces of the IDE
0370: // require the correct result immediately.
0371: synchronized (classLoaderLock) {
0372: return classLoader;
0373: }
0374: }
0375:
0376: /** Mark the current class loader as invalid and make a new one. */
0377: private void invalidateClassLoader() {
0378: synchronized (classLoaderLock) {
0379: classLoader.destroy(); // probably has no effect, but just in case...
0380: }
0381: // Set, not List, because if we have >1 bootstrap module (using Plain),
0382: // it is likely that some of these classloaders will overlap.
0383: Set<ClassLoader> foundParents = new HashSet<ClassLoader>(
0384: modules.size() * 4 / 3 + 2);
0385: List<ClassLoader> parents = new ArrayList<ClassLoader>(modules
0386: .size() + 1);
0387: ClassLoader base = ModuleManager.class.getClassLoader();
0388: foundParents.add(base);
0389: parents.add(base);
0390: for (Module m : modules) {
0391: if (!m.isEnabled()) {
0392: continue;
0393: }
0394: if (foundParents.add(m.getClassLoader())) {
0395: parents.add(m.getClassLoader());
0396: }
0397: }
0398: if (moduleFactory.removeBaseClassLoader()) {
0399: parents.remove(base);
0400: }
0401: ClassLoader[] parentCLs = parents
0402: .toArray(new ClassLoader[parents.size()]);
0403: SystemClassLoader nue;
0404: try {
0405: nue = new SystemClassLoader(classLoaderPatches, parentCLs,
0406: modules);
0407: } catch (IllegalArgumentException iae) {
0408: Util.err.log(Level.WARNING, null, iae);
0409: nue = new SystemClassLoader(classLoaderPatches,
0410: new ClassLoader[] { ModuleManager.class
0411: .getClassLoader() }, Collections
0412: .<Module> emptySet());
0413: }
0414: synchronized (classLoaderLock) {
0415: classLoader = nue;
0416: updateContextClassLoaders(classLoader, false);
0417: }
0418: firer.change(new ChangeFirer.Change(this , PROP_CLASS_LOADER,
0419: null, null));
0420: }
0421:
0422: private static void updateContextClassLoaders(ClassLoader l,
0423: boolean force) {
0424: // See #20663.
0425: ThreadGroup g = Thread.currentThread().getThreadGroup();
0426: while (g.getParent() != null)
0427: g = g.getParent();
0428: // Now g is the master thread group, hopefully.
0429: // See #4097747 for an explanation of the convoluted logic.
0430: while (true) {
0431: int s = g.activeCount() + 1;
0432: Thread[] ts = new Thread[s];
0433: int x = g.enumerate(ts, true);
0434: if (x < s) {
0435: // We got all of the threads, good.
0436: for (int i = 0; i < x; i++) {
0437: // The first time when we make the manager, set all of the
0438: // threads to have this context classloader. Let's hope no
0439: // threads needing a special context loader have been started
0440: // yet! On subsequent occasions, when the classloader
0441: // changes, we update all threads for which setContextClassLoader
0442: // has not been called with some other special classloader.
0443: if (force
0444: || (ts[i].getContextClassLoader() instanceof SystemClassLoader)) {
0445: //Util.err.fine("Setting ctxt CL on " + ts[i].getName() + " to " + l);
0446: ts[i].setContextClassLoader(l);
0447: } else {
0448: Util.err
0449: .fine("Not touching context class loader "
0450: + ts[i].getContextClassLoader()
0451: + " on thread "
0452: + ts[i].getName());
0453: }
0454: }
0455: Util.err.fine("Set context class loader on " + x
0456: + " threads");
0457: break;
0458: } else {
0459: Util.err
0460: .fine("Race condition getting all threads, restarting...");
0461: continue;
0462: }
0463: }
0464: }
0465:
0466: /** A classloader giving access to all the module classloaders at once. */
0467: private static final class SystemClassLoader extends JarClassLoader {
0468:
0469: private final PermissionCollection allPermissions;
0470: private final StringBuffer debugme;
0471: private boolean empty = true;
0472:
0473: public SystemClassLoader(List<File> files,
0474: ClassLoader[] parents, Set<Module> modules)
0475: throws IllegalArgumentException {
0476: super (files, parents, false);
0477: allPermissions = new Permissions();
0478: allPermissions.add(new AllPermission());
0479: allPermissions.setReadOnly();
0480: debugme = new StringBuffer(100 + 50 * modules.size());
0481: debugme.append("SystemClassLoader["); // NOI18N
0482: for (File file : files) {
0483: if (empty) {
0484: empty = false;
0485: } else {
0486: debugme.append(','); // NOI18N
0487: }
0488: debugme.append(file.getAbsolutePath());
0489: }
0490: record(modules);
0491: debugme.append(']'); // NOI18N
0492: }
0493:
0494: private void record(Collection<Module> modules) {
0495: for (Module m : modules) {
0496: if (empty) {
0497: empty = false;
0498: } else {
0499: debugme.append(','); // NOI18N
0500: }
0501: debugme.append(m.getCodeNameBase());
0502: }
0503: }
0504:
0505: public void append(ClassLoader[] ls, List<Module> modules)
0506: throws IllegalArgumentException {
0507: super .append(ls);
0508: debugme.deleteCharAt(debugme.length() - 1);
0509: record(modules);
0510: debugme.append(']'); // NOI18N
0511: }
0512:
0513: protected @Override
0514: void finalize() throws Throwable {
0515: super .finalize();
0516: Util.err.fine("Collected system class loader");
0517: }
0518:
0519: public @Override
0520: String toString() {
0521: if (debugme == null) {
0522: return "SystemClassLoader";
0523: }
0524: return debugme.toString();
0525: }
0526:
0527: /** Provide all permissions for any code loaded from the files list
0528: * (i.e. with netbeans.systemclassloader.patches).
0529: */
0530: protected @Override
0531: PermissionCollection getPermissions(CodeSource cs) {
0532: return allPermissions;
0533: }
0534:
0535: }
0536:
0537: /** @see #create(File,Object,boolean,boolean,boolean)
0538: * @deprecated since org.netbeans.core/1 1.3
0539: */
0540: @Deprecated
0541: public Module create(File jar, Object history, boolean reloadable,
0542: boolean autoload) throws IOException, DuplicateException {
0543: return create(jar, history, reloadable, autoload, false);
0544: }
0545:
0546: /** Create a module from a JAR and add it to the managed set.
0547: * Will initially be disabled, unless it is eager and can
0548: * be enabled immediately.
0549: * May throw an IOException if the JAR file cannot be opened
0550: * for some reason, or is malformed.
0551: * If there is already a module of the same name managed,
0552: * throws a duplicate exception. In this case you may wish
0553: * to delete the original and try again.
0554: * You must give it some history object which can be used
0555: * to provide context for where the module came from and
0556: * whether it has been here before.
0557: * You cannot request that a module be both autoload and eager.
0558: */
0559: public Module create(File jar, Object history, boolean reloadable,
0560: boolean autoload, boolean eager) throws IOException,
0561: DuplicateException {
0562: assertWritable();
0563: ev.log(Events.START_CREATE_REGULAR_MODULE, jar);
0564: Module m = moduleFactory.create(jar.getAbsoluteFile(), history,
0565: reloadable, autoload, eager, this , ev);
0566: ev.log(Events.FINISH_CREATE_REGULAR_MODULE, jar);
0567: subCreate(m);
0568: if (m.isEager()) {
0569: List<Module> immediate = simulateEnable(Collections
0570: .<Module> emptySet());
0571: if (!immediate.isEmpty()) {
0572: if (!immediate.contains(m)) {
0573: throw new IllegalStateException(
0574: "Can immediately enable modules "
0575: + immediate
0576: + ", but not including " + m
0577: + "; its problems: "
0578: + m.getProblems()); // NOI18N
0579: }
0580: boolean ok = true;
0581: for (Module other : immediate) {
0582: if (!other.isAutoload() && !other.isEager()) {
0583: // Nope, would require a real module to be turned on first.
0584: ok = false;
0585: break;
0586: }
0587: }
0588: if (ok) {
0589: Util.err.fine("Enabling " + m + " immediately");
0590: enable(Collections.<Module> emptySet());
0591: }
0592: }
0593: }
0594: return m;
0595: }
0596:
0597: /** Create a fixed module (e.g. from classpath).
0598: * Will initially be disabled.
0599: */
0600: public Module createFixed(Manifest mani, Object history,
0601: ClassLoader loader) throws InvalidException,
0602: DuplicateException {
0603: return createFixed(mani, history, loader, false, false);
0604: }
0605:
0606: /**
0607: * Create a fixed module (e.g. from classpath) with optional autoload and eager flags.
0608: * Will initially be disabled.
0609: * @since 2.7
0610: */
0611: public Module createFixed(Manifest mani, Object history,
0612: ClassLoader loader, boolean autoload, boolean eager)
0613: throws InvalidException, DuplicateException {
0614: assertWritable();
0615: if (mani == null || loader == null)
0616: throw new IllegalArgumentException(
0617: "null manifest or loader"); // NOI18N
0618: ev.log(Events.START_CREATE_BOOT_MODULE, history);
0619: Module m = moduleFactory.createFixed(mani, history, loader,
0620: autoload, eager, this , ev);
0621: ev.log(Events.FINISH_CREATE_BOOT_MODULE, history);
0622: subCreate(m);
0623: return m;
0624: }
0625:
0626: /** Used by Module to communicate with the ModuleInstaller re. dependencies. */
0627: void refineDependencies(Module m, Set<Dependency> dependencies) {
0628: installer.refineDependencies(m, dependencies);
0629: }
0630:
0631: /** Allows the installer to add provides (used to provide name of platform we run on)
0632: */
0633: String[] refineProvides(Module m) {
0634: return installer.refineProvides(m);
0635: }
0636:
0637: /** Used by Module to communicate with the ModuleInstaller re. classloader. */
0638: public void refineClassLoader(Module m, List parents) {
0639: // #27853:
0640: installer.refineClassLoader(m, parents);
0641: }
0642:
0643: /** Use by OneModuleClassLoader to communicate with the ModuleInstaller re. masking. */
0644: public boolean shouldDelegateResource(Module m, Module parent,
0645: String pkg) {
0646: // Cf. #19621:
0647: Module.PackageExport[] exports = (parent == null) ? null
0648: : parent.getPublicPackages();
0649: if (exports != null) {
0650: //Util.err.fine("exports=" + Arrays.asList(exports));
0651: // Packages from parent are restricted: #19621.
0652: boolean exported = false;
0653: if (parent.isDeclaredAsFriend(m)) { // meaning public to all, or at least to me
0654: for (int i = 0; i < exports.length; i++) {
0655: if (exports[i].recursive ? pkg
0656: .startsWith(exports[i].pkg) : pkg
0657: .equals(exports[i].pkg)) {
0658: //Util.err.fine("matches " + exports[i]);
0659: exported = true;
0660: break;
0661: }
0662: }
0663: }
0664: if (!exported) {
0665: // This package is not public. m must have a direct impl-version
0666: // dependency on parent or it has no right to use this package.
0667: boolean impldep = false;
0668: Dependency[] deps = m.getDependenciesArray();
0669: for (int i = 0; i < deps.length; i++) {
0670: if (deps[i].getType() == Dependency.TYPE_MODULE
0671: && deps[i].getComparison() == Dependency.COMPARE_IMPL
0672: && deps[i].getName().equals(
0673: parent.getCodeName())) {
0674: impldep = true;
0675: //Util.err.fine("impldep in " + deps[i]);
0676: break;
0677: }
0678: }
0679: if (!impldep) {
0680: // This module cannot use the package, sorry! It's private.
0681: //Util.err.fine("forbidden");
0682: if (Util.err.isLoggable(Level.FINE)) {
0683: // Note that this is usually harmless. Typical case: Introspector.getBeanInfo
0684: // is called on some module-supplied class; this looks in the module's classloader
0685: // for org.netbeans.beaninfo.ModuleClassBeanInfo, which of course would not be
0686: // found anyway.
0687: Util.err
0688: .fine("Refusing to load non-public package "
0689: + pkg
0690: + " for "
0691: + m
0692: + " from parent module "
0693: + parent
0694: + " without an impl dependency");
0695: }
0696: return false;
0697: }
0698: //Util.err.fine("impl dep");
0699: }
0700: //Util.err.fine("exported");
0701: }
0702: if (pkg.startsWith("META-INF/")) { // NOI18N
0703: // Modules should not make direct reference to metainfo dirs of
0704: // other modules. Don't bother logging it, however.
0705: return false;
0706: }
0707: // The installer can perform additional checks:
0708: return installer.shouldDelegateResource(m, parent, pkg);
0709: }
0710:
0711: // Again, access from Module to ModuleInstaller:
0712: Manifest loadManifest(File jar) throws IOException {
0713: return installer.loadManifest(jar);
0714: }
0715:
0716: private void subCreate(Module m) throws DuplicateException {
0717: Util.err.fine("created: " + m);
0718: Module old = get(m.getCodeNameBase());
0719: if (old != null) {
0720: throw new DuplicateException(old, m);
0721: }
0722: modules.add(m);
0723: modulesByName.put(m.getCodeNameBase(), m);
0724: possibleProviderAdded(m);
0725: lookup.add(m);
0726: firer.created(m);
0727: firer.change(new ChangeFirer.Change(this , PROP_MODULES, null,
0728: null));
0729: // It might have been that some other modules were thought to be missing
0730: // dependencies only because they needed this one. And other modules still
0731: // might have depended on this one, etc. So forget any cached info about
0732: // problems arising from inter-module dependencies.
0733: clearProblemCache();
0734: firer.fire();
0735: }
0736:
0737: private void possibleProviderAdded(Module m) {
0738: String[] provides = m.getProvides();
0739: for (int i = 0; i < provides.length; i++) {
0740: Set<Module> providing = providersOf.get(provides[i]);
0741: if (providing == null) {
0742: providing = new HashSet<Module>(16);
0743: providersOf.put(provides[i], providing);
0744: }
0745: providing.add(m);
0746: }
0747: }
0748:
0749: /** Remove a module from the managed set.
0750: * Must be disabled first.
0751: * Must not be a "fixed" module.
0752: */
0753: public void delete(Module m) throws IllegalArgumentException {
0754: assertWritable();
0755: if (m.isFixed())
0756: throw new IllegalArgumentException("fixed module: " + m); // NOI18N
0757: if (m.isEnabled())
0758: throw new IllegalArgumentException("enabled module: " + m); // NOI18N
0759: ev.log(Events.DELETE_MODULE, m);
0760: modules.remove(m);
0761: modulesByName.remove(m.getCodeNameBase());
0762: possibleProviderRemoved(m);
0763: lookup.remove(m);
0764: firer.deleted(m);
0765: firer.change(new ChangeFirer.Change(this , PROP_MODULES, null,
0766: null));
0767: firer.change(new ChangeFirer.Change(m, Module.PROP_VALID,
0768: Boolean.TRUE, Boolean.FALSE));
0769: // #14561: some other module might now be uninstallable as a result.
0770: clearProblemCache();
0771: m.destroy();
0772: firer.fire();
0773: }
0774:
0775: private void possibleProviderRemoved(Module m) {
0776: for (String token : m.getProvides()) {
0777: Set<Module> providing = providersOf.get(token);
0778: if (providing != null) {
0779: providing.remove(m);
0780: if (providing.isEmpty()) {
0781: providersOf.remove(token);
0782: }
0783: } else {
0784: // Else we called reload and m.reload threw IOException, so
0785: // it has already removed its provider list
0786: }
0787: }
0788: }
0789:
0790: /** Reload a module.
0791: * This could make a fresh copy of its JAR file preparing
0792: * to enable it with different contents; at least it will
0793: * rescan the manifest.
0794: * It must currently be disabled and not "fixed", and it will
0795: * stay disabled after this call; to actually reinstall it
0796: * requires a separate call.
0797: * It may or may not actually be marked "reloadable", but
0798: * for greatest reliability it should be.
0799: * Besides actually reloading the contents, any cached information
0800: * about failed dependencies or runtime problems with the module
0801: * is cleared so it may be tried again.
0802: */
0803: public void reload(Module m) throws IllegalArgumentException,
0804: IOException {
0805: assertWritable();
0806: // No Events, not a user- nor performance-interesting action.
0807: Util.err.fine("reload: " + m);
0808: if (m.isFixed())
0809: throw new IllegalArgumentException("reload fixed module: "
0810: + m); // NOI18N
0811: if (m.isEnabled())
0812: throw new IllegalArgumentException(
0813: "reload enabled module: " + m); // NOI18N
0814: possibleProviderRemoved(m);
0815: try {
0816: m.reload();
0817: } catch (IOException ioe) {
0818: // Module is trash, remove it from our list and pass on the exception.
0819: delete(m);
0820: throw ioe;
0821: }
0822: possibleProviderAdded(m);
0823: firer.change(new ChangeFirer.Change(m, Module.PROP_MANIFEST,
0824: null, null));
0825: // Some problem with this module may now have gone away. In turn, some
0826: // other modules may now no longer have problems. So clear the cache
0827: // of "soft" problems (interdependencies between modules).
0828: // Also clear any "hard" problems associated with this module, as they
0829: // may now have been fixed.
0830: moduleProblemsWithoutNeeds.remove(m);
0831: moduleProblemsWithNeeds.remove(m);
0832: firer.change(new ChangeFirer.Change(m, Module.PROP_PROBLEMS,
0833: null, null));
0834: clearProblemCache();
0835: firer.fire();
0836: }
0837:
0838: /** Enable a single module.
0839: * Must have satisfied its dependencies.
0840: * Must not be an autoload module, when supported.
0841: */
0842: public final void enable(Module m) throws IllegalArgumentException,
0843: InvalidException {
0844: enable(Collections.singleton(m));
0845: }
0846:
0847: /** Disable a single module.
0848: * Must not be required by any enabled modules.
0849: * Must not be an autoload module, when supported.
0850: */
0851: public final void disable(Module m) throws IllegalArgumentException {
0852: disable(Collections.singleton(m));
0853: }
0854:
0855: /** Enable a set of modules together.
0856: * Must have satisfied their dependencies
0857: * (possibly with one another).
0858: * Must not contain autoload nor eager modules.
0859: * Might contain fixed modules (they can only be installed once of course).
0860: * It is permissible to pass in modules which in fact at runtime cannot
0861: * satisfy their package dependencies, or which {@link ModuleInstaller#prepare}
0862: * rejects on the basis of missing contents. In such a case InvalidException
0863: * will be thrown and nothing will be installed. The InvalidException in such
0864: * a case should contain a reference to the offending module.
0865: */
0866: public void enable(Set<Module> modules)
0867: throws IllegalArgumentException, InvalidException {
0868: assertWritable();
0869: Util.err.fine("enable: " + modules);
0870: /* Consider eager modules:
0871: if (modules.isEmpty()) {
0872: return;
0873: }
0874: */
0875: ev.log(Events.PERF_START, "ModuleManager.enable"); // NOI18N
0876: // Basic problems will be caught here, and we also get the autoloads:
0877: List<Module> toEnable = simulateEnable(modules);
0878: ev.log(Events.PERF_TICK,
0879: "checked the required ordering and autoloads"); // NOI18N
0880:
0881: Util.err.fine("enable: toEnable=" + toEnable); // NOI18N
0882: {
0883: // Verify that we are cool as far as basic dependencies go.
0884: Set<Module> testing = new HashSet<Module>(toEnable);
0885: if (!testing.containsAll(modules)) {
0886: Set<Module> bogus = new HashSet<Module>(modules);
0887: bogus.removeAll(testing);
0888: throw new IllegalArgumentException(
0889: "Not all requested modules can be enabled: "
0890: + bogus); // NOI18N
0891: }
0892: for (Module m : testing) {
0893: if (!modules.contains(m) && !m.isAutoload()
0894: && !m.isEager()) {
0895: throw new IllegalArgumentException(
0896: "Would also need to enable " + m); // NOI18N
0897: }
0898: }
0899: }
0900: Util.err.fine("enable: verified dependencies");
0901: ev.log(Events.PERF_TICK, "verified dependencies"); // NOI18N
0902:
0903: ev.log(Events.START_ENABLE_MODULES, toEnable);
0904: {
0905: // Actually turn on the listed modules.
0906: // List of modules that need to be "rolled back".
0907: LinkedList<Module> fallback = new LinkedList<Module>();
0908: // Whether we were attempting to bring a classloader up.
0909: // This affects whether we need to rollback that change on the
0910: // problem module or not.
0911: boolean tryingClassLoaderUp = false;
0912: // If a failure due to package dep occurs, store it here.
0913: Dependency failedPackageDep = null;
0914: try {
0915: ev.log(Events.PERF_START, "module preparation"); // NOI18N
0916: for (Module m : toEnable) {
0917: fallback.addFirst(m);
0918: Util.err.fine("enable: bringing up: " + m);
0919: ev.log(Events.PERF_START,
0920: "bringing up classloader on "
0921: + m.getCodeName()); // NOI18N
0922: try {
0923: // Calculate the parents to initialize the classloader with.
0924: Dependency[] dependencies = m
0925: .getDependenciesArray();
0926: Set<Module> parents = new HashSet<Module>(
0927: dependencies.length * 4 / 3 + 1);
0928: for (int i = 0; i < dependencies.length; i++) {
0929: Dependency dep = dependencies[i];
0930: if (dep.getType() != Dependency.TYPE_MODULE) {
0931: // Token providers do *not* go into the parent classloader
0932: // list. The providing module must have been turned on first.
0933: // But you cannot automatically access classes from it.
0934: continue;
0935: }
0936: String name = (String) Util
0937: .parseCodeName(dep.getName())[0];
0938: Module parent = get(name);
0939: // Should not happen:
0940: if (parent == null)
0941: throw new IOException("Parent " + name
0942: + " not found!"); // NOI18N
0943: parents.add(parent);
0944: }
0945: m.classLoaderUp(parents);
0946: } catch (IOException ioe) {
0947: tryingClassLoaderUp = true;
0948: InvalidException ie = new InvalidException(m,
0949: ioe.toString());
0950: ie.initCause(ioe);
0951: throw ie;
0952: }
0953: m.setEnabled(true);
0954: ev.log(Events.PERF_END,
0955: "bringing up classloader on "
0956: + m.getCodeName()); // NOI18N
0957: // Check package dependencies.
0958: // ev.log(Events.PERF_START, "package dependency check on " + m.getCodeName() ); // NOI18N
0959: Util.err
0960: .fine("enable: checking package dependencies for "
0961: + m);
0962: Dependency[] dependencies = m
0963: .getDependenciesArray();
0964: for (int i = 0; i < dependencies.length; i++) {
0965: Dependency dep = dependencies[i];
0966: if (dep.getType() != Dependency.TYPE_PACKAGE) {
0967: continue;
0968: }
0969: if (!Util.checkPackageDependency(dep, m
0970: .getClassLoader())) {
0971: failedPackageDep = dep;
0972: throw new InvalidException(m,
0973: "Dependency failed on " + dep); // NOI18N
0974: }
0975: Util.err.fine("Successful check for: " + dep);
0976: }
0977: // ev.log(Events.PERF_END, "package dependency check on " + m.getCodeName() ); // NOI18N
0978: // Prepare to load it.
0979: ev.log(Events.PERF_START,
0980: "ModuleInstaller.prepare "
0981: + m.getCodeName()); // NOI18N
0982: installer.prepare(m);
0983: ev.log(Events.PERF_END, "ModuleInstaller.prepare "
0984: + m.getCodeName()); // NOI18N
0985: }
0986: ev.log(Events.PERF_END, "module preparation"); // NOI18N
0987:
0988: } catch (InvalidException ie) {
0989: // Remember that there was a problem with this guy.
0990: Module bad = ie.getModule();
0991: if (bad == null)
0992: throw new IllegalStateException(
0993: "Problem with no associated module: " + ie); // NOI18N
0994: Set<Union2<Dependency, InvalidException>> probs = moduleProblemsWithNeeds
0995: .get(bad);
0996: if (probs == null)
0997: throw new IllegalStateException(
0998: "Were trying to install a module that had never been checked: "
0999: + bad); // NOI18N
1000: if (!probs.isEmpty())
1001: throw new IllegalStateException(
1002: "Were trying to install a module that was known to be bad: "
1003: + bad); // NOI18N
1004: // Record for posterity.
1005: if (failedPackageDep != null) {
1006: // Structured package dependency failed, track this.
1007: probs
1008: .add(Union2
1009: .<Dependency, InvalidException> createFirst(failedPackageDep));
1010: } else {
1011: // Some other problem (exception).
1012: probs
1013: .add(Union2
1014: .<Dependency, InvalidException> createSecond(ie));
1015: }
1016: // Other modules may have depended on this one and now will not be OK.
1017: // So clear all "soft" problems from the cache.
1018: // Remember, the problem we just added will be left alone, only
1019: // inter-module dependencies will be cleared.
1020: clearProblemCache();
1021: // #14560: this one definitely changed its set of problems.
1022: firer.change(new ChangeFirer.Change(bad,
1023: Module.PROP_PROBLEMS, Collections.EMPTY_SET,
1024: Collections.singleton("something"))); // NOI18N
1025: // Rollback changes made so far before rethrowing.
1026: Util.err.fine("enable: will roll back from: " + ie);
1027: for (Module m : fallback) {
1028: if (m.isFixed()) {
1029: // cannot disable fixed modules
1030: continue;
1031: }
1032: m.setEnabled(false);
1033: if (tryingClassLoaderUp) {
1034: // OK, taken into account for first module, others are up.
1035: tryingClassLoaderUp = false;
1036: } else {
1037: m.classLoaderDown();
1038: System.gc();
1039: System.runFinalization();
1040: m.cleanup();
1041: }
1042: }
1043: firer.fire();
1044: throw ie;
1045: }
1046: // They all were OK so far; add to system classloader and install them.
1047: if (classLoader != null) {
1048: Util.err.fine("enable: adding to system classloader");
1049: LinkedList<ClassLoader> nueclassloaders = new LinkedList<ClassLoader>();
1050: if (moduleFactory.removeBaseClassLoader()) {
1051: ClassLoader base = ModuleManager.class
1052: .getClassLoader();
1053: nueclassloaders
1054: .add(moduleFactory
1055: .getClasspathDelegateClassLoader(
1056: this , base));
1057: for (Module m : toEnable) {
1058: ClassLoader c1 = m.getClassLoader();
1059: if (c1 != base) {
1060: nueclassloaders.add(c1);
1061: }
1062: }
1063: } else {
1064: for (Module m : toEnable) {
1065: if (m.getClassLoader() == ClassLoader
1066: .getSystemClassLoader()) {
1067: nueclassloaders
1068: .addFirst(m.getClassLoader());
1069: } else {
1070: nueclassloaders.add(m.getClassLoader());
1071: }
1072: }
1073: }
1074: classLoader
1075: .append(
1076: (nueclassloaders
1077: .toArray(new ClassLoader[nueclassloaders
1078: .size()])), toEnable);
1079: } else {
1080: Util.err
1081: .fine("enable: no class loader yet, not appending");
1082: }
1083: Util.err.fine("enable: continuing to installation");
1084: installer.load(toEnable);
1085: }
1086: {
1087: // Take care of notifying various changes.
1088: Util.err.fine("enable: firing changes");
1089: firer.change(new ChangeFirer.Change(this ,
1090: PROP_ENABLED_MODULES, null, null));
1091: // The class loader does not actually change as a result of this.
1092: for (Module m : toEnable) {
1093: firer.change(new ChangeFirer.Change(m,
1094: ModuleInfo.PROP_ENABLED, Boolean.FALSE,
1095: Boolean.TRUE));
1096: if (!m.isFixed()) {
1097: firer.change(new ChangeFirer.Change(m,
1098: Module.PROP_CLASS_LOADER, null, null));
1099: }
1100: }
1101: }
1102: ev.log(Events.FINISH_ENABLE_MODULES, toEnable);
1103: firer.fire();
1104: }
1105:
1106: /** Disable a set of modules together.
1107: * Must not be required by any enabled
1108: * modules (except one another).
1109: * Must not contain autoload nor eager modules.
1110: * Must not contain fixed modules.
1111: */
1112: public void disable(Set<Module> modules)
1113: throws IllegalArgumentException {
1114: assertWritable();
1115: Util.err.fine("disable: " + modules);
1116: if (modules.isEmpty()) {
1117: return;
1118: }
1119: // Checks for invalid items, plus includes autoloads to turn off.
1120: List<Module> toDisable = simulateDisable(modules);
1121: Util.err.fine("disable: toDisable=" + toDisable);
1122: {
1123: // Verify that dependencies are OK.
1124: for (Module m : toDisable) {
1125: if (!modules.contains(m) && !m.isAutoload()
1126: && !m.isEager()) {
1127: throw new IllegalArgumentException(
1128: "Would also need to disable: " + m); // NOI18N
1129: }
1130: }
1131: }
1132: Util.err.fine("disable: verified dependencies");
1133: ev.log(Events.START_DISABLE_MODULES, toDisable);
1134: {
1135: // Actually turn off all modules.
1136: installer.unload(toDisable);
1137: for (Module m : toDisable) {
1138: installer.dispose(m);
1139: m.setEnabled(false);
1140: m.classLoaderDown();
1141: }
1142: System.gc(); // hope OneModuleClassLoader.finalize() is called...
1143: System.runFinalization();
1144: // but probably it won't be. See #4405807.
1145: for (Module m : toDisable) {
1146: m.cleanup();
1147: }
1148: }
1149: Util.err.fine("disable: finished, will notify changes");
1150: {
1151: // Notify various changes.
1152: firer.change(new ChangeFirer.Change(this ,
1153: PROP_ENABLED_MODULES, null, null));
1154: // Class loader will change as a result.
1155: invalidateClassLoader();
1156: for (Module m : toDisable) {
1157: firer.change(new ChangeFirer.Change(m,
1158: ModuleInfo.PROP_ENABLED, Boolean.TRUE,
1159: Boolean.FALSE));
1160: firer.change(new ChangeFirer.Change(m,
1161: Module.PROP_CLASS_LOADER, null, null));
1162: }
1163: }
1164: ev.log(Events.FINISH_DISABLE_MODULES, toDisable);
1165: firer.fire();
1166: }
1167:
1168: /** Simulate what would happen if a set of modules were to be enabled.
1169: * None of the listed modules may be autoload modules, nor eager, nor currently enabled,
1170: * though they may be fixed (if they have not yet been enabled).
1171: * It may happen that some of them do not satisfy their dependencies.
1172: * It may also happen that some of them require other, currently disabled,
1173: * modules to be enabled in order for them to be enabled.
1174: * It may further happen that some currently disabled eager modules could
1175: * be enabled as a result of these modules being enabled.
1176: * The returned set is the set of all modules that actually could be enabled.
1177: * It will include the requested modules, minus any that cannot satisfy
1178: * their dependencies (even on each other), plus any managed but currently
1179: * disabled modules that would need to be enabled (including autoload modules
1180: * required by some listed module but not by any currently enabled module),
1181: * plus any eager modules which can be enabled with the other enablements
1182: * (and possibly any autoloads needed by those eager modules).
1183: * Where a requested module requires some token, either it will not be included
1184: * in the result (in case the dependency cannot be satisfied), or it will, and
1185: * all modules providing that token which can be included will be included, even
1186: * if it would suffice to choose only one - unless a module providing that token
1187: * is already enabled or in the requested list,
1188: * in which case just the requested module will be listed.
1189: * Modules are returned in an order in which they could be enabled (where
1190: * base modules are always enabled before dependent modules).
1191: * Note that the returned list might include modules which in fact cannot be
1192: * enabled either because some package dependencies (which are checked only
1193: * on a live classloader) cannot be met; or {@link ModuleInstaller#prepare}
1194: * indicates that the modules are not in a valid format to install; or
1195: * creating the module classloader fails unexpectedly.
1196: */
1197: public List<Module> simulateEnable(Set<Module> modules)
1198: throws IllegalArgumentException {
1199: /* Not quite, eager modules may change this:
1200: if (modules.isEmpty()) {
1201: return Collections.EMPTY_LIST;
1202: }
1203: */
1204: // XXX also optimize for modules.size == 1
1205: Set<Module> willEnable = new HashSet<Module>(
1206: modules.size() * 2 + 1);
1207: for (Module m : modules) {
1208: if (m.isAutoload())
1209: throw new IllegalArgumentException(
1210: "Cannot simulate enabling an autoload: " + m); // NOI18N
1211: if (m.isEager())
1212: throw new IllegalArgumentException(
1213: "Cannot simulate enabling an eager module: "
1214: + m); // NOI18N
1215: if (m.isEnabled())
1216: throw new IllegalArgumentException("Already enabled: "
1217: + m); // NOI18N
1218: if (!m.isValid())
1219: throw new IllegalArgumentException(
1220: "Not managed by me: " + m + " in " + m); // NOI18N
1221: maybeAddToEnableList(willEnable, modules, m, true);
1222: }
1223: // XXX clumsy but should work:
1224: Set<Module> stillDisabled = new HashSet<Module>(this .modules);
1225: Iterator<Module> it = stillDisabled.iterator();
1226: while (it.hasNext()) {
1227: Module m = it.next();
1228: if (m.isEnabled() || willEnable.contains(m)) {
1229: it.remove();
1230: }
1231: }
1232: while (searchForPossibleEager(willEnable, stillDisabled,
1233: modules)) {/* search again */
1234: }
1235: Map<Module, List<Module>> deps = Util.moduleDependencies(
1236: willEnable, modulesByName, providersOf);
1237: try {
1238: List<Module> l = Utilities
1239: .topologicalSort(willEnable, deps);
1240: Collections.reverse(l);
1241: return l;
1242: } catch (TopologicalSortException ex) {
1243: // Some kind of cycle involving prov-req deps. Should be extremely rare.
1244: // Example (from random failures of MMT.testProvReqCycles):
1245: // m1 => {m2 | m3}
1246: // m2 => {m1 | m4}
1247: // m3 => {m1}
1248: // m4 => {}
1249: // Now consider:
1250: // sE(m2) = ?
1251: // [m4, m2] is fine.
1252: // [m4, m2, m1] would be OK too, but will result in TSE.
1253: // Do not know what to do here, actually, so give up.
1254: if (PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES) {
1255: Util.err.log(Level.WARNING, null, ex);
1256: }
1257: Util.err
1258: .warning("Cyclic module dependencies, will refuse to enable: "
1259: + deps); // NOI18N
1260: return Collections.<Module> emptyList();
1261: }
1262: }
1263:
1264: private void maybeAddToEnableList(Set<Module> willEnable,
1265: Set<Module> mightEnable, Module m, boolean okToFail) {
1266: if (!missingDependencies(m).isEmpty()) {
1267: assert okToFail : "Module " + m
1268: + " had unexpected problems: "
1269: + missingDependencies(m) + " (willEnable: "
1270: + willEnable + " mightEnable: " + mightEnable + ")";
1271: // Cannot satisfy its dependencies, exclude it.
1272: return;
1273: }
1274: if (!willEnable.add(m)) {
1275: // Already there, done.
1276: return;
1277: }
1278: // Also add anything it depends on, if not already there,
1279: // or already enabled.
1280: for (Dependency dep : m.getDependenciesArray()) {
1281: if (dep.getType() == Dependency.TYPE_MODULE) {
1282: String codeNameBase = (String) Util.parseCodeName(dep
1283: .getName())[0];
1284: Module other = get(codeNameBase);
1285: // Should never happen:
1286: if (other == null)
1287: throw new IllegalStateException(
1288: "Should have found module: " + codeNameBase); // NOI18N
1289: if (!other.isEnabled()) {
1290: maybeAddToEnableList(willEnable, mightEnable,
1291: other, false);
1292: }
1293: } else if (dep.getType() == Dependency.TYPE_REQUIRES
1294: || dep.getType() == Dependency.TYPE_NEEDS
1295: || dep.getType() == Dependency.TYPE_RECOMMENDS) {
1296: Set<Module> providers = providersOf.get(dep.getName());
1297: if (providers == null) {
1298: assert dep.getType() == Dependency.TYPE_RECOMMENDS : "Should have found a provider of "
1299: + dep;
1300: continue;
1301: }
1302: // First check if >= 1 is already enabled or will be soon. If so, great.
1303: boolean foundOne = false;
1304: for (Module other : providers) {
1305: if (other.isEnabled()
1306: || (other.getProblems().isEmpty() && mightEnable
1307: .contains(other))) {
1308: foundOne = true;
1309: break;
1310: }
1311: }
1312: if (foundOne) {
1313: // OK, we are satisfied.
1314: continue;
1315: }
1316: // All disabled. So add them all to the enable list.
1317: for (Module other : providers) {
1318: // It is OK if one of them fails.
1319: maybeAddToEnableList(willEnable, mightEnable,
1320: other, true);
1321: // But we still check to ensure that at least one did not!
1322: if (!foundOne && willEnable.contains(other)) {
1323: foundOne = true;
1324: // and continue with the others, try to add them too...
1325: }
1326: }
1327: // Logic is that missingDependencies(m) should contain dep in this case.
1328: assert foundOne
1329: || dep.getType() == Dependency.TYPE_RECOMMENDS : "Should have found a nonproblematic provider of "
1330: + dep
1331: + " among "
1332: + providers
1333: + " with willEnable="
1334: + willEnable
1335: + " mightEnable=" + mightEnable;
1336: }
1337: // else some other kind of dependency that does not concern us
1338: }
1339: }
1340:
1341: private boolean searchForPossibleEager(Set<Module> willEnable,
1342: Set<Module> stillDisabled, Set<Module> mightEnable) {
1343: // Check for any eagers in stillDisabled which could be enabled based
1344: // on currently enabled modules and willEnable. For any such, remove from
1345: // stillDisabled and add to willEnable (using maybeAddToEnableList, so that
1346: // autoloads needed by them are picked up too). If any were found, return true.
1347: boolean found = false;
1348: Iterator<Module> it = stillDisabled.iterator();
1349: FIND_EAGER: while (it.hasNext()) {
1350: Module m = it.next();
1351: if (willEnable.contains(m)) {
1352: // Presumably real module M1, eager M2 dep. on M1, eager M3 dep.
1353: // on M2; already called couldBeEnabledWithEagers(M3) and it
1354: // added M3 to willEnable (thus M2 also) but only removed M3
1355: // from willEnable, so we should skip it now.
1356: it.remove();
1357: continue;
1358: }
1359: if (m.isEager()) {
1360: if (couldBeEnabledWithEagers(m, willEnable,
1361: new HashSet<Module>())) {
1362: // Go for it!
1363: found = true;
1364: it.remove();
1365: maybeAddToEnableList(willEnable, mightEnable, m,
1366: false);
1367: }
1368: }
1369: }
1370: return found;
1371: }
1372:
1373: private boolean couldBeEnabledWithEagers(Module m,
1374: Set<Module> willEnable, Set<Module> recursion) {
1375: // True if a search of the dependencies of this module reveals
1376: // only modules which are currently enabled; in the willEnable
1377: // list; or are autoloads or eager modules for which this predicate
1378: // is recursively true.
1379: if (m.isEnabled() || willEnable.contains(m))
1380: return true;
1381: if (!m.isAutoload() && !m.isEager())
1382: return false;
1383: if (!m.getProblems().isEmpty())
1384: return false;
1385: if (!recursion.add(m)) {
1386: // A cycle, they can enable one another...
1387: return true;
1388: }
1389: Dependency[] dependencies = m.getDependenciesArray();
1390: for (int i = 0; i < dependencies.length; i++) {
1391: Dependency dep = dependencies[i];
1392: if (dep.getType() == Dependency.TYPE_MODULE) {
1393: String codeNameBase = (String) Util.parseCodeName(dep
1394: .getName())[0];
1395: Module other = get(codeNameBase);
1396: // Should never happen:
1397: if (other == null)
1398: throw new IllegalStateException(
1399: "Should have found module: " + codeNameBase); // NOI18N
1400: if (!couldBeEnabledWithEagers(other, willEnable,
1401: recursion))
1402: return false;
1403: } else if (dep.getType() == Dependency.TYPE_REQUIRES) {
1404: Set<Module> providers = providersOf.get(dep.getName());
1405: if (providers == null)
1406: throw new IllegalStateException(
1407: "Should have found a provider of: "
1408: + dep.getName()); // NOI18N
1409: // Just need *one* to match.
1410: boolean foundOne = false;
1411: for (Module other : providers) {
1412: if (couldBeEnabledWithEagers(other, willEnable,
1413: recursion)) {
1414: foundOne = true;
1415: break;
1416: }
1417: }
1418: if (!foundOne)
1419: return false;
1420: }
1421: // else some other dep type
1422: }
1423: return true;
1424: }
1425:
1426: /** Simulate what would happen if a set of modules were to be disabled.
1427: * None of the listed modules may be autoload modules, nor eager, nor currently disabled, nor fixed.
1428: * The returned set will list all modules that would actually be disabled,
1429: * meaning the listed modules, plus any currently enabled but unlisted modules
1430: * (including autoloads) that require some listed modules, plus any autoloads
1431: * which would no longer be needed as they were only required by modules
1432: * otherwise disabled.
1433: * Provide-require pairs count for purposes of disablement: if the set of
1434: * requested modules includes all remaining enabled providers of some token,
1435: * and modules requiring that token will need to be disabled as well.
1436: * Modules are returned in an order in which they could be disabled (where
1437: * dependent modules are always disabled before base modules).
1438: */
1439: public List<Module> simulateDisable(Set<Module> modules)
1440: throws IllegalArgumentException {
1441: if (modules.isEmpty()) {
1442: return Collections.<Module> emptyList();
1443: }
1444: // XXX also optimize for modules.size == 1
1445: // Probably not a very efficient algorithm. But it probably does not need to be.
1446: Set<Module> willDisable = new HashSet<Module>(20);
1447: for (Module m : modules) {
1448: if (m.isAutoload())
1449: throw new IllegalArgumentException(
1450: "Cannot disable autoload: " + m); // NOI18N
1451: if (m.isEager())
1452: throw new IllegalArgumentException(
1453: "Cannot disable eager module: " + m); // NOI18N
1454: if (m.isFixed())
1455: throw new IllegalArgumentException(
1456: "Cannot disable fixed module: " + m); // NOI18N
1457: if (!m.isEnabled())
1458: throw new IllegalArgumentException("Already disabled: "
1459: + m); // NOI18N
1460: addToDisableList(willDisable, m);
1461: }
1462: Set<Module> stillEnabled = new HashSet<Module>(
1463: getEnabledModules());
1464: stillEnabled.removeAll(willDisable);
1465: while (searchForUnusedAutoloads(willDisable, stillEnabled)) {/* search again */
1466: }
1467: Map<Module, List<Module>> deps = Util.moduleDependencies(
1468: willDisable, modulesByName, providersOf);
1469: try {
1470: return Utilities.topologicalSort(willDisable, deps);
1471: } catch (TopologicalSortException ex) {
1472: // Again, don't know what to do exactly, so give up and just turn them off.
1473: if (PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES) {
1474: Util.err.log(Level.WARNING, null, ex);
1475: }
1476: Util.err
1477: .warning("Cyclic module dependencies, will turn them off in a random order: "
1478: + deps); // NOI18N
1479: return new ArrayList<Module>(willDisable);
1480: }
1481: }
1482:
1483: private void addToDisableList(Set<Module> willDisable, Module m) {
1484: if (willDisable.contains(m)) {
1485: // E.g. if original set had A then B, B depends on A.
1486: return;
1487: }
1488: willDisable.add(m);
1489: // Find any modules depending on this one which are currently enabled.
1490: // (And not already here.)
1491: // If there are any, add them.
1492: for (Module other : modules) {
1493: if (other.isFixed() || !other.isEnabled()
1494: || willDisable.contains(other)) {
1495: continue;
1496: }
1497: Dependency[] depenencies = other.getDependenciesArray();
1498: for (int i = 0; i < depenencies.length; i++) {
1499: Dependency dep = depenencies[i];
1500: if (dep.getType() == Dependency.TYPE_MODULE) {
1501: if (dep.getName().equals(m.getCodeName())) {
1502: // Need to disable this one too.
1503: addToDisableList(willDisable, other);
1504: // No need to scan the rest of its dependencies.
1505: break;
1506: }
1507: } else if (dep.getType() == Dependency.TYPE_REQUIRES
1508: || dep.getType() == Dependency.TYPE_NEEDS) {
1509: if (m.provides(dep.getName())) {
1510: // Careful. There may be some third module still enabled which
1511: // provides this same token too.
1512: boolean foundOne = false;
1513: for (Module third : getEnabledModules()) {
1514: if (third.isEnabled()
1515: && !willDisable.contains(third)
1516: && third.provides(dep.getName())) {
1517: foundOne = true;
1518: break;
1519: }
1520: }
1521: if (!foundOne) {
1522: // Nope, we were the only/last one to provide it.
1523: addToDisableList(willDisable, other);
1524: break;
1525: }
1526: }
1527: }
1528: // else some other kind of dependency, we do not care
1529: }
1530: }
1531: }
1532:
1533: private boolean searchForUnusedAutoloads(Set<Module> willDisable,
1534: Set<Module> stillEnabled) {
1535: // Check for any autoloads in stillEnabled which are not used by anything else
1536: // in stillEnabled. For each such, remove it from stillEnabled and add
1537: // to willDisable. If any were found, return true.
1538: boolean found = false;
1539: Iterator<Module> it = stillEnabled.iterator();
1540: FIND_AUTOLOADS: while (it.hasNext()) {
1541: Module m = it.next();
1542: if (m.isAutoload()) {
1543: for (Module other : stillEnabled) {
1544: Dependency[] dependencies = other
1545: .getDependenciesArray();
1546: for (int i = 0; i < dependencies.length; i++) {
1547: Dependency dep = dependencies[i];
1548: if (dep.getType() == Dependency.TYPE_MODULE) {
1549: if (Util.parseCodeName(dep.getName())[0]
1550: .equals(m.getCodeNameBase())) {
1551: // Still used, skip it.
1552: continue FIND_AUTOLOADS;
1553: }
1554: } else if (dep.getType() == Dependency.TYPE_REQUIRES
1555: || dep.getType() == Dependency.TYPE_NEEDS
1556: || dep.getType() == Dependency.TYPE_RECOMMENDS) {
1557: // Here we play it safe and leave autoloads on if they provide
1558: // something used by some module - even if technically it would
1559: // be possible to turn off the autoload because there is another
1560: // enabled module providing the same thing. Leave it on anyway.
1561: if (m.provides(dep.getName())) {
1562: continue FIND_AUTOLOADS;
1563: }
1564: }
1565: // else some other type
1566: }
1567: }
1568: // Nobody uses it!
1569: found = true;
1570: it.remove();
1571: willDisable.add(m);
1572: }
1573: }
1574: return found;
1575: }
1576:
1577: // dummy object to be placed in the problem set while recursive checking is in progress
1578: private static final Union2<Dependency, InvalidException> PROBING_IN_PROCESS = Union2
1579: .createSecond(new InvalidException("PROBING_IN_PROCESS"));
1580:
1581: // Access from Module.getProblems, q.v.
1582: // The probed module must not be currently enabled or fixed.
1583: Set<Union2<Dependency, InvalidException>> missingDependencies(
1584: Module probed) {
1585: return missingDependencies(probed, true);
1586: }
1587:
1588: private Set<Union2<Dependency, InvalidException>> missingDependencies(
1589: Module probed, boolean withNeeds) {
1590: // We need to synchronize here because though this method may be called
1591: // only within a read mutex, it can write to moduleProblems. Other places
1592: // where moduleProblems are used are write-mutex only and so do not have
1593: // to worry about contention.
1594: synchronized (moduleProblemsWithNeeds) {
1595: Map<Module, Set<Union2<Dependency, InvalidException>>> mP = (withNeeds ? moduleProblemsWithNeeds
1596: : moduleProblemsWithoutNeeds);
1597: Set<Union2<Dependency, InvalidException>> probs = mP
1598: .get(probed);
1599: if (probs == null) {
1600: probs = new HashSet<Union2<Dependency, InvalidException>>(
1601: 8);
1602: if (withNeeds) {
1603: probs.addAll(missingDependencies(probed, false));
1604: }
1605: probs.add(PROBING_IN_PROCESS);
1606: mP.put(probed, probs);
1607: for (Dependency dep : probed.getDependenciesArray()) {
1608: if (dep.getType() == Dependency.TYPE_PACKAGE) {
1609: // Can't check it in advance. Assume it is OK; if not
1610: // a problem will be indicated during an actual installation
1611: // attempt.
1612: } else if (dep.getType() == Dependency.TYPE_MODULE) {
1613: // Look for the corresponding module.
1614: Object[] depParse = Util.parseCodeName(dep
1615: .getName());
1616: String codeNameBase = (String) depParse[0];
1617: int relVersionMin = (depParse[1] != null) ? ((Integer) depParse[1])
1618: .intValue()
1619: : -1;
1620: int relVersionMax = (depParse[2] != null) ? ((Integer) depParse[2])
1621: .intValue()
1622: : relVersionMin;
1623: Module other = get(codeNameBase);
1624: if (other == null) {
1625: // No such module, bad.
1626: probs
1627: .add(Union2
1628: .<Dependency, InvalidException> createFirst(dep));
1629: continue;
1630: }
1631: if (relVersionMin == relVersionMax) {
1632: // Non-ranged dep.
1633: if (relVersionMin != other
1634: .getCodeNameRelease()) {
1635: // Wrong major version, bad.
1636: probs
1637: .add(Union2
1638: .<Dependency, InvalidException> createFirst(dep));
1639: continue;
1640: }
1641: if (dep.getComparison() == Dependency.COMPARE_IMPL
1642: && !Utilities
1643: .compareObjects(
1644: dep.getVersion(),
1645: other
1646: .getImplementationVersion())) { // NOI18N
1647: // Wrong impl version, bad.
1648: probs
1649: .add(Union2
1650: .<Dependency, InvalidException> createFirst(dep));
1651: continue;
1652: }
1653: if (dep.getComparison() == Dependency.COMPARE_SPEC
1654: && new SpecificationVersion(dep
1655: .getVersion())
1656: .compareTo(other
1657: .getSpecificationVersion()) > 0) {
1658: // Spec version not high enough, bad.
1659: probs
1660: .add(Union2
1661: .<Dependency, InvalidException> createFirst(dep));
1662: continue;
1663: }
1664: } else if (relVersionMin < relVersionMax) {
1665: // Ranged dep.
1666: int otherRel = other.getCodeNameRelease();
1667: if (otherRel < relVersionMin
1668: || otherRel > relVersionMax) {
1669: // Major version outside of range, bad.
1670: probs
1671: .add(Union2
1672: .<Dependency, InvalidException> createFirst(dep));
1673: continue;
1674: }
1675: if (dep.getComparison() == Dependency.COMPARE_IMPL) {
1676: throw new IllegalStateException(
1677: "No such thing as ranged impl dep"); // NOI18N
1678: }
1679: if (dep.getComparison() == Dependency.COMPARE_SPEC
1680: &&
1681: // Spec comparisons only apply to the earliest major rel.
1682: otherRel == relVersionMin
1683: && new SpecificationVersion(dep
1684: .getVersion())
1685: .compareTo(other
1686: .getSpecificationVersion()) > 0) {
1687: // Spec version not high enough, bad.
1688: probs
1689: .add(Union2
1690: .<Dependency, InvalidException> createFirst(dep));
1691: continue;
1692: }
1693: } else {
1694: throw new IllegalStateException(
1695: "Upside-down rel vers range"); // NOI18N
1696: }
1697: if (!other.isEnabled()) {
1698: // Need to make sure the other one is not missing anything either.
1699: // Nor that it depends (directly on indirectly) on this one.
1700: if ((!withNeeds && !missingDependencies(
1701: other, false).isEmpty())
1702: || (withNeeds && !isAlmostEmpty(missingDependencies(
1703: other, true)))) {
1704: // This is a little subtle. Either the other module had real
1705: // problems, in which case our dependency on it is not legit.
1706: // Or, the other actually depends cyclically on this one. In
1707: // that case, *it* would wind up calling missingDependencies
1708: // on this module, but this module has already put a nonempty
1709: // set in the mapping (containing at least the element
1710: // PROBING_IN_PROCESS), causing the other module to fail and
1711: // return a dependency on this module, causing this module to
1712: // also fail with a dependency on that module. In the process,
1713: // both modules get marked permanently bogus (unless you reload
1714: // them both of course).
1715: probs
1716: .add(Union2
1717: .<Dependency, InvalidException> createFirst(dep));
1718: continue;
1719: }
1720: // If the other module is thought to be OK, assume we can depend
1721: // on it if we need it.
1722: }
1723: // Already-installed modules are of course fine.
1724: } else if (dep.getType() == Dependency.TYPE_REQUIRES
1725: || (withNeeds && dep.getType() == Dependency.TYPE_NEEDS)) {
1726: // Works much like a regular module dependency. However it only
1727: // fails if there are no satisfying modules with no problems.
1728: String token = dep.getName();
1729: Set<Module> providers = providersOf.get(token);
1730: if (providers == null) {
1731: // Nobody provides it. This dep failed.
1732: probs
1733: .add(Union2
1734: .<Dependency, InvalidException> createFirst(dep));
1735: } else {
1736: // We have some possible providers. Check that at least one is good.
1737: boolean foundOne = false;
1738: for (Module other : providers) {
1739: if (foundOne) {
1740: break;
1741: }
1742: if (other.isEnabled()) {
1743: foundOne = true;
1744: } else {
1745: if ((!withNeeds && missingDependencies(
1746: other, false).isEmpty())
1747: || (withNeeds && isAlmostEmpty(missingDependencies(
1748: other, true)))) {
1749: // See comment above for regular module deps
1750: // re. use of PROBING_IN_PROCESS.
1751: foundOne = true;
1752: }
1753: }
1754: }
1755: if (!foundOne) {
1756: // Nobody can provide it, fail.
1757: probs
1758: .add(Union2
1759: .<Dependency, InvalidException> createFirst(dep));
1760: }
1761: }
1762: } else if (dep.getType() == Dependency.TYPE_JAVA) {
1763: // Java dependency. Fixed for whole VM session, safe to check once and keep.
1764: if (!Util.checkJavaDependency(dep)) {
1765: // Bad.
1766: probs
1767: .add(Union2
1768: .<Dependency, InvalidException> createFirst(dep));
1769: }
1770: }
1771: }
1772: probs.remove(PROBING_IN_PROCESS);
1773: }
1774: return probs;
1775: }
1776: }
1777:
1778: private static boolean isAlmostEmpty(
1779: Set<Union2<Dependency, InvalidException>> probs) {
1780: return probs.isEmpty()
1781: || probs.equals(Collections
1782: .singleton(PROBING_IN_PROCESS));
1783: }
1784:
1785: /** Forget about any possible "soft" problems there might have been.
1786: * Next time anyone asks, recompute them.
1787: * Currently enabled modules are left alone (no problems).
1788: * Otherwise, any problems which are "hard" (result from failed
1789: * Java/IDE/package dependencies, runtime errors, etc.) are left alone;
1790: * "soft" problems of inter-module dependencies are cleared
1791: * so they will be recomputed next time, and corresponding
1792: * changes are fired (since the next call to getProblem might
1793: * return a different result).
1794: */
1795: private void clearProblemCache() {
1796: clearProblemCache(moduleProblemsWithoutNeeds);
1797: clearProblemCache(moduleProblemsWithNeeds);
1798: }
1799:
1800: private void clearProblemCache(
1801: Map<Module, Set<Union2<Dependency, InvalidException>>> mP) {
1802: Iterator<Map.Entry<Module, Set<Union2<Dependency, InvalidException>>>> it = mP
1803: .entrySet().iterator();
1804: while (it.hasNext()) {
1805: Map.Entry<Module, Set<Union2<Dependency, InvalidException>>> entry = it
1806: .next();
1807: Module m = entry.getKey();
1808: if (!m.isEnabled()) {
1809: Set<Union2<Dependency, InvalidException>> s = entry
1810: .getValue();
1811: if (s != null) {
1812: boolean clear = false;
1813: for (Union2<Dependency, InvalidException> problem : s) {
1814: if (problem.hasSecond()) {
1815: // Hard problem, skip this one.
1816: continue;
1817: }
1818: Dependency dep = problem.first();
1819: if (dep.getType() != Dependency.TYPE_MODULE
1820: && dep.getType() != Dependency.TYPE_REQUIRES
1821: && dep.getType() != Dependency.TYPE_NEEDS
1822: && dep.getType() != Dependency.TYPE_RECOMMENDS) {
1823: // Also a hard problem.
1824: continue;
1825: }
1826: // Some soft problems found, i.e. module deps. Clear them all.
1827: // #76917: Even clear any hard problems.
1828: clear = true;
1829: break;
1830: }
1831: if (clear || s.isEmpty()) { // leave alone only if all hard problems
1832: it.remove();
1833: firer.change(new ChangeFirer.Change(m,
1834: Module.PROP_PROBLEMS, null, null));
1835: }
1836: }
1837: // if we never computed anything, make no change now
1838: }
1839: // enabled modules are definitely OK, no change there
1840: }
1841: }
1842:
1843: /** Try to shut down the system.
1844: * First all modules are asked if they wish to close, in the proper order.
1845: * Assuming they say yes, then they are informed of the close.
1846: * Returns true if they all said yes.
1847: */
1848: public boolean shutDown() {
1849: return shutDown(null);
1850: }
1851:
1852: /**
1853: * Try to shut down the system.
1854: * First all modules are asked if they wish to close, in the proper order.
1855: * Assuming they say yes, a hook is run, then they are informed of the close.
1856: * If they did not agree to close, the hook is not run.
1857: * @param midHook a hook to run before closing modules if they agree to close
1858: * @return true if they all said yes and the module system is now shut down
1859: * @since org.netbeans.core/1 1.11
1860: */
1861: public boolean shutDown(Runnable midHook) {
1862: assertWritable();
1863: Set<Module> unorderedModules = getEnabledModules();
1864: Map<Module, List<Module>> deps = Util.moduleDependencies(
1865: unorderedModules, modulesByName, providersOf);
1866: List<Module> sortedModules;
1867: try {
1868: sortedModules = Utilities.topologicalSort(unorderedModules,
1869: deps);
1870: } catch (TopologicalSortException ex) {
1871: // Once again, weird situation.
1872: if (PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES) {
1873: Util.err.log(Level.WARNING, null, ex);
1874: }
1875: Util.err
1876: .warning("Cyclic module dependencies, will not shut down cleanly: "
1877: + deps); // NOI18N
1878: return true;
1879: }
1880: if (!installer.closing(sortedModules)) {
1881: return false;
1882: }
1883: if (midHook != null) {
1884: try {
1885: midHook.run();
1886: } catch (RuntimeException e) {
1887: Util.err.log(Level.WARNING, null, e);
1888: } catch (LinkageError e) {
1889: Util.err.log(Level.WARNING, null, e);
1890: }
1891: }
1892: installer.close(sortedModules);
1893: return true;
1894: }
1895: }
|