org.netbeans.ModuleManager Maven / Gradle / Ivy
/*
* 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;
}
}
}