All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.ModuleManager Maven / Gradle / Ivy

There is a newer version: RELEASE230
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.jar.Manifest;
import java.util.logging.Level;
import org.openide.modules.Dependency;
import org.openide.modules.ModuleInfo;
import org.openide.modules.Modules;
import org.openide.modules.OnStop;
import org.openide.modules.Places;
import org.openide.modules.SpecificationVersion;
import org.openide.util.Enumerations;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.Task;
import org.openide.util.TopologicalSortException;
import org.openide.util.Union2;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;

/** Manages a collection of modules.
 * Must use {@link #mutex} to access its important methods.
 * @author Jesse Glick
 */
public final class ModuleManager extends Modules {

    public static final String PROP_MODULES = "modules"; // NOI18N
    public static final String PROP_ENABLED_MODULES = "enabledModules"; // NOI18N
    public static final String PROP_CLASS_LOADER = "classLoader"; // NOI18N

    // JST-PENDING: Document in arch. used in org.netbeans.core.startup tests
    // For unit testing only:
    static boolean PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES = !Boolean.getBoolean ("suppress.topological.exception"); // NOI18N

    // the modules being managed (not all need be installed)
    private final Set modules = new HashSet(100);
    // the same, indexed by code name base
    private final Map modulesByName = new HashMap(100);
    
    /**
     * Registers classloaders and module objects for bootstrap modules.
     */
    // @GuardedBy(this)
    private final Map>> bootstrapModules = new WeakHashMap<>();

    /**
     * Modules whose contents is injected into 
     */
    private final Map> fragmentModules = new HashMap>(5);

    // for any module, set of known failed dependencies or problems,
    // or null if this has not been computed yet
    private final Object MODULE_PROBLEMS_LOCK = new Object();
    private final Map>> moduleProblemsWithoutNeeds = new HashMap>>(100);
    private final Map>> moduleProblemsWithNeeds = new HashMap>>(100);
    private static final Set> EMPTY_COLLECTION = Collections.>emptySet();

    // modules providing a given requires token; set may never be empty
    private final ProvidersOf providersOf = new ProvidersOf();

    private final ModuleInstaller installer;
    private ModuleFactory moduleFactory;

    private SystemClassLoader classLoader;
    private List classLoaderPatches;
    private final Object classLoaderLock = new String("ModuleManager.classLoaderLock"); // NOI18N

    private final Events ev;
    private final ModuleDataCache mdc = new ModuleDataCache();
    private final NetigsoHandle netigso;

    /** Create a manager, initially with no managed modules.
     * The handler for installing modules is given.
     * Also the sink for event messages must be given.
     */
    public ModuleManager(ModuleInstaller installer, Events ev) {
        this.installer = installer;
        this.ev = ev;
        this.netigso = new NetigsoHandle(this);
        String patches = System.getProperty("netbeans.systemclassloader.patches");
        if (patches != null) {
            // Probably temporary helper for XTest. By setting this system property
            // to a classpath (list of directories and JARs separated by the normal
            // path separator) you may append to the system class loader.
            System.err.println("System class loader patches: " + patches); // NOI18N
            classLoaderPatches = new ArrayList();
            StringTokenizer tok = new StringTokenizer(patches, File.pathSeparator);
            while (tok.hasMoreTokens()) {
                classLoaderPatches.add(new File(tok.nextToken()));
            }
        } else {
            // Normal case.
            classLoaderPatches = Collections.emptyList();
        }
        classLoader = new SystemClassLoader(classLoaderPatches, new ClassLoader[] {installer.getClass ().getClassLoader()}, Collections.emptySet());
        updateContextClassLoaders(classLoader, true);
        
        moduleFactory = Lookup.getDefault().lookup(ModuleFactory.class);
        if (moduleFactory == null) {
            moduleFactory = new ModuleFactory();
        } else {
            // Custom module factory might want to replace
            // the system classloader by its own.
            // If it does not want to replace it the following
            // call should not change anything since the system classloader
            // should still be set to ClassLoader.getSystemClassLoader() so
            // the following call will set it to the same value.
            classLoader.setSystemClassLoader(
                moduleFactory.getClasspathDelegateClassLoader(this, 
                    ModuleManager.class.getClassLoader()));
        }
    }

    /** Access for ManifestSection.
     * @since JST-PENDING needed by ManifestSection
     */
    public final Events getEvents() {
        return ev;
    }

    private final Mutex.Privileged MUTEX_PRIVILEGED = new Mutex.Privileged();
    private final Mutex MUTEX = new Mutex(MUTEX_PRIVILEGED);
    /** Get a locking mutex for this module installer.
     * All calls other than adding or removing property change
     * listeners, or getting the module lookup, called on this
     * class must be done within the scope of this mutex
     * (with read or write access as appropriate). Methods
     * on ModuleInfo need not be called within it; methods
     * specifically on Module do need to be called within it
     * (read access is sufficient). Note that property changes
     * are fired with read access already held for convenience.
     * Please avoid entering the mutex from "sensitive" threads
     * such as the event thread, the folder recognizer/lookup
     * thread, etc., or with other locks held (such as the Children
     * mutex), especially when entering the mutex as a writer:
     * actions such as enabling modules in particular can call
     * arbitrary foreign module code which may do a number of
     * strange things (including consuming a significant amount of
     * time and waiting for other tasks such as lookup or data
     * object recognition). Use the request processor or the IDE's
     * main startup thread or the execution engine to be safe.
     */
    public final Mutex mutex() {
        return MUTEX;
    }
    /** Classes in this package can, if careful, use the privileged form.
     * @since JST-PENDING this had to be made public as the package is now split in two
     */
    public final Mutex.Privileged mutexPrivileged() {
        return MUTEX_PRIVILEGED;
    }
    // [PENDING] with improved API for Mutex, could throw
    // IllegalStateException if any thread attempts to call
    // a controlled method without holding the proper mutex lock

    /** Manages changes accumulating in this manager and fires them when ready.
     */
    private ChangeFirer firer = new ChangeFirer(this);
    /** True while firer is firing changes.
     */
    private boolean readOnly = false;

    /**
     * Release storage for all module manifests.
     * @see Module#releaseManifest
     */
    public void releaseModuleManifests() {
        for (Module m : modules) {
            m.releaseManifest();
        }
    }
    
