Bug 573026 - Apply NamespaceList to ModuleRevision and its builder

Change-Id: Ic3752324c8b166fbf06fdb76c3e3dd98b4e4a345
Signed-off-by: Hannes Wellmann <wellmann.hannes1@gmx.net>
Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.framework/+/180178
Tested-by: Equinox Bot <equinox-bot@eclipse.org>
Reviewed-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi.tests/bundles_src/storage.hooks.a/org/eclipse/osgi/tests/hooks/framework/storage/a/TestHookConfigurator.java b/bundles/org.eclipse.osgi.tests/bundles_src/storage.hooks.a/org/eclipse/osgi/tests/hooks/framework/storage/a/TestHookConfigurator.java
index d25cb8a..1b95e51 100644
--- a/bundles/org.eclipse.osgi.tests/bundles_src/storage.hooks.a/org/eclipse/osgi/tests/hooks/framework/storage/a/TestHookConfigurator.java
+++ b/bundles/org.eclipse.osgi.tests/bundles_src/storage.hooks.a/org/eclipse/osgi/tests/hooks/framework/storage/a/TestHookConfigurator.java
@@ -107,11 +107,9 @@
 					builder.addCapability("test.file.path", dirs, attrs);
 				}
 				if (TestHookConfigurator.adaptCapabilityAttribute) {
-					for (GenericInfo c : builder.getCapabilities()) {
-						if (BundleNamespace.BUNDLE_NAMESPACE.equals(c.getNamespace())) {
-							c.getAttributes().put("matching.attribute", "testAttribute");
-							c.getDirectives().put("matching.directive", "testDirective");
-						}
+					for (GenericInfo c : builder.getCapabilities(BundleNamespace.BUNDLE_NAMESPACE)) {
+						c.getAttributes().put("matching.attribute", "testAttribute");
+						c.getDirectives().put("matching.directive", "testDirective");
 					}
 				}
 				return builder;
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListBuilderTest.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListBuilderTest.java
index 9e45530..fdc3346 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListBuilderTest.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListBuilderTest.java
@@ -14,11 +14,14 @@
 package org.eclipse.osgi.tests.container;
 
 import static org.eclipse.osgi.tests.container.NamespaceListTest.build;
-import static org.eclipse.osgi.tests.container.NamespaceListTest.builderAddAfterLastMatch;
 import static org.eclipse.osgi.tests.container.NamespaceListTest.builderAddAll;
+import static org.eclipse.osgi.tests.container.NamespaceListTest.builderAddAllFiltered;
+import static org.eclipse.osgi.tests.container.NamespaceListTest.builderAddAllFilteredAfterLastMatch;
 import static org.eclipse.osgi.tests.container.NamespaceListTest.builderCreate;
+import static org.eclipse.osgi.tests.container.NamespaceListTest.builderGetNamespaceElements;
 import static org.eclipse.osgi.tests.container.NamespaceListTest.builderRemoveElementsOfNamespaceIf;
 import static org.eclipse.osgi.tests.container.NamespaceListTest.builderRemoveNamespaceIf;
+import static org.eclipse.osgi.tests.container.NamespaceListTest.builderTransformIntoCopy;
 import static org.eclipse.osgi.tests.container.NamespaceListTest.createEmptyNamespaceList;
 import static org.eclipse.osgi.tests.container.NamespaceListTest.getList;
 import static org.eclipse.osgi.tests.container.NamespaceListTest.newNamespace;
@@ -26,6 +29,7 @@
 import static org.eclipse.osgi.tests.container.NamespaceListTest.randomListSort;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -42,6 +46,7 @@
 import java.util.Map.Entry;
 import java.util.NoSuchElementException;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import org.eclipse.osgi.tests.container.NamespaceListTest.NamespaceElement;
 import org.junit.Test;
 
@@ -219,6 +224,71 @@
 		assertEquals(getList(namespaceList, "ns-1"), elements.subList(3, 6));
 	}
 
+	@Test
+	public void testGetNamespaceElements_contaiendNamespace() throws Exception {
+		List<NamespaceElement> elements = populate(3, 3);
+
+		Collection<NamespaceElement> builder = builderCreate();
+		builder.addAll(elements);
+
+		List<NamespaceElement> namespaceElements = builderGetNamespaceElements(builder, "ns-1");
+		assertEquals(namespaceElements, elements.subList(3, 6));
+	}
+
+	@Test
+	public void testGetNamespaceElements_nullNamespace() throws Exception {
+		List<NamespaceElement> elements = populate(3, 3);
+
+		Collection<NamespaceElement> builder = builderCreate();
+		builder.addAll(elements);
+
+		List<NamespaceElement> namespaceElements = builderGetNamespaceElements(builder, null);
+		assertEquals(elements, namespaceElements);
+	}
+
+	@Test
+	public void testGetNamespaceElements_notContainedNamespace() throws Exception {
+		List<NamespaceElement> elements = populate(3, 3);
+		Collection<NamespaceElement> builder = builderCreate();
+		builder.addAll(elements);
+
+		List<NamespaceElement> namespaceElements = builderGetNamespaceElements(builder, "ns-100");
+		assertEquals(Collections.emptyList(), namespaceElements);
+	}
+
+	@Test
+	public void testTransformIntoCopy_notEmptyBuilder() throws Exception {
+		List<NamespaceElement> elements = populate(3, 3);
+
+		Collection<NamespaceElement> builder = builderCreate();
+		builder.addAll(elements);
+
+		Function<String, String> getNamespace = e -> e.substring(0, 4);
+
+		Collection<String> transformedBuilder = builderTransformIntoCopy(builder, Object::toString, getNamespace);
+
+		List<String> transformedElements = elements.stream().map(Object::toString).collect(Collectors.toList());
+		assertStricEqualContent(transformedBuilder, transformedElements, getNamespace);
+		assertNotSame(builder, transformedBuilder);
+		// check that the original builder is not changed
+		assertStricEqualContent(builder, elements);
+	}
+
+	@Test
+	public void testTransformIntoCopy_emptyBuilder() throws Exception {
+
+		Collection<NamespaceElement> builder = builderCreate();
+
+		Function<String, String> getNamespace = e -> e.substring(0, 4);
+
+		Collection<String> transformedBuilder = builderTransformIntoCopy(builder, Object::toString, getNamespace);
+
+		assertNotSame(builder, transformedBuilder);
+		assertStricEqualContent(transformedBuilder, Collections.emptyList(), getNamespace);
+		// check that the original builder is not changed
+		assertStricEqualContent(builder, Collections.emptyList());
+	}
+
 	// --- test addition ---
 
 	@Test
