| /******************************************************************************* |
| * Copyright (c) 2004, 2020 IBM Corporation and others. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which accompanies this distribution, |
| * and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Danail Nachev - ProSyst - bug 218625 |
| * Rob Harrop - SpringSource Inc. (bug 247522) |
| * Karsten Thoms (itemis) - Expose developmentMode |
| ******************************************************************************/ |
| package org.eclipse.osgi.internal.module; |
| |
| import java.security.AccessController; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Dictionary; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import org.eclipse.osgi.framework.util.ArrayMap; |
| import org.eclipse.osgi.framework.util.SecureAction; |
| import org.eclipse.osgi.internal.debug.Debug; |
| import org.eclipse.osgi.internal.framework.EquinoxContainer; |
| import org.eclipse.osgi.internal.framework.FilterImpl; |
| import org.eclipse.osgi.internal.module.GroupingChecker.PackageRoots; |
| import org.eclipse.osgi.internal.resolver.BaseDescriptionImpl; |
| import org.eclipse.osgi.internal.resolver.BundleDescriptionImpl; |
| import org.eclipse.osgi.internal.resolver.ExportPackageDescriptionImpl; |
| import org.eclipse.osgi.internal.resolver.StateImpl; |
| import org.eclipse.osgi.service.resolver.BaseDescription; |
| import org.eclipse.osgi.service.resolver.BundleDescription; |
| import org.eclipse.osgi.service.resolver.BundleSpecification; |
| import org.eclipse.osgi.service.resolver.DisabledInfo; |
| import org.eclipse.osgi.service.resolver.ExportPackageDescription; |
| import org.eclipse.osgi.service.resolver.GenericDescription; |
| import org.eclipse.osgi.service.resolver.GenericSpecification; |
| import org.eclipse.osgi.service.resolver.HostSpecification; |
| import org.eclipse.osgi.service.resolver.ImportPackageSpecification; |
| import org.eclipse.osgi.service.resolver.NativeCodeDescription; |
| import org.eclipse.osgi.service.resolver.NativeCodeSpecification; |
| import org.eclipse.osgi.service.resolver.Resolver; |
| import org.eclipse.osgi.service.resolver.ResolverError; |
| import org.eclipse.osgi.service.resolver.State; |
| import org.eclipse.osgi.service.resolver.StateWire; |
| import org.eclipse.osgi.service.resolver.VersionConstraint; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Filter; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.hooks.resolver.ResolverHook; |
| import org.osgi.framework.namespace.IdentityNamespace; |
| import org.osgi.framework.wiring.BundleCapability; |
| import org.osgi.framework.wiring.BundleRevision; |
| |
| public class ResolverImpl implements Resolver { |
| // Debug fields |
| public static boolean DEBUG = false; |
| public static boolean DEBUG_WIRING = false; |
| public static boolean DEBUG_IMPORTS = false; |
| public static boolean DEBUG_REQUIRES = false; |
| public static boolean DEBUG_GENERICS = false; |
| public static boolean DEBUG_USES = false; |
| public static boolean DEBUG_CONFLICTS = false; |
| public static boolean DEBUG_CYCLES = false; |
| private static int MAX_MULTIPLE_SUPPLIERS_MERGE = 10; |
| private static int MAX_USES_TIME_BASE = 30000; // 30 seconds |
| private static int MAX_USES_TIME_LIMIT = 90000; // 90 seconds |
| private static final String USES_TIMEOUT_PROP = "osgi.usesTimeout"; //$NON-NLS-1$ |
| private static final String MULTIPLE_SUPPLIERS_LIMIT_PROP = "osgi.usesLimit"; //$NON-NLS-1$ |
| static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); |
| |
| private String[][] CURRENT_EES; |
| private ResolverHook hook; |
| |
| // The State associated with this resolver |
| private State state; |
| // Used to check permissions for import/export, provide/require, host/fragment |
| private final PermissionChecker permissionChecker; |
| // Set of bundles that are pending removal |
| private MappedList<Long, BundleDescription> removalPending = new MappedList<>(); |
| // Indicates whether this resolver has been initialized |
| private boolean initialized = false; |
| |
| // Repository for exports |
| private VersionHashMap<ResolverExport> resolverExports = null; |
| // Repository for bundles |
| private VersionHashMap<ResolverBundle> resolverBundles = null; |
| // Repository for generics |
| private Map<String, VersionHashMap<GenericCapability>> resolverGenerics = null; |
| // List of unresolved bundles |
| private HashSet<ResolverBundle> unresolvedBundles = null; |
| // Keys are BundleDescriptions, values are ResolverBundles |
| private HashMap<BundleDescription, ResolverBundle> bundleMapping = null; |
| private GroupingChecker groupingChecker; |
| private Comparator<BaseDescription> selectionPolicy; |
| private boolean developmentMode = false; |
| private boolean usesCalculationTimeout = false; |
| private long usesTimeout = -1; |
| private int usesMultipleSuppliersLimit; |
| private volatile CompositeResolveHelperRegistry compositeHelpers; |
| |
| public ResolverImpl(boolean checkPermissions) { |
| this.permissionChecker = new PermissionChecker(checkPermissions, this); |
| } |
| |
| PermissionChecker getPermissionChecker() { |
| return permissionChecker; |
| } |
| |
| // Initializes the resolver |
| private void initialize() { |
| resolverExports = new VersionHashMap<>(this); |
| resolverBundles = new VersionHashMap<>(this); |
| resolverGenerics = new HashMap<>(); |
| unresolvedBundles = new HashSet<>(); |
| bundleMapping = new HashMap<>(); |
| BundleDescription[] bundles = state.getBundles(); |
| groupingChecker = new GroupingChecker(); |
| |
| ArrayList<ResolverBundle> fragmentBundles = new ArrayList<>(); |
| // Add each bundle to the resolver's internal state |
| for (BundleDescription bundle : bundles) { |
| initResolverBundle(bundle, fragmentBundles, false); |
| } |
| // Add each removal pending bundle to the resolver's internal state |
| List<BundleDescription> removedBundles = removalPending.getAllValues(); |
| for (BundleDescription removed : removedBundles) |
| initResolverBundle(removed, fragmentBundles, true); |
| // Iterate over the resolved fragments and attach them to their hosts |
| for (ResolverBundle fragment : fragmentBundles) { |
| BundleDescription[] hosts = ((HostSpecification) fragment.getHost().getVersionConstraint()).getHosts(); |
| for (BundleDescription h : hosts) { |
| ResolverBundle host = bundleMapping.get(h); |
| if (host != null) |
| // Do not add fragment exports here because they would have been added by the host above. |
| host.attachFragment(fragment, false); |
| } |
| } |
| rewireBundles(); // Reconstruct wirings |
| setDebugOptions(); |
| initialized = true; |
| } |
| |
| private void initResolverBundle(BundleDescription bundleDesc, ArrayList<ResolverBundle> fragmentBundles, boolean pending) { |
| ResolverBundle bundle = new ResolverBundle(bundleDesc, this); |
| bundleMapping.put(bundleDesc, bundle); |
| if (!pending || bundleDesc.isResolved()) { |
| resolverExports.put(bundle.getExportPackages()); |
| resolverBundles.put(bundle.getName(), bundle); |
| addGenerics(bundle.getGenericCapabilities()); |
| } |
| if (bundleDesc.isResolved()) { |
| bundle.setState(ResolverBundle.RESOLVED); |
| if (bundleDesc.getHost() != null) |
| fragmentBundles.add(bundle); |
| } else { |
| if (!pending) |
| unresolvedBundles.add(bundle); |
| } |
| } |
| |
| // Re-wire previously resolved bundles |
| private void rewireBundles() { |
| List<ResolverBundle> visited = new ArrayList<>(bundleMapping.size()); |
| for (ResolverBundle rb : bundleMapping.values()) { |
| if (!rb.getBundleDescription().isResolved()) |
| continue; |
| rewireBundle(rb, visited); |
| } |
| } |
| |
| private void rewireBundle(ResolverBundle rb, List<ResolverBundle> visited) { |
| if (visited.contains(rb)) |
| return; |
| visited.add(rb); |
| // Wire requires to bundles |
| BundleConstraint[] requires = rb.getRequires(); |
| for (BundleConstraint require : requires) { |
| rewireRequire(require, visited); |
| } |
| // Wire imports to exports |
| ResolverImport[] imports = rb.getImportPackages(); |
| for (ResolverImport resolverImport : imports) { |
| rewireImport(resolverImport, visited); |
| } |
| // Wire generics |
| GenericConstraint[] genericRequires = rb.getGenericRequires(); |
| for (GenericConstraint genericRequire : genericRequires) { |
| rewireGeneric(genericRequire, visited); |
| } |
| } |
| |
| private void rewireGeneric(GenericConstraint constraint, List<ResolverBundle> visited) { |
| if (constraint.getSelectedSupplier() != null) |
| return; |
| GenericDescription[] suppliers = ((GenericSpecification) constraint.getVersionConstraint()).getSuppliers(); |
| if (suppliers == null) |
| return; |
| VersionHashMap<GenericCapability> namespace = resolverGenerics.get(constraint.getNameSpace()); |
| if (namespace == null) { |
| System.err.println("Could not find matching capability for " + constraint.getVersionConstraint()); //$NON-NLS-1$ |
| // TODO log error!! |
| return; |
| } |
| String constraintName = constraint.getName(); |
| List<GenericCapability> matches = constraintName == null ? namespace.get(constraintName) : namespace.getAllValues(); |
| for (GenericCapability match : matches) { |
| for (GenericDescription supplier : suppliers) |
| if (match.getBaseDescription() == supplier) |
| constraint.addPossibleSupplier(match); |
| } |
| VersionSupplier[] matchingCapabilities = constraint.getPossibleSuppliers(); |
| if (matchingCapabilities != null) |
| for (VersionSupplier matchingCapability : matchingCapabilities) { |
| rewireBundle(matchingCapability.getResolverBundle(), visited); |
| } |
| } |
| |
| private void rewireRequire(BundleConstraint req, List<ResolverBundle> visited) { |
| if (req.getSelectedSupplier() != null) |
| return; |
| ResolverBundle matchingBundle = bundleMapping.get(req.getVersionConstraint().getSupplier()); |
| req.addPossibleSupplier(matchingBundle); |
| if (matchingBundle == null && !req.isOptional()) { |
| System.err.println("Could not find matching bundle for " + req.getVersionConstraint()); //$NON-NLS-1$ |
| // TODO log error!! |
| } |
| if (matchingBundle != null) { |
| rewireBundle(matchingBundle, visited); |
| } |
| } |
| |
| private void rewireImport(ResolverImport imp, List<ResolverBundle> visited) { |
| if (imp.isDynamic() || imp.getSelectedSupplier() != null) |
| return; |
| // Re-wire 'imp' |
| ResolverExport matchingExport = null; |
| ExportPackageDescription importSupplier = (ExportPackageDescription) imp.getVersionConstraint().getSupplier(); |
| ResolverBundle exporter = importSupplier == null ? null : (ResolverBundle) bundleMapping.get(importSupplier.getExporter()); |
| List<ResolverExport> matches = resolverExports.get(imp.getName()); |
| for (ResolverExport export : matches) { |
| if (export.getExporter() == exporter && importSupplier == export.getExportPackageDescription()) { |
| matchingExport = export; |
| break; |
| } |
| } |
| imp.addPossibleSupplier(matchingExport); |
| // If we still have a null wire and it's not optional, then we have an error |
| if (imp.getSelectedSupplier() == null && !imp.isOptional()) { |
| System.err.println("Could not find matching export for " + imp.getVersionConstraint()); //$NON-NLS-1$ |
| // TODO log error!! |
| } |
| if (imp.getSelectedSupplier() != null) { |
| rewireBundle(((ResolverExport) imp.getSelectedSupplier()).getExporter(), visited); |
| } |
| } |
| |
| // Checks a bundle to make sure it is valid. If this method returns false for |
| // a given bundle, then that bundle will not even be considered for resolution |
| @SuppressWarnings("unchecked") |
| private boolean isResolvable(ResolverBundle bundle, Dictionary<Object, Object>[] platformProperties, Collection<ResolverBundle> hookDisabled) { |
| BundleDescription bundleDesc = bundle.getBundleDescription(); |
| |
| // check if the bundle is a hook disabled bundle |
| if (hookDisabled.contains(bundle)) { |
| state.addResolverError(bundleDesc, ResolverError.DISABLED_BUNDLE, "Resolver hook disabled bundle.", null); //$NON-NLS-1$ |
| return false; |
| } |
| // check to see if the bundle is disabled |
| DisabledInfo[] disabledInfos = state.getDisabledInfos(bundleDesc); |
| if (disabledInfos.length > 0) { |
| StringBuilder message = new StringBuilder(); |
| for (int i = 0; i < disabledInfos.length; i++) { |
| if (i > 0) |
| message.append(' '); |
| message.append('\"').append(disabledInfos[i].getPolicyName()).append(':').append(disabledInfos[i].getMessage()).append('\"'); |
| } |
| state.addResolverError(bundleDesc, ResolverError.DISABLED_BUNDLE, message.toString(), null); |
| return false; // fail because we are disable |
| } |
| |
| // check the required execution environment |
| String[] ees = bundleDesc.getExecutionEnvironments(); |
| boolean matchedEE = ees.length == 0; |
| if (!matchedEE) |
| for (int i = 0; i < ees.length && !matchedEE; i++) |
| for (int j = 0; j < CURRENT_EES.length && !matchedEE; j++) |
| for (int k = 0; k < CURRENT_EES[j].length && !matchedEE; k++) |
| if (CURRENT_EES[j][k].equals(ees[i])) { |
| ((BundleDescriptionImpl) bundleDesc).setEquinoxEE(j); |
| matchedEE = true; |
| } |
| if (!matchedEE) { |
| StringBuilder bundleEE = new StringBuilder(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT.length() + 20); |
| bundleEE.append(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT).append(": "); //$NON-NLS-1$ |
| for (int i = 0; i < ees.length; i++) { |
| if (i > 0) |
| bundleEE.append(","); //$NON-NLS-1$ |
| bundleEE.append(ees[i]); |
| } |
| state.addResolverError(bundleDesc, ResolverError.MISSING_EXECUTION_ENVIRONMENT, bundleEE.toString(), null); |
| return false; |
| } |
| |
| // check the native code specification |
| NativeCodeSpecification nativeCode = bundleDesc.getNativeCodeSpecification(); |
| if (nativeCode != null) { |
| NativeCodeDescription[] nativeCodeSuppliers = nativeCode.getPossibleSuppliers(); |
| NativeCodeDescription highestRanked = null; |
| for (NativeCodeDescription nativeCodeSupplier : nativeCodeSuppliers) { |
| if (nativeCode.isSatisfiedBy(nativeCodeSupplier) && (highestRanked == null || highestRanked.compareTo(nativeCodeSupplier) < 0)) { |
| highestRanked = nativeCodeSupplier; |
| } |
| } |
| if (highestRanked == null) { |
| if (!nativeCode.isOptional()) { |
| state.addResolverError(bundleDesc, ResolverError.NO_NATIVECODE_MATCH, nativeCode.toString(), nativeCode); |
| return false; |
| } |
| } else { |
| if (highestRanked.hasInvalidNativePaths()) { |
| state.addResolverError(bundleDesc, ResolverError.INVALID_NATIVECODE_PATHS, highestRanked.toString(), nativeCode); |
| return false; |
| } |
| } |
| state.resolveConstraint(nativeCode, highestRanked); |
| } |
| |
| // check the platform filter |
| String platformFilter = bundleDesc.getPlatformFilter(); |
| if (platformFilter == null) |
| return true; |
| if (platformProperties == null) |
| return false; |
| try { |
| Filter filter = FilterImpl.newInstance(platformFilter); |
| for (Dictionary<Object, Object> platformProperty : platformProperties) { |
| // using matchCase here in case of duplicate case invarient keys (bug 180817) |
| @SuppressWarnings(value = "rawtypes") |
| Dictionary props = platformProperty; |
| if (filter.matchCase(props)) |
| return true; |
| } |
| } catch (InvalidSyntaxException e) { |
| // return false below |
| } |
| state.addResolverError(bundleDesc, ResolverError.PLATFORM_FILTER, platformFilter, null); |
| return false; |
| } |
| |
| // Attach fragment to its host |
| private void attachFragment(ResolverBundle bundle, Collection<String> processedFragments) { |
| if (processedFragments.contains(bundle.getName())) |
| return; |
| processedFragments.add(bundle.getName()); |
| // we want to attach multiple versions of the same fragment |
| // from highest version to lowest to give the higher versions first pick |
| // of the available host bundles. |
| List<ResolverBundle> fragments = resolverBundles.get(bundle.getName()); |
| for (ResolverBundle fragment : fragments) { |
| if (!fragment.isResolved()) |
| attachFragment0(fragment); |
| } |
| } |
| |
| private void attachFragment0(ResolverBundle bundle) { |
| if (!bundle.isFragment() || !bundle.isResolvable()) |
| return; |
| bundle.clearWires(); |
| if (!resolveOSGiEE(bundle)) |
| return; |
| // no need to select singletons now; it will be done when we select the rest of the singleton bundles (bug 152042) |
| // find all available hosts to attach to. |
| boolean foundMatch = false; |
| BundleConstraint hostConstraint = bundle.getHost(); |
| long timestamp; |
| List<ResolverBundle> candidates; |
| do { |
| timestamp = state.getTimeStamp(); |
| List<ResolverBundle> hosts = resolverBundles.get(hostConstraint.getVersionConstraint().getName()); |
| candidates = new ArrayList<>(hosts); |
| List<BundleCapability> hostCapabilities = new ArrayList<>(hosts.size()); |
| // Must remove candidates that do not match before calling hooks. |
| for (Iterator<ResolverBundle> iCandidates = candidates.iterator(); iCandidates.hasNext();) { |
| ResolverBundle host = iCandidates.next(); |
| if (!host.isResolvable() || !host.getBundleDescription().attachFragments() || !hostConstraint.isSatisfiedBy(host)) { |
| iCandidates.remove(); |
| } else { |
| List<BundleCapability> h = host.getBundleDescription().getDeclaredCapabilities(BundleRevision.HOST_NAMESPACE); |
| // the bundle must have 1 host capability. |
| hostCapabilities.add(h.get(0)); |
| } |
| } |
| |
| if (hook != null) |
| hook.filterMatches(hostConstraint.getRequirement(), asCapabilities(new ArrayMap<>(hostCapabilities, candidates))); |
| } while (timestamp != state.getTimeStamp()); |
| // we are left with only candidates that satisfy the host constraint |
| for (ResolverBundle host : candidates) { |
| foundMatch = true; |
| host.attachFragment(bundle, true); |
| } |
| if (!foundMatch) |
| state.addResolverError(bundle.getBundleDescription(), ResolverError.MISSING_FRAGMENT_HOST, bundle.getHost().getVersionConstraint().toString(), bundle.getHost().getVersionConstraint()); |
| |
| } |
| |
| private boolean resolveOSGiEE(ResolverBundle bundle) { |
| GenericConstraint[] requirements = bundle.getGenericRequires(); |
| for (GenericConstraint requirement : requirements) { |
| if (!(StateImpl.OSGI_EE_NAMESPACE.equals(requirement.getNameSpace()) || requirement.isEffective())) |
| continue; |
| { |
| if (!resolveGenericReq(requirement, new ArrayList<ResolverBundle>(0))) { |
| if (DEBUG || DEBUG_GENERICS) |
| ResolverImpl.log("** GENERICS " + requirement.getVersionConstraint().getName() + "[" + requirement.getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| state.addResolverError(requirement.getVersionConstraint().getBundle(), ResolverError.MISSING_GENERIC_CAPABILITY, requirement.getVersionConstraint().toString(), requirement.getVersionConstraint()); |
| if (!developmentMode) { |
| // fail fast; otherwise we want to attempt to resolver other constraints in dev mode |
| return false; |
| } |
| } else { |
| VersionSupplier supplier = requirement.getSelectedSupplier(); |
| Integer ee = supplier == null ? null : (Integer) ((GenericDescription) supplier.getBaseDescription()).getAttributes().get(ExportPackageDescriptionImpl.EQUINOX_EE); |
| if (ee != null && ((BundleDescriptionImpl) bundle.getBaseDescription()).getEquinoxEE() < 0) |
| ((BundleDescriptionImpl) bundle.getBundleDescription()).setEquinoxEE(ee); |
| } |
| } |
| } |
| return true; |
| } |
| |
| public synchronized void resolve(BundleDescription[] reRefresh, Dictionary<Object, Object>[] platformProperties) { |
| if (DEBUG) |
| ResolverImpl.log("*** BEGIN RESOLUTION ***"); //$NON-NLS-1$ |
| if (state == null) |
| throw new IllegalStateException("RESOLVER_NO_STATE"); //$NON-NLS-1$ |
| |
| // set developmentMode each resolution |
| developmentMode = platformProperties.length == 0 ? false : StateImpl.DEVELOPMENT_MODE.equals(platformProperties[0].get(StateImpl.OSGI_RESOLVER_MODE)); |
| // set uses timeout each resolution |
| usesTimeout = getUsesTimeout(platformProperties); |
| // set limit for constraints with multiple suppliers each resolution |
| usesMultipleSuppliersLimit = getMultipleSuppliersLimit(platformProperties); |
| hook = (state instanceof StateImpl) ? ((StateImpl) state).getResolverHook() : null; |
| |
| if (!initialized) { |
| initialize(); |
| } |
| try { |
| reRefresh = addDevConstraints(reRefresh); |
| // Unresolve all the supplied bundles and their dependents |
| if (reRefresh != null) |
| for (BundleDescription description : reRefresh) { |
| ResolverBundle rb = bundleMapping.get(description); |
| if (rb != null) |
| unresolveBundle(rb, false); |
| } |
| // reorder exports and bundles after unresolving the bundles |
| resolverExports.reorder(); |
| resolverBundles.reorder(); |
| reorderGenerics(); |
| // always get the latest EEs |
| getCurrentEEs(platformProperties); |
| boolean resolveOptional = platformProperties.length == 0 ? false : "true".equals(platformProperties[0].get("osgi.resolveOptional")); //$NON-NLS-1$//$NON-NLS-2$ |
| ResolverBundle[] currentlyResolved = null; |
| if (resolveOptional) { |
| BundleDescription[] resolvedBundles = state.getResolvedBundles(); |
| currentlyResolved = new ResolverBundle[resolvedBundles.length]; |
| for (int i = 0; i < resolvedBundles.length; i++) |
| currentlyResolved[i] = bundleMapping.get(resolvedBundles[i]); |
| } |
| // attempt to resolve all unresolved bundles |
| Collection<ResolverBundle> hookDisabled = Collections.EMPTY_LIST; |
| if (hook != null) { |
| List<ResolverBundle> resolvableBundles = new ArrayList<>(unresolvedBundles); |
| List<BundleRevision> resolvableRevisions = new ArrayList<>(resolvableBundles.size()); |
| for (ResolverBundle bundle : resolvableBundles) |
| resolvableRevisions.add(bundle.getBundleDescription()); |
| ArrayMap<BundleRevision, ResolverBundle> resolvable = new ArrayMap<>(resolvableRevisions, resolvableBundles); |
| int size = resolvableBundles.size(); |
| hook.filterResolvable(resolvable); |
| if (resolvable.size() < size) { |
| hookDisabled = new ArrayList<>(unresolvedBundles); |
| hookDisabled.removeAll(resolvableBundles); |
| } |
| } |
| |
| usesCalculationTimeout = false; |
| |
| List<ResolverBundle> toResolve = new ArrayList<>(unresolvedBundles); |
| // first resolve the system bundle to allow osgi.ee capabilities to be resolved |
| List<ResolverBundle> unresolvedSystemBundles = new ArrayList<>(1); |
| String systemBSN = getSystemBundle(); |
| for (Iterator<ResolverBundle> iToResolve = toResolve.iterator(); iToResolve.hasNext();) { |
| ResolverBundle rb = iToResolve.next(); |
| String symbolicName = rb.getName(); |
| if (symbolicName != null && symbolicName.equals(systemBSN)) { |
| unresolvedSystemBundles.add(rb); |
| iToResolve.remove(); |
| } |
| } |
| if (!unresolvedSystemBundles.isEmpty()) |
| resolveBundles(unresolvedSystemBundles.toArray(new ResolverBundle[unresolvedSystemBundles.size()]), platformProperties, hookDisabled); |
| |
| // Now resolve the rest |
| resolveBundles(toResolve.toArray(new ResolverBundle[toResolve.size()]), platformProperties, hookDisabled); |
| |
| Collection<ResolverBundle> optionalResolved = resolveOptional ? resolveOptionalConstraints(currentlyResolved) : Collections.EMPTY_LIST; |
| ResolverHook current = hook; |
| if (current != null) { |
| hook = null; |
| current.end(); |
| } |
| |
| // set the resolved status of the bundles in the State |
| // Note this must be done after calling end above in case end throws errors |
| stateResolveBundles(bundleMapping.values().toArray(new ResolverBundle[bundleMapping.size()])); |
| |
| for (ResolverBundle bundle : optionalResolved) { |
| state.resolveBundle(bundle.getBundleDescription(), false, null, null, null, null, null, null, null, null); |
| stateResolveBundle(bundle); |
| } |
| // reorder exports and bundles after resolving the bundles |
| resolverExports.reorder(); |
| resolverBundles.reorder(); |
| reorderGenerics(); |
| if (resolveOptional) |
| resolveOptionalConstraints(currentlyResolved); |
| if (DEBUG) |
| ResolverImpl.log("*** END RESOLUTION ***"); //$NON-NLS-1$ |
| } finally { |
| if (hook != null) |
| hook.end(); // need to make sure end is always called |
| hook = null; |
| } |
| } |
| |
| private long getUsesTimeout(Dictionary<Object, Object>[] platformProperties) { |
| try { |
| Object timeout = platformProperties.length == 0 ? null : platformProperties[0].get(USES_TIMEOUT_PROP); |
| if (timeout != null) { |
| long temp = Long.parseLong(timeout.toString()); |
| if (temp < 0) { |
| return -1; |
| } else if (temp == 0) { |
| return Long.MAX_VALUE; |
| } else { |
| return temp; |
| } |
| } |
| } catch (NumberFormatException e) { |
| // nothing; |
| } |
| return -1; |
| } |
| |
| private int getMultipleSuppliersLimit(Dictionary<Object, Object>[] platformProperties) { |
| try { |
| Object limit = platformProperties.length == 0 ? null : platformProperties[0].get(MULTIPLE_SUPPLIERS_LIMIT_PROP); |
| if (limit != null) { |
| int temp = Integer.parseInt(limit.toString()); |
| if (temp < 0) { |
| return MAX_MULTIPLE_SUPPLIERS_MERGE; |
| } else if (temp == 0) { |
| return Integer.MAX_VALUE; |
| } else { |
| return temp; |
| } |
| } |
| } catch (NumberFormatException e) { |
| // nothing; |
| } |
| return MAX_MULTIPLE_SUPPLIERS_MERGE; |
| } |
| |
| private BundleDescription[] addDevConstraints(BundleDescription[] reRefresh) { |
| if (!developmentMode) |
| return reRefresh; // we don't care about this unless we are in development mode |
| // when in develoment mode we need to reRefresh hosts of unresolved fragments that add new constraints |
| // and reRefresh and unresolved bundles that have dependents |
| Set<BundleDescription> additionalRefresh = new HashSet<>(); |
| ResolverBundle[] allUnresolved = unresolvedBundles.toArray(new ResolverBundle[unresolvedBundles.size()]); |
| for (ResolverBundle unresolved : allUnresolved ) { |
| addUnresolvedWithDependents(unresolved, additionalRefresh); |
| addHostsFromFragmentConstraints(unresolved, additionalRefresh); |
| } |
| if (additionalRefresh.size() == 0) |
| return reRefresh; // no new bundles found to refresh |
| // add the original reRefresh bundles to the set |
| if (reRefresh != null) |
| Collections.addAll(additionalRefresh, reRefresh); |
| return additionalRefresh.toArray(new BundleDescription[additionalRefresh.size()]); |
| } |
| |
| private void addUnresolvedWithDependents(ResolverBundle unresolved, Set<BundleDescription> additionalRefresh) { |
| BundleDescription[] dependents = unresolved.getBundleDescription().getDependents(); |
| if (dependents.length > 0) |
| additionalRefresh.add(unresolved.getBundleDescription()); |
| } |
| |
| private void addHostsFromFragmentConstraints(ResolverBundle unresolved, Set<BundleDescription> additionalRefresh) { |
| if (!unresolved.isFragment()) |
| return; |
| ImportPackageSpecification[] newImports = unresolved.getBundleDescription().getImportPackages(); |
| BundleSpecification[] newRequires = unresolved.getBundleDescription().getRequiredBundles(); |
| if (newImports.length == 0 && newRequires.length == 0) |
| return; // the fragment does not have its own constraints |
| BundleConstraint hostConstraint = unresolved.getHost(); |
| List<ResolverBundle> hosts = resolverBundles.get(hostConstraint.getVersionConstraint().getName()); |
| for (ResolverBundle host : hosts) |
| if (hostConstraint.isSatisfiedBy(host) && host.isResolved()) |
| // we found a host that is resolved; |
| // add it to the set of bundle to refresh so we can ensure this fragment is allowed to resolve |
| additionalRefresh.add(host.getBundleDescription()); |
| |
| } |
| |
| private Collection<ResolverBundle> resolveOptionalConstraints(ResolverBundle[] bundles) { |
| Collection<ResolverBundle> result = new ArrayList<>(); |
| for (ResolverBundle bundle : bundles) { |
| if (bundle != null && resolveOptionalConstraints(bundle)) { |
| result.add(bundle); |
| } |
| } |
| return result; |
| } |
| |
| // TODO this does not do proper uses constraint verification. |
| private boolean resolveOptionalConstraints(ResolverBundle bundle) { |
| BundleConstraint[] requires = bundle.getRequires(); |
| List<ResolverBundle> cycle = new ArrayList<>(); |
| boolean resolvedOptional = false; |
| for (BundleConstraint require : requires) { |
| if (require.isOptional() && require.getSelectedSupplier() == null) { |
| cycle.clear(); |
| resolveRequire(require, cycle); |
| if (require.getSelectedSupplier() != null) { |
| resolvedOptional = true; |
| } |
| } |
| } |
| ResolverImport[] imports = bundle.getImportPackages(); |
| for (ResolverImport resolverImport : imports) { |
| if (resolverImport.isOptional() && resolverImport.getSelectedSupplier() == null) { |
| cycle.clear(); |
| resolveImport(resolverImport, cycle); |
| if (resolverImport.getSelectedSupplier() != null) { |
| resolvedOptional = true; |
| } |
| } |
| } |
| return resolvedOptional; |
| } |
| |
| private void getCurrentEEs(Dictionary<Object, Object>[] platformProperties) { |
| CURRENT_EES = new String[platformProperties.length][]; |
| for (int i = 0; i < platformProperties.length; i++) { |
| String eeSpecs = (String) platformProperties[i].get(Constants.FRAMEWORK_EXECUTIONENVIRONMENT); |
| CURRENT_EES[i] = ManifestElement.getArrayFromList(eeSpecs, ","); //$NON-NLS-1$ |
| } |
| } |
| |
| private void resolveBundles(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties, Collection<ResolverBundle> hookDisabled) { |
| |
| // First check that all the meta-data is valid for each unresolved bundle |
| // This will reset the resolvable flag for each bundle |
| for (ResolverBundle bundle : bundles) { |
| state.removeResolverErrors(bundle.getBundleDescription()); |
| // if in development mode then make all bundles resolvable |
| // we still want to call isResolvable here to populate any possible ResolverErrors for the bundle |
| bundle.setResolvable(isResolvable(bundle, platformProperties, hookDisabled) || developmentMode); |
| } |
| selectSingletons(bundles); |
| resolveBundles0(bundles, platformProperties); |
| if (DEBUG_WIRING) |
| printWirings(); |
| } |
| |
| private void selectSingletons(ResolverBundle[] bundles) { |
| if (developmentMode) |
| return; // want all singletons to resolve in devmode |
| Map<String, Collection<ResolverBundle>> selectedSingletons = new HashMap<>(bundles.length); |
| for (ResolverBundle bundle : bundles) { |
| if (!bundle.getBundleDescription().isSingleton() || !bundle.isResolvable()) |
| continue; |
| String bsn = bundle.getName(); |
| Collection<ResolverBundle> selected = selectedSingletons.get(bsn); |
| if (selected != null) |
| continue; // already processed the bsn |
| selected = new ArrayList<>(1); |
| selectedSingletons.put(bsn, selected); |
| |
| List<ResolverBundle> sameBSN = resolverBundles.get(bsn); |
| if (sameBSN.size() < 2) { |
| selected.add(bundle); |
| continue; |
| } |
| // prime selected with resolved singleton bundles |
| for (ResolverBundle singleton : sameBSN) { |
| if (singleton.getBundleDescription().isSingleton() && singleton.getBundleDescription().isResolved()) |
| selected.add(singleton); |
| } |
| // get the collision map for the BSN |
| Map<ResolverBundle, Collection<ResolverBundle>> collisionMap = getCollisionMap(sameBSN); |
| // process the collision map |
| for (ResolverBundle singleton : sameBSN) { |
| if (selected.contains(singleton)) |
| continue; // no need to process resolved bundles |
| Collection<ResolverBundle> collisions = collisionMap.get(singleton); |
| if (collisions == null || !singleton.isResolvable()) |
| continue; // not a singleton or not resolvable |
| Collection<ResolverBundle> pickOneToResolve = new ArrayList<>(); |
| for (ResolverBundle collision : collisions) { |
| if (selected.contains(collision)) { |
| // Must fail since there is already a selected bundle which is a collision of the singleton bundle |
| singleton.setResolvable(false); |
| state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collision.getBundleDescription().toString(), null); |
| break; |
| } |
| if (!pickOneToResolve.contains(collision)) |
| pickOneToResolve.add(collision); |
| } |
| // need to make sure the bundle does not collide from the POV of another entry |
| for (Map.Entry<ResolverBundle, Collection<ResolverBundle>> collisionEntry : collisionMap.entrySet()) { |
| if (collisionEntry.getKey() != singleton && collisionEntry.getValue().contains(singleton)) { |
| if (selected.contains(collisionEntry.getKey())) { |
| // Must fail since there is already a selected bundle for which the singleton bundle is a collision |
| singleton.setResolvable(false); |
| state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collisionEntry.getKey().getBundleDescription().toString(), null); |
| break; |
| } |
| if (!pickOneToResolve.contains(collisionEntry.getKey())) |
| pickOneToResolve.add(collisionEntry.getKey()); |
| } |
| } |
| if (singleton.isResolvable()) { |
| pickOneToResolve.add(singleton); |
| selected.add(pickOneToResolve(pickOneToResolve)); |
| } |
| } |
| } |
| } |
| |
| private ResolverBundle pickOneToResolve(Collection<ResolverBundle> pickOneToResolve) { |
| ResolverBundle selectedVersion = null; |
| for (ResolverBundle singleton : pickOneToResolve) { |
| if (selectedVersion == null) |
| selectedVersion = singleton; |
| boolean higherVersion = selectionPolicy != null ? selectionPolicy.compare(selectedVersion.getBundleDescription(), singleton.getBundleDescription()) > 0 : selectedVersion.getVersion().compareTo(singleton.getVersion()) < 0; |
| if (higherVersion) |
| selectedVersion = singleton; |
| } |
| |
| for (ResolverBundle singleton : pickOneToResolve) { |
| if (singleton != selectedVersion) { |
| singleton.setResolvable(false); |
| state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, selectedVersion.getBundleDescription().toString(), null); |
| } |
| } |
| return selectedVersion; |
| } |
| |
| private Map<ResolverBundle, Collection<ResolverBundle>> getCollisionMap(List<ResolverBundle> sameBSN) { |
| Map<ResolverBundle, Collection<ResolverBundle>> result = new HashMap<>(); |
| for (ResolverBundle singleton : sameBSN) { |
| if (!singleton.getBundleDescription().isSingleton() || !singleton.isResolvable()) |
| continue; // ignore non-singleton and non-resolvable |
| List<ResolverBundle> collisionCandidates = new ArrayList<>(sameBSN.size() - 1); |
| List<BundleCapability> capabilities = new ArrayList<>(sameBSN.size() - 1); |
| for (ResolverBundle collision : sameBSN) { |
| if (collision == singleton || !collision.getBundleDescription().isSingleton() || !collision.isResolvable()) |
| continue; // Ignore the bundle we are checking and non-singletons and non-resolvable |
| collisionCandidates.add(collision); |
| capabilities.add(getIdentity(collision)); |
| } |
| if (hook != null) |
| hook.filterSingletonCollisions(getIdentity(singleton), asCapabilities(new ArrayMap<>(capabilities, collisionCandidates))); |
| result.put(singleton, collisionCandidates); |
| } |
| return result; |
| } |
| |
| private BundleCapability getIdentity(ResolverBundle bundle) { |
| List<BundleCapability> identities = bundle.getBundleDescription().getDeclaredCapabilities(IdentityNamespace.IDENTITY_NAMESPACE); |
| return identities.size() == 1 ? identities.get(0) : bundle.getCapability(); |
| } |
| |
| private void resolveBundles0(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { |
| if (developmentMode) |
| // need to sort bundles to keep consistent order for fragment attachment (bug 174930) |
| Arrays.sort(bundles); |
| // First attach all fragments to the matching hosts |
| Collection<String> processedFragments = new HashSet<>(bundles.length); |
| for (ResolverBundle bundle : bundles) { |
| attachFragment(bundle, processedFragments); |
| } |
| |
| // Lists of cyclic dependencies recording during resolving |
| List<ResolverBundle> cycle = new ArrayList<>(1); // start small |
| // Attempt to resolve all unresolved bundles |
| for (ResolverBundle bundle : bundles) { |
| if (DEBUG) { |
| ResolverImpl.log("** RESOLVING " + bundle + " **"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| cycle.clear(); |
| resolveBundle(bundle, cycle); |
| // Check for any bundles involved in a cycle. |
| // if any bundles in the cycle are not resolved then we need to resolve the resolvable ones |
| checkCycle(cycle); |
| } |
| // Resolve all fragments that are still attached to at least one host. |
| if (unresolvedBundles.size() > 0) { |
| ResolverBundle[] unresolved = unresolvedBundles.toArray(new ResolverBundle[unresolvedBundles.size()]); |
| for (ResolverBundle toResolve : unresolved) { |
| resolveFragment(toResolve); |
| } |
| } |
| checkUsesConstraints(bundles, platformProperties); |
| checkComposites(bundles, platformProperties); |
| } |
| |
| private void checkComposites(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { |
| CompositeResolveHelperRegistry helpers = getCompositeHelpers(); |
| if (helpers == null) |
| return; |
| Set<ResolverBundle> exclude = null; |
| for (ResolverBundle bundle : bundles) { |
| CompositeResolveHelper helper = helpers.getCompositeResolveHelper(bundle.getBundleDescription()); |
| if (helper == null) |
| continue; |
| if (!bundle.isResolved()) { |
| continue; |
| } |
| if (!helper.giveExports(getExportsWiredTo(bundle, null))) { |
| state.addResolverError(bundle.getBundleDescription(), ResolverError.DISABLED_BUNDLE, null, null); |
| bundle.setResolvable(false); |
| // We pass false for keepFragmentsAttached because we need to redo the attachments (bug 272561) |
| setBundleUnresolved(bundle, false, false); |
| if (exclude == null) |
| exclude = new HashSet<>(1); |
| exclude.add(bundle); |
| } |
| } |
| reResolveBundles(exclude, bundles, platformProperties); |
| } |
| |
| private void checkUsesConstraints(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { |
| List<ResolverConstraint> conflictingConstraints = findBestCombination(bundles, platformProperties); |
| if (conflictingConstraints == null) |
| return; |
| Set<ResolverBundle> conflictedBundles = null; |
| for (ResolverConstraint conflict : conflictingConstraints) { |
| if (conflict.isOptional()) { |
| conflict.clearPossibleSuppliers(); |
| continue; |
| } |
| if (conflictedBundles == null) |
| conflictedBundles = new HashSet<>(conflictingConstraints.size()); |
| ResolverBundle conflictedBundle; |
| if (conflict.isFromFragment()) |
| conflictedBundle = bundleMapping.get(conflict.getVersionConstraint().getBundle()); |
| else |
| conflictedBundle = conflict.getBundle(); |
| if (conflictedBundle != null) { |
| if (DEBUG_USES) |
| System.out.println("Found conflicting constraint: " + conflict + " in bundle " + conflictedBundle); //$NON-NLS-1$//$NON-NLS-2$ |
| conflictedBundles.add(conflictedBundle); |
| int type = conflict instanceof ResolverImport ? ResolverError.IMPORT_PACKAGE_USES_CONFLICT : ResolverError.REQUIRE_BUNDLE_USES_CONFLICT; |
| state.addResolverError(conflictedBundle.getBundleDescription(), type, conflict.getVersionConstraint().toString(), conflict.getVersionConstraint()); |
| conflictedBundle.setResolvable(false); |
| // We pass false for keepFragmentsAttached because we need to redo the attachments (bug 272561) |
| setBundleUnresolved(conflictedBundle, false, false); |
| } |
| } |
| reResolveBundles(conflictedBundles, bundles, platformProperties); |
| } |
| |
| private void reResolveBundles(Set<ResolverBundle> exclude, ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { |
| if (exclude == null || exclude.size() == 0) |
| return; |
| List<ResolverBundle> remainingUnresolved = new ArrayList<>(); |
| for (ResolverBundle bundle : bundles) { |
| if (!exclude.contains(bundle)) { |
| // We pass false for keepFragmentsAttached because we need to redo the attachments (bug 272561) |
| setBundleUnresolved(bundle, false, false); |
| remainingUnresolved.add(bundle); |
| } |
| } |
| resolveBundles0(remainingUnresolved.toArray(new ResolverBundle[remainingUnresolved.size()]), platformProperties); |
| } |
| |
| private List<ResolverConstraint> findBestCombination(ResolverBundle[] bundles, Dictionary<Object, Object>[] platformProperties) { |
| Object usesMode = platformProperties.length == 0 ? null : platformProperties[0].get("osgi.resolver.usesMode"); //$NON-NLS-1$ |
| if (usesMode == null) |
| usesMode = secureAction.getProperty("osgi.resolver.usesMode"); //$NON-NLS-1$ |
| if ("ignore".equals(usesMode) || developmentMode) //$NON-NLS-1$ |
| return null; |
| Set<String> bundleConstraints = new HashSet<>(); |
| Set<String> packageConstraints = new HashSet<>(); |
| Collection<GenericConstraint> multiRequirementWithMultiSuppliers = new ArrayList<>(); |
| // first try out the initial selections |
| List<ResolverConstraint> initialConflicts = getConflicts(bundles, packageConstraints, bundleConstraints, multiRequirementWithMultiSuppliers); |
| if (initialConflicts == null || "tryFirst".equals(usesMode) || usesCalculationTimeout) { //$NON-NLS-1$ |
| groupingChecker.clear(); |
| // the first combination have no conflicts or |
| // we only are trying the first combination or |
| // we have timed out the calculation; return without iterating over all combinations |
| return initialConflicts; |
| } |
| ResolverConstraint[][] multipleSuppliers = getMultipleSuppliers(bundles, packageConstraints, bundleConstraints); |
| List<ResolverConstraint> conflicts = null; |
| int[] bestCombination = new int[multipleSuppliers.length]; |
| conflicts = findBestCombination(bundles, multipleSuppliers, bestCombination, initialConflicts); |
| if (DEBUG_USES) { |
| System.out.print("Best combination found: "); //$NON-NLS-1$ |
| printCombination(bestCombination); |
| } |
| for (int i = 0; i < bestCombination.length; i++) { |
| for (ResolverConstraint constraint : multipleSuppliers[i]) { |
| constraint.setSelectedSupplier(bestCombination[i]); |
| // sanity check to make sure we did not just get wired to our own dropped export |
| VersionSupplier selectedSupplier = constraint.getSelectedSupplier(); |
| if (selectedSupplier != null) |
| selectedSupplier.setSubstitute(null); |
| } |
| } |
| if (!multiRequirementWithMultiSuppliers.isEmpty()) { |
| groupingChecker.clear(); |
| for (GenericConstraint multiConstraint : multiRequirementWithMultiSuppliers) { |
| VersionSupplier[] matchingSuppliers = multiConstraint.getMatchingCapabilities(); |
| if (matchingSuppliers != null) { |
| for (VersionSupplier supplier : matchingSuppliers) { |
| if (groupingChecker.isConsistent(multiConstraint.getBundle(), (GenericCapability) supplier) != null) { |
| multiConstraint.removePossibleSupplier(supplier); |
| } |
| } |
| } |
| } |
| } |
| // do not need to keep uses data in memory |
| groupingChecker.clear(); |
| return conflicts; |
| } |
| |
| private int[] getCombination(ResolverConstraint[][] multipleSuppliers, int[] combination) { |
| for (int i = 0; i < combination.length; i++) |
| combination[i] = multipleSuppliers[i][0].getSelectedSupplierIndex(); |
| return combination; |
| } |
| |
| private List<ResolverConstraint> findBestCombination(ResolverBundle[] bundles, ResolverConstraint[][] multipleSuppliers, int[] bestCombination, List<ResolverConstraint> bestConflicts) { |
| // now iterate over every possible combination until either zero conflicts are found |
| // or we have run out of combinations |
| // if all combinations are tried then return the combination with the lowest number of conflicts |
| long initialTime = System.currentTimeMillis(); |
| long timeLimit; |
| if (usesTimeout < 0) |
| timeLimit = Math.min(MAX_USES_TIME_BASE + (bundles.length * 30), MAX_USES_TIME_LIMIT); |
| else |
| timeLimit = usesTimeout; |
| |
| if (DEBUG_USES) { |
| System.out.println(multipleSuppliers.length + " Uses constraint were found for the following declarations: "); //$NON-NLS-1$ |
| for (ResolverConstraint[] constraint : multipleSuppliers) { |
| System.out.println(Arrays.toString(constraint)); |
| } |
| } |
| |
| int bestConflictCount = getConflictCount(bestConflicts); |
| ResolverBundle[] bestConflictBundles = getConflictedBundles(bestConflicts); |
| while (bestConflictCount != 0 && getNextCombination(multipleSuppliers)) { |
| if ((System.currentTimeMillis() - initialTime) > timeLimit) { |
| if (DEBUG_USES) |
| System.out.println("Uses constraint check has timedout. Using the best solution found so far."); //$NON-NLS-1$ |
| usesCalculationTimeout = true; |
| break; |
| } |
| if (DEBUG_USES) |
| printCombination(getCombination(multipleSuppliers, new int[multipleSuppliers.length])); |
| // first count the conflicts for the bundles with conflicts from the best combination |
| // this significantly reduces the time it takes to populate the GroupingChecker for cases where |
| // the combination is no better. |
| List<ResolverConstraint> conflicts = getConflicts(bestConflictBundles, null, null, null); |
| int conflictCount = getConflictCount(conflicts); |
| if (conflictCount >= bestConflictCount) { |
| if (DEBUG_USES) |
| System.out.println("Combination is not better than current best: " + conflictCount + ">=" + bestConflictCount); //$NON-NLS-1$ //$NON-NLS-2$ |
| // no need to test the other bundles; |
| // this combination is no better for the bundles which conflict with the current best combination |
| continue; |
| } |
| // this combination improves upon the conflicts for the bundles which conflict with the current best combination; |
| // do an complete conflict count |
| conflicts = getConflicts(bundles, null, null, null); |
| conflictCount = getConflictCount(conflicts); |
| if (conflictCount < bestConflictCount) { |
| // this combination is better that the current best combination; save this combination as the current best |
| bestConflictCount = conflictCount; |
| bestConflicts = conflicts; |
| getCombination(multipleSuppliers, bestCombination); |
| bestConflictBundles = getConflictedBundles(bestConflicts); |
| if (DEBUG_USES) |
| System.out.println("Combination selected as current best: number of conflicts: " + bestConflictCount); //$NON-NLS-1$ |
| } else if (DEBUG_USES) { |
| System.out.println("Combination is not better than current best: " + conflictCount + ">=" + bestConflictCount); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| if (DEBUG_USES && !usesCalculationTimeout) |
| System.out.println("Uses constraint check has finished after " + (System.currentTimeMillis() - initialTime) + "ms out of " + timeLimit + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| return bestConflicts; |
| } |
| |
| private void printCombination(int[] curCombination) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('['); |
| for (int i = 0; i < curCombination.length; i++) { |
| sb.append(curCombination[i]); |
| if (i < curCombination.length - 1) |
| sb.append(','); |
| } |
| sb.append(']'); |
| System.out.println(sb.toString()); |
| } |
| |
| private ResolverBundle[] getConflictedBundles(List<ResolverConstraint> bestConflicts) { |
| if (bestConflicts == null) |
| return new ResolverBundle[0]; |
| List<ResolverBundle> conflictedBundles = new ArrayList<>(bestConflicts.size()); |
| for (ResolverConstraint constraint : bestConflicts) |
| if (!conflictedBundles.contains(constraint.getBundle())) |
| conflictedBundles.add(constraint.getBundle()); |
| return conflictedBundles.toArray(new ResolverBundle[conflictedBundles.size()]); |
| } |
| |
| private boolean getNextCombination(ResolverConstraint[][] multipleSuppliers) { |
| int current = 0; |
| while (current < multipleSuppliers.length) { |
| if (multipleSuppliers[current][0].selectNextSupplier()) { |
| for (int i = 1; i < multipleSuppliers[current].length; i++) |
| multipleSuppliers[current][i].selectNextSupplier(); |
| return true; // the current slot has a next supplier |
| } |
| for (ResolverConstraint multipleSupplier : multipleSuppliers[current]) { |
| multipleSupplier.setSelectedSupplier(0); // reset the current slot |
| } |
| current++; // move to the next slot |
| } |
| return false; |
| } |
| |
| // only count non-optional conflicts |
| private int getConflictCount(List<ResolverConstraint> conflicts) { |
| if (conflicts == null || conflicts.size() == 0) |
| return 0; |
| int result = 0; |
| for (ResolverConstraint constraint : conflicts) |
| if (!constraint.isOptional()) |
| result += 1; |
| return result; |
| } |
| |
| private List<ResolverConstraint> getConflicts(ResolverBundle[] bundles, Set<String> packageConstraints, Set<String> bundleConstraints, Collection<GenericConstraint> multiRequirementWithMultiSuppliers) { |
| groupingChecker.clear(); |
| List<ResolverConstraint> conflicts = null; |
| for (ResolverBundle bundle : bundles) { |
| conflicts = addConflicts(bundle, packageConstraints, bundleConstraints, multiRequirementWithMultiSuppliers, conflicts); |
| } |
| return conflicts; |
| } |
| |
| private List<ResolverConstraint> addConflicts(ResolverBundle bundle, Set<String> packageConstraints, Set<String> bundleConstraints, Collection<GenericConstraint> multiRequirementWithMultiSuppliers, List<ResolverConstraint> conflicts) { |
| BundleConstraint[] requires = bundle.getRequires(); |
| for (BundleConstraint require : requires) { |
| ResolverBundle selectedSupplier = (ResolverBundle) require.getSelectedSupplier(); |
| PackageRoots[][] conflict = selectedSupplier == null ? null : groupingChecker.isConsistent(bundle, selectedSupplier); |
| if (conflict != null) { |
| addConflictNames(conflict, packageConstraints, bundleConstraints); |
| if (DEBUG_CONFLICTS) { |
| printConflict(conflict, require, bundle); |
| } |
| if (conflicts == null) |
| conflicts = new ArrayList<>(1); |
| conflicts.add(require); |
| } |
| } |
| ResolverImport[] imports = bundle.getImportPackages(); |
| for (ResolverImport importConflict : imports) { |
| ResolverExport selectedSupplier = (ResolverExport) importConflict.getSelectedSupplier(); |
| PackageRoots[][] conflict = selectedSupplier == null ? null : groupingChecker.isConsistent(bundle, selectedSupplier); |
| if (conflict != null) { |
| addConflictNames(conflict, packageConstraints, bundleConstraints); |
| if (DEBUG_CONFLICTS) { |
| printConflict(conflict, importConflict, bundle); |
| } |
| if (conflicts == null) |
| conflicts = new ArrayList<>(1); |
| conflicts.add(importConflict); |
| } |
| } |
| |
| GenericConstraint[] genericRequires = bundle.getGenericRequires(); |
| for (GenericConstraint capabilityRequirement : genericRequires) { |
| VersionSupplier[] suppliers = capabilityRequirement.getMatchingCapabilities(); |
| if (suppliers == null) |
| continue; |
| |
| if (multiRequirementWithMultiSuppliers != null && capabilityRequirement.isMultiple() && suppliers.length > 1) { |
| multiRequirementWithMultiSuppliers.add(capabilityRequirement); |
| } |
| // search for at least one capability that does not conflict |
| // in case of single cardinality there will only be one matching supplier |
| // in case of multiple there may be multiple suppliers, but we only need one or more to not conflict with the class space |
| Collection<PackageRoots[][]> capabilityConflicts = null; |
| for (VersionSupplier supplier : suppliers) { |
| PackageRoots[][] conflict = groupingChecker.isConsistent(bundle, (GenericCapability) supplier); |
| if (conflict != null) { |
| if (capabilityConflicts == null) |
| capabilityConflicts = new ArrayList<>(1); |
| capabilityConflicts.add(conflict); |
| } |
| } |
| if (capabilityConflicts != null) { |
| for (PackageRoots[][] conflict : capabilityConflicts) { |
| addConflictNames(conflict, packageConstraints, bundleConstraints); |
| } |
| if (capabilityConflicts.size() == suppliers.length) { |
| // every capability conflicted |
| if (conflicts == null) |
| conflicts = new ArrayList<>(1); |
| conflicts.add(capabilityRequirement); |
| } |
| } |
| } |
| return conflicts; |
| } |
| |
| private void printConflict(PackageRoots[][] conflict, ResolverConstraint constraint, ResolverBundle bundle) { |
| System.out.println("Found conflict for bundle: " + bundle + ", when trying to resolve constraint: " + constraint); //$NON-NLS-1$//$NON-NLS-2$ |
| for (PackageRoots[] rootConflicts : conflict) { |
| ResolverExport export0 = rootConflicts[0].getRoots()[0]; |
| ResolverExport export1 = rootConflicts[1].getRoots()[0]; |
| System.out.println(" " + export0 + ", provided by bundle: " + export0.getResolverBundle() + " conflicts with " + export1 + ", provided by bundle: " + export1.getResolverBundle()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| } |
| |
| // records the conflict names we can use to scope down the list of multiple suppliers |
| private void addConflictNames(PackageRoots[][] conflicts, Set<String> packageConstraints, Set<String> bundleConstraints) { |
| if (packageConstraints == null || bundleConstraints == null) |
| return; |
| for (PackageRoots[] conflict : conflicts) { |
| packageConstraints.add(conflict[0].getName()); |
| packageConstraints.add(conflict[1].getName()); |
| ResolverExport[] exports0 = conflict[0].getRoots(); |
| if (exports0 != null) { |
| for (ResolverExport exportConflict : exports0) { |
| ResolverBundle exporter = exportConflict.getExporter(); |
| if (exporter != null && exporter.getName() != null) |
| bundleConstraints.add(exporter.getName()); |
| } |
| } |
| ResolverExport[] exports1 = conflict[1].getRoots(); |
| if (exports1 != null) { |
| for (ResolverExport exportConflict : exports1) { |
| ResolverBundle exporter = exportConflict.getExporter(); |
| if (exporter != null && exporter.getName() != null) |
| bundleConstraints.add(exporter.getName()); |
| } |
| } |
| } |
| } |
| |
| // get a list of resolver constraints that have multiple suppliers |
| // a 2 demensional array is used each entry is a list of identical constraints that have identical suppliers. |
| private ResolverConstraint[][] getMultipleSuppliers(ResolverBundle[] bundles, Set<String> packageConstraints, Set<String> bundleConstraints) { |
| List<ResolverImport> multipleImportSupplierList = new ArrayList<>(1); |
| List<BundleConstraint> multipleRequireSupplierList = new ArrayList<>(1); |
| List<GenericConstraint> multipleGenericSupplierList = new ArrayList<>(1); |
| for (ResolverBundle bundle : bundles) { |
| BundleConstraint[] requires = bundle.getRequires(); |
| for (BundleConstraint require : requires) |
| if (require.getNumPossibleSuppliers() > 1) |
| multipleRequireSupplierList.add(require); |
| ResolverImport[] imports = bundle.getImportPackages(); |
| for (ResolverImport importPkg : imports) { |
| if (importPkg.getNumPossibleSuppliers() > 1) { |
| Integer eeProfile = (Integer) ((ResolverExport) importPkg.getSelectedSupplier()).getExportPackageDescription().getDirective(ExportPackageDescriptionImpl.EQUINOX_EE); |
| if (eeProfile.intValue() < 0) { |
| // this is a normal package; always add it |
| multipleImportSupplierList.add(importPkg); |
| } else { |
| // this is a system bundle export |
| // If other exporters of this package also require the system bundle |
| // then this package does not need to be added to the mix |
| // this is an optimization for bundles like org.eclipse.xerces |
| // that export lots of packages also exported by the system bundle on J2SE 1.4 |
| VersionSupplier[] suppliers = importPkg.getPossibleSuppliers(); |
| for (int suppliersIndex = 1; suppliersIndex < suppliers.length; suppliersIndex++) { |
| Integer ee = (Integer) ((ResolverExport) suppliers[suppliersIndex]).getExportPackageDescription().getDirective(ExportPackageDescriptionImpl.EQUINOX_EE); |
| if (ee.intValue() >= 0) |
| continue; |
| if (((ResolverExport) suppliers[suppliersIndex]).getExporter().getRequire(getSystemBundle()) == null) |
| if (((ResolverExport) suppliers[suppliersIndex]).getExporter().getRequire(Constants.SYSTEM_BUNDLE_SYMBOLICNAME) == null) { |
| multipleImportSupplierList.add(importPkg); |
| break; |
| } |
| } |
| } |
| } |
| } |
| GenericConstraint[] genericRequires = bundle.getGenericRequires(); |
| for (GenericConstraint genericRequire : genericRequires) |
| if (genericRequire.getNumPossibleSuppliers() > 1 && !genericRequire.isMultiple()) |
| multipleGenericSupplierList.add(genericRequire); |
| } |
| List<ResolverConstraint[]> results = new ArrayList<>(); |
| if (multipleImportSupplierList.size() + multipleRequireSupplierList.size() + multipleGenericSupplierList.size() > usesMultipleSuppliersLimit) { |
| // we have hit a max on the multiple suppliers in the lists without merging. |
| // first merge the identical constraints that have identical suppliers |
| Map<String, List<List<ResolverConstraint>>> multipleImportSupplierMaps = new HashMap<>(); |
| for (ResolverImport importPkg : multipleImportSupplierList) |
| addMutipleSupplierConstraint(multipleImportSupplierMaps, importPkg, importPkg.getName()); |
| Map<String, List<List<ResolverConstraint>>> multipleRequireSupplierMaps = new HashMap<>(); |
| for (BundleConstraint requireBundle : multipleRequireSupplierList) |
| addMutipleSupplierConstraint(multipleRequireSupplierMaps, requireBundle, requireBundle.getName()); |
| Map<String, List<List<ResolverConstraint>>> multipleGenericSupplierMaps = new HashMap<>(); |
| for (GenericConstraint genericRequire : multipleGenericSupplierList) |
| addMutipleSupplierConstraint(multipleGenericSupplierMaps, genericRequire, genericRequire.getNameSpace()); |
| addMergedSuppliers(results, multipleImportSupplierMaps); |
| addMergedSuppliers(results, multipleRequireSupplierMaps); |
| addMergedSuppliers(results, multipleGenericSupplierMaps); |
| // check the results to see if we have reduced the number enough |
| if (results.size() > usesMultipleSuppliersLimit && packageConstraints != null && bundleConstraints != null) { |
| // we still have too big of a list; filter out constraints that are not in conflict |
| List<ResolverConstraint[]> tooBig = results; |
| results = new ArrayList<>(); |
| for (ResolverConstraint[] constraints : tooBig) { |
| ResolverConstraint constraint = constraints.length > 0 ? constraints[0] : null; |
| if (constraint instanceof ResolverImport) { |
| if (packageConstraints.contains(constraint.getName())) |
| results.add(constraints); |
| } else if (constraint instanceof BundleConstraint) { |
| if (bundleConstraints.contains(constraint.getName())) |
| results.add(constraints); |
| } |
| } |
| } |
| } else { |
| // the size is acceptable; just copy the lists as-is |
| for (ResolverConstraint constraint : multipleImportSupplierList) |
| results.add(new ResolverConstraint[] {constraint}); |
| for (ResolverConstraint constraint : multipleRequireSupplierList) |
| results.add(new ResolverConstraint[] {constraint}); |
| for (ResolverConstraint constraint : multipleGenericSupplierList) |
| results.add(new ResolverConstraint[] {constraint}); |
| |
| } |
| return results.toArray(new ResolverConstraint[results.size()][]); |
| } |
| |
| String getSystemBundle() { |
| Dictionary<?, ?>[] platformProperties = state.getPlatformProperties(); |
| String systemBundle = platformProperties.length == 0 ? null : (String) platformProperties[0].get(StateImpl.STATE_SYSTEM_BUNDLE); |
| if (systemBundle == null) |
| systemBundle = EquinoxContainer.NAME; |
| return systemBundle; |
| } |
| |
| private void addMergedSuppliers(List<ResolverConstraint[]> mergedSuppliers, Map<String, List<List<ResolverConstraint>>> constraints) { |
| for (List<List<ResolverConstraint>> mergedConstraintLists : constraints.values()) { |
| for (List<ResolverConstraint> constraintList : mergedConstraintLists) { |
| mergedSuppliers.add(constraintList.toArray(new ResolverConstraint[constraintList.size()])); |
| } |
| } |
| } |
| |
| private void addMutipleSupplierConstraint(Map<String, List<List<ResolverConstraint>>> constraints, ResolverConstraint constraint, String key) { |
| List<List<ResolverConstraint>> mergedConstraintLists = constraints.get(key); |
| if (mergedConstraintLists == null) { |
| mergedConstraintLists = new ArrayList<>(0); |
| List<ResolverConstraint> constraintList = new ArrayList<>(1); |
| constraintList.add(constraint); |
| mergedConstraintLists.add(constraintList); |
| constraints.put(key, mergedConstraintLists); |
| return; |
| } |
| for (List<ResolverConstraint> constraintList : mergedConstraintLists) { |
| ResolverConstraint mergedConstraint = constraintList.get(0); |
| VersionSupplier[] suppliers1 = constraint.getPossibleSuppliers(); |
| VersionSupplier[] suppliers2 = mergedConstraint.getPossibleSuppliers(); |
| if (suppliers1.length != suppliers2.length) |
| continue; |
| for (int i = 0; i < suppliers1.length; i++) |
| if (suppliers1[i] != suppliers2[i]) |
| continue; |
| constraintList.add(constraint); |
| return; |
| } |
| List<ResolverConstraint> constraintList = new ArrayList<>(1); |
| constraintList.add(constraint); |
| mergedConstraintLists.add(constraintList); |
| } |
| |
| private void checkCycle(List<ResolverBundle> cycle) { |
| int cycleSize = cycle.size(); |
| if (cycleSize == 0) |
| return; |
| cycleLoop: for (Iterator<ResolverBundle> iCycle = cycle.iterator(); iCycle.hasNext();) { |
| ResolverBundle cycleBundle = iCycle.next(); |
| if (!cycleBundle.isResolvable()) { |
| iCycle.remove(); // remove this bundle from the list of bundles that need re-resolved |
| continue cycleLoop; |
| } |
| // Check that we haven't wired to any dropped exports |
| ResolverImport[] imports = cycleBundle.getImportPackages(); |
| for (ResolverImport resolverImport : imports) { |
| // check for dropped exports |
| while (resolverImport.getSelectedSupplier() != null) { |
| ResolverExport importSupplier = (ResolverExport) resolverImport.getSelectedSupplier(); |
| if (importSupplier.getSubstitute() != null) { |
| resolverImport.selectNextSupplier(); |
| } else { |
| break; |
| } |
| } |
| if (!resolverImport.isDynamic() && !resolverImport.isOptional() && resolverImport.getSelectedSupplier() == null) { |
| cycleBundle.setResolvable(false); |
| state.addResolverError(resolverImport.getVersionConstraint().getBundle(), ResolverError.MISSING_IMPORT_PACKAGE, resolverImport.getVersionConstraint().toString(), resolverImport.getVersionConstraint()); |
| iCycle.remove(); |
| continue cycleLoop; |
| } |
| } |
| } |
| if (cycle.size() != cycleSize) { |
| //we removed an un-resolvable bundle; must re-resolve remaining cycle |
| for (int i = 0; i < cycle.size(); i++) { |
| ResolverBundle cycleBundle = cycle.get(i); |
| cycleBundle.clearWires(); |
| } |
| List<ResolverBundle> innerCycle = new ArrayList<>(cycle.size()); |
| for (int i = 0; i < cycle.size(); i++) |
| resolveBundle(cycle.get(i), innerCycle); |
| checkCycle(innerCycle); |
| } else { |
| for (int i = 0; i < cycle.size(); i++) { |
| if (DEBUG || DEBUG_CYCLES) |
| ResolverImpl.log("Pushing " + cycle.get(i) + " to RESOLVED"); //$NON-NLS-1$ //$NON-NLS-2$ |
| setBundleResolved(cycle.get(i)); |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| static Collection<BundleCapability> asCapabilities(Collection<? extends BundleCapability> capabilities) { |
| return (Collection<BundleCapability>) capabilities; |
| } |
| |
| private void resolveFragment(ResolverBundle fragment) { |
| if (!fragment.isFragment()) |
| return; |
| if (fragment.getHost().getNumPossibleSuppliers() > 0) |
| if (!developmentMode || state.getResolverErrors(fragment.getBundleDescription()).length == 0) |
| setBundleResolved(fragment); |
| } |
| |
| // This method will attempt to resolve the supplied bundle and any bundles that it is dependent on |
| private boolean resolveBundle(ResolverBundle bundle, List<ResolverBundle> cycle) { |
| if (bundle.isFragment()) |
| return false; |
| if (!bundle.isResolvable()) { |
| if (DEBUG) |
| ResolverImpl.log(" - " + bundle + " is unresolvable"); //$NON-NLS-1$ //$NON-NLS-2$ |
| return false; |
| } |
| switch (bundle.getState()) { |
| case ResolverBundle.RESOLVED : |
| // 'bundle' is already resolved so just return |
| if (DEBUG) |
| ResolverImpl.log(" - " + bundle + " already resolved"); //$NON-NLS-1$ //$NON-NLS-2$ |
| return true; |
| case ResolverBundle.UNRESOLVED : |
| // 'bundle' is UNRESOLVED so move to RESOLVING |
| bundle.clearWires(); |
| setBundleResolving(bundle); |
| break; |
| case ResolverBundle.RESOLVING : |
| if (cycle.contains(bundle)) |
| return true; |
| break; |
| default : |
| break; |
| } |
| |
| boolean failed = false; |
| |
| if (!failed) { |
| GenericConstraint[] genericRequires = bundle.getGenericRequires(); |
| for (GenericConstraint genericRequire : genericRequires) { |
| if (genericRequire.isEffective()) { |
| if (!resolveGenericReq(genericRequire, cycle)) { |
| if (DEBUG || DEBUG_GENERICS) { |
| ResolverImpl.log("** GENERICS " + genericRequire.getVersionConstraint().getName() + "[" + genericRequire.getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| state.addResolverError(genericRequire.getVersionConstraint().getBundle(), ResolverError.MISSING_GENERIC_CAPABILITY, genericRequire.getVersionConstraint().toString(), genericRequire.getVersionConstraint()); |
| if (genericRequire.isFromFragment()) { |
| if (!developmentMode) { // only detach fragments when not in devmode |
| bundle.detachFragment(bundleMapping.get(genericRequire.getVersionConstraint().getBundle()), null); |
| } |
| continue; |
| } |
| if (!developmentMode) { |
| // fail fast; otherwise we want to attempt to resolver other constraints in dev mode |
| failed = true; |
| break; |
| } |
| } else { |
| if (StateImpl.OSGI_EE_NAMESPACE.equals(genericRequire.getNameSpace())) { |
| VersionSupplier supplier = genericRequire.getSelectedSupplier(); |
| Integer ee = supplier == null ? null : (Integer) ((GenericDescription) supplier.getBaseDescription()).getAttributes().get(ExportPackageDescriptionImpl.EQUINOX_EE); |
| if (ee != null && ((BundleDescriptionImpl) bundle.getBaseDescription()).getEquinoxEE() < 0) |
| ((BundleDescriptionImpl) bundle.getBundleDescription()).setEquinoxEE(ee); |
| } |
| } |
| } |
| } |
| } |
| |
| if (!failed) { |
| // Iterate thru required bundles of 'bundle' trying to find matching bundles. |
| BundleConstraint[] requires = bundle.getRequires(); |
| for (BundleConstraint require : requires) { |
| if (!resolveRequire(require, cycle)) { |
| if (DEBUG || DEBUG_REQUIRES) { |
| ResolverImpl.log("** REQUIRE " + require.getVersionConstraint().getName() + "[" + require.getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| state.addResolverError(require.getVersionConstraint().getBundle(), ResolverError.MISSING_REQUIRE_BUNDLE, require.getVersionConstraint().toString(), require.getVersionConstraint()); |
| // If the require has failed to resolve and it is from a fragment, then remove the fragment from the host |
| if (require.isFromFragment()) { |
| if (!developmentMode) { // only detach fragments when not in devmode |
| bundle.detachFragment(bundleMapping.get(require.getVersionConstraint().getBundle()), require); |
| } |
| continue; |
| } |
| if (!developmentMode) { |
| // fail fast; otherwise we want to attempt to resolver other constraints in dev mode |
| failed = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (!failed) { |
| // Iterate thru imports of 'bundle' trying to find matching exports. |
| ResolverImport[] imports = bundle.getImportPackages(); |
| for (ResolverImport resolverImport : imports) { |
| // Only resolve non-dynamic imports here |
| if (!resolverImport.isDynamic() && !resolveImport(resolverImport, cycle)) { |
| if (DEBUG || DEBUG_IMPORTS) { |
| ResolverImpl.log("** IMPORT " + resolverImport.getName() + "[" + resolverImport.getBundleDescription() + "] failed to resolve"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| // If the import has failed to resolve and it is from a fragment, then remove the fragment from the host |
| state.addResolverError(resolverImport.getVersionConstraint().getBundle(), ResolverError.MISSING_IMPORT_PACKAGE, resolverImport.getVersionConstraint().toString(), resolverImport.getVersionConstraint()); |
| if (resolverImport.isFromFragment()) { |
| if (!developmentMode) { // only detach fragments when not in devmode |
| bundle.detachFragment(bundleMapping.get(resolverImport.getVersionConstraint().getBundle()), resolverImport); |
| } |
| continue; |
| } |
| if (!developmentMode) { |
| // fail fast; otherwise we want to attempt to resolver other constraints in dev mode |
| failed = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| // check that fragment constraints are met by the constraints that got resolved to the host |
| checkFragmentConstraints(bundle); |
| |
| // do some extra checking when in development mode to see if other resolver error occurred |
| if (developmentMode && !failed && state.getResolverErrors(bundle.getBundleDescription()).length > 0) |
| failed = true; |
| |
| // Need to check that all mandatory imports are wired. If they are then |
| // set the bundle RESOLVED, otherwise set it back to UNRESOLVED |
| if (failed) { |
| setBundleUnresolved(bundle, false, developmentMode); |
| if (DEBUG) |
| ResolverImpl.log(bundle + " NOT RESOLVED"); //$NON-NLS-1$ |
| } else if (!cycle.contains(bundle)) { |
| setBundleResolved(bundle); |
| if (DEBUG) |
| ResolverImpl.log(bundle + " RESOLVED"); //$NON-NLS-1$ |
| } |
| |
| if (bundle.getState() == ResolverBundle.UNRESOLVED) |
| bundle.setResolvable(false); // Set it to unresolvable so we don't attempt to resolve it again in this round |
| |
| return bundle.getState() != ResolverBundle.UNRESOLVED; |
| } |
| |
| private void checkFragmentConstraints(ResolverBundle bundle) { |
| // get all currently attached fragments and ensure that any constraints |
| // they have do not conflict with the constraints resolved to by the host |
| ResolverBundle[] fragments = bundle.getFragments(); |
| for (ResolverBundle resolverFragment : fragments) { |
| BundleDescription fragment = resolverFragment.getBundleDescription(); |
| if (bundle.constraintsConflict(fragment, fragment.getImportPackages(), fragment.getRequiredBundles(), fragment.getGenericRequires()) && !developmentMode) { |
| // found some conflicts; detach the fragment |
| bundle.detachFragment(resolverFragment, null); |
| } |
| } |
| } |
| |
| private boolean resolveGenericReq(GenericConstraint constraint, List<ResolverBundle> cycle) { |
| if (DEBUG_GENERICS) |
| ResolverImpl.log("Trying to resolve: " + constraint.getBundle() + ", " + constraint.getVersionConstraint()); //$NON-NLS-1$ //$NON-NLS-2$ |
| VersionSupplier matchingCapability = constraint.getSelectedSupplier(); |
| if (matchingCapability != null) { |
| if (!cycle.contains(constraint.getBundle())) { |
| cycle.add(constraint.getBundle()); |
| if (DEBUG_CYCLES) |
| ResolverImpl.log("generic cycle: " + constraint.getBundle() + " -> " + constraint.getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| if (DEBUG_GENERICS) |
| ResolverImpl.log(" - already wired"); //$NON-NLS-1$ |
| return true; // Already wired (due to grouping dependencies) so just return |
| } |
| List<GenericCapability> candidates; |
| long timestamp; |
| do { |
| timestamp = state.getTimeStamp(); |
| VersionHashMap<GenericCapability> namespace = resolverGenerics.get(constraint.getNameSpace()); |
| String name = constraint.getName(); |
| List<GenericCapability> capabilities; |
| if (namespace == null) |
| capabilities = Collections.EMPTY_LIST; |
| else |
| capabilities = name == null || name.indexOf('*') >= 0 ? namespace.getAllValues() : namespace.get(name); |
| candidates = new ArrayList<>(capabilities); |
| List<BundleCapability> genCapabilities = new ArrayList<>(candidates.size()); |
| // Must remove candidates that do not match before calling hooks. |
| for (Iterator<GenericCapability> iCandidates = candidates.iterator(); iCandidates.hasNext();) { |
| GenericCapability capability = iCandidates.next(); |
| if (!constraint.isSatisfiedBy(capability)) { |
| iCandidates.remove(); |
| } else { |
| genCapabilities.add(capability.getCapability()); |
| } |
| } |
| if (hook != null) |
| hook.filterMatches(constraint.getRequirement(), asCapabilities(new ArrayMap<>(genCapabilities, candidates))); |
| } while (timestamp != state.getTimeStamp()); |
| boolean result = false; |
| // We are left with only capabilities that satisfy the constraint. |
| for (GenericCapability capability : candidates) { |
| if (DEBUG_GENERICS) |
| ResolverImpl.log("CHECKING GENERICS: " + capability.getBaseDescription()); //$NON-NLS-1$ |
| |
| // first add the possible supplier; this is done before resolving the supplier bundle to prevent endless cycle loops. |
| constraint.addPossibleSupplier(capability); // Wire to the capability |
| if (constraint.getBundle() == capability.getResolverBundle()) { |
| result = true; // Wired to ourselves |
| continue; |
| } |
| VersionSupplier[] capabilityHosts = capability.getResolverBundle().isFragment() ? capability.getResolverBundle().getHost().getPossibleSuppliers() : new ResolverBundle[] {capability.getResolverBundle()}; |
| boolean foundResolvedMatch = false; |
| for (int i = 0; capabilityHosts != null && i < capabilityHosts.length; i++) { |
| ResolverBundle capabilitySupplier = capabilityHosts[i].getResolverBundle(); |
| if (capabilitySupplier == constraint.getBundle()) { |
| // the capability is from a fragment attached to this host do not recursively resolve the host again |
| foundResolvedMatch = true; |
| continue; |
| } |
| boolean successfulResolve = false; |
| if (capabilitySupplier.getState() != ResolverBundle.RESOLVED) { |
| // only attempt to resolve the supplier if not osgi.ee name space |
| if (!StateImpl.OSGI_EE_NAMESPACE.equals(constraint.getNameSpace())) |
| successfulResolve = resolveBundle(capabilitySupplier, cycle); |
| } |
| |
| // if in dev mode then allow a constraint to resolve to an unresolved bundle |
| if (capabilitySupplier.getState() == ResolverBundle.RESOLVED || (successfulResolve || developmentMode)) { |
| foundResolvedMatch |= !capability.getResolverBundle().isFragment() ? true : capability.getResolverBundle().getHost().getPossibleSuppliers() != null; |
| // Check cyclic dependencies |
| if (capabilitySupplier.getState() == ResolverBundle.RESOLVING) |
| if (!cycle.contains(capabilitySupplier)) |
| cycle.add(capabilitySupplier); |
| } |
| } |
| if (!foundResolvedMatch) { |
| constraint.removePossibleSupplier(capability); |
| continue; // constraint hasn't resolved |
| } |
| if (DEBUG_GENERICS) |
| ResolverImpl.log("Found match: " + capability.getBaseDescription() + ". Wiring"); //$NON-NLS-1$ //$NON-NLS-2$ |
| result = true; |
| } |
| return result ? true : constraint.isOptional() || constraint.isFromRequiredEE(); |
| } |
| |
| // Resolve the supplied import. Returns true if the import can be resolved, false otherwise |
| private boolean resolveRequire(BundleConstraint req, List<ResolverBundle> cycle) { |
| if (DEBUG_REQUIRES) |
| ResolverImpl.log("Trying to resolve: " + req.getBundle() + ", " + req.getVersionConstraint()); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (req.getSelectedSupplier() != null) { |
| // Check for unrecorded cyclic dependency |
| if (!cycle.contains(req.getBundle())) { |
| cycle.add(req.getBundle()); |
| if (DEBUG_CYCLES) |
| ResolverImpl.log("require-bundle cycle: " + req.getBundle() + " -> " + req.getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| if (DEBUG_REQUIRES) |
| ResolverImpl.log(" - already wired"); //$NON-NLS-1$ |
| return true; // Already wired (due to grouping dependencies) so just return |
| } |
| List<ResolverBundle> candidates; |
| long timestamp; |
| do { |
| timestamp = state.getTimeStamp(); |
| List<ResolverBundle> bundles = resolverBundles.get(req.getVersionConstraint().getName()); |
| candidates = new ArrayList<>(bundles); |
| List<BundleCapability> capabilities = new ArrayList<>(candidates.size()); |
| // Must remove candidates that do not match before calling hooks. |
| for (Iterator<ResolverBundle> iCandidates = candidates.iterator(); iCandidates.hasNext();) { |
| ResolverBundle bundle = iCandidates.next(); |
| if (!req.isSatisfiedBy(bundle)) { |
| iCandidates.remove(); |
| } else { |
| capabilities.add(bundle.getCapability()); |
| } |
| } |
| if (hook != null) |
| hook.filterMatches(req.getRequirement(), asCapabilities(new ArrayMap<>(capabilities, candidates))); |
| } while (timestamp != state.getTimeStamp()); |
| // We are left with only capabilities that satisfy the require bundle. |
| boolean result = false; |
| for (ResolverBundle bundle : candidates) { |
| if (DEBUG_REQUIRES) |
| ResolverImpl.log("CHECKING: " + bundle.getBundleDescription()); //$NON-NLS-1$ |
| |
| // first add the possible supplier; this is done before resolving the supplier bundle to prevent endless cycle loops. |
| req.addPossibleSupplier(bundle); |
| if (req.getBundle() != bundle) { |
| // if in dev mode then allow a constraint to resolve to an unresolved bundle |
| if (bundle.getState() != ResolverBundle.RESOLVED && !resolveBundle(bundle, cycle) && !developmentMode) { |
| req.removePossibleSupplier(bundle); |
| continue; // Bundle hasn't resolved |
| } |
| } |
| // Check cyclic dependencies |
| if (req.getBundle() != bundle) { |
| if (bundle.getState() == ResolverBundle.RESOLVING) |
| // If the bundle is RESOLVING, we have a cyclic dependency |
| if (!cycle.contains(req.getBundle())) { |
| cycle.add(req.getBundle()); |
| if (DEBUG_CYCLES) |
| ResolverImpl.log("require-bundle cycle: " + req.getBundle() + " -> " + req.getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| if (DEBUG_REQUIRES) |
| ResolverImpl.log("Found match: " + bundle.getBundleDescription() + ". Wiring"); //$NON-NLS-1$ //$NON-NLS-2$ |
| result = true; |
| } |
| |
| if (result || req.isOptional()) |
| return true; // If the req is optional then just return true |
| |
| return false; |
| } |
| |
| // Resolve the supplied import. Returns true if the import can be resolved, false otherwise |
| private boolean resolveImport(ResolverImport imp, List<ResolverBundle> cycle) { |
| if (DEBUG_IMPORTS) |
| ResolverImpl.log("Trying to resolve: " + imp.getBundle() + ", " + imp.getName()); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (imp.getSelectedSupplier() != null) { |
| // Check for unrecorded cyclic dependency |
| if (!cycle.contains(imp.getBundle())) { |
| cycle.add(imp.getBundle()); |
| if (DEBUG_CYCLES) |
| ResolverImpl.log("import-package cycle: " + imp.getBundle() + " -> " + imp.getSelectedSupplier() + " from " + imp.getSelectedSupplier().getBundleDescription()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| if (DEBUG_IMPORTS) |
| ResolverImpl.log(" - already wired"); //$NON-NLS-1$ |
| return true; // Already wired (due to grouping dependencies) so just return |
| } |
| boolean result = false; |
| ResolverExport[] substitutableExps = imp.getBundle().getExports(imp.getName()); |
| long timestamp; |
| List<ResolverExport> candidates; |
| do { |
| timestamp = state.getTimeStamp(); |
| List<ResolverExport> exports = resolverExports.get(imp.getName()); |
| candidates = new ArrayList<>(exports); |
| List<BundleCapability> capabilities = new ArrayList<>(candidates.size()); |
| // Must remove candidates that do not match before calling hooks. |
| for (Iterator<ResolverExport> iCandidates = candidates.iterator(); iCandidates.hasNext();) { |
| ResolverExport export = iCandidates.next(); |
| if (!imp.isSatisfiedBy(export)) { |
| iCandidates.remove(); |
| } else { |
| capabilities.add(export.getCapability()); |
| } |
| } |
| if (hook != null) |
| hook.filterMatches(imp.getRequirement(), asCapabilities(new ArrayMap<>(capabilities, candidates))); |
| } while (timestamp != state.getTimeStamp()); |
| // We are left with only capabilities that satisfy the import. |
| for (ResolverExport export : candidates) { |
| if (DEBUG_IMPORTS) |
| ResolverImpl.log("CHECKING: " + export.getExporter().getBundleDescription() + ", " + export.getName()); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| int originalState = export.getExporter().getState(); |
| if (imp.isDynamic() && originalState != ResolverBundle.RESOLVED) |
| continue; // Must not attempt to resolve an exporter when dynamic |
| if (imp.getSelectedSupplier() != null && ((ResolverExport) imp.getSelectedSupplier()).getExporter() == imp.getBundle()) |
| break; // We wired to ourselves; nobody else matters |
| // first add the possible supplier; this is done before resolving the supplier bundle to prevent endless cycle loops. |
| imp.addPossibleSupplier(export); |
| if (imp.getBundle() != export.getExporter()) { |
| for (ResolverExport substitutableExp : substitutableExps) { |
| if (substitutableExp.getSubstitute() == null) { |
| substitutableExp.setSubstitute(export); // Import wins, drop export |
| } |
| } |
| // if in dev mode then allow a constraint to resolve to an unresolved bundle |
| if ((originalState != ResolverBundle.RESOLVED && !resolveBundle(export.getExporter(), cycle) && !developmentMode) || export.getSubstitute() != null) { |
| // remove the possible supplier |
| imp.removePossibleSupplier(export); |
| // add back the exports of this package from the importer |
| if (imp.getSelectedSupplier() == null) |
| for (ResolverExport substitutableExp : substitutableExps) { |
| if (substitutableExp.getSubstitute() == export) { |
| substitutableExp.setSubstitute(null); |
| } |
| } |
| continue; // Bundle hasn't resolved || export has not been selected and is unavailable |
| } |
| } else if (export.getSubstitute() != null) |
| continue; // we already found a possible import that satisifies us; our export is dropped |
| |
| // Record any cyclic dependencies |
| if (imp.getBundle() != export.getExporter()) |
| if (export.getExporter().getState() == ResolverBundle.RESOLVING) { |
| // If the exporter is RESOLVING, we have a cyclic dependency |
| if (!cycle.contains(imp.getBundle())) { |
| cycle.add(imp.getBundle()); |
| if (DEBUG_CYCLES) |
| ResolverImpl.log("import-package cycle: " + imp.getBundle() + " -> " + imp.getSelectedSupplier() + " from " + imp.getSelectedSupplier().getBundleDescription()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| if (DEBUG_IMPORTS) |
| ResolverImpl.log("Found match: " + export.getExporter() + ". Wiring " + imp.getBundle() + ":" + imp.getName()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| result = true; |
| } |
| |
| if (result) |
| return true; |
| if (imp.isOptional()) |
| return true; // If the import is optional then just return true |
| if (substitutableExps.length > 0 && substitutableExps[0].getSubstitute() == null) |
| return true; // If we still have an export that is not substituted return true |
| return false; |
| } |
| |
| // Move a bundle to UNRESOLVED |
| private void setBundleUnresolved(ResolverBundle bundle, boolean removed, boolean keepFragsAttached) { |
| if (bundle.getState() == ResolverBundle.UNRESOLVED && !developmentMode) |
| // in this case there is nothing more to do |
| return; |
| // Note that when in dev mode we only want to force the fragment detach if asked to; |
| // this would be done only when forcing a dependency chain to unresolve from unresolveBundle method |
| if (removed || !keepFragsAttached) { |
| // Force the initialization of the bundle, its exports and its capabilities. This is needed to force proper attachment of fragments. |
| resolverExports.remove(bundle.getExportPackages()); |
| removeGenerics(bundle.getGenericCapabilities()); |
| bundle.detachAllFragments(); |
| bundle.detachFromHosts(); |
| bundle.initialize(false); |
| if (!removed) { |
| // add back the available exports/capabilities |
| resolverExports.put(bundle.getExportPackages()); |
| addGenerics(bundle.getGenericCapabilities()); |
| } |
| } |
| // TODO unresolvedBundles should be a set; for now only need to do a contains check in devMode. |
| if (!removed && (!developmentMode || !unresolvedBundles.contains(bundle))) |
| unresolvedBundles.add(bundle); |
| bundle.setState(ResolverBundle.UNRESOLVED); |
| } |
| |
| // Move a bundle to RESOLVED |
| private void setBundleResolved(ResolverBundle bundle) { |
| if (bundle.getState() == ResolverBundle.RESOLVED) |
| return; |
| unresolvedBundles.remove(bundle); |
| bundle.setState(ResolverBundle.RESOLVED); |
| } |
| |
| // Move a bundle to RESOLVING |
| private void setBundleResolving(ResolverBundle bundle) { |
| if (bundle.getState() == ResolverBundle.RESOLVING) |
| return; |
| unresolvedBundles.remove(bundle); |
| bundle.setState(ResolverBundle.RESOLVING); |
| } |
| |
| // Resolves the bundles in the State |
| private void stateResolveBundles(ResolverBundle[] resolvedBundles) { |
| for (ResolverBundle resolvedBundle : resolvedBundles) { |
| if (!resolvedBundle.getBundleDescription().isResolved()) { |
| stateResolveBundle(resolvedBundle); |
| } |
| } |
| } |
| |
| private void stateResolveConstraints(ResolverBundle rb) { |
| ResolverImport[] imports = rb.getImportPackages(); |
| for (ResolverImport resolverImport : imports) { |
| ResolverExport export = (ResolverExport) resolverImport.getSelectedSupplier(); |
| BaseDescription supplier = export == null ? null : export.getExportPackageDescription(); |
| state.resolveConstraint(resolverImport.getVersionConstraint(), supplier); |
| } |
| BundleConstraint[] requires = rb.getRequires(); |
| for (BundleConstraint require : requires) { |
| ResolverBundle bundle = (ResolverBundle) require.getSelectedSupplier(); |
| BaseDescription supplier = bundle == null ? null : bundle.getBundleDescription(); |
| state.resolveConstraint(require.getVersionConstraint(), supplier); |
| } |
| GenericConstraint[] genericRequires = rb.getGenericRequires(); |
| for (GenericConstraint genericRequire : genericRequires) { |
| VersionSupplier[] matchingCapabilities = genericRequire.getMatchingCapabilities(); |
| if (matchingCapabilities == null) { |
| state.resolveConstraint(genericRequire.getVersionConstraint(), null); |
| } else { |
| for (VersionSupplier matchingCapability : matchingCapabilities) { |
| state.resolveConstraint(genericRequire.getVersionConstraint(), matchingCapability.getBaseDescription()); |
| } |
| } |
| } |
| } |
| |
| private void stateResolveFragConstraints(ResolverBundle rb) { |
| ResolverBundle host = (ResolverBundle) rb.getHost().getSelectedSupplier(); |
| ImportPackageSpecification[] imports = rb.getBundleDescription().getImportPackages(); |
| for (ImportPackageSpecification importSpecification : imports) { |
| ResolverImport hostImport = host == null ? null : host.getImport(importSpecification.getName()); |
| ResolverExport export = (ResolverExport) (hostImport == null ? null : hostImport.getSelectedSupplier()); |
| BaseDescription supplier = export == null ? null : export.getExportPackageDescription(); |
| state.resolveConstraint(importSpecification, supplier); |
| } |
| BundleSpecification[] requires = rb.getBundleDescription().getRequiredBundles(); |
| for (BundleSpecification require : requires) { |
| BundleConstraint hostRequire = host == null ? null : host.getRequire(require.getName()); |
| ResolverBundle bundle = (ResolverBundle) (hostRequire == null ? null : hostRequire.getSelectedSupplier()); |
| BaseDescription supplier = bundle == null ? null : bundle.getBundleDescription(); |
| state.resolveConstraint(require, supplier); |
| } |
| GenericConstraint[] genericRequires = rb.getGenericRequires(); |
| for (GenericConstraint genericRequire : genericRequires) { |
| VersionSupplier[] matchingCapabilities = genericRequire.getMatchingCapabilities(); |
| if (matchingCapabilities == null) { |
| state.resolveConstraint(genericRequire.getVersionConstraint(), null); |
| } else { |
| for (VersionSupplier matchingCapability : matchingCapabilities) { |
| state.resolveConstraint(genericRequire.getVersionConstraint(), matchingCapability.getBaseDescription()); |
| } |
| } |
| } |
| } |
| |
| private void stateResolveBundle(ResolverBundle rb) { |
| // if in dev mode then we want to tell the state about the constraints we were able to resolve |
| if (!rb.isResolved() && !developmentMode) |
| return; |
| if (rb.isFragment()) |
| stateResolveFragConstraints(rb); |
| else |
| stateResolveConstraints(rb); |
| |
| // Build up the state wires |
| Map<String, List<StateWire>> stateWires = new HashMap<>(); |
| |
| // Gather selected exports |
| ResolverExport[] exports = rb.getSelectedExports(); |
| List<ExportPackageDescription> selectedExports = new ArrayList<>(exports.length); |
| for (ResolverExport export : exports) { |
| if (permissionChecker.checkPackagePermission(export.getExportPackageDescription())) { |
| selectedExports.add(export.getExportPackageDescription()); |
| } |
| } |
| ExportPackageDescription[] selectedExportsArray = selectedExports.toArray(new ExportPackageDescription[selectedExports.size()]); |
| |
| // Gather substitute exports |
| ResolverExport[] substituted = rb.getSubstitutedExports(); |
| List<ExportPackageDescription> substitutedExports = new ArrayList<>(substituted.length); |
| for (ResolverExport substitutedExport : substituted) { |
| substitutedExports.add(substitutedExport.getExportPackageDescription()); |
| } |
| ExportPackageDescription[] substitutedExportsArray = substitutedExports.toArray(new ExportPackageDescription[substitutedExports.size()]); |
| |
| // Gather exports that have been wired to |
| ExportPackageDescription[] exportsWiredToArray = getExportsWiredTo(rb, stateWires); |
| |
| // Gather bundles that have been wired to |
| BundleConstraint[] requires = rb.getRequires(); |
| List<BundleDescription> bundlesWiredTo = new ArrayList<>(requires.length); |
| List<StateWire> requireWires = new ArrayList<>(requires.length); |
| for (BundleConstraint require : requires) { |
| if (require.getSelectedSupplier() != null) { |
| BundleDescription supplier = (BundleDescription) require.getSelectedSupplier().getBaseDescription(); |
| bundlesWiredTo.add(supplier); |
| StateWire requireWire = newStateWire(rb.getBundleDescription(), require.getVersionConstraint(), supplier, supplier); |
| requireWires.add(requireWire); |
| } |
| } |
| BundleDescription[] bundlesWiredToArray = bundlesWiredTo.toArray(new BundleDescription[bundlesWiredTo.size()]); |
| if (!requireWires.isEmpty()) |
| stateWires.put(BundleRevision.BUNDLE_NAMESPACE, requireWires); |
| |
| GenericCapability[] capabilities = rb.getGenericCapabilities(); |
| List<GenericDescription> selectedCapabilities = new ArrayList<>(capabilities.length); |
| for (GenericCapability capability : capabilities) |
| if (capability.isEffective() && permissionChecker.checkCapabilityPermission(capability.getGenericDescription())) |
| selectedCapabilities.add(capability.getGenericDescription()); |
| GenericDescription[] selectedCapabilitiesArray = selectedCapabilities.toArray(new GenericDescription[selectedCapabilities.size()]); |
| |
| GenericConstraint[] genericRequires = rb.getGenericRequires(); |
| List<GenericDescription> resolvedGenericRequires = new ArrayList<>(genericRequires.length); |
| for (GenericConstraint genericConstraint : genericRequires) { |
| VersionSupplier[] matching = genericConstraint.getMatchingCapabilities(); |
| if (matching != null) |
| for (VersionSupplier capability : matching) { |
| GenericDescription supplier = ((GenericCapability) capability).getGenericDescription(); |
| resolvedGenericRequires.add(supplier); |
| StateWire genericWire = newStateWire(rb.getBundleDescription(), genericConstraint.getVersionConstraint(), supplier.getSupplier(), supplier); |
| List<StateWire> genericWires = stateWires.get(genericConstraint.getNameSpace()); |
| if (genericWires == null) { |
| genericWires = new ArrayList<>(); |
| stateWires.put(genericConstraint.getNameSpace(), genericWires); |
| } |
| genericWires.add(genericWire); |
| } |
| } |
| GenericDescription[] capabilitiesWiredToArray = resolvedGenericRequires.toArray(new GenericDescription[resolvedGenericRequires.size()]); |
| |
| BundleDescription[] hostBundles = null; |
| if (rb.isFragment()) { |
| VersionSupplier[] matchingBundles = rb.getHost().getPossibleSuppliers(); |
| if (matchingBundles != null && matchingBundles.length > 0) { |
| hostBundles = new BundleDescription[matchingBundles.length]; |
| List<StateWire> hostWires = new ArrayList<>(matchingBundles.length); |
| stateWires.put(BundleRevision.HOST_NAMESPACE, hostWires); |
| for (int i = 0; i < matchingBundles.length; i++) { |
| hostBundles[i] = matchingBundles[i].getBundleDescription(); |
| StateWire hostWire = newStateWire(rb.getBundleDescription(), rb.getHost().getVersionConstraint(), hostBundles[i], hostBundles[i]); |
| hostWires.add(hostWire); |
| if (hostBundles[i].isResolved()) { |
| ExportPackageDescription[] newSelectedExports = null; |
| GenericDescription[] newSelectedCapabilities = null; |
| if (rb.isNewFragmentExports()) { |
| // update the host's set of selected exports |
| ResolverExport[] hostExports = ((ResolverBundle) matchingBundles[i]).getSelectedExports(); |
| newSelectedExports = new ExportPackageDescription[hostExports.length]; |
| for (int j = 0; j < hostExports.length; j++) |
| newSelectedExports[j] = hostExports[j].getExportPackageDescription(); |
| } |
| if (rb.isNewFragmentCapabilities()) { |
| // update the host's set of selected capabilities |
| GenericCapability[] hostCapabilities = ((ResolverBundle) matchingBundles[i]).getGenericCapabilities(); |
| newSelectedCapabilities = new GenericDescription[hostCapabilities.length]; |
| for (int j = 0; j < hostCapabilities.length; j++) |
| newSelectedCapabilities[j] = hostCapabilities[j].getGenericDescription(); |
| } |
| if (newSelectedCapabilities != null || newSelectedExports != null) { |
| if (newSelectedCapabilities == null) |
| newSelectedCapabilities = hostBundles[i].getSelectedGenericCapabilities(); |
| if (newSelectedExports == null) |
| newSelectedExports = hostBundles[i].getSelectedExports(); |
| state.resolveBundle(hostBundles[i], true, null, newSelectedExports, hostBundles[i].getSubstitutedExports(), newSelectedCapabilities, hostBundles[i].getResolvedRequires(), hostBundles[i].getResolvedImports(), hostBundles[i].getResolvedGenericRequires(), ((BundleDescriptionImpl) hostBundles[i]).getWires()); |
| } |
| } |
| } |
| } |
| } |
| |
| // Resolve the bundle in the state |
| state.resolveBundle(rb.getBundleDescription(), rb.isResolved(), hostBundles, selectedExportsArray, substitutedExportsArray, selectedCapabilitiesArray, bundlesWiredToArray, exportsWiredToArray, capabilitiesWiredToArray, stateWires); |
| } |
| |
| private static ExportPackageDescription[] getExportsWiredTo(ResolverBundle rb, Map<String, List<StateWire>> stateWires) { |
| // Gather exports that have been wired to |
| ResolverImport[] imports = rb.getImportPackages(); |
| List<ExportPackageDescription> exportsWiredTo = new ArrayList<>(imports.length); |
| List<StateWire> importWires = new ArrayList<>(imports.length); |
| for (ResolverImport resolverImport : imports) { |
| if (resolverImport.getSelectedSupplier() != null) { |
| ExportPackageDescription supplier = (ExportPackageDescription) resolverImport.getSelectedSupplier().getBaseDescription(); |
| exportsWiredTo.add(supplier); |
| StateWire wire = newStateWire(rb.getBundleDescription(), resolverImport.getVersionConstraint(), supplier.getExporter(), supplier); |
| importWires.add(wire); |
| } |
| } |
| if (stateWires != null && !importWires.isEmpty()) |
| stateWires.put(BundleRevision.PACKAGE_NAMESPACE, importWires); |
| return exportsWiredTo.toArray(new ExportPackageDescription[exportsWiredTo.size()]); |
| } |
| |
| private static StateWire newStateWire(BundleDescription requirementHost, VersionConstraint declaredRequirement, BundleDescription capabilityHost, BaseDescription declaredCapability) { |
| BaseDescription fragDeclared = ((BaseDescriptionImpl) declaredCapability).getFragmentDeclaration(); |
| declaredCapability = fragDeclared != null ? fragDeclared : declaredCapability; |
| return new StateWire(requirementHost, declaredRequirement, capabilityHost, declaredCapability); |
| } |
| |
| // Resolve dynamic import |
| public synchronized ExportPackageDescription resolveDynamicImport(BundleDescription importingBundle, String requestedPackage) { |
| if (state == null) |
| throw new IllegalStateException("RESOLVER_NO_STATE"); //$NON-NLS-1$ |
| |
| // Make sure the resolver is initialized |
| if (!initialized) |
| initialize(); |
| hook = (state instanceof StateImpl) ? ((StateImpl) state).getResolverHook() : null; |
| try { |
| ResolverBundle rb = bundleMapping.get(importingBundle); |
| if (rb.getExport(requestedPackage) != null) |
| return null; // do not allow dynamic wires for packages which this bundle exports |
| ResolverImport[] resolverImports = rb.getImportPackages(); |
| // Check through the ResolverImports of this bundle. |
| // If there is a matching one then pass it into resolveImport() |
| for (ResolverImport resolverImport : resolverImports) { |
| // Make sure it is a dynamic import |
| if (!resolverImport.isDynamic()) { |
| continue; |
| } |
| // Resolve the import |
| ExportPackageDescription supplier = resolveDynamicImport(resolverImport, requestedPackage); |
| if (supplier != null) |
| return supplier; |
| } |
| // look for packages added dynamically |
| ImportPackageSpecification[] addedDynamicImports = importingBundle.getAddedDynamicImportPackages(); |
| for (ImportPackageSpecification addedDynamicImport : addedDynamicImports) { |
| ResolverImport newImport = new ResolverImport(rb, addedDynamicImport); |
| ExportPackageDescription supplier = resolveDynamicImport(newImport, requestedPackage); |
| if (supplier != null) |
| return supplier; |
| } |
| |
| if (DEBUG || DEBUG_IMPORTS) |
| ResolverImpl.log("Failed to resolve dynamic import: " + requestedPackage); //$NON-NLS-1$ |
| return null; // Couldn't resolve the import, so return null |
| } finally { |
| hook = null; |
| } |
| } |
| |
| private void addStateWire(BundleDescription importingBundle, VersionConstraint requirement, BundleDescription capabilityHost, ExportPackageDescription capability) { |
| Map<String, List<StateWire>> wires = ((BundleDescriptionImpl) importingBundle).getWires(); |
| List<StateWire> imports = wires.get(BundleRevision.PACKAGE_NAMESPACE); |
| if (imports == null) { |
| imports = new ArrayList<>(); |
| wires.put(BundleRevision.PACKAGE_NAMESPACE, imports); |
| } |
| imports.add(newStateWire(importingBundle, requirement, capabilityHost, capability)); |
| } |
| |
| private ExportPackageDescription resolveDynamicImport(ResolverImport dynamicImport, String requestedPackage) { |
| String importName = dynamicImport.getName(); |
| // If the import uses a wildcard, then temporarily replace this with the requested package |
| if (importName.equals("*") || //$NON-NLS-1$ |
| (importName.endsWith(".*") && requestedPackage.startsWith(importName.substring(0, importName.length() - 1)))) { //$NON-NLS-1$ |
| dynamicImport.setName(requestedPackage); |
| } |
| try { |
| // Resolve the import |
| if (!requestedPackage.equals(dynamicImport.getName())) |
| return null; |
| |
| if (resolveImport(dynamicImport, new ArrayList<ResolverBundle>())) { |
| // populate the grouping checker with current imports |
| groupingChecker.populateRoots(dynamicImport.getBundle()); |
| while (dynamicImport.getSelectedSupplier() != null) { |
| if (groupingChecker.isDynamicConsistent(dynamicImport.getBundle(), (ResolverExport) dynamicImport.getSelectedSupplier()) != null) { |
| dynamicImport.selectNextSupplier(); // not consistent; try the next |
| } else { |
| // If the import resolved then return it's matching export |
| if (DEBUG_IMPORTS) |
| ResolverImpl.log("Resolved dynamic import: " + dynamicImport.getBundle() + ":" + dynamicImport.getName() + " -> " + ((ResolverExport) dynamicImport.getSelectedSupplier()).getExporter() + ":" + requestedPackage); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| |
| // now that we have an export to wire to; populate the roots for that package for the bundle |
| ResolverExport export = (ResolverExport) dynamicImport.getSelectedSupplier(); |
| groupingChecker.populateRoots(dynamicImport.getBundle(), export); |
| |
| ExportPackageDescription supplier = export.getExportPackageDescription(); |
| if (supplier != null) |
| addStateWire(dynamicImport.getBundleDescription(), dynamicImport.getVersionConstraint(), supplier.getExporter(), supplier); |
| return supplier; |
| } |
| } |
| dynamicImport.clearPossibleSuppliers(); |
| } |
| } finally { |
| // If it is a wildcard import then clear the wire, so other |
| // exported packages can be found for it |
| if (importName.endsWith("*")) //$NON-NLS-1$ |
| dynamicImport.clearPossibleSuppliers(); |
| // Reset the import package name |
| dynamicImport.setName(null); |
| } |
| return null; |
| } |
| |
| public void bundleAdded(BundleDescription bundle) { |
| if (!initialized) |
| return; |
| |
| if (bundleMapping.get(bundle) != null) |
| return; // this description already exists in the resolver |
| ResolverBundle rb = new ResolverBundle(bundle, this); |
| bundleMapping.put(bundle, rb); |
| unresolvedBundles.add(rb); |
| resolverExports.put(rb.getExportPackages()); |
| resolverBundles.put(rb.getName(), rb); |
| addGenerics(rb.getGenericCapabilities()); |
| if (hook != null && rb.isFragment()) { |
| attachFragment0(rb); |
| } |
| } |
| |
| public void bundleRemoved(BundleDescription bundle, boolean pending) { |
| ResolverBundle rb = initialized ? (ResolverBundle) bundleMapping.get(bundle) : null; |
| if (rb != null) |
| rb.setUninstalled(); |
| internalBundleRemoved(bundle, pending); |
| } |
| |
| private void internalBundleRemoved(BundleDescription bundle, boolean pending) { |
| // check if there are any dependants |
| if (pending) |
| removalPending.put(Long.valueOf(bundle.getBundleId()), bundle); |
| if (!initialized) |
| return; |
| ResolverBundle rb = bundleMapping.get(bundle); |
| if (rb == null) |
| return; |
| |
| if (!pending) { |
| bundleMapping.remove(bundle); |
| groupingChecker.clear(rb); |
| } |
| if (!pending || !bundle.isResolved()) { |
| resolverExports.remove(rb.getExportPackages()); |
| resolverBundles.remove(rb); |
| removeGenerics(rb.getGenericCapabilities()); |
| } |
| unresolvedBundles.remove(rb); |
| } |
| |
| private void unresolveBundle(ResolverBundle bundle, boolean removed) { |
| // check the removed list if unresolving then remove from the removed list |
| List<BundleDescription> removedBundles = removalPending.remove(Long.valueOf(bundle.getBundleDescription().getBundleId())); |
| for (BundleDescription removedDesc : removedBundles) { |
| ResolverBundle re = bundleMapping.get(removedDesc); |
| if (re == null) { |
| continue; |
| } |
| unresolveBundle(re, true); |
| state.removeBundleComplete(removedDesc); |
| resolverExports.remove(re.getExportPackages()); |
| resolverBundles.remove(re); |
| removeGenerics(re.getGenericCapabilities()); |
| bundleMapping.remove(removedDesc); |
| groupingChecker.clear(re); |
| // the bundle is removed |
| if (removedDesc == bundle.getBundleDescription()) |
| removed = true; |
| } |
| |
| if (!bundle.getBundleDescription().isResolved() && !developmentMode) |
| return; |
| CompositeResolveHelperRegistry currentLinks = compositeHelpers; |
| if (currentLinks != null) { |
| CompositeResolveHelper helper = currentLinks.getCompositeResolveHelper(bundle.getBundleDescription()); |
| if (helper != null) |
| helper.giveExports(null); |
| } |
| // if not removed then add to the list of unresolvedBundles, |
| // passing false for devmode because we need all fragments detached |
| setBundleUnresolved(bundle, removed, false); |
| // Get bundles dependent on 'bundle' |
| BundleDescription[] dependents = bundle.getBundleDescription().getDependents(); |
| state.resolveBundle(bundle.getBundleDescription(), false, null, null, null, null, null, null, null, null); |
| // Unresolve dependents of 'bundle' |
| for (BundleDescription dependent : dependents) { |
| ResolverBundle db = bundleMapping.get(dependent); |
| if (db == null) { |
| continue; |
| } |
| unresolveBundle(db, false); |
| } |
| } |
| |
| public void bundleUpdated(BundleDescription newDescription, BundleDescription existingDescription, boolean pending) { |
| internalBundleRemoved(existingDescription, pending); |
| bundleAdded(newDescription); |
| } |
| |
| public void flush() { |
| resolverExports = null; |
| resolverBundles = null; |
| resolverGenerics = null; |
| unresolvedBundles = null; |
| bundleMapping = null; |
| List<BundleDescription> removed = removalPending.getAllValues(); |
| for (BundleDescription removedDesc : removed) |
| state.removeBundleComplete(removedDesc); |
| removalPending.clear(); |
| initialized = false; |
| } |
| |
| public State getState() { |
| return state; |
| } |
| |
| public void setState(State newState) { |
| if (this.state != null) { |
| throw new IllegalStateException("Cannot change the State of a Resolver"); //$NON-NLS-1$ |
| } |
| state = newState; |
| flush(); |
| } |
| |
| /* |
| private static final String RESOLVER = EquinoxContainer.NAME + "/resolver"; //$NON-NLS-1$ |
| private static final String OPTION_DEBUG = RESOLVER + "/debug";//$NON-NLS-1$ |
| private static final String OPTION_WIRING = RESOLVER + "/wiring"; //$NON-NLS-1$ |
| private static final String OPTION_IMPORTS = RESOLVER + "/imports"; //$NON-NLS-1$ |
| private static final String OPTION_REQUIRES = RESOLVER + "/requires"; //$NON-NLS-1$ |
| private static final String OPTION_GENERICS = RESOLVER + "/generics"; //$NON-NLS-1$ |
| private static final String OPTION_USES = RESOLVER + "/uses"; //$NON-NLS-1$ |
| private static final String OPTION_CONFLICTS = RESOLVER + "/conflicts"; //$NON-NLS-1$ |
| private static final String OPTION_CYCLES = RESOLVER + "/cycles"; //$NON-NLS-1$ |
| */ |
| private void setDebugOptions() { |
| //TODO do not have access to debug options here |
| /* |
| FrameworkDebugOptions options = null; //FrameworkDebugOptions.getDefault(); |
| // may be null if debugging is not enabled |
| if (options == null) |
| return; |
| DEBUG = options.getBooleanOption(OPTION_DEBUG, false); |
| DEBUG_WIRING = options.getBooleanOption(OPTION_WIRING, false); |
| DEBUG_IMPORTS = options.getBooleanOption(OPTION_IMPORTS, false); |
| DEBUG_REQUIRES = options.getBooleanOption(OPTION_REQUIRES, false); |
| DEBUG_GENERICS = options.getBooleanOption(OPTION_GENERICS, false); |
| DEBUG_USES = options.getBooleanOption(OPTION_USES, false); |
| DEBUG_CONFLICTS = options.getBooleanOption(OPTION_CONFLICTS, false); |
| DEBUG_CYCLES = options.getBooleanOption(OPTION_CYCLES, false); |
| */ |
| } |
| |
| // LOGGING METHODS |
| private void printWirings() { |
| ResolverImpl.log("****** Result Wirings ******"); //$NON-NLS-1$ |
| List<ResolverBundle> bundles = resolverBundles.getAllValues(); |
| for (ResolverBundle rb : bundles) { |
| if (rb.getBundleDescription().isResolved()) { |
| continue; |
| } |
| ResolverImpl.log(" * WIRING for " + rb); //$NON-NLS-1$ |
| // Require bundles |
| BundleConstraint[] requireBundles = rb.getRequires(); |
| if (requireBundles.length == 0) { |
| ResolverImpl.log(" (r) no requires"); //$NON-NLS-1$ |
| } else { |
| for (BundleConstraint requireBundle : requireBundles) { |
| if (requireBundle.getSelectedSupplier() == null) { |
| ResolverImpl.log(" (r) " + rb.getBundleDescription() + " -> NULL!!!"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } else { |
| ResolverImpl.log(" (r) " + rb.getBundleDescription() + " -> " + requireBundle.getSelectedSupplier()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| } |
| // Hosts |
| BundleConstraint hostSpec = rb.getHost(); |
| if (hostSpec != null) { |
| VersionSupplier[] hosts = hostSpec.getPossibleSuppliers(); |
| if (hosts != null) |
| for (VersionSupplier host : hosts) { |
| ResolverImpl.log(" (h) " + rb.getBundleDescription() + " -> " + host.getBundleDescription()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| // Imports |
| ResolverImport[] imports = rb.getImportPackages(); |
| if (imports.length == 0) { |
| ResolverImpl.log(" (w) no imports"); //$NON-NLS-1$ |
| continue; |
| } |
| for (ResolverImport resolverImport : imports) { |
| if (resolverImport.isDynamic() && resolverImport.getSelectedSupplier() == null) { |
| ResolverImpl.log(" (w) " + resolverImport.getBundle() + ":" + resolverImport.getName() + " -> DYNAMIC"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } else if (resolverImport.isOptional() && resolverImport.getSelectedSupplier() == null) { |
| ResolverImpl.log(" (w) " + resolverImport.getBundle() + ":" + resolverImport.getName() + " -> OPTIONAL (could not be wired)"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } else if (resolverImport.getSelectedSupplier() == null) { |
| ResolverImpl.log(" (w) " + resolverImport.getBundle() + ":" + resolverImport.getName() + " -> NULL!!!"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } else { |
| ResolverImpl.log(" (w) " + resolverImport.getBundle() + ":" + resolverImport.getName() + " -> " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| ((ResolverExport) resolverImport.getSelectedSupplier()).getExporter() + ":" + resolverImport.getSelectedSupplier().getName()); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| |
| static void log(String message) { |
| Debug.println(message); |
| } |
| |
| VersionHashMap<ResolverExport> getResolverExports() { |
| return resolverExports; |
| } |
| |
| public void setSelectionPolicy(Comparator<BaseDescription> selectionPolicy) { |
| this.selectionPolicy = selectionPolicy; |
| } |
| |
| public Comparator<BaseDescription> getSelectionPolicy() { |
| return selectionPolicy; |
| } |
| |
| public void setCompositeResolveHelperRegistry(CompositeResolveHelperRegistry compositeHelpers) { |
| this.compositeHelpers = compositeHelpers; |
| } |
| |
| CompositeResolveHelperRegistry getCompositeHelpers() { |
| return compositeHelpers; |
| } |
| |
| private void reorderGenerics() { |
| for (VersionHashMap<GenericCapability> namespace : resolverGenerics.values()) |
| namespace.reorder(); |
| } |
| |
| void removeGenerics(GenericCapability[] generics) { |
| for (GenericCapability capability : generics) { |
| VersionHashMap<GenericCapability> namespace = resolverGenerics.get(capability.getGenericDescription().getType()); |
| if (namespace != null) |
| namespace.remove(capability); |
| } |
| } |
| |
| void addGenerics(GenericCapability[] generics) { |
| for (GenericCapability capability : generics) { |
| if (!capability.isEffective()) |
| continue; |
| String type = capability.getGenericDescription().getType(); |
| VersionHashMap<GenericCapability> namespace = resolverGenerics.get(type); |
| if (namespace == null) { |
| namespace = new VersionHashMap<>(this); |
| resolverGenerics.put(type, namespace); |
| } |
| namespace.put(capability.getName(), capability); |
| } |
| } |
| |
| boolean isDevelopmentMode() { |
| return developmentMode; |
| } |
| } |