blob: 9d1f0b08b829db9aa2105ca9bd99085ec62c8ef4 [file] [log] [blame]
/*
* 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.modules.autoupdate.services;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.Module;
import org.netbeans.ModuleManager;
import org.netbeans.api.autoupdate.UpdateElement;
import org.netbeans.api.autoupdate.UpdateUnit;
import org.openide.modules.Dependency;
import org.openide.modules.ModuleInfo;
/**
*
* @author Radek Matous, Jiri Rechtacek
*/
abstract class OperationValidator {
private final static OperationValidator FOR_INSTALL = new InstallValidator();
private final static OperationValidator FOR_INTERNAL_UPDATE = new InternalUpdateValidator();
private final static OperationValidator FOR_UNINSTALL = new UninstallValidator();
private final static OperationValidator FOR_UPDATE = new UpdateValidator();
private final static OperationValidator FOR_ENABLE = new EnableValidator();
private final static OperationValidator FOR_DISABLE = new DisableValidator();
private final static OperationValidator FOR_CUSTOM_INSTALL = new CustomInstallValidator();
private final static OperationValidator FOR_CUSTOM_UNINSTALL = new CustomUninstallValidator();
private static final Logger LOGGER = Logger.getLogger (OperationValidator.class.getName ());
/** Creates a new instance of OperationValidator */
private OperationValidator() {}
public static boolean isValidOperation(OperationContainerImpl.OperationType type, UpdateUnit updateUnit, UpdateElement updateElement) {
if (updateUnit.isPending ()) {
return false;
}
boolean isValid = false;
switch(type){
case INSTALL:
isValid = FOR_INSTALL.isValidOperationImpl(updateUnit, updateElement);
break;
case INTERNAL_UPDATE:
isValid = FOR_INTERNAL_UPDATE.isValidOperationImpl(updateUnit, updateElement);
break;
case DIRECT_UNINSTALL:
case UNINSTALL:
isValid = FOR_UNINSTALL.isValidOperationImpl(updateUnit, updateElement);
break;
case UPDATE:
isValid = FOR_UPDATE.isValidOperationImpl(updateUnit, updateElement);
break;
case ENABLE:
isValid = FOR_ENABLE.isValidOperationImpl(updateUnit, updateElement);
break;
case DIRECT_DISABLE:
case DISABLE:
isValid = FOR_DISABLE.isValidOperationImpl(updateUnit, updateElement);
break;
case CUSTOM_INSTALL:
isValid = FOR_CUSTOM_INSTALL.isValidOperationImpl(updateUnit, updateElement);
break;
case CUSTOM_UNINSTALL:
isValid = FOR_CUSTOM_UNINSTALL.isValidOperationImpl(updateUnit, updateElement);
break;
default:
assert false;
}
return isValid;
}
public static List<UpdateElement> getRequiredElements (OperationContainerImpl.OperationType type,
UpdateElement updateElement,
List<ModuleInfo> moduleInfos,
Collection<String> brokenDependencies,
Collection<UpdateElement> recommendedElements) {
List<UpdateElement> retval = Collections.emptyList ();
switch(type){
case INSTALL:
retval = FOR_INSTALL.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
case DIRECT_UNINSTALL:
case UNINSTALL:
retval = FOR_UNINSTALL.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
case UPDATE:
retval = FOR_UPDATE.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
case ENABLE:
retval = FOR_ENABLE.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
case DIRECT_DISABLE:
case DISABLE:
retval = FOR_DISABLE.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
case CUSTOM_INSTALL:
retval = FOR_CUSTOM_INSTALL.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
case CUSTOM_UNINSTALL:
retval = FOR_CUSTOM_UNINSTALL.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
case INTERNAL_UPDATE:
retval = FOR_INTERNAL_UPDATE.getRequiredElementsImpl(updateElement, moduleInfos, brokenDependencies, recommendedElements);
break;
default:
assert false;
}
if (LOGGER.isLoggable (Level.FINE)) {
LOGGER.log (Level.FINE, "== do getRequiredElements for " + type + " of " + updateElement + " ==");
for (UpdateElement el : retval) {
LOGGER.log (Level.FINE, "--> " + el);
}
LOGGER.log (Level.FINE, "== done. ==");
}
return retval;
}
public static Set<String> getBrokenDependencies (OperationContainerImpl.OperationType type,
UpdateElement updateElement,
List<ModuleInfo> moduleInfos) {
Set<String> broken = new HashSet<String> ();
switch (type) {
case ENABLE :
broken = Utilities.getBrokenDependenciesInInstalledModules (updateElement);
break;
case INSTALL :
case UPDATE :
case INTERNAL_UPDATE:
Set<UpdateElement> recommeded = new HashSet<UpdateElement>();
getRequiredElements (type, updateElement, moduleInfos, broken, recommeded);
if (! recommeded.isEmpty() && ! broken.isEmpty()) {
broken = new HashSet<String> ();
getRequiredElements(type, updateElement, moduleInfos, broken, recommeded);
}
break;
case UNINSTALL :
case DIRECT_UNINSTALL :
case CUSTOM_UNINSTALL :
case DISABLE :
case DIRECT_DISABLE :
case CUSTOM_INSTALL:
broken = Utilities.getBrokenDependencies (updateElement, moduleInfos);
break;
default:
assert false : "Unknown type of operation " + type;
}
return broken;
}
abstract boolean isValidOperationImpl(UpdateUnit updateUnit, UpdateElement uElement);
abstract List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement,
List<ModuleInfo> moduleInfos,
Collection<String> brokenDependencies,
Collection<UpdateElement> recommendedElements);
private static class InternalUpdateValidator extends UpdateValidator {
@Override
boolean isValidOperationImpl(UpdateUnit unit, UpdateElement uElement) {
return uElement.equals(unit.getInstalled()) || containsElement (uElement, unit);
}
}
private static class InstallValidator extends OperationValidator {
@Override
boolean isValidOperationImpl(UpdateUnit unit, UpdateElement uElement) {
return unit.getInstalled() == null && containsElement (uElement, unit);
}
@Override
List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement, List<ModuleInfo> moduleInfos,
Collection<String> brokenDependencies, Collection<UpdateElement> recommendedElements) {
Set<Dependency> brokenDeps = new HashSet<Dependency> ();
List<UpdateElement> res = new LinkedList<UpdateElement> (Utilities.findRequiredUpdateElements (uElement, moduleInfos, brokenDeps, false, recommendedElements));
if (brokenDependencies != null) {
for (Dependency dep : brokenDeps) {
brokenDependencies.add (dep.toString ());
}
}
return res;
}
}
private static Map<Module, Set<Module>> module2depending = new HashMap<Module, Set<Module>> ();
private static Map<Module, Set<Module>> module2required = new HashMap<Module, Set<Module>> ();
public static void clearMaps () {
module2depending = new HashMap<Module, Set<Module>> ();
module2required = new HashMap<Module, Set<Module>> ();
}
private static class UninstallValidator extends OperationValidator {
@Override
boolean isValidOperationImpl(UpdateUnit unit, UpdateElement uElement) {
return unit.getInstalled() != null && isValidOperationImpl (Trampoline.API.impl (uElement));
}
private boolean isValidOperationImpl (UpdateElementImpl impl) {
boolean res = false;
switch (impl.getType ()) {
case KIT_MODULE :
case MODULE :
Module m = Utilities.toModule (((ModuleUpdateElementImpl) impl).getModuleInfo ());
res = ModuleDeleterImpl.getInstance ().canDelete (m);
break;
case STANDALONE_MODULE :
case FEATURE :
for (ModuleInfo info : ((FeatureUpdateElementImpl) impl).getModuleInfos ()) {
Module module = Utilities.toModule (info);
res |= ModuleDeleterImpl.getInstance ().canDelete (module);
}
break;
case CUSTOM_HANDLED_COMPONENT :
LOGGER.log (Level.INFO, "CUSTOM_HANDLED_COMPONENT doesn't support custom uninstaller yet."); // XXX
res = false;
break;
default:
assert false : "Not supported for impl " + impl;
}
return res;
}
@Override
List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement, List<ModuleInfo> moduleInfos, Collection<String> brokenDependencies, Collection<UpdateElement> recommendedElements) {
ModuleManager mm = null;
final Set<Module> modules = new LinkedHashSet<Module>();
for (ModuleInfo moduleInfo : moduleInfos) {
Module m = Utilities.toModule (moduleInfo);
if (m == null) {
continue;
}
if (! Utilities.isEssentialModule (m)) {
modules.add (m);
}
if (mm == null) {
mm = m.getManager();
}
}
Set<UpdateElement> retval = new HashSet<UpdateElement>();
if (mm != null) {
Set<Module> toUninstall = findRequiredModulesForDeactivate (modules, mm);
toUninstall.removeAll (modules);
for (Module module : toUninstall) {
if (Utilities.isEssentialModule (module)) {
LOGGER.log (Level.WARNING, "Essential module cannot be planned for uninstall but " + module);
continue;
} else if (! ModuleDeleterImpl.getInstance ().canDelete (module)) {
LOGGER.log (Level.WARNING, "The module " + module + " cannot be planned for uninstall because is read-only.");
continue;
}
// !!! e.g. applemodule can be found in the list for uninstall but has UpdateUnit nowhere else MacXOS
UpdateUnit unit = Utilities.toUpdateUnit (module);
if (unit != null) {
retval.add (unit.getInstalled ());
}
}
}
return new ArrayList<UpdateElement> (retval);
}
}
private static class UpdateValidator extends OperationValidator {
@Override
boolean isValidOperationImpl(UpdateUnit unit, UpdateElement uElement) {
return unit.getInstalled() != null && containsElement (uElement, unit);
}
@Override
List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement, List<ModuleInfo> moduleInfos,
Collection<String> brokenDependencies, Collection<UpdateElement> recommendedElements) {
Set<Dependency> brokenDeps = new HashSet<Dependency> ();
List<UpdateElement> res = new LinkedList<UpdateElement> (Utilities.findRequiredUpdateElements (uElement, moduleInfos, brokenDeps, true, recommendedElements));
if (brokenDependencies != null) {
for (Dependency dep : brokenDeps) {
brokenDependencies.add (dep.toString ());
}
}
return res;
}
}
private static boolean containsElement (UpdateElement el, UpdateUnit unit) {
return unit.getAvailableUpdates ().contains (el);
}
private static class EnableValidator extends OperationValidator {
@Override
boolean isValidOperationImpl(UpdateUnit unit, UpdateElement uElement) {
return unit.getInstalled () != null && isValidOperationImpl (Trampoline.API.impl (uElement));
}
private boolean isValidOperationImpl (UpdateElementImpl impl) {
boolean res = false;
switch (impl.getType ()) {
case KIT_MODULE :
case MODULE :
Module module = Utilities.toModule (((ModuleUpdateElementImpl) impl).getModuleInfo ());
res = Utilities.canEnable (module);
break;
case STANDALONE_MODULE :
case FEATURE :
for (ModuleInfo info : ((FeatureUpdateElementImpl) impl).getModuleInfos ()) {
Module m = Utilities.toModule (info);
res |= Utilities.canEnable (m);
}
break;
case CUSTOM_HANDLED_COMPONENT :
res = false;
break;
default:
assert false : "Not supported for impl " + impl;
}
return res;
}
private List<Module> getModulesToEnable(ModuleManager mm, final Set<Module> modules) {
List<Module> toEnable = new ArrayList<Module>();
boolean stateChanged = true;
while (stateChanged) {
stateChanged = false;
try {
toEnable = mm.simulateEnable(modules);
} catch (IllegalArgumentException e) {
//#160500
LOGGER.log(Level.INFO, "Cannot enable all modules " + modules, e);
Set<Module> tempModules = new LinkedHashSet<Module>(modules);
for (Module module : tempModules) {
if (!Utilities.canEnable(module)) {
modules.remove(module);
stateChanged = true;
}
}
assert stateChanged : "Can`t enable modules " + modules;
}
}
return toEnable;
}
@Override
List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement, List<ModuleInfo> moduleInfos, Collection<String> brokenDependencies, Collection<UpdateElement> recommendedElements) {
UpdateElementImpl uElementImpl = Trampoline.API.impl(uElement);
List<ModuleInfo> expandedModuleInfos = new ArrayList(moduleInfos);
expandedModuleInfos.addAll(uElementImpl.getModuleInfos(true));
ModuleManager mm = null;
final Set<Module> modules = new LinkedHashSet<Module>();
for (ModuleInfo moduleInfo : expandedModuleInfos) {
Module m = Utilities.toModule (moduleInfo);
if (Utilities.canEnable (m)) {
modules.add(m);
}
if (mm == null) {
mm = m.getManager();
}
}
List<UpdateElement> retval = new ArrayList<UpdateElement>();
if (mm != null) {
List<Module> toEnable = getModulesToEnable(mm, modules);
for (Module module : toEnable) {
if (!modules.contains(module) && Utilities.canEnable (module)) {
if (Utilities.toUpdateUnit(module).getInstalled() != null) {
retval.add(Utilities.toUpdateUnit(module).getInstalled());
}
}
}
}
return retval;
}
}
private static class DisableValidator extends OperationValidator {
@Override
boolean isValidOperationImpl(UpdateUnit unit, UpdateElement uElement) {
return unit.getInstalled () != null && isValidOperationImpl (Trampoline.API.impl (uElement));
}
private boolean isValidOperationImpl (UpdateElementImpl impl) {
boolean res = false;
switch (impl.getType ()) {
case KIT_MODULE :
case MODULE :
Module module = Utilities.toModule (((ModuleUpdateElementImpl) impl).getModuleInfo ());
res = Utilities.canDisable (module);
break;
case STANDALONE_MODULE :
case FEATURE :
for (ModuleInfo info : ((FeatureUpdateElementImpl) impl).getModuleInfos ()) {
Module m = Utilities.toModule (info);
res |= Utilities.canDisable (m);
}
break;
case CUSTOM_HANDLED_COMPONENT :
res = false;
break;
default:
assert false : "Not supported for impl " + impl;
}
return res;
}
@Override
List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement, List<ModuleInfo> moduleInfos, Collection<String> brokenDependencies, Collection<UpdateElement> recommendedElements) {
ModuleManager mm = null;
final Set<Module> modules = new LinkedHashSet<Module>();
for (ModuleInfo moduleInfo : moduleInfos) {
Module m = Utilities.toModule (moduleInfo);
if (Utilities.canDisable (m)) {
modules.add(m);
}
if (mm == null) {
mm = m.getManager();
}
}
if (mm == null) {
LOGGER.log (Level.WARNING, "No modules can be disabled when disabling UpdateElement " + uElement);
return Collections.emptyList ();
}
Set<Module> requestedToDisable = findRequiredModulesForDeactivate (modules, mm);
List<Module> toDisable = mm.simulateDisable (modules);
boolean wasAdded = requestedToDisable.addAll (toDisable);
// XXX why sometimes happens that no all module for deactivated found?
// assert ! wasAdded : "The requestedToDisable cannot be enlarged by " + toDisable;
if (LOGGER.isLoggable (Level.FINE) && wasAdded) {
toDisable.removeAll (filterCandidatesToDeactivate (modules, requestedToDisable, mm));
LOGGER.log (Level.FINE, "requestedToDisable was enlarged by " + toDisable);
}
Set<UpdateElement> retval = new HashSet<UpdateElement> ();
for (Module module : requestedToDisable) {
if (! modules.contains (module) && Utilities.canDisable (module)) {
// !!! e.g. applemodule can be found in the list for uninstall but has UpdateUnit nowhere else MacXOS
UpdateUnit unit = Utilities.toUpdateUnit (module);
if (unit != null) {
retval.add (unit.getInstalled ());
}
}
}
return new ArrayList<UpdateElement> (retval);
}
}
private static class CustomInstallValidator extends OperationValidator {
@Override
boolean isValidOperationImpl (UpdateUnit unit, UpdateElement uElement) {
boolean res = false;
UpdateElementImpl impl = Trampoline.API.impl (uElement);
assert impl != null;
if (impl != null && impl instanceof NativeComponentUpdateElementImpl) {
NativeComponentUpdateElementImpl ni = (NativeComponentUpdateElementImpl) impl;
if (ni.getInstallInfo ().getCustomInstaller () != null) {
res = containsElement (uElement, unit);
}
}
return res;
}
@Override
List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement, List<ModuleInfo> moduleInfos, Collection<String> brokenDependencies, Collection<UpdateElement> recommendedElements) {
LOGGER.log (Level.INFO, "CustomInstallValidator doesn't care about required elements."); // XXX
return Collections.emptyList ();
}
}
private static class CustomUninstallValidator extends OperationValidator {
@Override
boolean isValidOperationImpl (UpdateUnit unit, UpdateElement uElement) {
boolean res = false;
UpdateElementImpl impl = Trampoline.API.impl (uElement);
assert impl != null;
if (impl != null && impl instanceof NativeComponentUpdateElementImpl) {
NativeComponentUpdateElementImpl ni = (NativeComponentUpdateElementImpl) impl;
res = ni.getNativeItem ().getUpdateItemDeploymentImpl ().getCustomUninstaller () != null;
}
return res;
}
@Override
List<UpdateElement> getRequiredElementsImpl (UpdateElement uElement, List<ModuleInfo> moduleInfos, Collection<String> brokenDependencies, Collection<UpdateElement> recommendedElements) {
LOGGER.log (Level.INFO, "CustomUninstallValidator doesn't care about required elements."); // XXX
return Collections.emptyList ();
//return new LinkedList<UpdateElement> (Utilities.findRequiredUpdateElements (uElement, moduleInfos));
}
}
private static Set<Module> findRequiredModulesForDeactivate (final Set<Module> requestedToDeactivate, ModuleManager mm) {
// go up and find kits which depending on requestedToDeactivate modules
Set<Module> extendReqToDeactivate = new HashSet<Module> (requestedToDeactivate);
boolean inscreasing = true;
while (inscreasing) {
inscreasing = false;
Set<Module> tmp = new HashSet<Module> (extendReqToDeactivate);
for (Module dep : tmp) {
Set<Module> deps = Utilities.findDependingModules (dep, mm, module2depending);
inscreasing |= extendReqToDeactivate.addAll (deps);
}
}
// go down and find all modules (except for other kits) which can be deactivated
Set<Module> moreToDeactivate = new HashSet<Module> (extendReqToDeactivate);
inscreasing = true;
while (inscreasing) {
inscreasing = false;
Set<Module> tmp = new HashSet<Module> (moreToDeactivate);
for (Module req : tmp) {
if ((! Utilities.isKitModule (req) && ! Utilities.isEssentialModule (req)) || extendReqToDeactivate.contains (req)) {
Set<Module> reqs = Utilities.findRequiredModules (req, mm, module2required);
inscreasing |= moreToDeactivate.addAll (reqs);
}
}
}
return filterCandidatesToDeactivate (extendReqToDeactivate, moreToDeactivate, mm);
}
private static Set<Module> filterCandidatesToDeactivate (final Collection<Module> requested, final Collection<Module> candidates, ModuleManager mm) {
// go down and find all modules (except other kits) which can be deactivated
Set<Module> result = new HashSet<Module> ();
Set<Module> compactSet = new HashSet<Module> (candidates);
// create collection of all installed eagers
Set<Module> installedEagers = new HashSet<Module> ();
for (UpdateElement eagerEl : UpdateManagerImpl.getInstance ().getInstalledEagers ()) {
// take a module
UpdateElementImpl impl = Trampoline.API.impl (eagerEl);
if(impl instanceof ModuleUpdateElementImpl) {
ModuleInfo mi = ((ModuleUpdateElementImpl) impl).getModuleInfo ();
installedEagers.add (Utilities.toModule (mi));
} else if(impl instanceof FeatureUpdateElementImpl) {
List <ModuleInfo> infos = ((FeatureUpdateElementImpl) impl).getModuleInfos();
for(ModuleInfo mi : infos) {
installedEagers.add (Utilities.toModule (mi));
}
} else {
assert false : eagerEl + " is instanceof neither ModuleUpdateElementImpl nor FeatureUpdateElementImpl";
}
}
// add installedEagers into affected modules to don't break uninstall of candidates
compactSet.addAll (installedEagers);
Set<Module> mustRemain = new HashSet<Module> ();
Set<Module> affectedEagers = new HashSet<Module> ();
for (Module depM : candidates) {
if ((Utilities.isKitModule (depM) || Utilities.isEssentialModule (depM)) && ! requested.contains (depM)) {
if (LOGGER.isLoggable (Level.FINE)) {
LOGGER.log(Level.FINE, "The module " + depM.getCodeNameBase() +
" is KIT_MODULE and won't be deactivated now not even " + Utilities.findRequiredModules(depM, mm, module2required));
}
mustRemain.add (depM);
} else if (mustRemain.contains (depM)) {
LOGGER.log (Level.FINE, "The module " + depM.getCodeNameBase () + " was investigated already and won't be deactivated now.");
} else {
Set<Module> depends = Utilities.findDependingModules (depM, mm, module2depending);
if (! compactSet.containsAll (depends)) {
mustRemain.add (depM);
Set<Module> otherRequiredModules = Utilities.findRequiredModules(depM, mm, module2required);
mustRemain.addAll (otherRequiredModules);
LOGGER.log (Level.FINE, "The module " + depM.getCodeNameBase () + " is shared and cannot be deactivated now.");
if (LOGGER.isLoggable (Level.FINER)) {
Set<Module> outsideModules = new HashSet<Module> (depends);
outsideModules.removeAll (compactSet);
LOGGER.log (Level.FINER, "On " + depM.getCodeNameBase () + " depending modules outside of set now deactivating modules: " + outsideModules);
LOGGER.log (Level.FINER, "With " + depM.getCodeNameBase () + " must remain also these required modules: " + otherRequiredModules);
}
} else {
result.add (depM);
Collection<Module> reducedDepends = new HashSet<Module> (depends);
reducedDepends.retainAll (installedEagers);
if (! reducedDepends.isEmpty ()) {
affectedEagers.addAll (reducedDepends);
}
}
}
}
result.removeAll (installedEagers);
// add only affected eagers again
LOGGER.log(Level.FINE, "Possible affected eagers are " + affectedEagers);
result.removeAll(findDeepRequired(mustRemain, mm));
// once again check the eagers
Set<Module> notAffectedEagers = new HashSet<Module>();
for (Module eager : affectedEagers) {
Set<Module> requiredByEager = Utilities.findRequiredModules(eager, mm, module2depending);
if (! requiredByEager.removeAll(result)) {
notAffectedEagers.add(eager);
}
}
affectedEagers.removeAll(notAffectedEagers);
result.addAll(affectedEagers);
LOGGER.log(Level.FINE, "Real affected eagers are " + affectedEagers);
return result;
}
private static Set<Module> findDeepRequired (Set<Module> orig, ModuleManager mm) {
Set<Module> more = new HashSet<Module> (orig);
boolean inscreasing = true;
while (inscreasing) {
Set<Module> tmp = new HashSet<Module> (more);
inscreasing = false;
for (Module req : tmp) {
Set<Module> reqs = Utilities.findRequiredModules (req, mm, module2required);
inscreasing |= more.addAll (reqs);
}
}
return more;
}
}