@@ -306,53 +376,162 @@
 	}
 
 	@Test
-	public void testAddAfterLastMatch_matchUpUntilTheMiddle() throws Exception {
+	public void testAddAll_builderArgument() throws Exception {
+		List<NamespaceElement> elements = populate(2, 2);
+		Collection<NamespaceElement> builder = builderCreate();
+		builder.addAll(elements);
+
+		Collection<NamespaceElement> builder2 = builderCreate();
+		List<NamespaceElement> elements2 = populate(2, 2);
+		builder2.addAll(elements2);
+
+		builder.addAll(builder2);
+
+		elements.addAll(4, elements2.subList(2, 4));
+		elements.addAll(2, elements2.subList(0, 2));
+
+		assertStricEqualContent(builder, elements);
+	}
+
+	@Test
+	public void testAddAllFiltered_notEmptyBuilder() throws Exception {
+		List<NamespaceElement> elements = populate(4, 3);
+
+		Collection<NamespaceElement> builder = builderCreate();
+		builder.addAll(elements);
+
+		List<NamespaceElement> newElements = new ArrayList<>();
+		newElements.add(new NamespaceElement(0, "ns-1"));
+		newElements.add(new NamespaceElement(10, "ns-1"));
+		newElements.add(new NamespaceElement(2, "ns-20"));
+		newElements.add(new NamespaceElement(0, "ns-2"));
+		newElements.add(new NamespaceElement(2, "ns-0"));
+		newElements.add(new NamespaceElement(1, "ns-2"));
+		newElements.add(new NamespaceElement(10, "ns-20"));
+		newElements.add(new NamespaceElement(0, "ns-20"));
+		Object namespaceList = newNamespace(newElements);
+
+		builderAddAllFiltered(builder, namespaceList, n -> !n.equals("ns-2"), e -> e.id < 10);
+
+		List<NamespaceElement> expectedElements = new ArrayList<>();
+		expectedElements.add(new NamespaceElement(0, "ns-0"));
+		expectedElements.add(new NamespaceElement(1, "ns-0"));
+		expectedElements.add(new NamespaceElement(2, "ns-0"));
+		expectedElements.add(new NamespaceElement(3, "ns-0"));
+		expectedElements.add(new NamespaceElement(2, "ns-0"));
+
+		expectedElements.add(new NamespaceElement(0, "ns-1"));
+		expectedElements.add(new NamespaceElement(1, "ns-1"));
+		expectedElements.add(new NamespaceElement(2, "ns-1"));
+		expectedElements.add(new NamespaceElement(3, "ns-1"));
+		expectedElements.add(new NamespaceElement(0, "ns-1"));
+
+		expectedElements.add(new NamespaceElement(0, "ns-2"));
+		expectedElements.add(new NamespaceElement(1, "ns-2"));
+		expectedElements.add(new NamespaceElement(2, "ns-2"));
+		expectedElements.add(new NamespaceElement(3, "ns-2"));
+
+		expectedElements.add(new NamespaceElement(2, "ns-20"));
+		expectedElements.add(new NamespaceElement(0, "ns-20"));
+
+		assertStricEqualContent(builder, expectedElements);
+	}
+
+	@Test
+	public void testAddAllFiltered_emptyBuilder() throws Exception {
+
+		Collection<NamespaceElement> builder = builderCreate();
+
+		List<NamespaceElement> newElements2 = new ArrayList<>();
+		newElements2.add(new NamespaceElement(0, "ns-1"));
+		newElements2.add(new NamespaceElement(10, "ns-1"));
+		newElements2.add(new NamespaceElement(2, "ns-20"));
+		newElements2.add(new NamespaceElement(0, "ns-2"));
+		newElements2.add(new NamespaceElement(2, "ns-0"));
+		newElements2.add(new NamespaceElement(1, "ns-2"));
+		newElements2.add(new NamespaceElement(10, "ns-20"));
+		newElements2.add(new NamespaceElement(0, "ns-20"));
+		Object namespaceList = newNamespace(newElements2);
+
+		builderAddAllFiltered(builder, namespaceList, n -> !n.equals("ns-2"), e -> e.id < 10);
+
+		List<NamespaceElement> expectedElements = new ArrayList<>();
+		expectedElements.add(new NamespaceElement(0, "ns-1"));
+		expectedElements.add(new NamespaceElement(2, "ns-20"));
+		expectedElements.add(new NamespaceElement(0, "ns-20"));
+		expectedElements.add(new NamespaceElement(2, "ns-0"));
+
+		assertStricEqualContent(builder, expectedElements);
+	}
+
+	@Test
+	public void testAddAllFiltered_allElementsOfNewNamespaceFiltered() throws Exception {
+
+		Collection<NamespaceElement> builder = builderCreate();
+
+		List<NamespaceElement> newElements = new ArrayList<>();
+		newElements.add(new NamespaceElement(10, "ns-1"));
+		newElements.add(new NamespaceElement(11, "ns-1"));
+		Object namespaceList = newNamespace(newElements);
+
+		builderAddAllFiltered(builder, namespaceList, n -> true, e -> e.id < 5);
+		// All elements of namespace ns-1 are filtered
+
+		assertStricEqualContent(builder, Collections.emptyList());
+	}
+
+	@Test
+	public void testAddAllFilteredAfterLastMatch_matchUpUntilTheMiddle() throws Exception {
 		List<NamespaceElement> elements = populate(4, "ns-0");
 
 		Collection<NamespaceElement> builder = builderCreate();
 		builder.addAll(elements);
 
 		NamespaceElement element = new NamespaceElement(5, "ns-0");
-		builderAddAfterLastMatch(builder, element, e -> e.id < 2);
+		Object namespaceList = newNamespace(Collections.singletonList(element));
+		builderAddAllFilteredAfterLastMatch(builder, namespaceList, n -> true, e -> true, (toAdd, e) -> e.id < 2);
 
 		elements.add(2, element);
 		assertStricEqualContent(builder, elements);
 	}
 
 	@Test
-	public void testAddAfterLastMatch_allElementsMatches() throws Exception {
+	public void testAddAllFilteredAfterLastMatch_allElementsMatches() throws Exception {
 		List<NamespaceElement> elements = populate(4, "ns-0");
 
 		Collection<NamespaceElement> builder = builderCreate();
 		builder.addAll(elements);
 
 		NamespaceElement element = new NamespaceElement(5, "ns-0");
-		builderAddAfterLastMatch(builder, element, e -> true);
+		Object namespaceList = newNamespace(Collections.singletonList(element));
+		builderAddAllFilteredAfterLastMatch(builder, namespaceList, n -> true, e -> true, (toAdd, e) -> true);
 
 		elements.add(4, element);
 		assertStricEqualContent(builder, elements);
 	}
 
 	@Test
-	public void testAddAfterLastMatch_noMatch() throws Exception {
+	public void testAddAllFilteredAfterLastMatch_noMatch() throws Exception {
 		List<NamespaceElement> elements = populate(4, "ns-0");
 
 		Collection<NamespaceElement> builder = builderCreate();
 		builder.addAll(elements);
 
 		NamespaceElement element = new NamespaceElement(5, "ns-0");
-		builderAddAfterLastMatch(builder, element, e -> e.id > 100);
+		Object namespaceList = newNamespace(Collections.singletonList(element));
+		builderAddAllFilteredAfterLastMatch(builder, namespaceList, n -> true, e -> true, (toAdd, e) -> e.id > 100);
 
 		elements.add(0, element);
 		assertStricEqualContent(builder, elements);
 	}
 
 	@Test
-	public void testAddAfterLastMatch_emptyNamespaceList() throws Exception {
+	public void testAddAllFilteredAfterLastMatch_emptyNamespaceList() throws Exception {
 		Collection<NamespaceElement> builder = builderCreate();
 
 		NamespaceElement element = new NamespaceElement(5, "ns-0");
-		builderAddAfterLastMatch(builder, element, e -> e.id < 2);
+		Object namespaceList = newNamespace(Collections.singletonList(element));
+		builderAddAllFilteredAfterLastMatch(builder, namespaceList, n -> true, e -> true, (toAdd, e) -> e.id < 2);
 
 		assertStricEqualContent(builder, Collections.singletonList(element));
 	}
@@ -771,6 +950,7 @@
 			String namespace = entry.getKey();
 			List<E> elements = entry.getValue();
 			assertEquals(elements, getList(namespaceList, namespace));
+			assertEquals(elements, builderGetNamespaceElements(builder, namespace));
 		}
 
 		assertEquals(expectedElements, getList(namespaceList, null));
@@ -794,6 +974,7 @@
 			String namespace = entry.getKey();
 			List<NamespaceElement> elements = entry.getValue();
 			assertContentEquals(elements, getList(namespaceList, namespace));
+			assertContentEquals(elements, builderGetNamespaceElements(builder, namespace));
 		}
 
 		assertContentEquals(expectedElements, getList(namespaceList, null));
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListTest.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListTest.java
index 65a8af7..07a908e 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListTest.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListTest.java
@@ -10,7 +10,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
- *     Hannes Wellmann - Bug 573025: introduce and apply NamespaceList.Builder
+ *     Hannes Wellmann - Bug 573025 & 573026: introduce and apply NamespaceList.Builder
  *******************************************************************************/
 package org.eclipse.osgi.tests.container;
 
@@ -26,6 +26,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Random;
+import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import org.junit.Test;
@@ -42,8 +43,11 @@
 
 	// only access fields reflectively that are not part of the Collection-API
 	static final Method BUILDER_CREATE;
+	static final Method BUILDER_GET_NAMESPACE_ELEMENTS;
+	static final Method BUILDER_TRANSFORM_INTO_COPY;
 	static final Method BUILDER_ADD_ALL;
-	static final Method BUILDER_ADD_AFTER_LAST_MATCH;
+	static final Method BUILDER_ADD_ALL_FILTERED;
+	static final Method BUILDER_ADD_ALL_FILTERED_AFTER_LAST_MATCH;
 	static final Method BUILDER_REMOVE_NAMESPACE_IF;
 	static final Method BUILDER_REMOVE_ELEMENTS_OF_NAMESPACE_IF;
 	static final Method BUILDER_BUILD;
@@ -61,9 +65,14 @@
 			NAMESPACELIST_CREATE_BUILDER = namespaceList.getMethod("createBuilder");
 
 			BUILDER_CREATE = namespaceListBuilder.getMethod("create", Function.class);
+			BUILDER_GET_NAMESPACE_ELEMENTS = namespaceListBuilder.getMethod("getNamespaceElements", String.class);
+			BUILDER_TRANSFORM_INTO_COPY = namespaceListBuilder.getMethod("transformIntoCopy", Function.class,
+					Function.class);
 			BUILDER_ADD_ALL = namespaceListBuilder.getMethod("addAll", namespaceList);
-			BUILDER_ADD_AFTER_LAST_MATCH = namespaceListBuilder.getMethod("addAfterLastMatch", Object.class,
+			BUILDER_ADD_ALL_FILTERED = namespaceListBuilder.getMethod("addAllFiltered", namespaceList, Predicate.class,
 					Predicate.class);
+			BUILDER_ADD_ALL_FILTERED_AFTER_LAST_MATCH = namespaceListBuilder.getMethod("addAllFilteredAfterLastMatch",
+					namespaceList, Predicate.class, Predicate.class, BiPredicate.class);
 			BUILDER_REMOVE_NAMESPACE_IF = namespaceListBuilder.getMethod("removeNamespaceIf", Predicate.class);
 			BUILDER_REMOVE_ELEMENTS_OF_NAMESPACE_IF = namespaceListBuilder.getMethod("removeElementsOfNamespaceIf",
 					String.class, Predicate.class);
@@ -104,12 +113,29 @@
 		return (Collection<NamespaceElement>) BUILDER_CREATE.invoke(null, getNamespace);
 	}
 
+	static <E> List<E> builderGetNamespaceElements(Collection<E> builder, String namespace) throws Exception {
+		return (List<E>) BUILDER_GET_NAMESPACE_ELEMENTS.invoke(builder, namespace);
+	}
+
+	static <T, R> Collection<R> builderTransformIntoCopy(Collection<T> builder, Function<T, R> transformation,
+			Function<R, String> newGetNamespace) throws Exception {
+		return (Collection<R>) BUILDER_TRANSFORM_INTO_COPY.invoke(builder, transformation, newGetNamespace);
+	}
+
 	static <E> void builderAddAll(Collection<E> builder, Object namespaceList) throws Exception {
 		BUILDER_ADD_ALL.invoke(builder, namespaceList);
 	}
 
-	static <E> void builderAddAfterLastMatch(Collection<E> builder, E e, Predicate<E> matcher) throws Exception {
-		BUILDER_ADD_AFTER_LAST_MATCH.invoke(builder, e, matcher);
+	static <E> void builderAddAllFiltered(Collection<E> builder, Object namespaceList,
+			Predicate<? super String> namespaceFilter, Predicate<E> elementFilter) throws Exception {
+		BUILDER_ADD_ALL_FILTERED.invoke(builder, namespaceList, namespaceFilter, elementFilter);
+	}
+
+	static <E> void builderAddAllFilteredAfterLastMatch(Collection<E> builder, Object namespaceList,
+			Predicate<? super String> namespaceFilter, Predicate<E> elementFilter, BiPredicate<E, E> insertionMatcher)
+			throws Exception {
+		BUILDER_ADD_ALL_FILTERED_AFTER_LAST_MATCH.invoke(builder, namespaceList, namespaceFilter, elementFilter,
+				insertionMatcher);
 	}
 
 	static <E> void builderRemoveNamespaceIf(Collection<E> builder, Predicate<String> filter) throws Exception {
diff --git a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
index c721058..0edabb2 100644
--- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
@@ -104,7 +104,7 @@
 Bundle-Description: %systemBundle
 Bundle-Copyright: %copyright
 Bundle-Vendor: %eclipse.org
-Bundle-Version: 3.16.400.qualifier
+Bundle-Version: 3.17.0.qualifier
 Bundle-Localization: systembundle
 Bundle-DocUrl: http://www.eclipse.org
 Eclipse-ExtensibleAPI: true
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDatabase.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDatabase.java
index 108714d..3b537f4 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDatabase.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDatabase.java
@@ -249,20 +249,18 @@
 		if ((builder.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
 			return null;
 		}
-		for (GenericInfo info : builder.getCapabilities()) {
-			if (EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE.equals(info.getNamespace())) {
-				if (EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(info.getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY))) {
-					String compatibilityStartLazy = adaptor.getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_START_LAZY);
-					if (compatibilityStartLazy == null || Boolean.valueOf(compatibilityStartLazy)) {
-						// TODO hack until p2 is fixed (bug 177641)
-						EnumSet<Settings> settings = EnumSet.noneOf(Settings.class);
-						settings.add(Settings.USE_ACTIVATION_POLICY);
-						settings.add(Settings.AUTO_START);
-						return settings;
-					}
+		for (GenericInfo info : builder.getCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE)) {
+			if (EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(info.getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY))) {
+				String compatibilityStartLazy = adaptor.getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_START_LAZY);
+				if (compatibilityStartLazy == null || Boolean.valueOf(compatibilityStartLazy)) {
+					// TODO hack until p2 is fixed (bug 177641)
+					EnumSet<Settings> settings = EnumSet.noneOf(Settings.class);
+					settings.add(Settings.USE_ACTIVATION_POLICY);
+					settings.add(Settings.AUTO_START);
+					return settings;
 				}
-				return null;
 			}
+			return null;
 		}
 		return null;
 	}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java
index 91eebcf..bf82607 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java
@@ -13,8 +13,6 @@
  *******************************************************************************/
 package org.eclipse.osgi.container;
 
-import static org.eclipse.osgi.internal.container.NamespaceList.CAPABILITY;
-import static org.eclipse.osgi.internal.container.NamespaceList.REQUIREMENT;
 import static org.eclipse.osgi.internal.container.NamespaceList.WIRE;
 
 import java.security.Permission;
@@ -46,7 +44,6 @@
 import org.eclipse.osgi.container.namespaces.EquinoxFragmentNamespace;
 import org.eclipse.osgi.internal.container.InternalUtils;
 import org.eclipse.osgi.internal.container.NamespaceList;
-import org.eclipse.osgi.internal.container.NamespaceList.Builder;
 import org.eclipse.osgi.internal.debug.Debug;
 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
 import org.eclipse.osgi.internal.framework.EquinoxContainer;
@@ -234,10 +231,8 @@
 		Map<ModuleCapability, List<ModuleWire>> providedWireMap = provided.getOrDefault(revision, Collections.emptyMap());
 		NamespaceList<ModuleWire> requiredWires = required.getOrDefault(revision, NamespaceList.empty(WIRE));
 
-		NamespaceList.Builder<ModuleCapability> capabilities = Builder.create(CAPABILITY);
-		capabilities.addAll(revision.getModuleCapabilities(null));
-		NamespaceList.Builder<ModuleRequirement> requirements = Builder.create(REQUIREMENT);
-		requirements.addAll(revision.getModuleRequirements(null));
+		NamespaceList.Builder<ModuleCapability> capabilities = revision.getCapabilities().createBuilder();
+		NamespaceList.Builder<ModuleRequirement> requirements = revision.getRequirements().createBuilder();
 
 		// if revision is a fragment remove payload requirements and capabilities
 		if ((BundleRevision.TYPE_FRAGMENT & revision.getTypes()) != 0) {
@@ -341,35 +336,29 @@
 		for (ModuleWire hostWire : hostWires) {
 
 			// add fragment capabilities
-			List<ModuleCapability> fragmentCapabilities = hostWire.getRequirer().getModuleCapabilities(null);
-			for (ModuleCapability fragmentCapability : fragmentCapabilities) {
-				if (NON_PAYLOAD_CAPABILITIES.contains(fragmentCapability.getNamespace())) {
-					continue; // don't include, not a payload capability
-				}
-				Object effective = fragmentCapability.getDirectives().get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
-				if (effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective)) {
-					continue; // don't include, not effective
-				}
-				capabilities.add(fragmentCapability);
-			}
+			NamespaceList<ModuleCapability> fragmentCapabilities = hostWire.getRequirer().getCapabilities();
+			capabilities.addAllFiltered(fragmentCapabilities,
+
+					n -> !NON_PAYLOAD_CAPABILITIES.contains(n),
+
+					fc -> { // don't include, not effective
+						Object effective = fc.getDirectives().get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
+						return effective == null || Namespace.EFFECTIVE_RESOLVE.equals(effective);
+					});
 
 			// add fragment requirements
-			List<ModuleRequirement> fragmentRequriements = hostWire.getRequirer().getModuleRequirements(null);
-			for (ModuleRequirement fragmentRequirement : fragmentRequriements) {
-				if (NON_PAYLOAD_REQUIREMENTS.contains(fragmentRequirement.getNamespace())) {
-					continue; // don't include, not a payload requirement
-				}
-				Object effective = fragmentRequirement.getDirectives().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
-				if (effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective)) {
-					continue; // don't include, not effective
-				}
-				if (!PackageNamespace.PACKAGE_NAMESPACE.equals(fragmentRequirement.getNamespace())
-						|| isDynamic(fragmentRequirement)) {
-					requirements.add(fragmentRequirement);
-				} else {
-					requirements.addAfterLastMatch(fragmentRequirement, r -> !isDynamic(r));
-				}
-			}
+			NamespaceList<ModuleRequirement> fragmentRequriements = hostWire.getRequirer().getRequirements();
+			requirements.addAllFilteredAfterLastMatch(fragmentRequriements,
+
+					n -> !NON_PAYLOAD_REQUIREMENTS.contains(n),
+
+					fr -> { // don't include, not effective
+						Object effective = fr.getDirectives().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
+						return !(effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective));
+					},
+
+					(fr, r) -> !PackageNamespace.PACKAGE_NAMESPACE.equals(fr.getNamespace()) || isDynamic(fr)
+							|| !isDynamic(r));
 		}
 	}
 
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java
index b7261b4..86f9270 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java
@@ -13,7 +13,6 @@
  *******************************************************************************/
 package org.eclipse.osgi.container;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -23,6 +22,7 @@
 import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
 import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace;
 import org.eclipse.osgi.internal.container.InternalUtils;
