blob: ffdac0a33500fd7d59569a8f1928650497ee2377 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 Cloudsmith Inc. 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:
* Cloudsmith Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.index;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.equinox.internal.p2.metadata.IRequiredCapability;
import org.eclipse.equinox.internal.p2.metadata.InstallableUnit;
import org.eclipse.equinox.internal.p2.metadata.ProvidedCapability;
import org.eclipse.equinox.internal.p2.metadata.RequiredCapability;
import org.eclipse.equinox.internal.p2.metadata.expression.CollectionFilter;
import org.eclipse.equinox.internal.p2.metadata.expression.Expression;
import org.eclipse.equinox.internal.p2.metadata.expression.ExpressionFactory;
import org.eclipse.equinox.internal.p2.metadata.expression.LambdaExpression;
import org.eclipse.equinox.internal.p2.metadata.expression.Matches;
import org.eclipse.equinox.internal.p2.metadata.expression.Member;
import org.eclipse.equinox.internal.p2.metadata.expression.Parameter;
import org.eclipse.equinox.internal.p2.metadata.expression.Unary;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IProvidedCapability;
import org.eclipse.equinox.p2.metadata.IRequirement;
import org.eclipse.equinox.p2.metadata.expression.ExpressionUtil;
import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext;
import org.eclipse.equinox.p2.metadata.expression.IExpression;
import org.eclipse.equinox.p2.metadata.expression.IMatchExpression;
/**
* An in-memory implementation of a CapabilityIndex based on a Map.
*/
@SuppressWarnings("unchecked")
public class CapabilityIndex extends Index<IInstallableUnit> {
private final Map<String, Set<IInstallableUnit>> namespaceMap;
private final Map<String, Object> nameMap;
public CapabilityIndex(Iterator<IInstallableUnit> itor) {
nameMap = new HashMap<>(300);
namespaceMap = new HashMap<>(10);
while (itor.hasNext()) {
IInstallableUnit iu = itor.next();
Collection<IProvidedCapability> pcs = iu.getProvidedCapabilities();
for (IProvidedCapability pc : pcs) {
namespaceMap.computeIfAbsent(pc.getNamespace(), namespace -> new HashSet<>()).add(iu);
nameMap.compute(pc.getName(), (name, prev) -> {
if (prev == null || prev == iu) {
return iu;
} else if (prev instanceof IInstallableUnit) {
Collection<IInstallableUnit> ius = new HashSet<>();
ius.add((IInstallableUnit) prev);
ius.add(iu);
return ius;
} else {
((Collection<IInstallableUnit>) prev).add(iu);
return prev;
}
});
}
}
}
private Object getRequirementIDs(IEvaluationContext ctx, IExpression requirement, Object queriedKeys) {
switch (requirement.getExpressionType()) {
case IExpression.TYPE_AND :
// AND is OK if at least one of the branches require the queried key
for (IExpression expr : ExpressionUtil.getOperands(requirement)) {
Object test = getRequirementIDs(ctx, expr, queriedKeys);
if (test != null) {
if (test == Boolean.FALSE)
// Failing exists so the AND will fail altogether
return test;
// It's safe to break here since an and'ing several queries
// for different keys and the same input will yield false anyway.
return test;
}
}
return null;
case IExpression.TYPE_OR :
// OR is OK if all the branches require the queried key
for (IExpression expr : ExpressionUtil.getOperands(requirement)) {
Object test = getRequirementIDs(ctx, expr, queriedKeys);
if (test == null)
// This branch did not require the key so index cannot be used
return null;
if (test == Boolean.FALSE)
// Branch will always fail regardless of input, so just ignore
continue;
queriedKeys = test;
}
return queriedKeys;
case IExpression.TYPE_ALL :
case IExpression.TYPE_EXISTS :
CollectionFilter cf = (CollectionFilter) requirement;
if (isIndexedMember(cf.getOperand(), ExpressionFactory.THIS, InstallableUnit.MEMBER_PROVIDED_CAPABILITIES)) {
LambdaExpression lambda = cf.lambda;
return getQueriedIDs(ctx, lambda.getItemVariable(), ProvidedCapability.MEMBER_NAME, lambda.getOperand(), queriedKeys);
}
}
return null;
}
@Override
protected Object getQueriedIDs(IEvaluationContext ctx, IExpression variable, String memberName, IExpression booleanExpr, Object queriedKeys) {
if (booleanExpr.getExpressionType() != IExpression.TYPE_MATCHES)
return super.getQueriedIDs(ctx, variable, memberName, booleanExpr, queriedKeys);
Matches matches = (Matches) booleanExpr;
if (matches.lhs != variable)
return null;
Object rhsObj = matches.rhs.evaluate(ctx);
if (!(rhsObj instanceof IRequirement))
return null;
// Let the requirement expression participate in the
// index usage query
//
IMatchExpression<IInstallableUnit> rm = ((IRequirement) rhsObj).getMatches();
return RequiredCapability.isVersionRangeRequirement(rm) ? concatenateUnique(queriedKeys, rm.getParameters()[0]) : getRequirementIDs(rm.createContext(), ((Unary) rm).operand, queriedKeys);
}
@Override
public Iterator<IInstallableUnit> getCandidates(IEvaluationContext ctx, IExpression variable, IExpression booleanExpr) {
Object queriedKeys = null;
Map<String, ?> indexMapToUse = nameMap;
// booleanExpression must be a collection filter on providedCapabilities
// or an IInstallableUnit used in a match expression.
//
IExpression expr = booleanExpr;
int type = booleanExpr.getExpressionType();
if (type == 0) {
// wrapper
expr = ((Unary) booleanExpr).operand;
type = expr.getExpressionType();
}
switch (type) {
case IExpression.TYPE_ALL :
case IExpression.TYPE_EXISTS :
CollectionFilter cf = (CollectionFilter) expr;
if (isIndexedMember(cf.getOperand(), variable, InstallableUnit.MEMBER_PROVIDED_CAPABILITIES)) {
// This is providedCapabilities.exists or providedCapabilites.all
//
LambdaExpression lambda = cf.lambda;
queriedKeys = getQueriedIDs(ctx, lambda.getItemVariable(), ProvidedCapability.MEMBER_NAME, lambda.getOperand(), queriedKeys);
if (queriedKeys == null) {
// Special handling to support expressions for arbitrary namespaces without "name" property such as
// osgi.ee; (&(osgi.ee=JavaSE)(version=1.8))
// providedCapabilities.exists(cap | cap.namespace == $0 && cap.properties ~= $1)
// or
// osgi.service; (objectClass=org.osgi.service.event.EventAdmin)
// providedCapabilities.exists(cap | cap.namespace == $0 && cap.properties ~= $1)
// in a performant way as this reduces the result set significantly
queriedKeys = getQueriedIDs(ctx, lambda.getItemVariable(), ProvidedCapability.MEMBER_NAMESPACE, lambda.getOperand(), queriedKeys);
if (queriedKeys != null) {
indexMapToUse = namespaceMap;
break;
}
}
} else {
// Might be the requirements array.
//
Expression op = cf.getOperand();
if (op instanceof Member && InstallableUnit.MEMBER_REQUIREMENTS.equals(((Member) op).getName())) {
queriedKeys = getQueriedIDs(ctx, variable, ProvidedCapability.MEMBER_NAME, booleanExpr, queriedKeys);
}
}
if (queriedKeys == null) {
// Might be a parameterized query of requirements
// If matching class is InstallableUnit && paramter exists && parameter is IRequirement
if (cf.getOperand() instanceof Parameter && ctx.getParameter(0) instanceof Collection<?>) {
// Check that the parameter really is the requirement array
// This only really works for IRequiredCapabilities, not any IRequirements
Collection<?> collection = (Collection<?>) ctx.getParameter(0);
boolean instance = !collection.isEmpty();
for (Object object : collection) {
instance &= (object instanceof IRequiredCapability);
}
if (instance) {
Collection<String> result = new ArrayList<>();
for (Object object : collection) {
// This instance of check was done above
IRequiredCapability capability = (IRequiredCapability) object;
result.add(capability.getName());
}
if (result.size() > 0) {
queriedKeys = result;
}
}
}
}
break;
case IExpression.TYPE_MATCHES :
Matches matches = (Matches) expr;
if (matches.lhs != variable)
break;
Object rhsObj = matches.rhs.evaluate(ctx);
if (!(rhsObj instanceof IRequirement))
break;
// Let the requirement expression participate in the
// index usage query
//
IMatchExpression<IInstallableUnit> rm = ((IRequirement) rhsObj).getMatches();
queriedKeys = RequiredCapability.isVersionRangeRequirement(rm) ? concatenateUnique(queriedKeys, rm.getParameters()[0]) : getRequirementIDs(rm.createContext(), ((Unary) rm).operand, queriedKeys);
break;
default :
queriedKeys = null;
}
if (queriedKeys == null)
// Index cannot be used.
return null;
Collection<IInstallableUnit> matchingIUs;
if (queriedKeys == Boolean.FALSE) {
// It has been determined that the expression has no chance
// to succeed regardless of input
matchingIUs = Collections.emptySet();
} else if (queriedKeys instanceof Collection<?>) {
matchingIUs = new HashSet<>();
for (Object key : (Collection<Object>) queriedKeys)
collectMatchingIUs(indexMapToUse, (String) key, matchingIUs);
} else {
Object v = indexMapToUse.get(queriedKeys);
if (v == null)
matchingIUs = Collections.emptySet();
else if (v instanceof IInstallableUnit)
matchingIUs = Collections.singleton((IInstallableUnit) v);
else
matchingIUs = (Collection<IInstallableUnit>) v;
}
return matchingIUs.iterator();
}
private static void collectMatchingIUs(Map<String, ?> indexToUse, String name, Collection<IInstallableUnit> collector) {
Object v = indexToUse.get(name);
if (v == null)
return;
if (v instanceof IInstallableUnit)
collector.add((IInstallableUnit) v);
else
collector.addAll((Collection<IInstallableUnit>) v);
}
}