    /** Sets the r/o flag. Access from ChangeFirer.
     * @param ro if true, cannot make any changes until set to false again
     */
    void readOnly(boolean ro) {
        readOnly = ro;
    }
    /** Assert that the current thread state permits writing.
     * Currently does not check that there is a write mutex!
     * (Pending #13352.)
     * But does check that I am not firing changes.
     * @throws IllegalThreadStateException if currently firing changes
     */
    void assertWritable() throws IllegalThreadStateException {
        if (readOnly) {
            throw new IllegalThreadStateException("You are attempting to make changes to " + this + " 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
        }
    }

    private PropertyChangeSupport changeSupport;

    /** Add a change listener.
     * Only the declared properties will be fired, and they are
     * not guaranteed to be fired synchronously with the change
     * (currently they are not in fact, for safety). The change
     * events are not guaranteed to provide an old and new value,
     * so you will need to use the proper
     * getter methods. When the changes are fired, you are inside
     * the mutex with read access.
     */
    public final void addPropertyChangeListener(PropertyChangeListener l) {
        synchronized (this) {
            if (changeSupport == null)
                changeSupport = new PropertyChangeSupport(this);
        }
        changeSupport.addPropertyChangeListener(l);
    }

    /** Remove a change listener. */
    public final void removePropertyChangeListener(PropertyChangeListener l) {
        if (changeSupport != null)
            changeSupport.removePropertyChangeListener(l);
    }

    // Access from ChangeFirer:
    final void firePropertyChange(String prop, Object old, Object nue) {
        if (Util.err.isLoggable(Level.FINE)) {
            Util.err.fine("ModuleManager.propertyChange: " + prop + ": " + old + " -> " + nue);
        }
        if (changeSupport != null)
            changeSupport.firePropertyChange(prop, old, nue);
    }

    /** For access from Module. */
    final void fireReloadable(Module m) {
        firer.change(new ChangeFirer.Change(m, Module.PROP_RELOADABLE, null, null));
        firer.fire();
    }

    private final Util.ModuleLookup lookup = new Util.ModuleLookup();
    private final Lookup completeLookup = new ProxyLookup(Lookups.fixed(this), lookup);
    /** Retrieve set of modules in Lookup form.
     * The core top manager should install this into the set of
     * available lookups. Will fire lookup events when the
     * set of modules changes (not for enabling/disabling/etc.).
     * No other subsystem should make any attempt to provide an instance of
     * ModuleInfo via lookup, so an optimization could be to jump
     * straight to this lookup when ModuleInfo/Module is requested.
     */
    public Lookup getModuleLookup() {
        return completeLookup;
    }
    // Access from ChangeFirer:
    final void fireModulesCreatedDeleted(Set created, Set deleted) {
        if (Util.err.isLoggable(Level.FINE)) {
            Util.err.fine("lookup created: " + created + " deleted: " + deleted);
        }
        lookup.changed();
    }

    /** Get a set of {@link Module}s being managed.
     * No two contained modules may at any time share the same code name base.
     * @see #PROP_MODULES
     */
    public Set getModules() {
        return new HashSet(modules);
    }

    final int getModuleCount() {
        return modules.size();
    }

    /** Get a set of modules managed which are currently enabled.
     * Convenience method only.
     * @see #PROP_ENABLED_MODULES
     */
    public final Set getEnabledModules() {
        Set s = new HashSet(modules);
        Iterator it = s.iterator();
        while (it.hasNext()) {
            Module m = it.next();
            if (! m.isEnabled()) {
                it.remove();
            }
        }
        return s;
    }

    /** Convenience method to find a module by name.
     * Returns null if there is no such managed module.
     */
    public final Module get(String codeNameBase) {
        return modulesByName.get(codeNameBase);
    }

    @Override
    public ModuleInfo findCodeNameBase(String cnb) {
        return get(cnb);
    }

    public @Override ModuleInfo ownerOf(Class clazz) {
        ClassLoader cl = clazz.getClassLoader();
        if (cl instanceof Util.ModuleProvider) {
            return ((Util.ModuleProvider) cl).getModule();
        }
        String codename = Module.findClasspathModuleCodeName(clazz);
        if (codename != null) {
            return get(codename.replaceFirst("/\\d+$", "")); // NOI18N
        }
        return null;
    }

    /**
     * @deprecated Use {@link #getModuleInterdependencies(Module, boolean, boolean, boolean)} instead.
     */
    @Deprecated
    public Set getModuleInterdependencies(Module m, boolean reverse, boolean transitive) {
        return Util.moduleInterdependencies(m, reverse, transitive, true, modules, modulesByName, getProvidersOf());
    }

    /**
     * Get a set of modules depended upon or depending on this module.
     * 

Note that provide-require/need dependencies are listed alongside direct * dependencies; a module with a required token is considered to depend on * all modules providing that token (though in fact only one is needed * to enable it). *

Illegal cyclic dependencies are omitted. * @param m a module to start from; may be enabled or not, but must be owned by this manager * @param reverse if true, find modules depending on this module; if false, find * modules this module depends upon * @param transitive if true, these dependencies are considered transitively as well * @param considerNeeds if true, dependencies of type {@link Dependency#TYPE_NEEDS} are considered * @return a set (possibly empty) of modules managed by this manager, never including m * @since org.netbeans.bootstrap/1 > 2.48 */ public Set getModuleInterdependencies(Module m, boolean reverse, boolean transitive, boolean considerNeeds) { return Util.moduleInterdependencies(m, reverse, transitive, considerNeeds, modules, modulesByName, getProvidersOf()); } /** Get a classloader capable of loading from any * of the enabled modules or their declared extensions. * Normally used as {@link Thread#getContextClassLoader}. * Thread-safe. * @see #PROP_CLASS_LOADER */ public ClassLoader getClassLoader() { // #16265: should not require mutex to get at. Many pieces of the IDE // require the correct result immediately. synchronized (classLoaderLock) { return classLoader; } } /** Mark the current class loader as invalid and make a new one. */ private void invalidateClassLoader() { synchronized (classLoaderLock) { classLoader.destroy(); // probably has no effect, but just in case... } // Set, not List, because if we have >1 bootstrap module (using Plain), // it is likely that some of these classloaders will overlap. Set foundParents = new HashSet(modules.size() * 4 / 3 + 2); List parents = new ArrayList(modules.size() + 1); ClassLoader base = ModuleManager.class.getClassLoader(); foundParents.add(base); parents.add(base); for (Module m : modules) { if (! m.isEnabled()) { continue; } if (foundParents.add(m.getClassLoader())) { parents.add(m.getClassLoader()); } } if (moduleFactory.removeBaseClassLoader()) { parents.remove(base); } ClassLoader[] parentCLs = parents.toArray(new ClassLoader[parents.size()]); SystemClassLoader nue; try { nue = new SystemClassLoader(classLoaderPatches, parentCLs, modules); } catch (IllegalArgumentException iae) { Util.err.log(Level.WARNING, null, iae); nue = new SystemClassLoader(classLoaderPatches, new ClassLoader[] {ModuleManager.class.getClassLoader()}, Collections.emptySet()); } synchronized (classLoaderLock) { classLoader = nue; updateContextClassLoaders(classLoader, false); } firer.change(new ChangeFirer.Change(this, PROP_CLASS_LOADER, null, null)); } private static void updateContextClassLoaders(ClassLoader l, boolean force) { // See #20663. ThreadGroup g = Thread.currentThread().getThreadGroup(); while (g.getParent() != null) g = g.getParent(); // Now g is the master thread group, hopefully. // See #4097747 for an explanation of the convoluted logic. while (true) { int s = g.activeCount() + 1; Thread[] ts = new Thread[s]; int x = g.enumerate(ts, true); if (x < s) { // We got all of the threads, good. for (int i = 0; i < x; i++) { // The first time when we make the manager, set all of the // threads to have this context classloader. Let's hope no // threads needing a special context loader have been started // yet! On subsequent occasions, when the classloader // changes, we update all threads for which setContextClassLoader // has not been called with some other special classloader. if (force || (ts[i].getContextClassLoader() instanceof SystemClassLoader)) { //Util.err.fine("Setting ctxt CL on " + ts[i].getName() + " to " + l); try { ts[i].setContextClassLoader(l); } catch (SecurityException se) { if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("Cannot set context ClassLoader to the Thread: "+ts[i]); // NOI18N } } } else { if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("Not touching context class loader " + ts[i].getContextClassLoader() + " on thread " + ts[i].getName()); } } } if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("Set context class loader on " + x + " threads"); } break; } else { Util.err.fine("Race condition getting all threads, restarting..."); continue; } } } /** Only for use with Javeleon modules. */ public void replaceJaveleonModule(Module module, Module newModule) { assert newModule instanceof JaveleonModule; modules.remove(module); modulesByName.remove(module.getCodeNameBase()); modules.add(newModule); modulesByName.put(newModule.getCodeNameBase(), newModule); invalidateClassLoader(); } private static void checkMissingModules( Set requested, List reallyEnabled ) throws InvalidException { InvalidException ex = null; HashSet reallyEnabledSet = new HashSet(reallyEnabled); for (Module m : requested) { if (reallyEnabledSet.contains(m)) { continue; } InvalidException newEx = new InvalidException( m, "Requested by OSGi bundle" // NOI18N ); if (ex != null) { newEx.initCause(ex); } ex = newEx; } if (ex != null) { throw ex; } } private static int countEnabled(List toEnable) { int cnt = 0; for (Module m : toEnable) { if (m.isEnabled()) { cnt++; } } return cnt; } /** Checks whether the module is supposed be OSGi or not * @return null if it is not known */ final Boolean isOSGi(File jar) { return mdc.isOSGi(jar.getPath()); } /** Obtains (and destroys) data for given JAR file. * @return stream with data or null if not found in cache */ final InputStream dataFor(File jar) { if (jar == null) { return null; } byte[] arr = mdc.getModuleState(jar.getPath()); return arr == null ? null : new ByteArrayInputStream(arr); } /** Obtains cnb for given JAR file. * @return stream with data or null if not found in cache */ final String cnbFor(File jar) { if (jar == null) { return null; } return mdc.getCnb(jar.getPath()); } final String fragmentFor(File jar) { if (jar == null) { return null; } return mdc.getFragment(jar.getPath()); } private Map> getProvidersOf() { return providersOf.getProvidersOf(); } static void registerProviders(Module m, Map> po) { String[] provides = m.getProvides(); for (int i = 0; i < provides.length; i++) { Set providing = po.get(provides[i]); if (providing == null) { providing = new HashSet(16); po.put(provides[i], providing); } providing.add(m); } } final NetigsoFramework netigso() { return netigso.getDefault(); } final void netigsoLoaderUp(NetigsoModule nm) throws IOException { netigso.classLoaderUp(nm); } final void netigsoLoaderDown(NetigsoModule nm) { netigso.classLoaderDown(nm); } private class ProvidersOf { private Map> providersOf; public ProvidersOf() { } final synchronized Map> getProvidersOf() { if (providersOf == null) { providersOf = new HashMap>(); for (Module m : modules) { possibleProviderAdded(m); } } return providersOf; } final synchronized void possibleProviderAdded(Module m) { if (providersOf == null) { return; } registerProviders(m, providersOf); } final synchronized void possibleProviderRemoved(Module m) { if (providersOf == null) { return; } for (String token : m.getProvides()) { Set providing = providersOf.get(token); if (providing != null) { providing.remove(m); if (providing.isEmpty()) { providersOf.remove(token); } } else { // Else we called reload and m.reload threw IOException, so // it has already removed its provider list } } } } /** A classloader giving access to all the module classloaders at once. */ private final class SystemClassLoader extends JarClassLoader { private final PermissionCollection allPermissions; int size; public SystemClassLoader(List files, ClassLoader[] parents, Set modules) throws IllegalArgumentException { super(files, parents, false); allPermissions = new Permissions(); allPermissions.add(new AllPermission()); allPermissions.setReadOnly(); size = modules.size(); } protected @Override void finalize() throws Throwable { super.finalize(); Util.err.fine("Collected system class loader"); } public @Override String toString() { return "SystemClassLoader[" + size + " modules]"; } /** Provide all permissions for any code loaded from the files list * (i.e. with netbeans.systemclassloader.patches). */ protected @Override PermissionCollection getPermissions(CodeSource cs) { return allPermissions; } private final Set JRE_PROVIDED_FACTORIES = new HashSet(Arrays.asList( "META-INF/services/javax.xml.parsers.SAXParserFactory", // NOI18N "META-INF/services/javax.xml.parsers.DocumentBuilderFactory", // NOI18N "META-INF/services/javax.xml.transform.TransformerFactory", // NOI18N "META-INF/services/javax.xml.validation.SchemaFactory")); // NOI18N @Override public InputStream getResourceAsStream(String name) { if (JRE_PROVIDED_FACTORIES.contains(name)) { // #146082: prefer JRE versions of JAXP factories when available. // #147082: use empty file rather than null (~ delegation to ClassLoader.systemClassLoader) to work around JAXP #6723276 return new ByteArrayInputStream(new byte[0]); } else { InputStream is = super.getResourceAsStream(name); if (is == null) { ClassLoader l = netigso.findFallbackLoader(); if (l != null && l != this) { is = l.getResourceAsStream(name); } } return is; } } @Override final URL getResourceImpl(String name) { URL u = super.getResourceImpl(name); if (u == null) { ClassLoader l = netigso.findFallbackLoader(); if (l != null && l != this) { u = l.getResource(name); } } return u; } @Override synchronized Enumeration getResourcesImpl(String name) throws IOException { if (JRE_PROVIDED_FACTORIES.contains(name)) { // #146082: prefer JRE versions of JAXP factories when available. // #147082: use empty file rather than null (~ delegation to ClassLoader.systemClassLoader) to work around JAXP #6723276 return parents.systemCL().getResources(name); } else { Enumeration first = super.getResourcesImpl(name); ClassLoader l = netigso.findFallbackLoader(); if (l != null && l != this) { return Enumerations.removeDuplicates( Enumerations.concat(first, l.getResources(name)) ); } else { return first; } } } protected @Override boolean shouldDelegateResource(String pkg, ClassLoader parent) { ClassLoader trueParent = getParent(); boolean parentIsJRE; if (trueParent != null && trueParent.getClass().getName().equals("com.sun.jnlp.JNLPClassLoader")) { // #177120 NOI18N parentIsJRE = false; } else if (parent == null) { parentIsJRE = true; } else if (parent instanceof MainImpl.BootClassLoader) { parentIsJRE = true; } else { parentIsJRE = false; } if (parentIsJRE && !installer.shouldDelegateClasspathResource(pkg)) { return false; } return super.shouldDelegateResource(pkg, parent); } @Override protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { ProxyClassLoader priviledged = null; NetigsoLoader osgi = null; if (!name.startsWith("java.")) { // NOI18N Class[] stack = TopSecurityManager.getStack(); for (Class c: stack) { ClassLoader l = c.getClassLoader(); if (l == this) { continue; } if (l == getClass().getClassLoader()) { continue; } if (l instanceof ProxyClassLoader) { priviledged = (ProxyClassLoader) l; break; } if (l instanceof NetigsoLoader) { osgi = (NetigsoLoader) l; break; } } } ClassNotFoundException prev = null; try { if (priviledged != null) try { return priviledged.loadClass(name, resolve); } catch (ClassNotFoundException inner) { prev = inner; } if (osgi != null) try { return osgi.loadClass(name, resolve); } catch (ClassNotFoundException inner) { prev = inner; } return super.loadClass(name, resolve); } catch (ClassNotFoundException ex) { ClassLoader l = netigso.findFallbackLoader(); if (l == null || l == this) { if (prev != null) { try { ex.initCause(prev); } catch (IllegalStateException cantChangeCause) { // ignore } } throw ex; } return Class.forName(name, resolve, l); } } } /** @see #create(File,Object,boolean,boolean,boolean) * @deprecated since org.netbeans.core/1 1.3 */ @Deprecated public Module create(File jar, Object history, boolean reloadable, boolean autoload) throws IOException, DuplicateException { return create(jar, history, reloadable, autoload, false); } /** Create a module from a JAR and add it to the managed set. *

Will initially be disabled. * To make sure all available eager modules get enabled, just call: * {@link #enable}({@link Collections#emptySet}) *

May throw an IOException if the JAR file cannot be opened * for some reason, or is malformed. *

If there is already a module of the same name managed, * throws a duplicate exception. In this case you may wish * to delete the original and try again. *

You must give it some history object which can be used * to provide context for where the module came from and * whether it has been here before. *

You cannot request that a module be both autoload and eager. */ public Module create(File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException, DuplicateException { assertWritable(); ev.log(Events.START_CREATE_REGULAR_MODULE, jar); Module m = moduleFactory.create(jar.getAbsoluteFile(), history, reloadable, autoload, eager, this, ev); ev.log(Events.FINISH_CREATE_REGULAR_MODULE, jar); subCreate(m); return m; } /** Create a module from a JAR representing an OSGi bundle * and adds it to the managed set. * Behavior is the same as {@link #create(java.io.File, java.lang.Object, boolean, boolean, boolean)} * just adds additional start level info. * * @param startLevel an OSGi start level. Zero indicates, no changes to default level. * @throws InvalidException if the JAR does not represent an OSGi bundle * @since 2.43 */ public Module createBundle( File jar, Object history, boolean reloadable, boolean autoload, boolean eager, int startLevel ) throws IOException, DuplicateException { assertWritable(); ev.log(Events.START_CREATE_REGULAR_MODULE, jar); Module m = moduleFactory.create(jar.getAbsoluteFile(), history, reloadable, autoload, eager, this, ev); if (m instanceof NetigsoModule) { NetigsoModule nm = (NetigsoModule)m; nm.setStartLevel(startLevel); } else { throw new InvalidException("Expecting an OSGI bundle in " + jar); } ev.log(Events.FINISH_CREATE_REGULAR_MODULE, jar); subCreate(m); return m; } /** Create a fixed module (e.g. from classpath). * Will initially be disabled. */ public Module createFixed(Manifest mani, Object history, ClassLoader loader) throws InvalidException, DuplicateException { return createFixed(mani, history, loader, false, false); } /** * Create a fixed module (e.g. from classpath) with optional autoload and eager flags. * Will initially be disabled. * @since 2.7 */ public Module createFixed(Manifest mani, Object history, ClassLoader loader, boolean autoload, boolean eager) throws InvalidException, DuplicateException { assertWritable(); if (mani == null || loader == null) throw new IllegalArgumentException("null manifest or loader"); // NOI18N ev.log(Events.START_CREATE_BOOT_MODULE, history); Module m = moduleFactory.createFixed(mani, history, loader, autoload, eager, this, ev); ev.log(Events.FINISH_CREATE_BOOT_MODULE, history); subCreate(m); synchronized (this) { Collection> oldMods = bootstrapModules.get(loader); Collection> mods; if (oldMods == null) { mods = new ArrayList<>(); } else { mods = new ArrayList<>(oldMods); for (Iterator> rit = mods.iterator(); rit.hasNext(); ) { Reference r = rit.next(); if (r.get() == null) { rit.remove(); } } } mods.add(new WeakReference<>(m)); bootstrapModules.put(loader, mods); } return m; } /** Used by Module to communicate with the ModuleInstaller re. dependencies. */ void refineDependencies(Module m, Set dependencies) { installer.refineDependencies(m, dependencies); } Set loadDependencies(String cnb) { return installer.loadDependencies(cnb); } /** Allows the installer to add provides (used to provide name of platform we run on) */ String[] refineProvides (Module m) { return installer.refineProvides (m); } /** Used by Module to communicate with the ModuleInstaller re. classloader. */ public ClassLoader refineClassLoader(Module m, List parents) { // #27853: installer.refineClassLoader(m, parents); // if fragment, integrate into the host's classloader. Should be called under mutex() String fragmentHost = m.getFragmentHostCodeName(); if (fragmentHost == null) { return null; } Module theHost = modulesByName.get(fragmentHost); if (theHost == null) { throw new IllegalStateException("Missing hosting module " + fragmentHost + " for fragment " + m.getCodeName()); } if (!theHost.isEnabled()) { throw new IllegalStateException("Host module for " + m.getCodeName() + " should have been enabled: " + theHost); } return theHost.getClassLoader(); } public Collection getAttachedFragments(Module m) { String cdn = m.getCodeNameBase(); Collection frags = fragmentModules.get(cdn); return frags == null ? Collections.emptySet() : frags; } /** * Refines the module's own path with patches from other modules * @param m the module * @param path the ordered list of classpath fragments */ void refineModulePath(Module m, List path) { String cnb = m.getCodeNameBase(); Collection injectList = fragmentModules.get(cnb); if (injectList == null) { return; } for (Module inject : injectList) { Util.err.log(Level.FINER, "Compat: injecting contents of fragment " + inject.getCodeNameBase() + " into " + m.getCodeNameBase()); List allJars = inject.getAllJars(); // PENDING: shouldn't we add those jars first, so they take precedence ? path.addAll(allJars); } } /** Use by OneModuleClassLoader to communicate with the ModuleInstaller re. masking. * @deprecated Use {@link #shouldDelegateResource(org.netbeans.Module, org.netbeans.Module, java.lang.String, java.lang.ClassLoader)}. */ @Deprecated public boolean shouldDelegateResource(Module m, Module parent, String pkg) { return shouldDelegateResource(m, parent, pkg, null); } /** * Determines if module `m' should delegate loading resources from package `p' to the * `parent'. The parent is identified either by module specification (parent) or by a classloader * which should load the package. For system or bootstrap classes, `parent' may be {@code null}, since * boostrap classloaders load more modules together. *

* If both `parent' and `ldr' are {@code null}, access to system/application classpath will be checked. * * @param m module that attempts to load resources. * @param parent parent classloader which may eventually load the resource, could be {@code null} to indicate bootstrap or system class * @param pkg package (folder) with the resource * @param ldr the classloader which should load the resource; may be {@code null} * @return true, if the loading should be delegated to the classloader * @since 2.80 */ public boolean shouldDelegateResource(Module m, Module parent, String pkg, ClassLoader ldr) { // Cf. #19621: Module.PackageExport[] exports; if (parent != null) { exports = parent.getPublicPackages(); } else if (ldr != null) { Collection loaderMods = null; synchronized (this) { // create exports from modules for that classloader Collection> refMods = bootstrapModules.get(ldr); if (refMods != null) { loaderMods = new HashSet<>(); for (Iterator> rmit = refMods.iterator(); rmit.hasNext(); ) { Reference refMod = rmit.next(); Module mm = refMod.get(); if (mm == null) { rmit.remove(); } else { loaderMods.add(mm); } } } } Set cbn = new HashSet<>(); for (Dependency d : m.getDependenciesArray()) { if (d.getType() == Dependency.TYPE_MODULE) { cbn.add(d.getName()); } } if (loaderMods != null) { for (Module lm : loaderMods) { if (cbn.remove(lm.getCodeName()) && shouldDelegateResource(m, lm, pkg, ldr)) { return true; } } return false; } exports = null; } else { exports = null; } if (exports != null) { //Util.err.fine("exports=" + Arrays.asList(exports)); // Packages from parent are restricted: #19621. boolean exported = false; if (parent.isDeclaredAsFriend(m)) { // meaning public to all, or at least to me for (int i = 0; i < exports.length; i++) { if (exports[i].recursive ? pkg.startsWith(exports[i].pkg) : pkg.equals(exports[i].pkg)) { //Util.err.fine("matches " + exports[i]); exported = true; break; } } } if (!exported) { // This package is not public. m must have a direct impl-version // dependency on parent or it has no right to use this package. boolean impldep = false; Dependency[] deps = m.getDependenciesArray(); for (int i = 0; i < deps.length; i++) { if (deps[i].getType() == Dependency.TYPE_MODULE && deps[i].getComparison() == Dependency.COMPARE_IMPL && deps[i].getName().equals(parent.getCodeName())) { impldep = true; //Util.err.fine("impldep in " + deps[i]); break; } } if (!impldep) { // This module cannot use the package, sorry! It's private. //Util.err.fine("forbidden"); if (Util.err.isLoggable(Level.FINE)) { // Note that this is usually harmless. Typical case: Introspector.getBeanInfo // is called on some module-supplied class; this looks in the module's classloader // for org.netbeans.beaninfo.ModuleClassBeanInfo, which of course would not be // found anyway. Util.err.fine("Refusing to load non-public package " + pkg + " for " + m + " from parent module " + parent + " without an impl dependency"); } return false; } //Util.err.fine("impl dep"); } //Util.err.fine("exported"); } if (pkg.startsWith("META-INF/")) { // NOI18N // Modules should not make direct reference to metainfo dirs of // other modules. Don't bother logging it, however. return false; } // The installer can perform additional checks: return installer.shouldDelegateResource(m, parent, pkg); } // Again, access from Module to ModuleInstaller: Manifest loadManifest(File jar) throws IOException { return installer.loadManifest(jar); } private void subCreate(Module m) throws DuplicateException { Module old = get(m.getCodeNameBase()); if (old != null) { if (!Boolean.getBoolean("netbeans.ignore.dupmodule")) { throw new DuplicateException(old, m); } else { // ignore duplicate module, log and gracefuly exit Util.err.warning("Duplicate loading ignored: " + old + " and " + m); return; } } modules.add(m); modulesByName.put(m.getCodeNameBase(), m); providersOf.possibleProviderAdded(m); // must register module fragments early, to be enabled along with their hosts. registerModuleFragment(m); lookup.add(m); firer.created(m); firer.change(new ChangeFirer.Change(this, PROP_MODULES, null, null)); // It might have been that some other modules were thought to be missing // dependencies only because they needed this one. And other modules still // might have depended on this one, etc. So forget any cached info about // problems arising from inter-module dependencies. clearProblemCache(); firer.fire(); } /** * Attaches a fragment to an existing module. The hosting module must NOT * be already enabled, otherwise an exception will be thrown. Enabled module * may have some classes already loaded, and they cannot be patched. * * @param m module to attach if it is a fragment */ private Module attachModuleFragment(Module m) { String codeNameBase = m.getFragmentHostCodeName(); if (codeNameBase == null) { return null; } Module host = modulesByName.get(codeNameBase); if (host != null && host.isEnabled() && host.getClassLoader() != null) { throw new IllegalStateException("Host module " + host + " was enabled before, will not accept fragment " + m); } return host; } private boolean registerModuleFragment(Module m) { String codeNameBase = m.getFragmentHostCodeName(); if (codeNameBase == null) { return true; } Module host = modulesByName.get(codeNameBase); if (host != null && host.isEnabled()) { return false; } Collection frags = fragmentModules.get(codeNameBase); if (frags == null) { frags = new HashSet(1); fragmentModules.put(codeNameBase, frags); } frags.add(m); return true; } /** * Removes a fragment module. Throws an exception if the fragment's * host is already enabled and its classloader may have loaded fragment's * contents. *

* The method does nothing for non-fragment modules * * @param m the module to remove */ private void removeFragmentFromHost(Module m) { String fragHost = m.getFragmentHostCodeName(); if (fragHost == null) { return; } Module hostMod = modulesByName.get(fragHost); if (hostMod != null && hostMod.isEnabled() && m.isEnabled()) { throw new IllegalStateException("Host module " + m.getCodeName() + " was loaded, cannot remove fragment"); } Collection frags = fragmentModules.get(fragHost); if (frags != null) { frags.remove(m); } } /** Remove a module from the managed set. * Must be disabled first. * Must not be a "fixed" module. */ public void delete(Module m) throws IllegalArgumentException { assertWritable(); if (m.isFixed()) throw new IllegalArgumentException("fixed module: " + m); // NOI18N if (m.isEnabled()) throw new IllegalArgumentException("enabled module: " + m); // NOI18N ev.log(Events.DELETE_MODULE, m); removeFragmentFromHost(m); modules.remove(m); modulesByName.remove(m.getCodeNameBase()); providersOf.possibleProviderRemoved(m); lookup.remove(m); firer.deleted(m); firer.change(new ChangeFirer.Change(this, PROP_MODULES, null, null)); firer.change(new ChangeFirer.Change(m, Module.PROP_VALID, Boolean.TRUE, Boolean.FALSE)); // #14561: some other module might now be uninstallable as a result. clearProblemCache(); m.destroy(); firer.fire(); } /** Reload a module. * This could make a fresh copy of its JAR file preparing * to enable it with different contents; at least it will * rescan the manifest. * It must currently be disabled and not "fixed", and it will * stay disabled after this call; to actually reinstall it * requires a separate call. * It may or may not actually be marked "reloadable", but * for greatest reliability it should be. * Besides actually reloading the contents, any cached information * about failed dependencies or runtime problems with the module * is cleared so it may be tried again. */ public void reload(Module m) throws IllegalArgumentException, IOException { assertWritable(); // No Events, not a user- nor performance-interesting action. if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("reload: " + m); } if (m.isFixed()) throw new IllegalArgumentException("reload fixed module: " + m); // NOI18N if (m.isEnabled()) throw new IllegalArgumentException("reload enabled module: " + m); // NOI18N providersOf.possibleProviderRemoved(m); try { m.reload(); } catch (IOException ioe) { // Module is trash, remove it from our list and pass on the exception. delete(m); throw ioe; } providersOf.possibleProviderAdded(m); firer.change(new ChangeFirer.Change(m, Module.PROP_MANIFEST, null, null)); // Some problem with this module may now have gone away. In turn, some // other modules may now no longer have problems. So clear the cache // of "soft" problems (interdependencies between modules). // Also clear any "hard" problems associated with this module, as they // may now have been fixed. synchronized (MODULE_PROBLEMS_LOCK) { moduleProblemsWithoutNeeds.remove(m); moduleProblemsWithNeeds.remove(m); } firer.change(new ChangeFirer.Change(m, Module.PROP_PROBLEMS, null, null)); clearProblemCache(); firer.fire(); } /** Enable a single module. * Must have satisfied its dependencies. * Must not be an autoload module, when supported. * @see #enable(Set) */ public final void enable(Module m) throws IllegalArgumentException, InvalidException { enable(m, true); } final void enable(Module m, boolean honor) throws IllegalArgumentException, InvalidException { enable(Collections.singleton(m), honor); } /** Disable a single module. * Must not be required by any enabled modules. * Must not be an autoload module, when supported. * @see #disable(Set) */ public final void disable(Module m) throws IllegalArgumentException { disable(Collections.singleton(m)); } /** Enable a set of modules together. *

Must have satisfied their dependencies * (possibly with one another). *

Must not contain autoload nor eager modules. * Might contain fixed modules (they can only be installed once of course). * Other modules may become enabled automatically according to {@link #simulateEnable}. *

It is permissible to pass in modules which in fact at runtime cannot * satisfy their package dependencies, or which {@link ModuleInstaller#prepare} * rejects on the basis of missing contents. In such a case {@link InvalidException} * will be thrown and nothing will be installed. The {@link InvalidException} in such * a case should contain a reference to the offending module. */ public void enable(Set modules) throws IllegalArgumentException, InvalidException { enable(modules, true); } private void enable(Set modules, boolean honorAutoloadEager) throws IllegalArgumentException, InvalidException { assertWritable(); Util.err.log(Level.FINE, "enable: {0}", modules); /* Consider eager modules: if (modules.isEmpty()) { return; } */ ev.log(Events.PERF_START, "ModuleManager.enable"); // NOI18N // Basic problems will be caught here, and we also get the autoloads: List toEnable = simulateEnable(modules, honorAutoloadEager); ev.log(Events.PERF_TICK, "checked the required ordering and autoloads"); // NOI18N if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("enable: toEnable=" + toEnable); // NOI18N } { // Verify that we are cool as far as basic dependencies go. Set testing = new HashSet(toEnable); if (! testing.containsAll(modules)) { Set bogus = new HashSet(modules); bogus.removeAll(testing); throw new IllegalArgumentException("Not all requested modules can be enabled: " + bogus); // NOI18N } for (Module m : testing) { if (!modules.contains(m) && !m.isAutoload() && !m.isEager()) { throw new IllegalArgumentException("Would also need to enable " + m); // NOI18N } } } Util.err.fine("enable: verified dependencies"); ev.log(Events.PERF_TICK, "verified dependencies"); // NOI18N ev.log(Events.START_ENABLE_MODULES, toEnable); netigso.willEnable(toEnable); for (;;) { // first connect fragments to their hosts, so classloaders are populated for (Module m: toEnable) { if (m.isEnabled()) { continue; } // store information from fragment modules for early initialization of hosting classlaoder: attachModuleFragment(m); } // Actually turn on the listed modules. // List of modules that need to be "rolled back". LinkedList fallback = new LinkedList(); // Whether we were attempting to bring a classloader up. // This affects whether we need to rollback that change on the // problem module or not. boolean tryingClassLoaderUp = false; // If a failure due to package dep occurs, store it here. Dependency failedPackageDep = null; try { ev.log(Events.PERF_START, "module preparation" ); // NOI18N for (Module m: toEnable) { if (m.isEnabled()) { continue; } fallback.addFirst(m); if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("enable: bringing up: " + m); } ev.log(Events.PERF_START, "bringing up classloader on " + m.getCodeNameBase()); // NOI18N try { Set parents = calculateParents(m); m.classLoaderUp(parents); } catch (IOException ioe) { tryingClassLoaderUp = true; InvalidException ie = new InvalidException(m, ioe.toString()); ie.initCause(ioe); throw ie; } m.setEnabled(true); ev.log(Events.PERF_END, "bringing up classloader on " + m.getCodeNameBase() ); // NOI18N // Check package dependencies. // ev.log(Events.PERF_START, "package dependency check on " + m.getCodeName() ); // NOI18N if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("enable: checking package dependencies for " + m); } Dependency[] dependencies = m.getDependenciesArray(); for (int i = 0; i < dependencies.length; i++) { Dependency dep = dependencies[i]; if (dep.getType() != Dependency.TYPE_PACKAGE) { continue; } if (! Util.checkPackageDependency(dep, m.getClassLoader())) { failedPackageDep = dep; String polite = (String)m.getLocalizedAttribute("OpenIDE-Module-Package-Dependency-Message"); // NOI18N throw new InvalidException(m, "Dependency failed on " + dep, polite); // NOI18N } if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("Successful check for: " + dep); } } // ev.log(Events.PERF_END, "package dependency check on " + m.getCodeName() ); // NOI18N // Prepare to load it. ev.log(Events.PERF_START, "ModuleInstaller.prepare " + m.getCodeName() ); // NOI18N installer.prepare(m); ev.log(Events.PERF_END, "ModuleInstaller.prepare " + m.getCodeName() ); // NOI18N } ev.log(Events.PERF_END, "module preparation" ); // NOI18N } catch (InvalidException ie) { // Remember that there was a problem with this guy. Module bad = ie.getModule(); if (bad == null) throw new IllegalStateException("Problem with no associated module: " + ie, ie); // NOI18N Set> probs = moduleProblemsWithNeeds.get(bad); if (probs == null) throw new IllegalStateException("Were trying to install a module that had never been checked: " + bad, ie); // NOI18N if (! probs.isEmpty()) throw new IllegalStateException("Were trying to install a module that was known to be bad: " + bad + " " + probs, ie); // NOI18N // Record for posterity. if (probs == EMPTY_COLLECTION) { probs = new HashSet>(8); moduleProblemsWithNeeds.put(bad, probs); } if (failedPackageDep != null) { // Structured package dependency failed, track this. probs.add(Union2.createFirst(failedPackageDep)); } else { // Some other problem (exception). probs.add(Union2.createSecond(ie)); } // Other modules may have depended on this one and now will not be OK. // So clear all "soft" problems from the cache. // Remember, the problem we just added will be left alone, only // inter-module dependencies will be cleared. clearProblemCache(); // #14560: this one definitely changed its set of problems. firer.change(new ChangeFirer.Change(bad, Module.PROP_PROBLEMS, Collections.EMPTY_SET, Collections.singleton("something"))); // NOI18N // Rollback changes made so far before rethrowing. if (Util.err.isLoggable(Level.FINE)) { Util.err.fine("enable: will roll back from: " + ie); } for (Module m : fallback) { if (m.isFixed()) { // cannot disable fixed modules continue; } m.setEnabled(false); if (tryingClassLoaderUp) { // OK, taken into account for first module, others are up. tryingClassLoaderUp = false; } else { m.classLoaderDown(); System.gc(); System.runFinalization(); m.cleanup(); } } firer.fire(); throw ie; } // They all were OK so far; add to system classloader and install them. if (classLoader != null) { Util.err.fine("enable: adding to system classloader"); LinkedList nueclassloaders = new LinkedList(); if (moduleFactory.removeBaseClassLoader()) { ClassLoader base = ModuleManager.class.getClassLoader(); nueclassloaders.add(moduleFactory.getClasspathDelegateClassLoader(this, base)); for (Module m : toEnable) { ClassLoader c1 = m.getClassLoader(); if (c1 != base) { nueclassloaders.add(c1); } } } else { for (Module m : toEnable) { if (m.getClassLoader() == ClassLoader.getSystemClassLoader()) { nueclassloaders.addFirst(m.getClassLoader()); } else { nueclassloaders.add(m.getClassLoader()); } } } classLoader.append((nueclassloaders.toArray(new ClassLoader[nueclassloaders.size()]))); classLoader.size += toEnable.size(); } else { Util.err.fine("enable: no class loader yet, not appending"); } Util.err.fine("enable: fixing classloader"); installer.classLoaderUp(classLoader); Util.err.fine("enable: continuing to installation"); Set enableMore = netigso.turnOn(classLoader, Collections.unmodifiableCollection(new ArrayList(this.modules))); if (!enableMore.isEmpty()) { Util.err.log(Level.FINE, "netigso needs additional modules: {0}", enableMore); List toEnableMore = simulateEnable(enableMore, false); checkMissingModules(enableMore, toEnableMore); toEnable.addAll(toEnableMore); Util.err.log(Level.FINE, "Adding {0} and trying again", toEnableMore); continue; } if (!toEnable.isEmpty() && countEnabled(toEnable) == 0) { throw new InvalidException("No module could be enabled: " + toEnable); } installer.load(toEnable); netigso.startFramework(); break; } // register bytecode manipulation agents for (Module m : toEnable) { try { final String agentClass = m.dataWithCheck().getAgentClass(); if (agentClass != null) { m.assignInstrumentation(NbInstrumentation.registerAgent(m.getClassLoader(), agentClass)); } } catch (InvalidException ex) { Util.err.log(Level.FINE, null, ex); } } { // Take care of notifying various changes. Util.err.fine("enable: firing changes"); firer.change(new ChangeFirer.Change(this, PROP_ENABLED_MODULES, null, null)); // The class loader does not actually change as a result of this. for (Module m : toEnable) { firer.change(new ChangeFirer.Change(m, ModuleInfo.PROP_ENABLED, Boolean.FALSE, Boolean.TRUE)); if (! m.isFixed()) { firer.change(new ChangeFirer.Change(m, Module.PROP_CLASS_LOADER, null, null)); } } } ev.log(Events.FINISH_ENABLE_MODULES, toEnable); firer.fire(); } /** Disable a set of modules together. *

Must not be required by any enabled * modules (except one another). *

Must not contain autoload nor eager modules. * Must not contain fixed modules. * Other modules may become disabled automatically according to {@link #simulateDisable}. */ public void disable(Set modules) throws IllegalArgumentException { assertWritable(); Util.err.fine("disable: " + modules); if (modules.isEmpty()) { return; } // Checks for invalid items, plus includes autoloads to turn off. List toDisable = simulateDisable(modules); Util.err.fine("disable: toDisable=" + toDisable); { // Verify that dependencies are OK. for (Module m: toDisable) { if (!modules.contains(m) && !m.isAutoload() && !m.isEager()) { throw new IllegalArgumentException("Would also need to disable: " + m); // NOI18N } } } Util.err.fine("disable: verified dependencies"); ev.log(Events.START_DISABLE_MODULES, toDisable); { // Actually turn off all modules. installer.unload(toDisable); for (Module m : toDisable) { installer.dispose(m); m.setEnabled(false); m.unregisterInstrumentation(); m.classLoaderDown(); } System.gc(); // hope OneModuleClassLoader.finalize() is called... System.runFinalization(); // but probably it won't be. See #4405807. for (Module m : toDisable) { m.cleanup(); } } Util.err.fine("disable: finished, will notify changes"); { // Notify various changes. firer.change(new ChangeFirer.Change(this, PROP_ENABLED_MODULES, null, null)); // Class loader will change as a result. invalidateClassLoader(); for (Module m : toDisable) { firer.change(new ChangeFirer.Change(m, ModuleInfo.PROP_ENABLED, Boolean.TRUE, Boolean.FALSE)); firer.change(new ChangeFirer.Change(m, Module.PROP_CLASS_LOADER, null, null)); } } ev.log(Events.FINISH_DISABLE_MODULES, toDisable); firer.fire(); } private static class CodeNameBaseComparator implements Comparator { public @Override int compare(Module m1, Module m2) { return m1.getCodeNameBase().compareTo(m2.getCodeNameBase()); } } private final Set calculateParents(Module m) throws NumberFormatException, IOException { // Calculate the parents to initialize the classloader with. Dependency[] dependencies = m.getDependenciesArray(); Set res = new HashSet(dependencies.length * 4 / 3 + 1); for (int i = 0; i < dependencies.length; i++) { Dependency dep = dependencies[i]; if (dep.getType() != Dependency.TYPE_MODULE) { // Token providers do *not* go into the parent classloader // list. The providing module must have been turned on first. // But you cannot automatically access classes from it. continue; } String name = (String) Util.parseCodeName(dep.getName())[0]; Module parent = get(name); // Should not happen: if (parent == null) { throw new IOException("Parent " + name + " not found!"); // NOI18N } res.add(parent); } // dependencies of fragment modules should be injected into the main module. Collection fragments = getAttachedFragments(m); if (!fragments.isEmpty()) { for (Module frag : fragments) { Set mods = calculateParents(frag); mods.remove(m); res.addAll(mods); } } return res; } /** Simulate what would happen if a set of modules were to be enabled. * None of the listed modules may be autoload modules, nor eager, nor currently enabled, * though they may be fixed (if they have not yet been enabled). * It may happen that some of them do not satisfy their dependencies. * It may also happen that some of them require other, currently disabled, * modules to be enabled in order for them to be enabled. * It may further happen that some currently disabled eager modules could * be enabled as a result of these modules being enabled. * The returned set is the set of all modules that actually could be enabled. * It will include the requested modules, minus any that cannot satisfy * their dependencies (even on each other), plus any managed but currently * disabled modules that would need to be enabled (including autoload modules * required by some listed module but not by any currently enabled module), * plus any eager modules which can be enabled with the other enablements * (and possibly any autoloads needed by those eager modules). * Where a requested module requires some token, either it will not be included * in the result (in case the dependency cannot be satisfied), or it will, and * all modules providing that token which can be included will be included, even * if it would suffice to choose only one - unless a module providing that token * is already enabled or in the requested list, * in which case just the requested module will be listed. * Modules are returned in an order in which they could be enabled (where * base modules are always enabled before dependent modules). * Note that the returned list might include modules which in fact cannot be * enabled either because some package dependencies (which are checked only * on a live classloader) cannot be met; or {@link ModuleInstaller#prepare} * indicates that the modules are not in a valid format to install; or * creating the module classloader fails unexpectedly. */ public List simulateEnable(Set modules) throws IllegalArgumentException { return simulateEnable(modules, true); } final List simulateEnable(Set modules, boolean honorAutoloadEager) throws IllegalArgumentException { List cnbs = mdc.simulateEnable(modules); if (cnbs != null) { List arr = new ArrayList(cnbs.size()); for (String cnb : cnbs) { arr.add(get(cnb)); } assert !arr.contains(null) : arr; return arr; } /* Not quite, eager modules may change this: if (modules.isEmpty()) { return Collections.EMPTY_LIST; } */ // XXX also optimize for modules.size == 1 Set willEnable = new TreeSet(new CodeNameBaseComparator()); for (Module m: modules) { if (honorAutoloadEager) { if (m.isAutoload()) throw new IllegalArgumentException("Cannot simulate enabling an autoload: " + m); // NOI18N if (m.isEager()) throw new IllegalArgumentException("Cannot simulate enabling an eager module: " + m); // NOI18N } if (m.isEnabled()) throw new IllegalArgumentException("Already enabled: " + m); // NOI18N if (!m.isValid()) throw new IllegalArgumentException("Not managed by me: " + m + " in " + m); // NOI18N maybeAddToEnableList(willEnable, modules, m, true); } // XXX clumsy but should work: Set stillDisabled = new HashSet(this.modules); Iterator it = stillDisabled.iterator(); while (it.hasNext()) { Module m = it.next(); if (m.isEnabled() || willEnable.contains(m)) { it.remove(); } } while (searchForPossibleEager(willEnable, stillDisabled, modules)) {/* search again */} Map> deps = Util.moduleDependencies( willEnable, modulesByName, getProvidersOf(), fragmentModules); try { List l = Utilities.topologicalSort(willEnable, deps); Collections.reverse(l); mdc.registerEnable(modules, l); return l; } catch (TopologicalSortException ex) { // Some kind of cycle involving prov-req deps. Should be extremely rare. // Example (from random failures of MMT.testProvReqCycles): // m1 => {m2 | m3} // m2 => {m1 | m4} // m3 => {m1} // m4 => {} // Now consider: // sE(m2) = ? // [m4, m2] is fine. // [m4, m2, m1] would be OK too, but will result in TSE. // Do not know what to do here, actually, so give up. if (PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES) { Util.err.log(Level.WARNING, null, ex); } Util.err.warning("Cyclic module dependencies, will refuse to enable: " + deps); // NOI18N return Collections.emptyList(); } } /** * Determines if enabling compat modules is disruptive. Compat modules are often * fragment modules augmenting regular ones. If the host module is already enabled / loaded, * the compat module cannot back-patch already existing classes, and IDE restart is needed. * * @param modules initial set of modules * @return true, if the operation requires a restart * @throws IllegalArgumentException */ public boolean hasToEnableCompatModules(Set modules) throws IllegalArgumentException { List toEnable = simulateEnable(modules); for (Module m : toEnable) { String fragmentHostCodeName = m.getFragmentHostCodeName(); if (fragmentHostCodeName != null && !fragmentHostCodeName.isEmpty()) { Module fragHost = get(fragmentHostCodeName); if (fragHost != null && fragHost.isEnabled()) { return true; } } } return false; } private void maybeAddToEnableList(Set willEnable, Set mightEnable, Module m, boolean okToFail) { if (! missingDependencies(m).isEmpty()) { if (!okToFail) { Util.err.warning("Module " + m + " had unexpected problems: " + missingDependencies(m) + " (willEnable: " + willEnable + " mightEnable: " + mightEnable + ")"); } // Cannot satisfy its dependencies, exclude it. return; } if (!willEnable.add(m)) { // Already there, done. return; } // need to register fragments eagerly, so they are available during // dependency sort Module host = attachModuleFragment(m); if (host != null && !host.isEnabled()) { maybeAddToEnableList(willEnable, mightEnable, host, okToFail); } // Also add anything it depends on, if not already there, // or already enabled. for (Dependency dep : m.getDependenciesArray()) { if (dep.getType() == Dependency.TYPE_MODULE) { String codeNameBase = (String)Util.parseCodeName(dep.getName())[0]; Module other = get(codeNameBase); // Should never happen: if (other == null) throw new IllegalStateException("Should have found module: " + codeNameBase); // NOI18N if (! other.isEnabled()) { maybeAddToEnableList(willEnable, mightEnable, other, false); } } else if ( dep.getType() == Dependency.TYPE_REQUIRES || dep.getType() == Dependency.TYPE_NEEDS || dep.getType() == Dependency.TYPE_RECOMMENDS ) { Set providers = getProvidersOf().get(dep.getName()); if (providers == null) { assert dep.getType() == Dependency.TYPE_RECOMMENDS : "Should have found a provider of " + dep; continue; } // First check if >= 1 is already enabled or will be soon. If so, great. boolean foundOne = false; for (Module other : providers) { if (other.isEnabled() || (other.getProblems().isEmpty() && mightEnable.contains(other))) { foundOne = true; break; } } if (foundOne) { // OK, we are satisfied. continue; } // All disabled. So add them all to the enable list. for (Module other : providers) { // It is OK if one of them fails. maybeAddToEnableList(willEnable, mightEnable, other, true); // But we still check to ensure that at least one did not! if (!foundOne && willEnable.contains(other)) { foundOne = true; // and continue with the others, try to add them too... } } // Logic is that missingDependencies(m) should contain dep in this case. assert foundOne || dep.getType() == Dependency.TYPE_RECOMMENDS : "Should have found a nonproblematic provider of " + dep + " among " + providers + " with willEnable=" + willEnable + " mightEnable=" + mightEnable; } // else some other kind of dependency that does not concern us } Collection frags = getAttachedFragments(m); for (Module fragMod : frags) { if (! fragMod.isEnabled()) { maybeAddToEnableList(willEnable, mightEnable, fragMod, fragMod.isEager()); } } } private boolean searchForPossibleEager(Set willEnable, Set stillDisabled, Set mightEnable) { // Check for any eagers in stillDisabled which could be enabled based // on currently enabled modules and willEnable. For any such, remove from // stillDisabled and add to willEnable (using maybeAddToEnableList, so that // autoloads needed by them are picked up too). If any were found, return true. boolean found = false; Iterator it = stillDisabled.iterator(); FIND_EAGER: while (it.hasNext()) { Module m = it.next(); if (willEnable.contains(m)) { // Presumably real module M1, eager M2 dep. on M1, eager M3 dep. // on M2; already called couldBeEnabledWithEagers(M3) and it // added M3 to willEnable (thus M2 also) but only removed M3 // from willEnable, so we should skip it now. it.remove(); continue; } if (m.isEager()) { if (couldBeEnabledWithEagers(m, willEnable, new HashSet())) { // Go for it! found = true; it.remove(); maybeAddToEnableList(willEnable, mightEnable, m, false); } } } return found; } private boolean couldBeEnabledWithEagers(Module m, Set willEnable, Set recursion) { // True if a search of the dependencies of this module reveals // only modules which are currently enabled; in the willEnable // list; or are autoloads or eager modules for which this predicate // is recursively true. if (m.isEnabled() || willEnable.contains(m)) return true; if (!m.isAutoload() && !m.isEager()) return false; if (!m.getProblems().isEmpty()) return false; if (!recursion.add(m)) { // A cycle, they can enable one another... return true; } Dependency[] dependencies = m.getDependenciesArray(); for (int i = 0; i < dependencies.length; i++) { Dependency dep = dependencies[i]; if (dep.getType() == Dependency.TYPE_MODULE) { String codeNameBase = (String)Util.parseCodeName(dep.getName())[0]; Module other = get(codeNameBase); // Should never happen: if (other == null) throw new IllegalStateException("Should have found module: " + codeNameBase); // NOI18N if (!couldBeEnabledWithEagers(other, willEnable, recursion)) return false; } else if (dep.getType() == Dependency.TYPE_REQUIRES || dep.getType() == Dependency.TYPE_NEEDS) { Set providers = getProvidersOf().get(dep.getName()); if (providers == null) throw new IllegalStateException("Should have found a provider of: " + dep.getName()); // NOI18N // Just need *one* to match. boolean foundOne = false; for (Module other : providers) { if (couldBeEnabledWithEagers(other, willEnable, recursion)) { foundOne = true; break; } } if (!foundOne) return false; } // else some other dep type } return true; } /** Only for use from Javeleon code. */ public List simulateJaveleonReload(Module moduleToReload) throws IllegalArgumentException { Set transitiveDependents = new HashSet(20); addToJaveleonDisableList(transitiveDependents, moduleToReload); Map> deps = Util.moduleDependencies(transitiveDependents, modulesByName, getProvidersOf()); try { LinkedList orderedForEnabling = new LinkedList(); for (Module m : Utilities.topologicalSort(transitiveDependents, deps)) { if (m != moduleToReload) { orderedForEnabling.addFirst(m); } } return orderedForEnabling; } catch (TopologicalSortException ex) { return new ArrayList(transitiveDependents); } } private void addToJaveleonDisableList(Set willDisable, Module m) { if (willDisable.contains(m)) { return; } willDisable.add(m); for (Module other : modules) { if (! other.isEnabled() || willDisable.contains(other)) { continue; } Dependency[] depenencies = other.getDependenciesArray(); for (int i = 0; i < depenencies.length; i++) { Dependency dep = depenencies[i]; if (dep.getType() == Dependency.TYPE_MODULE) { if (Util.parseCodeName(dep.getName())[0].equals(m.getCodeNameBase())) { addToJaveleonDisableList(willDisable, other); break; } } } } } /** Simulate what would happen if a set of modules were to be disabled. * None of the listed modules may be autoload modules, nor eager, nor currently disabled, nor fixed. * The returned set will list all modules that would actually be disabled, * meaning the listed modules, plus any currently enabled but unlisted modules * (including autoloads) that require some listed modules, plus any autoloads * which would no longer be needed as they were only required by modules * otherwise disabled. * Provide-require pairs count for purposes of disablement: if the set of * requested modules includes all remaining enabled providers of some token, * and modules requiring that token will need to be disabled as well. * Modules are returned in an order in which they could be disabled (where * dependent modules are always disabled before base modules). */ public List simulateDisable(Set modules) throws IllegalArgumentException { if (modules.isEmpty()) { return Collections.emptyList(); } // XXX also optimize for modules.size == 1 // Probably not a very efficient algorithm. But it probably does not need to be. Set willDisable = new TreeSet(new CodeNameBaseComparator()); for (Module m : modules) { if (m.isAutoload()) throw new IllegalArgumentException("Cannot disable autoload: " + m); // NOI18N if (m.isEager()) throw new IllegalArgumentException("Cannot disable eager module: " + m); // NOI18N if (m.isFixed()) throw new IllegalArgumentException("Cannot disable fixed module: " + m); // NOI18N if (! m.isEnabled()) throw new IllegalArgumentException("Already disabled: " + m); // NOI18N addToDisableList(willDisable, m); } Set stillEnabled = new HashSet(getEnabledModules()); stillEnabled.removeAll(willDisable); while (searchForUnusedAutoloads(willDisable, stillEnabled)) {/* search again */} Map> deps = Util.moduleDependencies(willDisable, modulesByName, getProvidersOf()); try { return Utilities.topologicalSort(willDisable, deps); } catch (TopologicalSortException ex) { // Again, don't know what to do exactly, so give up and just turn them off. if (PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES) { Util.err.log(Level.WARNING, null, ex); } Util.err.warning("Cyclic module dependencies, will turn them off in a random order: " + deps); // NOI18N return new ArrayList(willDisable); } } private void addToDisableList(Set willDisable, Module m) { if (willDisable.contains(m)) { // E.g. if original set had A then B, B depends on A. return; } willDisable.add(m); // Find any modules depending on this one which are currently enabled. // (And not already here.) // If there are any, add them. for (Module other : modules) { if (other.isFixed() || ! other.isEnabled() || willDisable.contains(other)) { continue; } Dependency[] depenencies = other.getDependenciesArray(); for (int i = 0; i < depenencies.length; i++) { Dependency dep = depenencies[i]; if (dep.getType() == Dependency.TYPE_MODULE) { if (Util.parseCodeName(dep.getName())[0].equals(m.getCodeNameBase())) { // Need to disable this one too. addToDisableList(willDisable, other); // No need to scan the rest of its dependencies. break; } } else if ( dep.getType() == Dependency.TYPE_REQUIRES || dep.getType() == Dependency.TYPE_NEEDS ) { if (m.provides(dep.getName())) { // Careful. There may be some third module still enabled which // provides this same token too. boolean foundOne = false; for (Module third: getEnabledModules()) { if (third.isEnabled() && !willDisable.contains(third) && third.provides(dep.getName())) { foundOne = true; break; } } if (!foundOne) { // Nope, we were the only/last one to provide it. addToDisableList(willDisable, other); break; } } } // else some other kind of dependency, we do not care } } } private boolean searchForUnusedAutoloads(Set willDisable, Set stillEnabled) { // Check for any autoloads in stillEnabled which are not used by anything else // in stillEnabled. For each such, remove it from stillEnabled and add // to willDisable. If any were found, return true. boolean found = false; Iterator it = stillEnabled.iterator(); FIND_AUTOLOADS: while (it.hasNext()) { Module m = it.next(); if (m.isAutoload()) { for (Module other: stillEnabled) { Dependency[] dependencies = other.getDependenciesArray(); for (int i = 0; i < dependencies.length; i++) { Dependency dep = dependencies[i]; if (dep.getType() == Dependency.TYPE_MODULE) { if (Util.parseCodeName(dep.getName())[0].equals(m.getCodeNameBase())) { // Still used, skip it. continue FIND_AUTOLOADS; } } else if ( dep.getType() == Dependency.TYPE_REQUIRES || dep.getType() == Dependency.TYPE_NEEDS || dep.getType() == Dependency.TYPE_RECOMMENDS ) { // Here we play it safe and leave autoloads on if they provide // something used by some module - even if technically it would // be possible to turn off the autoload because there is another // enabled module providing the same thing. Leave it on anyway. if (m.provides(dep.getName())) { continue FIND_AUTOLOADS; } } // else some other type } } // Nobody uses it! found = true; it.remove(); willDisable.add(m); } } return found; } // dummy object to be placed in the problem set while recursive checking is in progress private static final Union2 PROBING_IN_PROCESS = Union2.createSecond(new InvalidException("PROBING_IN_PROCESS")); // Access from Module.getProblems, q.v. // The probed module must not be currently enabled or fixed. Set> missingDependencies(Module probed) { return missingDependencies(probed, true); } private Set> missingDependencies(Module probed, boolean withNeeds) { // We need to synchronize here because though this method may be called // only within a read mutex, it can write to moduleProblems. Other places // where moduleProblems are used are write-mutex only and so do not have // to worry about contention. synchronized (MODULE_PROBLEMS_LOCK) { Map>> mP = (withNeeds ? moduleProblemsWithNeeds : moduleProblemsWithoutNeeds); Set> probs = mP.get(probed); if (probs == null) { probs = new HashSet>(8); if (withNeeds) { probs.addAll(missingDependencies(probed, false)); } probs.add(PROBING_IN_PROCESS); mP.put(probed, probs); for (Dependency dep : probed.getDependenciesArray()) { if (dep.getType() == Dependency.TYPE_PACKAGE) { // Can't check it in advance. Assume it is OK; if not // a problem will be indicated during an actual installation // attempt. } else if (dep.getType() == Dependency.TYPE_MODULE) { // Look for the corresponding module. Object[] depParse = Util.parseCodeName(dep.getName()); String codeNameBase = (String)depParse[0]; int relVersionMin = (depParse[1] != null) ? ((Integer)depParse[1]).intValue() : -1; int relVersionMax = (depParse[2] != null) ? ((Integer)depParse[2]).intValue() : relVersionMin; Module other = get(codeNameBase); if (other == null) { // No such module, bad. probs.add(Union2.createFirst(dep)); continue; } SpecificationVersion otherSpec = other.getSpecificationVersion(); if (otherSpec == null) { otherSpec = new SpecificationVersion("0"); // NOI18N } if (relVersionMin == relVersionMax) { // Non-ranged dep. if (relVersionMin != other.getCodeNameRelease()) { // Wrong major version, bad. probs.add(Union2.createFirst(dep)); continue; } if (dep.getComparison() == Dependency.COMPARE_IMPL && ! Utilities.compareObjects(dep.getVersion(), other.getImplementationVersion())) { // NOI18N // Wrong impl version, bad. probs.add(Union2.createFirst(dep)); continue; } if (dep.getComparison() == Dependency.COMPARE_SPEC && new SpecificationVersion(dep.getVersion()).compareTo( otherSpec) > 0) { // Spec version not high enough, bad. probs.add(Union2.createFirst(dep)); continue; } } else if (relVersionMin < relVersionMax) { // Ranged dep. int otherRel = other.getCodeNameRelease(); if (otherRel < relVersionMin || otherRel > relVersionMax) { // Major version outside of range, bad. probs.add(Union2.createFirst(dep)); continue; } if (dep.getComparison() == Dependency.COMPARE_IMPL) { throw new IllegalStateException("No such thing as ranged impl dep"); // NOI18N } if (dep.getComparison() == Dependency.COMPARE_SPEC && // Spec comparisons only apply to the earliest major rel. otherRel == relVersionMin && new SpecificationVersion(dep.getVersion()).compareTo( otherSpec) > 0) { // Spec version not high enough, bad. probs.add(Union2.createFirst(dep)); continue; } } else { throw new IllegalStateException("Upside-down rel vers range"); // NOI18N } if (! other.isEnabled()) { // Need to make sure the other one is not missing anything either. // Nor that it depends (directly on indirectly) on this one. if ((!withNeeds && !missingDependencies(other, false).isEmpty()) || (withNeeds && !isAlmostEmpty(missingDependencies(other, true)))) { // This is a little subtle. Either the other module had real // problems, in which case our dependency on it is not legit. // Or, the other actually depends cyclically on this one. In // that case, *it* would wind up calling missingDependencies // on this module, but this module has already put a nonempty // set in the mapping (containing at least the element // PROBING_IN_PROCESS), causing the other module to fail and // return a dependency on this module, causing this module to // also fail with a dependency on that module. In the process, // both modules get marked permanently bogus (unless you reload // them both of course). probs.add(Union2.createFirst(dep)); continue; } // If the other module is thought to be OK, assume we can depend // on it if we need it. } // Already-installed modules are of course fine. } else if (dep.getType() == Dependency.TYPE_REQUIRES || (withNeeds && dep.getType() == Dependency.TYPE_NEEDS)) { // Works much like a regular module dependency. However it only // fails if there are no satisfying modules with no problems. String token = dep.getName(); Set providers = getProvidersOf().get(token); if (providers == null) { // Nobody provides it. This dep failed. probs.add(Union2.createFirst(dep)); } else { // We have some possible providers. Check that at least one is good. boolean foundOne = false; for (Module other : providers) { if (foundOne) { break; } if (other.isEnabled()) { foundOne = true; } else { if ((!withNeeds && missingDependencies(other, false).isEmpty()) || (withNeeds && isAlmostEmpty(missingDependencies(other, true)))) { // See comment above for regular module deps // re. use of PROBING_IN_PROCESS. foundOne = true; } } } if (!foundOne) { // Nobody can provide it, fail. probs.add(Union2.createFirst(dep)); } } } else if (dep.getType() == Dependency.TYPE_JAVA) { // Java dependency. Fixed for whole VM session, safe to check once and keep. if (! Util.checkJavaDependency(dep)) { // Bad. probs.add(Union2.createFirst(dep)); } } } probs.remove(PROBING_IN_PROCESS); if (probs.isEmpty()) { mP.put(probed, EMPTY_COLLECTION); } } return probs; } } private static boolean isAlmostEmpty(Set> probs) { return probs.isEmpty() || probs.equals(Collections.singleton(PROBING_IN_PROCESS)); } /** Forget about any possible "soft" problems there might have been. * Next time anyone asks, recompute them. * Currently enabled modules are left alone (no problems). * Otherwise, any problems which are "hard" (result from failed * Java/IDE/package dependencies, runtime errors, etc.) are left alone; * "soft" problems of inter-module dependencies are cleared * so they will be recomputed next time, and corresponding * changes are fired (since the next call to getProblem might * return a different result). */ private void clearProblemCache() { synchronized (MODULE_PROBLEMS_LOCK) { clearProblemCache(moduleProblemsWithoutNeeds); clearProblemCache(moduleProblemsWithNeeds); } } private void clearProblemCache(Map>> mP) { Iterator>>> it = mP.entrySet().iterator(); while (it.hasNext()) { Map.Entry>> entry = it.next(); Module m = entry.getKey(); if (! m.isEnabled()) { Set> s = entry.getValue(); if (s != null) { boolean clear = false; for (Union2 problem : s) { if (problem.hasSecond()) { // Hard problem, skip this one. continue; } Dependency dep = problem.first(); if (dep.getType() != Dependency.TYPE_MODULE && dep.getType() != Dependency.TYPE_REQUIRES && dep.getType() != Dependency.TYPE_NEEDS && dep.getType() != Dependency.TYPE_RECOMMENDS ) { // Also a hard problem. continue; } // Some soft problems found, i.e. module deps. Clear them all. // #76917: Even clear any hard problems. clear = true; break; } if (clear || s.isEmpty()) { // leave alone only if all hard problems it.remove(); firer.change(new ChangeFirer.Change(m, Module.PROP_PROBLEMS, null, null)); } } // if we never computed anything, make no change now } // enabled modules are definitely OK, no change there } } /** Try to shut down the system. * First all modules are asked if they wish to close, in the proper order. * Assuming they say yes, then they are informed of the close. * Returns true if they all said yes. */ public boolean shutDown() { return shutDown(null); } /** * Try to shut down the system. * First all modules are asked if they wish to close, in the proper order. * Assuming they say yes, a hook is run, then they are informed of the close. * If they did not agree to close, the hook is not run. * @param midHook a hook to run before closing modules if they agree to close * @return true if they all said yes and the module system is now shut down * @since org.netbeans.core/1 1.11 */ public boolean shutDown(Runnable midHook) { try { return shutDownAsync(midHook).get(); } catch (InterruptedException ex) { Exceptions.printStackTrace(ex); } catch (ExecutionException ex) { Exceptions.printStackTrace(ex); } return false; } /** Partially asynchronous support for shutdown of the system. * First all modules are asked if they wish to close, in the proper order. * Assuming they say yes, a hook is run, then they are informed of the close. * If they did not agree to close, the hook is not run. * All {@link OnStop} runnables are executed in asynchronously and * one can wait for the result of such execution by observing the * returned {@link Future}. * * @param midHook a hook to run before closing modules if they agree to close * @return a future with final result. true if modules agreed the shutdown. * false when they didn't. * As soon as the get() method returns true * the module system is properly shut down. * @since 2.56 */ public Future shutDownAsync(Runnable midHook) { assertWritable(); Set unorderedModules = getEnabledModules(); Map> providersMap = new HashMap>(); for (Module m : unorderedModules) { registerProviders(m, providersMap); } Map> deps = Util.moduleDependencies(unorderedModules, modulesByName, providersMap); List sortedModules; try { sortedModules = Utilities.topologicalSort(unorderedModules, deps); } catch (TopologicalSortException ex) { // Once again, weird situation. if (PRINT_TOPOLOGICAL_EXCEPTION_STACK_TRACES) { Util.err.log(Level.WARNING, null, ex); } Util.err.warning("Cyclic module dependencies, will not shut down cleanly: " + deps); // NOI18N return new TaskFuture(true, Task.EMPTY); } if (!TopSecurityManager.officialExit && !installer.closing(sortedModules)) { return new TaskFuture(false, Task.EMPTY); } if (midHook != null) { try { midHook.run(); } catch (RuntimeException e) { Util.err.log(Level.WARNING, null, e); } catch (LinkageError e) { Util.err.log(Level.WARNING, null, e); } } netigso.shutdownFramework(); Task task = installer.closeAsync(sortedModules); return new TaskFuture(true, task); } private class ModuleDataCache implements Stamps.Updater { private static final String CACHE = "all-manifests.dat"; private final Map path2Data; private final Map path2OSGi; private final Map path2Cnb; private final Map path2Fragment; private final int moduleCount; private Set toEnable; private List willEnable; public ModuleDataCache() { InputStream is = Stamps.getModulesJARs().asStream(CACHE); Map map = null; Map osgi = null; Map cnbs = null; Map frags = null; Set toEn = null; List toWi = null; int cnt = -1; char otherChar = File.separatorChar == '/' ? '\\' : '/'; if (is != null) try { DataInputStream dis = new DataInputStream(is); String locale = dis.readUTF(); String branding = dis.readUTF(); if (!Locale.getDefault().toString().equals(locale)) { throw new IOException(); } if (!branding.equals(nonNullBranding())) { throw new IOException(); } map = new HashMap(); osgi = new HashMap(); cnbs = new HashMap(); frags = new HashMap(); cnt = dis.readInt(); for (;;) { String path = Stamps.readRelativePath(dis).replace(otherChar, File.separatorChar); if (path.isEmpty()) { break; } boolean isOSGi = dis.readBoolean(); osgi.put(path, isOSGi); cnbs.put(path, dis.readUTF()); int len = dis.readInt(); byte[] data = new byte[len]; dis.readFully(data); map.put(path, data); String fhost = dis.readUTF(); if (fhost != null) { // retain empty Strings, as they count as "known data". frags.put(path, fhost); } } toEn = readCnbs(dis, new HashSet()); toWi = readCnbs(dis, new ArrayList()); dis.close(); } catch (IOException ex) { Util.err.log(Level.FINE, "Cannot read " + Places.getCacheSubfile(CACHE), ex); map = null; osgi = null; cnbs = null; toEn = null; toWi = null; frags = null; } path2Data = map; path2OSGi = osgi; path2Cnb = cnbs; path2Fragment = frags; toEnable = toEn; willEnable = toWi; moduleCount = cnt; if (map == null) { reset(); } } public Boolean isOSGi(String path) { if (path2OSGi == null) { return null; } return path2OSGi.get(path); } public synchronized byte[] getModuleState(String path) { byte[] res = null; if (path2Data != null) { res = path2Data.remove(path); } if (res == null) { reset(); } return res; } final String getCnb(String path) { return path2Cnb == null ? null : path2Cnb.get(path); } final String getFragment(String path) { return path2Fragment == null ? null : path2Fragment.get(path); } @Override public void flushCaches(DataOutputStream os) throws IOException { os.writeUTF(Locale.getDefault().toString()); os.writeUTF(nonNullBranding()); Set store = getModules(); os.writeInt(store.size()); for (Module m : store) { final File path = m.getJarFile(); if (path == null) { assert m instanceof FixedModule : "Only fixed modules are excluded from caches " + m; continue; } Stamps.writeRelativePath(path.getPath(), os); os.writeBoolean(m.isNetigso()); os.writeUTF(m.getCodeNameBase()); ByteArrayOutputStream data = new ByteArrayOutputStream(); ObjectOutputStream dos = new ObjectOutputStream(data); m.writeData(dos); dos.close(); byte[] arr = data.toByteArray(); os.writeInt(arr.length); os.write(arr); String s = m.getFragmentHostCodeName(); os.writeUTF(s == null ? "" : s); // NOI18N } Stamps.writeRelativePath("", os); synchronized (this) { writeCnbs(os, toEnable); writeCnbs(os, willEnable); } } @Override public void cacheReady() { } private synchronized void reset() { toEnable = null; willEnable = null; } synchronized final void registerEnable(Set modules, List l) { toEnable = new HashSet(); for (Module m : modules) { toEnable.add(m.getCodeNameBase()); } List arr = new ArrayList(l.size()); for (Module m : l) { arr.add(m.getCodeNameBase()); } willEnable = Collections.unmodifiableList(arr); Stamps.getModulesJARs().scheduleSave(this, CACHE, false); } synchronized final List simulateEnable(Set modules) { if ( toEnable != null && modules.size() == toEnable.size() && moduleCount == getModuleCount() ) { Set clone = new HashSet(toEnable); for (Module m : modules) { if (!clone.remove(m.getCodeNameBase())) { return null; } } if (clone.isEmpty()) { return willEnable; } } return null; } private > T readCnbs(DataInputStream dis, T fill) throws IOException { int size = dis.readInt(); if (size == -1) { return null; } while (size-- > 0) { fill.add(dis.readUTF()); } return fill; } private void writeCnbs(DataOutputStream os, Collection cnbs) throws IOException { if (cnbs == null) { os.writeInt(-1); return; } os.writeInt(cnbs.size()); for (String s : cnbs) { os.writeUTF(s); } } private String nonNullBranding() { String s = NbBundle.getBranding(); return s == null ? "" : s; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy