Bug 545179 - Improve CapabilityIndex performance for arbitrary
namespaces

Follow-up on I957d401c3f043daf40db13397829993fe3fc5dc2 which improves
performance for queries with arbitrary namespaces and without a "name"
property.

Change-Id: I44ebbb140242d71cba855fd1d5f6fe30e62f4aad
Signed-off-by: Sebastian Ratz <sebastian.ratz@sap.com>
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java
index e742b63..ffdac0a 100644
--- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java
+++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/index/CapabilityIndex.java
@@ -47,37 +47,32 @@
 @SuppressWarnings("unchecked")
 public class CapabilityIndex extends Index<IInstallableUnit> {
 
-	private static final String NAMESPACE_EXECUTION_ENVIRONMENT = "osgi.ee";
-	private final Map<String, Object> capabilityMap;
-	private final Set<IInstallableUnit> eeProvidersSet;
+	private final Map<String, Set<IInstallableUnit>> namespaceMap;
+	private final Map<String, Object> nameMap;
 
 	public CapabilityIndex(Iterator<IInstallableUnit> itor) {
-		HashMap<String, Object> index = new HashMap<>(300);
-		Set<IInstallableUnit> eeProviders = new HashSet<>(10);
+		nameMap = new HashMap<>(300);
+		namespaceMap = new HashMap<>(10);
 		while (itor.hasNext()) {
 			IInstallableUnit iu = itor.next();
 			Collection<IProvidedCapability> pcs = iu.getProvidedCapabilities();
 			for (IProvidedCapability pc : pcs) {
-				if (NAMESPACE_EXECUTION_ENVIRONMENT.equals(pc.getNamespace())) {
-					eeProviders.add(iu);
-				}
-				String name = pc.getName();
-				Object prev = index.put(name, iu);
-				if (prev == null || prev == iu)
-					continue;
-
-				ArrayList<IInstallableUnit> list;
-				if (prev instanceof IInstallableUnit) {
-					list = new ArrayList<>();
-					list.add((IInstallableUnit) prev);
-				} else
-					list = (ArrayList<IInstallableUnit>) prev;
-				list.add(iu);
-				index.put(name, list);
+				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;
+					}
+				});
 			}
 		}
-		this.capabilityMap = index;
-		this.eeProvidersSet = Collections.unmodifiableSet(eeProviders);
 	}
 
 	private Object getRequirementIDs(IEvaluationContext ctx, IExpression requirement, Object queriedKeys) {
@@ -148,6 +143,7 @@
 	@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.
@@ -171,12 +167,17 @@
 					LambdaExpression lambda = cf.lambda;
 					queriedKeys = getQueriedIDs(ctx, lambda.getItemVariable(), ProvidedCapability.MEMBER_NAME, lambda.getOperand(), queriedKeys);
 					if (queriedKeys == null) {
-						// Special handling to support
+						// 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)
-						// in a performant way
-						if (NAMESPACE_EXECUTION_ENVIRONMENT.equals(getQueriedIDs(ctx, lambda.getItemVariable(), ProvidedCapability.MEMBER_NAMESPACE, lambda.getOperand(), queriedKeys))) {
-							return this.eeProvidersSet.iterator();
+						// 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 {
@@ -245,9 +246,9 @@
 		} else if (queriedKeys instanceof Collection<?>) {
 			matchingIUs = new HashSet<>();
 			for (Object key : (Collection<Object>) queriedKeys)
-				collectMatchingIUs((String) key, matchingIUs);
+				collectMatchingIUs(indexMapToUse, (String) key, matchingIUs);
 		} else {
-			Object v = capabilityMap.get(queriedKeys);
+			Object v = indexMapToUse.get(queriedKeys);
 			if (v == null)
 				matchingIUs = Collections.emptySet();
 			else if (v instanceof IInstallableUnit)
@@ -258,8 +259,8 @@
 		return matchingIUs.iterator();
 	}
 
-	private void collectMatchingIUs(String name, Collection<IInstallableUnit> collector) {
-		Object v = capabilityMap.get(name);
+	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)
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/ql/PerformanceTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/ql/PerformanceTest.java
index 473d5f4..c98cdff 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/ql/PerformanceTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/ql/PerformanceTest.java
@@ -293,6 +293,24 @@
 		System.out.println();
 	}
 
+	public void testCapabilityQueryPerformanceOsgiService() throws Exception {
+
+		IMetadataRepository repo = getMDR("/testData/2018-12");
+
+		IRequirement capability = MetadataFactory.createRequirement("osgi.service", "(objectClass=org.osgi.service.event.EventAdmin)", null, 0, 0, false);
+		IQuery<IInstallableUnit> capabilityQuery = QueryUtil.createMatchQuery(capability.getMatches());
+		IQueryResult<IInstallableUnit> result;
+
+		long start = System.currentTimeMillis();
+		for (int i = 0; i < 1000; ++i) {
+			result = repo.query(capabilityQuery, new NullProgressMonitor());
+			assertEquals(1, queryResultSize(result));
+			assertEquals("org.eclipse.equinox.event", result.iterator().next().getId());
+		}
+		System.out.println("1000 * CapabilityQuery for osgi.service took: " + (System.currentTimeMillis() - start) + " milliseconds");
+		System.out.println();
+	}
+
 	public void testIUPropertyQueryPerformance() throws Exception {
 
 		IMetadataRepository repo = getMDR("/testData/galileoM7");
diff --git a/bundles/org.eclipse.equinox.p2.tests/testData/2018-12/content.jar b/bundles/org.eclipse.equinox.p2.tests/testData/2018-12/content.jar
new file mode 100644
index 0000000..7f3cc65
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.tests/testData/2018-12/content.jar
Binary files differ