+import org.eclipse.osgi.internal.container.NamespaceList;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Version;
 import org.osgi.framework.namespace.IdentityNamespace;
@@ -40,13 +40,13 @@
 	private final String symbolicName;
 	private final Version version;
 	private final int types;
-	private final List<ModuleCapability> capabilities;
-	private final List<ModuleRequirement> requirements;
+	private final NamespaceList<ModuleCapability> capabilities;
+	private final NamespaceList<ModuleRequirement> requirements;
 	private final ModuleRevisions revisions;
 	private final Object revisionInfo;
 	private volatile Boolean lazyActivationPolicy = null;
 
-	ModuleRevision(String symbolicName, Version version, int types, List<GenericInfo> capabilityInfos, List<GenericInfo> requirementInfos, ModuleRevisions revisions, Object revisionInfo) {
+	ModuleRevision(String symbolicName, Version version, int types, NamespaceList.Builder<GenericInfo> capabilityInfos, NamespaceList.Builder<GenericInfo> requirementInfos, ModuleRevisions revisions, Object revisionInfo) {
 		this.symbolicName = symbolicName;
 		this.version = version;
 		this.types = types;
@@ -56,18 +56,12 @@
 		this.revisionInfo = revisionInfo;
 	}
 
-	private List<ModuleCapability> createCapabilities(List<GenericInfo> capabilityInfos) {
-		if (capabilityInfos == null || capabilityInfos.isEmpty())
-			return Collections.emptyList();
-		List<ModuleCapability> result = new ArrayList<>(capabilityInfos.size());
-		for (GenericInfo info : capabilityInfos) {
-			if (info.mutable) {
-				result.add(new ModuleCapability(info.namespace, copyUnmodifiableMap(info.directives), copyUnmodifiableMap(info.attributes), this));
-			} else {
-				result.add(new ModuleCapability(info.namespace, info.directives, info.attributes, this));
-			}
-		}
-		return result;
+	private NamespaceList<ModuleCapability> createCapabilities(NamespaceList.Builder<GenericInfo> capabilityInfos) {
+		return capabilityInfos.transformIntoCopy(i -> {
+			Map<String, String> directives = i.mutable ? copyUnmodifiableMap(i.directives) : i.directives;
+			Map<String, Object> attributes = i.mutable ? copyUnmodifiableMap(i.attributes) : i.attributes;
+			return new ModuleCapability(i.namespace, directives, attributes, this);
+		}, NamespaceList.CAPABILITY).build();
 	}
 
 	private static <K, V> Map<K, V> copyUnmodifiableMap(Map<K, V> map) {
@@ -82,14 +76,9 @@
 		return Collections.unmodifiableMap(new HashMap<>(map));
 	}
 
-	private List<ModuleRequirement> createRequirements(List<GenericInfo> requirementInfos) {
-		if (requirementInfos == null || requirementInfos.isEmpty())
-			return Collections.emptyList();
-		List<ModuleRequirement> result = new ArrayList<>(requirementInfos.size());
-		for (GenericInfo info : requirementInfos) {
-			result.add(new ModuleRequirement(info.namespace, info.directives, info.attributes, this));
-		}
-		return result;
+	private NamespaceList<ModuleRequirement> createRequirements(NamespaceList.Builder<GenericInfo> infos) {
+		return infos.transformIntoCopy(i -> new ModuleRequirement(i.namespace, i.directives, i.attributes, this),
+				NamespaceList.REQUIREMENT).build();
 	}
 
 	@Override
@@ -124,15 +113,7 @@
 	 * @return An unmodifiable list containing the declared capabilities.
 	 */
 	public List<ModuleCapability> getModuleCapabilities(String namespace) {
-		if (namespace == null)
-			return Collections.unmodifiableList(capabilities);
-		List<ModuleCapability> result = new ArrayList<>();
-		for (ModuleCapability capability : capabilities) {
-			if (namespace.equals(capability.getNamespace())) {
-				result.add(capability);
-			}
-		}
-		return Collections.unmodifiableList(result);
+		return capabilities.getList(namespace);
 	}
 
 	/**
@@ -142,15 +123,7 @@
 	 * @return An unmodifiable list containing the declared requirements.
 	 */
 	public List<ModuleRequirement> getModuleRequirements(String namespace) {
-		if (namespace == null)
-			return Collections.unmodifiableList(requirements);
-		List<ModuleRequirement> result = new ArrayList<>();
-		for (ModuleRequirement requirement : requirements) {
-			if (namespace.equals(requirement.getNamespace())) {
-				result.add(requirement);
-			}
-		}
-		return Collections.unmodifiableList(result);
+		return requirements.getList(namespace);
 	}
 
 	@Override
@@ -239,7 +212,7 @@
 			if (value instanceof List) {
 				@SuppressWarnings("unchecked")
 				List<Object> list = (List<Object>) value;
-				if (list.size() == 0)
+				if (list.isEmpty())
 					continue;
 				Object component = list.get(0);
 				String className = component.getClass().getName();
@@ -260,4 +233,12 @@
 		}
 		return sb.toString();
 	}
+
+	NamespaceList<ModuleCapability> getCapabilities() {
+		return capabilities;
+	}
+
+	NamespaceList<ModuleRequirement> getRequirements() {
+		return requirements;
+	}
 }
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java
index 0d9b6f3..7549469 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java
@@ -14,12 +14,13 @@
 package org.eclipse.osgi.container;
 
 import java.security.AllPermission;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import org.eclipse.osgi.internal.container.NamespaceList;
+import org.eclipse.osgi.internal.container.NamespaceList.Builder;
 import org.eclipse.osgi.internal.framework.FilterImpl;
 import org.osgi.framework.AdminPermission;
 import org.osgi.framework.Bundle;
@@ -89,8 +90,8 @@
 	private String symbolicName = null;
 	private Version version = Version.emptyVersion;
 	private int types = 0;
-	private final List<GenericInfo> capabilityInfos = new ArrayList<>();
-	private final List<GenericInfo> requirementInfos = new ArrayList<>();
+	private final NamespaceList.Builder<GenericInfo> capabilityInfos = Builder.create(GenericInfo::getNamespace);
+	private final NamespaceList.Builder<GenericInfo> requirementInfos = Builder.create(GenericInfo::getNamespace);
 	private long id = -1;
 
 	/**
@@ -166,7 +167,20 @@
 	 * @return the capabilities
 	 */
 	public List<GenericInfo> getCapabilities() {
-		return new ArrayList<>(capabilityInfos);
+		return getCapabilities(null);
+	}
+
+	/**
+	 * Returns a snapshot of the capabilities in the given namespace for this
+	 * builder
+	 * 
+	 * @param namespace The namespace of the capabilities to return or null to
+	 *                  return the capabilities from all namespaces.
+	 * @return the capabilities
+	 * @since 3.17
+	 */
+	public List<GenericInfo> getCapabilities(String namespace) {
+		return capabilityInfos.getNamespaceElements(namespace);
 	}
 
 	/**
@@ -184,7 +198,24 @@
 	 * @return the requirements
 	 */
 	public List<GenericInfo> getRequirements() {
-		return new ArrayList<>(requirementInfos);
+		return getRequirements(null);
+	}
+
+	NamespaceList.Builder<GenericInfo> getRequirementsBuilder() {
+		return requirementInfos;
+	}
+
+	/**
+	 * Returns a snapshot of the requirements in the given namespace for this
+	 * builder
+	 * 
+	 * @param namespace The namespace of the requirements to return or null to
+	 *                  return the requirements from all namespaces.
+	 * @return the requirements
+	 * @since 3.17
+	 */
+	public List<GenericInfo> getRequirements(String namespace) {
+		return requirementInfos.getNamespaceElements(namespace);
 	}
 
 	/**
@@ -289,18 +320,14 @@
 								module.getContainer().checkAdminPermission(module.getBundle(), AdminPermission.EXTENSIONLIFECYCLE);
 							}
 						}
-					} catch (InvalidSyntaxException e) {
-						continue;
+					} catch (InvalidSyntaxException e) { // ignore
 					}
 				}
 			}
 		}
 	}
 
-	private void addGenericInfo(List<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) {
-		if (infos == null) {
-			infos = new ArrayList<>();
-		}
+	private void addGenericInfo(NamespaceList.Builder<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) {
 		infos.add(new GenericInfo(namespace, directives, attributes, true));
 	}
 
@@ -312,7 +339,7 @@
 		basicAddGenericInfo(requirementInfos, namespace, directives, attributes);
 	}
 
-	private static void basicAddGenericInfo(List<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) {
+	private static void basicAddGenericInfo(NamespaceList.Builder<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) {
 		infos.add(new GenericInfo(namespace, unmodifiableMap(directives), unmodifiableMap(attributes), false));
 	}
 
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java
index 9e6777e..3772f11 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java
@@ -405,9 +405,8 @@
 	 * @param builder the builder that defines the new dynamic imports.
 	 */
 	public void addDynamicImports(ModuleRevisionBuilder builder) {
-		List<GenericInfo> newImports = builder.getRequirements();
-		List<ModuleRequirement> newRequirements = new ArrayList<>();
-		for (GenericInfo info : newImports) {
+		NamespaceList.Builder<GenericInfo> newImports = builder.getRequirementsBuilder();
+		NamespaceList.Builder<ModuleRequirement> newRequirements = newImports.transformIntoCopy(info -> {
 			if (!PackageNamespace.PACKAGE_NAMESPACE.equals(info.getNamespace())) {
 				throw new IllegalArgumentException("Invalid namespace for package imports: " + info.getNamespace()); //$NON-NLS-1$
 			}
@@ -415,8 +414,9 @@
 			Map<String, String> directives = new HashMap<>(info.getDirectives());
 			directives.put(DYNAMICALLY_ADDED_IMPORT_DIRECTIVE, "true"); //$NON-NLS-1$
 			directives.put(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, PackageNamespace.RESOLUTION_DYNAMIC);
-			newRequirements.add(new ModuleRequirement(info.getNamespace(), directives, attributes, revision));
-		}
+			return new ModuleRequirement(info.getNamespace(), directives, attributes, revision);
+		}, NamespaceList.REQUIREMENT);
+
 		ModuleDatabase moduleDatabase = revision.getRevisions().getContainer().moduleDatabase;
 		moduleDatabase.writeLock();
 		try {
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/connect/ConnectHookConfigurator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/connect/ConnectHookConfigurator.java
index a7e764c..a5f5a65 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/connect/ConnectHookConfigurator.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/connect/ConnectHookConfigurator.java
@@ -89,24 +89,21 @@
 					@Override
 					public ModuleRevisionBuilder adaptModuleRevisionBuilder(ModuleEvent operation, Module origin, ModuleRevisionBuilder builder) {
 						if (m != null) {
-							builder.getCapabilities()
-								.stream() //
-								.filter(c -> CONNECT_TAG_NAMESPACES.contains(c.getNamespace())) //
-								.forEach((c) -> {
-									c.getAttributes().compute(IdentityNamespace.CAPABILITY_TAGS_ATTRIBUTE, (k, v) -> {
-										if (v == null) {
-											return Collections.singletonList(ConnectContent.TAG_OSGI_CONNECT);
-										}
-										if (v instanceof List) {
-											@SuppressWarnings({"unchecked", "rawtypes"})
-											List<String> l = new ArrayList<>((List) v);
-											l.add(ConnectContent.TAG_OSGI_CONNECT);
-											return Collections.unmodifiableList(l);
-										}
-										// should not get here, but just recover 
-										return Arrays.asList(v, ConnectContent.TAG_OSGI_CONNECT);
-									});
-								});
+							CONNECT_TAG_NAMESPACES.stream().map(builder::getCapabilities).flatMap(List::stream)
+									.forEach(c -> c.getAttributes().compute(IdentityNamespace.CAPABILITY_TAGS_ATTRIBUTE,
+											(k, v) -> {
+												if (v == null) {
+													return Collections.singletonList(ConnectContent.TAG_OSGI_CONNECT);
+												}
+												if (v instanceof List) {
+													@SuppressWarnings({ "unchecked", "rawtypes" })
+													List<String> l = new ArrayList<>((List) v);
+													l.add(ConnectContent.TAG_OSGI_CONNECT);
+													return Collections.unmodifiableList(l);
+												}
+												// should not get here, but just recover
+												return Arrays.asList(v, ConnectContent.TAG_OSGI_CONNECT);
+											}));
 							return builder;
 						}
 						return null;
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/NamespaceList.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/NamespaceList.java
index ce320de..e23ef3f 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/NamespaceList.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/NamespaceList.java
@@ -10,7 +10,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
- *     Hannes Wellmann - Bug 573025: introduce and apply NamespaceList.Builder
+ *     Hannes Wellmann - Bug 573025 & 573026: introduce and apply NamespaceList.Builder
  *******************************************************************************/
 package org.eclipse.osgi.internal.container;
 
@@ -23,6 +23,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import org.eclipse.osgi.container.ModuleCapability;
@@ -200,6 +201,56 @@
 			size = 0;
 		}
 
+		/**
+		 * Returns an immutable list of elements with the specified namespace in this
+		 * builder.
+		 * <p>
+		 * An empty list is returned if there are no elements with the specified
+		 * namespace. For the {@code null} namespace the elements of all namespaces are
+		 * returned as flat.
+		 * </p>
+		 * 
+		 * @param namespace the namespace of the elements to return. May be {@code null}
+		 * @return the list of element with the specified namespace
+		 */
+		public List<E> getNamespaceElements(String namespace) {
+			if (namespace == null) {
+				List<E> list = new ArrayList<>(size);
+				namespaceElements.values().forEach(list::addAll);
+				return Collections.unmodifiableList(list);
+			}
+			List<E> namespaceList = namespaceElements.get(namespace);
+			return namespaceList != null ? Collections.unmodifiableList(new ArrayList<>(namespaceList))
+					: Collections.emptyList();
+		}
+
+		/**
+		 * Returns a new builder whose content is the result of applying the specified
+		 * transformation to each element of this builder.
+		 * <p>
+		 * It is assumed that the transformation does not change the element's
+		 * namespace, so the namespace of original and transformed element are the same!
+		 * This builder is not modified.
+		 * </p>
+		 * 
+		 * @param <R>             the type of elements in the returned builder
+		 * @param transformation  the transformation applied to each element
+		 * @param newGetNamespace the function to compute the namespace of a transformed
+		 *                        element
+		 * @return a new builder containing the result of applying the transformation to
+		 *         each element in this builder
+		 */
+		public <R> Builder<R> transformIntoCopy(Function<E, R> transformation, Function<R, String> newGetNamespace) {
+			Builder<R> transformedBuilder = new Builder<>(newGetNamespace, this.namespaceElements.size());
+			transformedBuilder.size = this.size;
+			this.namespaceElements.forEach((n, es) -> {
+				List<R> transformedElements = new ArrayList<>(es.size());
+				es.forEach(e -> transformedElements.add(transformation.apply(e)));
+				transformedBuilder.namespaceElements.put(n, transformedElements);
+			});
+			return transformedBuilder;
+		}
+
 		// --- addition ---
 
 		@Override
@@ -219,6 +270,11 @@
 			}
 			prepareModification();
 
+			if (c instanceof Builder) {
+				@SuppressWarnings("unchecked")
+				Builder<E> builder = (Builder<E>) c;
+				return addAll(builder.namespaceElements);
+			}
 			String currentNamespace = null; // $NON-NLS-1$
 			List<E> currentNamespaceList = null;
 			for (E e : c) {
@@ -246,7 +302,11 @@
 			}
 			prepareModification();
 
-			list.namespaces().forEach((n, es) -> {
+			return addAll(list.namespaces());
+		}
+
+		private boolean addAll(Map<String, List<E>> perNamespaceElements) {
+			perNamespaceElements.forEach((n, es) -> {
 				getNamespaceList(n).addAll(es);
 				this.size += es.size();
 			});
@@ -257,13 +317,77 @@
 			return namespaceElements.computeIfAbsent(namespace, n -> new ArrayList<>());
 		}
 
-		public void addAfterLastMatch(E toAdd, Predicate<E> matcher) {
+		/**
+		 * Appends all elements in the specified NamespaceList to this builder that
+		 * satisfy both specified predicates for themselves and their namespace.
+		 * <p>
+		 * For an element to be added both predicates, the one for the namespace as well
+		 * as the one for the element itself must be satisfied.
+		 * </p>
+		 * 
+		 * @param list            the NamespaceList containing elements to be added
+		 * @param namespaceFilter the predicate that returns true for namespaces whose
+		 *                        elements should not be excluded from being added
+		 * @param elementFilter   the predicate that returns true for elements to be
+		 *                        added
+		 * 
+		 */
+		public void addAllFiltered(NamespaceList<E> list, Predicate<? super String> namespaceFilter,
+				Predicate<? super E> elementFilter) {
+
+			addAllFilteredAfterLastMatch(list, namespaceFilter, elementFilter, null);
+		}
+
+		/**
+		 * Inserts all elements in the specified NamespaceList to this builder that
+		 * satisfy both specified predicates for themselves and their namespace, after
+		 * the last element in this builder for the corresponding namespace that
+		 * satisfies the specified bi-predicate together with the corresponding element
+		 * to be added.
+		 * <p>
+		 * For an element to be added both predicates, the one for the namespace as well
+		 * as the one for the element itself must be satisfied. If both predicates are
+		 * satisfied by an element of the specified list, it is added after the <em>
+		 * last</em> element with the same namespace in this builder that satisfies the
+		 * specified bi-predicate together with the element to add.
+		 * </p>
+		 * 
+		 * @param list             the NamespaceList containing elements to be added
+		 * @param namespaceFilter  the predicate that returns true for namespaces whose
+		 *                         elements should not be excluded from being added
+		 * @param elementFilter    the predicate that returns true for elements to be
+		 *                         added
+		 * @param insertionMatcher the bi-predicate whose first argument is the element
+		 *                         to add and second argument is an element for the same
+		 *                         namespace in this builder, which returns true if the
+		 *                         element to add can be added after this builder's
+		 *                         element
+		 */
+		public void addAllFilteredAfterLastMatch(NamespaceList<E> list, Predicate<? super String> namespaceFilter,
+				Predicate<? super E> elementFilter, BiPredicate<E, E> insertionMatcher) {
+			if (list.isEmpty()) {
+				return;
+			}
 			prepareModification();
 
-			String namespace = getNamespace.apply(toAdd);
-			List<E> namespaceList = getNamespaceList(namespace);
-			addAfterLastMatch(toAdd, namespaceList, matcher);
-			this.size++;
+			list.namespaces().forEach((namespace, elementsToAdd) -> {
+				if (namespaceFilter.test(namespace)) {
+					List<E> targetList = getNamespaceList(namespace);
+					for (E toAdd : elementsToAdd) {
+						if (elementFilter.test(toAdd)) {
+							if (insertionMatcher == null) {
+								targetList.add(toAdd);
+							} else {
+								addAfterLastMatch(toAdd, targetList, e -> insertionMatcher.test(toAdd, e));
+							}
+							this.size++;
+						}
+					}
+					if (targetList.isEmpty()) { // maybe no elements are added
+						namespaceElements.remove(namespace);
+					}
+				}
+			});
 		}
 
 		private void addAfterLastMatch(E e, List<E> list, Predicate<E> matcher) {
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
index 34000ee..19f988b 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
@@ -826,11 +826,10 @@
 					(generation.getContentType() == Type.CONNECT ? "" : null), //$NON-NLS-1$
 					(allowRestrictedProvides ? "" : null)); //$NON-NLS-1$
 			if ((builder.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
-				for (ModuleRevisionBuilder.GenericInfo reqInfo : builder.getRequirements()) {
-					if (HostNamespace.HOST_NAMESPACE.equals(reqInfo.getNamespace())) {
-						if (HostNamespace.EXTENSION_BOOTCLASSPATH.equals(reqInfo.getDirectives().get(HostNamespace.REQUIREMENT_EXTENSION_DIRECTIVE))) {
-							throw new BundleException("Boot classpath extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$
-						}
+				for (ModuleRevisionBuilder.GenericInfo reqInfo : builder.getRequirements(HostNamespace.HOST_NAMESPACE)) {
+					if (HostNamespace.EXTENSION_BOOTCLASSPATH.equals(reqInfo.getDirectives().get(HostNamespace.REQUIREMENT_EXTENSION_DIRECTIVE))) {
+						throw new BundleException("Boot classpath extensions are not supported.", //$NON-NLS-1$
+								BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException());
 					}
 				}
 			}
diff --git a/bundles/org.eclipse.osgi/pom.xml b/bundles/org.eclipse.osgi/pom.xml
index cbd9ecd..c70978d 100644
--- a/bundles/org.eclipse.osgi/pom.xml
+++ b/bundles/org.eclipse.osgi/pom.xml
@@ -19,7 +19,7 @@
   </parent>
   <groupId>org.eclipse.osgi</groupId>
   <artifactId>org.eclipse.osgi</artifactId>
-  <version>3.16.400-SNAPSHOT</version>
+  <version>3.17.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
   <build>