Bug 572605 - Improve performance for name space wiring lookup

NamespaceList is introduced to index a flat list of elements which have
name spaces.  The NamespaceList assumes the element list is ordered such
that all elements with the same name space are order in one continuous
sequence.  Sublists of the original flat list are used to populate
a map indexed by the namespace.

NamespaceList is immutable.  Any updates require a copy of the full list
to be made and modified and then a new NamespaceList to be created.

Additional fixes are included to properly update the requirements and
capabilities for a host wiring when fragments are dynamically attached.

Change-Id: Iff2856fca7aaf5ab9fd1efe08b8778408860a146
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
Signed-off-by: Hannes Wellmann <wellmann.hannes1@gmx.net>
Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.framework/+/178989
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/AllTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/AllTests.java
index 47c527c..ab08f8e 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/AllTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/AllTests.java
@@ -23,6 +23,7 @@
 		suite.addTest(new JUnit4TestAdapter(TestModuleContainer.class));
 		suite.addTest(new JUnit4TestAdapter(ResolutionReportTest.class));
 		suite.addTest(new JUnit4TestAdapter(ModuleContainerUsageTest.class));
+		suite.addTest(new JUnit4TestAdapter(NamespaceListTest.class));
 		return suite;
 	}
 }
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
new file mode 100644
index 0000000..dea96f9
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/NamespaceListTest.java
@@ -0,0 +1,249 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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
+ *******************************************************************************/
+package org.eclipse.osgi.tests.container;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.function.Function;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+
+/**
+ * Using reflection because to avoid exporting internals.
+ */
+public class NamespaceListTest extends AbstractTest {
+	static final Method getList;
+	static final Method isEmpty;
+	static final Method getNamespaceIndex;
+	static final Method copyList;
+	static final Constructor<?> newNamespaceList;
+	static {
+		try {
+			Class<?> namespaceList = Bundle.class.getClassLoader()
+					.loadClass("org.eclipse.osgi.internal.container.NamespaceList");
+			getList = namespaceList.getMethod("getList", String.class);
+			isEmpty = namespaceList.getMethod("isEmpty");
+			getNamespaceIndex = namespaceList.getMethod("getNamespaceIndex", String.class);
+			copyList = namespaceList.getMethod("copyList");
+			newNamespaceList = namespaceList.getConstructor(List.class, Function.class);
+		} catch (Throwable t) {
+			throw new RuntimeException(t);
+		}
+	}
+	static class NamespaceElement {
+		final int id;
+		final String namespace;
+
+		public NamespaceElement(int id, String namespace) {
+			super();
+			this.id = id;
+			this.namespace = namespace;
+		}
+
+		public int getId() {
+			return id;
+		}
+
+		public String getNamespace() {
+			return namespace;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof NamespaceElement) {
+				NamespaceElement other = (NamespaceElement) o;
+				return this.id == other.id && this.namespace.equals(other.namespace);
+			}
+			return false;
+		}
+
+		@Override
+		public int hashCode() {
+			return namespace.hashCode() ^ id;
+		}
+
+		@Override
+		public String toString() {
+			return namespace + ':' + id;
+		}
+	}
+
+	static final Function<NamespaceElement, String> getNamespaceFunc = (Function<NamespaceElement, String>) NamespaceElement::getNamespace;
+	Object newNamespace(List<NamespaceElement> elements) throws Exception {
+		return newNamespaceList.newInstance(elements, getNamespaceFunc);
+	}
+	@Test
+	public void testIsEmpty() throws Exception {
+		Object namespaceList = newNamespace(Collections.emptyList());
+		assertTrue("List is not empty.", isEmpty(namespaceList));
+
+		List<NamespaceElement> elements = new ArrayList<>();
+		elements.add(new NamespaceElement(0, "ns1"));
+		namespaceList = newNamespace(elements);
+		assertFalse("List is empty.", isEmpty(namespaceList));
+
+		elements.add(new NamespaceElement(1, "ns2"));
+		namespaceList = newNamespace(elements);
+		assertFalse("List is empty.", isEmpty(namespaceList));
+	}
+
+	private boolean isEmpty(Object namespaceList) throws Exception {
+		return (boolean) isEmpty.invoke(namespaceList);
+	}
+
+	private List<NamespaceElement> getList(Object namespaceList, String namespace) throws Exception {
+		return (List<NamespaceElement>) getList.invoke(namespaceList, namespace);
+	}
+
+	@Test
+	public void testGetList() throws Exception {
+		Object namespaceList = newNamespace(Collections.emptyList());
+
+		List<NamespaceElement> list = getList(namespaceList, null);
+		assertTrue("List is not empty.", list.isEmpty());
+		failAdd(list);
+
+		list = getList(namespaceList, "ns-1");
+		assertTrue("List is not empty.", list.isEmpty());
+		failAdd(list);
+
+		List<NamespaceElement> elements = populate(5, 10);
+
+		namespaceList = newNamespace(elements);
+
+		for (int i = 0; i < 10; i++) {
+			list = getList(namespaceList, "ns-" + i);
+			failAdd(list);
+			assertEquals("Wrong list.", populate(5, "ns-" + i), list);
+		}
+
+		list = getList(namespaceList, null);
+		failAdd(list);
+		assertEquals("Wrong list.", populate(5, 10), list);
+	}
+
+	@Test
+	public void testGetNamespaceIndex() throws Exception {
+		Object namespaceList = newNamespace(Collections.emptyList());
+		assertNull("Unexpected index.", getNamespaceIndex(namespaceList, "ns-0"));
+
+		List<NamespaceElement> elements = populate(21, 13);
+
+		namespaceList = newNamespace(elements);
+		Entry<Integer, Integer> nsIndex = getNamespaceIndex(namespaceList, "ns-0");
+		assertNotNull("Expected an index", nsIndex);
+		checkIndex(nsIndex, 0, 21);
+
+		nsIndex = getNamespaceIndex(namespaceList, "ns-12");
+		assertNotNull("Expected an index", nsIndex);
+		checkIndex(nsIndex, 21 * 12, 21 * 13);
+
+		nsIndex = getNamespaceIndex(namespaceList, "ns-4");
+		assertNotNull("Expected an index", nsIndex);
+		checkIndex(nsIndex, 21 * 4, 21 * 5);
+	}
+
+	@Test
+	public void testOutOfOrderNamespace() throws Exception {
+		List<NamespaceElement> elements = populate(4, 4);
+		// random sort by hashcode
+		elements.sort((n1, n2) -> n1.hashCode() - n2.hashCode());
+		Object namespaceList = newNamespace(elements);
+		for (int i = 0; i < 4; i++) {
+			List<NamespaceElement> list = getList(namespaceList, "ns-" + i);
+			failAdd(list);
+			// list is random order now, but should all have the same namespace
+			assertEquals("Wrong number of elements", 4, list.size());
+			for (NamespaceElement e : list) {
+				assertEquals("Wrong namespace", "ns-" + i, e.getNamespace());
+			}
+		}
+	}
+
+	@Test
+	public void testCopyList() throws Exception {
+		Object namespaceList = newNamespace(Collections.emptyList());
+		List<NamespaceElement> copy = copyList(namespaceList);
+		assertEquals("Wrong list.", Collections.emptyList(), copy);
+		successAdd(copy);
+		copy = copyList(namespaceList);
+		assertEquals("Wrong list.", Collections.emptyList(), copy);
+
+		List<NamespaceElement> elements = populate(100, 13);
+		namespaceList = newNamespace(elements);
+		copy = copyList(namespaceList);
+		assertEquals("Wrong list.", elements, copy);
+		successAdd(copy);
+		copy = copyList(namespaceList);
+		assertEquals("Wrong list.", elements, copy);
+	}
+
+	private List<NamespaceElement> copyList(Object namespaceList) throws Exception {
+		return (List<NamespaceElement>) copyList.invoke(namespaceList);
+	}
+
+	private void checkIndex(Entry<Integer, Integer> nsIndex, int start, int end) {
+		assertEquals("Unexpected Start", start, (int) nsIndex.getKey());
+	}
+
+	private Entry<Integer, Integer> getNamespaceIndex(Object namespaceList, String namespace) throws Exception {
+		return (Entry<Integer, Integer>) getNamespaceIndex.invoke(namespaceList, namespace);
+	}
+
+	private void failAdd(List<NamespaceElement> list) {
+		try {
+			list.add(new NamespaceElement(0, "ns"));
+			fail("Should fail to modify list");
+		} catch (UnsupportedOperationException e) {
+			// expected
+		}
+	}
+
+	private void successAdd(List<NamespaceElement> list) {
+		try {
+			list.add(new NamespaceElement(0, "ns"));
+		} catch (UnsupportedOperationException e) {
+			fail("Should not fail to modify list");
+		}
+	}
+
+	private List<NamespaceElement> populate(int numElementsPerNS, int numNS) {
+		ArrayList<NamespaceElement> elements = new ArrayList<>(numElementsPerNS * numNS);
+		for (int namespace = 0; namespace < numNS; namespace++) {
+			for (int element = 0; element < numElementsPerNS; element++) {
+				elements.add(new NamespaceElement(element, "ns-" + namespace));
+			}
+		}
+		return elements;
+	}
+
+	private List<NamespaceElement> populate(int numElements, String namespace) {
+		ArrayList<NamespaceElement> elements = new ArrayList<>(numElements);
+		for (int element = 0; element < numElements; element++) {
+			elements.add(new NamespaceElement(element, namespace));
+		}
+		return elements;
+	}
+}
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java
index 53888f6..eaaac92 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/TestModuleContainer.java
@@ -3806,6 +3806,127 @@
 		assertEquals("Wrong state for moduleBF", State.INSTALLED, moduleBF.getState());
 	}
 
+	@Test
+	public void testModuleWiringLookup() throws BundleException {
+		DummyContainerAdaptor adaptor = createDummyAdaptor();
+		ModuleContainer container = adaptor.getContainer();
+
+		Map<String, String> manifestCore = new HashMap<>();
+		manifestCore.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		manifestCore.put(Constants.BUNDLE_SYMBOLICNAME, "core");
+		manifestCore.put(Constants.BUNDLE_VERSION, "1");
+		manifestCore.put(Constants.PROVIDE_CAPABILITY, "core");
+		manifestCore.put(Constants.EXPORT_PACKAGE, "core.a, core.b, core.dynamic.a, core.dynamic.b");
+		installDummyModule(manifestCore, "core", container);
+
+		Map<String, String> manifestA = new HashMap<>();
+		manifestA.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		manifestA.put(Constants.BUNDLE_SYMBOLICNAME, "a");
+		manifestA.put(Constants.BUNDLE_VERSION, "1");
+		manifestA.put(Constants.PROVIDE_CAPABILITY, "ca; ca=1, ca; ca=2, cb; cb=1, cb; cb=2, ca; ca=3");
+		manifestA.put(Constants.REQUIRE_CAPABILITY, "core");
+		manifestA.put(Constants.IMPORT_PACKAGE, "core.a, core.b");
+		manifestA.put(Constants.DYNAMICIMPORT_PACKAGE, "core.dynamic.*");
+		Module moduleA = installDummyModule(manifestA, "a", container);
+
+		Map<String, String> manifestB = new HashMap<>();
+		manifestB.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		manifestB.put(Constants.BUNDLE_SYMBOLICNAME, "b");
+		manifestB.put(Constants.BUNDLE_VERSION, "1");
+		manifestB.put(Constants.REQUIRE_CAPABILITY,
+				"ca; filter:=\"(ca=1)\", ca; filter:=\"(ca=2)\", cb; filter:=\"(cb=1)\", cb; filter:=\"(cb=2)\", ca; filter:=\"(ca=3)\"");
+		Module moduleB = installDummyModule(manifestB, "b", container);
+
+		container.resolve(Arrays.asList(moduleA, moduleB), false);
+
+		ModuleWiring wiringA = moduleA.getCurrentRevision().getWiring();
+		List<ModuleCapability> caCaps = wiringA.getModuleCapabilities("ca");
+		assertEquals("Wrong number of capabilities", 3, caCaps.size());
+		List<ModuleWire> caProvidedWires = wiringA.getProvidedModuleWires("ca");
+		assertEquals("Wrong number of wires.", 3, caProvidedWires.size());
+
+		ModuleWiring wiringB = moduleB.getCurrentRevision().getWiring();
+		List<ModuleRequirement> caReqs = wiringB.getModuleRequirements("ca");
+		assertEquals("Wrong number of requirements", 3, caReqs.size());
+		List<ModuleWire> caRequiredWires = wiringB.getRequiredModuleWires("ca");
+		assertEquals("Wrong number of wires.", 3, caRequiredWires.size());
+
+		Map<String, String> manifestAFrag = new HashMap<>();
+		manifestAFrag.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		manifestAFrag.put(Constants.BUNDLE_SYMBOLICNAME, "a.frag");
+		manifestAFrag.put(Constants.BUNDLE_VERSION, "1");
+		manifestAFrag.put(Constants.FRAGMENT_HOST, "a");
+		manifestAFrag.put(Constants.PROVIDE_CAPABILITY, "ca; ca=4, ca; ca=5, cb; cb=3, cb; cb=4, ca; ca=6");
+		installDummyModule(manifestAFrag, "a.frag", container);
+
+		Map<String, String> manifestBFrag = new HashMap<>();
+		manifestBFrag.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		manifestBFrag.put(Constants.BUNDLE_SYMBOLICNAME, "b.frag");
+		manifestBFrag.put(Constants.BUNDLE_VERSION, "1");
+		manifestBFrag.put(Constants.FRAGMENT_HOST, "b");
+		manifestBFrag.put(Constants.REQUIRE_CAPABILITY,
+				"ca; filter:=\"(ca=4)\", ca; filter:=\"(ca=5)\", cb; filter:=\"(cb=3)\", cb; filter:=\"(cb=4)\", ca; filter:=\"(ca=6)\"");
+		installDummyModule(manifestBFrag, "b.frag", container);
+
+		container.refresh(Arrays.asList(moduleA, moduleB));
+
+		wiringA = moduleA.getCurrentRevision().getWiring();
+		caCaps = wiringA.getModuleCapabilities("ca");
+		assertEquals("Wrong number of capabilities", 6, caCaps.size());
+		caProvidedWires = wiringA.getProvidedModuleWires("ca");
+		assertEquals("Wrong number of wires.", 6, caProvidedWires.size());
+
+		wiringB = moduleB.getCurrentRevision().getWiring();
+		caReqs = wiringB.getModuleRequirements("ca");
+		assertEquals("Wrong number of requirements", 6, caReqs.size());
+		caRequiredWires = wiringB.getRequiredModuleWires("ca");
+		assertEquals("Wrong number of wires.", 6, caRequiredWires.size());
+
+		// dynamically resolve a fragment to already resolved host, providing more
+		// capabilities and requirements
+		Map<String, String> manifestA2Frag = new HashMap<>();
+		manifestA2Frag.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		manifestA2Frag.put(Constants.BUNDLE_SYMBOLICNAME, "a.frag2");
+		manifestA2Frag.put(Constants.BUNDLE_VERSION, "1");
+		manifestA2Frag.put(Constants.FRAGMENT_HOST, "a");
+		manifestA2Frag.put(Constants.PROVIDE_CAPABILITY, "ca; ca=7, ca; ca=8, cb; cb=5, cb; cb=6, ca; ca=9");
+		manifestA2Frag.put(Constants.REQUIRE_CAPABILITY, "ca; filter:=\"(ca=6)\"");
+		Module moduleAFrag2 = installDummyModule(manifestA2Frag, "a.frag1", container);
+
+		container.resolve(Arrays.asList(moduleAFrag2), true);
+		assertEquals("Wrong state for frag2", State.RESOLVED, moduleAFrag2.getState());
+		caCaps = wiringA.getModuleCapabilities("ca");
+		assertEquals("Wrong number of capabilities", 9, caCaps.size());
+		caProvidedWires = wiringA.getProvidedModuleWires("ca");
+		assertEquals("Wrong number of wires.", 7, caProvidedWires.size());
+		caReqs = wiringA.getModuleRequirements("ca");
+		assertEquals("Wrong number of requirements.", 1, caReqs.size());
+		caRequiredWires = wiringA.getRequiredModuleWires("ca");
+		assertEquals("Wrong number of wires.", 1, caRequiredWires.size());
+
+		List<ModuleRequirement> pkgReqs = wiringA.getModuleRequirements(PackageNamespace.PACKAGE_NAMESPACE);
+		assertEquals("Wrong number of requirements.", 3, pkgReqs.size());
+		List<ModuleWire> pkgWires = wiringA.getRequiredModuleWires(PackageNamespace.PACKAGE_NAMESPACE);
+		assertEquals("Wring number of wires.", 2, pkgWires.size());
+
+		ModuleWire dynamicImport1 = container.resolveDynamic("core.dynamic.a", moduleA.getCurrentRevision());
+		assertNotNull("Dynamic resolve failed", dynamicImport1);
+
+		pkgReqs = wiringA.getModuleRequirements(PackageNamespace.PACKAGE_NAMESPACE);
+		assertEquals("Wrong number of requirements.", 3, pkgReqs.size());
+		assertEquals("Wrong last package requirement.", PackageNamespace.RESOLUTION_DYNAMIC,
+				pkgReqs.get(pkgReqs.size() - 1).getDirectives().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE));
+		pkgWires = wiringA.getRequiredModuleWires(PackageNamespace.PACKAGE_NAMESPACE);
+		assertEquals("Wring number of wires.", 3, pkgWires.size());
+		assertEquals("Wrong last package wire.", dynamicImport1, pkgWires.get(pkgWires.size() - 1));
+
+		ModuleWire dynamicImport2 = container.resolveDynamic("core.dynamic.b", moduleA.getCurrentRevision());
+		assertNotNull("Dynamic resolve failed", dynamicImport2);
+		pkgWires = wiringA.getRequiredModuleWires(PackageNamespace.PACKAGE_NAMESPACE);
+		assertEquals("Wring number of wires.", 4, pkgWires.size());
+		assertEquals("Wrong last package wire.", dynamicImport2, pkgWires.get(pkgWires.size() - 1));
+	}
+
 	private static void assertWires(List<ModuleWire> required, List<ModuleWire>... provided) {
 		for (ModuleWire requiredWire : required) {
 			for (List<ModuleWire> providedList : provided) {
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java
index 619dec1..52367f3 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java
@@ -13,6 +13,8 @@
  *******************************************************************************/
 package org.eclipse.osgi.container;
 
+import static org.eclipse.osgi.internal.container.NamespaceList.WIRE;
+
 import java.io.Closeable;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -51,6 +53,7 @@
 import org.eclipse.osgi.framework.util.ThreadInfoReport;
 import org.eclipse.osgi.internal.container.InternalUtils;
 import org.eclipse.osgi.internal.container.LockSet;
+import org.eclipse.osgi.internal.container.NamespaceList;
 import org.eclipse.osgi.internal.debug.Debug;
 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
 import org.eclipse.osgi.internal.messages.Msg;
@@ -805,9 +808,10 @@
 					ModuleWiring current = wiringCopy.get(deltaEntry.getKey());
 					if (current != null) {
 						// need to update the provided capabilities, provided and required wires for currently resolved
-						current.setCapabilities(deltaEntry.getValue().getModuleCapabilities(null));
-						current.setProvidedWires(deltaEntry.getValue().getProvidedModuleWires(null));
-						current.setRequiredWires(deltaEntry.getValue().getRequiredModuleWires(null));
+						current.setCapabilities(deltaEntry.getValue().getCapabilities());
+						current.setProvidedWires(deltaEntry.getValue().getProvidedWires());
+						current.setRequirements(deltaEntry.getValue().getRequirements());
+						current.setRequiredWires(deltaEntry.getValue().getRequiredWires());
 						deltaEntry.setValue(current); // set the real wiring into the delta
 					} else {
 						ModuleRevision revision = deltaEntry.getValue().getRevision();
@@ -1045,9 +1049,9 @@
 					return null; // need to try again
 				// remove any wires from unresolved wirings that got removed
 				for (Map.Entry<ModuleWiring, Collection<ModuleWire>> entry : toRemoveWireLists.entrySet()) {
-					List<ModuleWire> provided = entry.getKey().getProvidedModuleWires(null);
+					List<ModuleWire> provided = entry.getKey().getProvidedWires().copyList();
 					provided.removeAll(entry.getValue());
-					entry.getKey().setProvidedWires(provided);
+					entry.getKey().setProvidedWires(new NamespaceList<>(provided, WIRE));
 					for (ModuleWire removedWire : entry.getValue()) {
 						// invalidate the wire
 						removedWire.invalidate();
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 9ce1075..4e8689a 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
@@ -13,6 +13,8 @@
  *******************************************************************************/
 package org.eclipse.osgi.container;
 
+import static org.eclipse.osgi.internal.container.NamespaceList.WIRE;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -41,6 +43,7 @@
 import org.eclipse.osgi.framework.util.ObjectPool;
 import org.eclipse.osgi.internal.container.Capabilities;
 import org.eclipse.osgi.internal.container.ComputeNodeOrder;
+import org.eclipse.osgi.internal.container.NamespaceList;
 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
@@ -409,10 +412,9 @@
 				}
 				// remove any wires from unresolved wirings that got removed
 				for (Map.Entry<ModuleWiring, Collection<ModuleWire>> entry : toRemoveWireLists.entrySet()) {
-					List<ModuleWire> provided = entry.getKey().getProvidedModuleWires(null);
-					// No null checks; we are holding the write lock here.
+					List<ModuleWire> provided = entry.getKey().getProvidedWires().copyList();
 					provided.removeAll(entry.getValue());
-					entry.getKey().setProvidedWires(provided);
+					entry.getKey().setProvidedWires(new NamespaceList<>(provided, WIRE));
 					for (ModuleWire removedWire : entry.getValue()) {
 						// invalidate the wire
 						removedWire.invalidate();
@@ -503,7 +505,9 @@
 		try {
 			Map<ModuleRevision, ModuleWiring> clonedWirings = new HashMap<>();
 			for (Map.Entry<ModuleRevision, ModuleWiring> entry : wirings.entrySet()) {
-				ModuleWiring wiring = new ModuleWiring(entry.getKey(), entry.getValue().getModuleCapabilities(null), entry.getValue().getModuleRequirements(null), entry.getValue().getProvidedModuleWires(null), entry.getValue().getRequiredModuleWires(null), entry.getValue().getSubstitutedNames());
+				ModuleWiring wiring = new ModuleWiring(entry.getKey(), entry.getValue().getCapabilities(),
+						entry.getValue().getRequirements(), entry.getValue().getProvidedWires(),
+						entry.getValue().getRequiredWires(), entry.getValue().getSubstitutedNames());
 				clonedWirings.put(entry.getKey(), wiring);
 			}
 			return clonedWirings;
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 c072677..3a40b5b 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,6 +13,10 @@
  *******************************************************************************/
 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;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,12 +40,14 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
 import org.apache.felix.resolver.Logger;
 import org.apache.felix.resolver.ResolutionError;
 import org.apache.felix.resolver.ResolverImpl;
 import org.eclipse.osgi.container.ModuleRequirement.DynamicModuleRequirement;
 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.debug.Debug;
 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
 import org.eclipse.osgi.internal.framework.EquinoxContainer;
@@ -183,9 +189,11 @@
 		for (Map.Entry<Resource, List<Wire>> resultEntry : result.entrySet()) {
 			ModuleRevision revision = (ModuleRevision) resultEntry.getKey();
 			List<ModuleWire> requiredWires = new ArrayList<>(resultEntry.getValue().size());
+			List<String> seen = new ArrayList<>(5);
 			for (Wire wire : resultEntry.getValue()) {
 				ModuleWire moduleWire = new ModuleWire((ModuleCapability) wire.getCapability(), (ModuleRevision) wire.getProvider(), (ModuleRequirement) wire.getRequirement(), (ModuleRevision) wire.getRequirer());
-				requiredWires.add(moduleWire);
+				requiredWires.add(getInsertIndex(moduleWire.getCapability().getNamespace(), requiredWires, seen,
+						NamespaceList.WIRE), moduleWire);
 				Map<ModuleCapability, List<ModuleWire>> providedWiresMap = provided.get(moduleWire.getProvider());
 				if (providedWiresMap == null) {
 					providedWiresMap = new HashMap<>();
@@ -232,9 +240,9 @@
 		if (requiredWires == null)
 			requiredWires = Collections.emptyList();
 
-		List<ModuleCapability> capabilities = new ArrayList<>(revision.getModuleCapabilities(null));
+		List<ModuleCapability> capabilities = sortByNamespaceCopy(revision.getModuleCapabilities(null), CAPABILITY);
 		ListIterator<ModuleCapability> iCapabilities = capabilities.listIterator(capabilities.size());
-		List<ModuleRequirement> requirements = new ArrayList<>(revision.getModuleRequirements(null));
+		List<ModuleRequirement> requirements = sortByNamespaceCopy(revision.getModuleRequirements(null), REQUIREMENT);
 		ListIterator<ModuleRequirement> iRequirements = requirements.listIterator(requirements.size());
 
 		// if revision is a fragment remove payload requirements and capabilities
@@ -257,7 +265,33 @@
 		addProvidedWires(providedWireMap, providedWires, capabilities);
 
 		InternalUtils.filterCapabilityPermissions(capabilities);
-		return new ModuleWiring(revision, capabilities, requirements, providedWires, requiredWires, substituted);
+		return new ModuleWiring(revision, new NamespaceList<>(capabilities, CAPABILITY),
+				new NamespaceList<>(requirements, REQUIREMENT), new NamespaceList<>(providedWires, WIRE),
+				new NamespaceList<>(requiredWires, WIRE), substituted);
+	}
+
+	private <E> List<E> sortByNamespaceCopy(List<E> elements, Function<E, String> getNamespace) {
+		List<E> result = new ArrayList<>(elements.size());
+		List<String> seen = new ArrayList<>();
+		for (E e : elements) {
+			String namespace = getNamespace.apply(e);
+			result.add(getInsertIndex(namespace, result, seen, getNamespace), e);
+		}
+		return result;
+	}
+
+	private <E> int getInsertIndex(String namespace, List<E> elements, List<String> seen,
+			Function<E, String> getNamespace) {
+		if (seen.contains(namespace)) {
+			for (int i = elements.size() - 1; i > -1; i--) {
+				if (namespace.equals(getNamespace.apply(elements.get(i)))) {
+					return i + 1;
+				}
+			}
+			throw new IllegalStateException();
+		}
+		seen.add(namespace);
+		return elements.size();
 	}
 
 	private static void removePayloadContent(ListIterator<ModuleCapability> iCapabilities, ListIterator<ModuleRequirement> iRequirements) {
@@ -379,6 +413,9 @@
 						}
 					}
 				}
+				if (!iCapabilities.hasPrevious()) {
+					fastForward(iCapabilities);
+				}
 				iCapabilities.add(fragmentCapability);
 			}
 			// add fragment requirements
@@ -406,6 +443,9 @@
 						}
 					}
 				}
+				if (!iRequirements.hasPrevious()) {
+					fastForward(iRequirements);
+				}
 				iRequirements.add(fragmentRequirement);
 			}
 		}
@@ -466,10 +506,10 @@
 
 	private static ModuleWiring createWiringDelta(ModuleRevision revision, ModuleWiring existingWiring, Map<ModuleCapability, List<ModuleWire>> providedWireMap, List<ModuleWire> requiredWires) {
 		// No null checks are done here on the wires since this is a copy.
-		List<ModuleWire> existingProvidedWires = existingWiring.getProvidedModuleWires(null);
-		List<ModuleCapability> existingCapabilities = existingWiring.getModuleCapabilities(null);
-		List<ModuleWire> existingRequiredWires = existingWiring.getRequiredModuleWires(null);
-		List<ModuleRequirement> existingRequirements = existingWiring.getModuleRequirements(null);
+		List<ModuleWire> existingProvidedWires = existingWiring.getProvidedWires().copyList();
+		List<ModuleCapability> existingCapabilities = existingWiring.getCapabilities().copyList();
+		List<ModuleWire> existingRequiredWires = existingWiring.getRequiredWires().copyList();
+		List<ModuleRequirement> existingRequirements = existingWiring.getRequirements().copyList();
 
 		// First, add newly resolved fragment capabilities and requirements
 		if (providedWireMap != null) {
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 7f21644..e63df7f 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
@@ -13,19 +13,39 @@
  *******************************************************************************/
 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.net.URL;
-import java.util.*;
+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.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicReference;
 import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
 import org.eclipse.osgi.internal.container.AtomicLazyInitializer;
 import org.eclipse.osgi.internal.container.InternalUtils;
+import org.eclipse.osgi.internal.container.NamespaceList;
 import org.osgi.framework.AdminPermission;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.namespace.HostNamespace;
 import org.osgi.framework.namespace.PackageNamespace;
-import org.osgi.framework.wiring.*;
-import org.osgi.resource.*;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Wire;
 
 /**
  * An implementation of {@link BundleWiring}.
@@ -45,24 +65,38 @@
 	private static final RuntimePermission GET_CLASSLOADER_PERM = new RuntimePermission("getClassLoader"); //$NON-NLS-1$
 	private static final String DYNAMICALLY_ADDED_IMPORT_DIRECTIVE = "x.dynamically.added"; //$NON-NLS-1$
 	private final ModuleRevision revision;
-	private volatile List<ModuleCapability> capabilities;
-	private volatile List<ModuleRequirement> requirements;
+	private volatile NamespaceList<ModuleCapability> capabilities;
+	private volatile NamespaceList<ModuleRequirement> requirements;
 	private final Collection<String> substitutedPkgNames;
 	private final AtomicLazyInitializer<ModuleLoader> loader = new AtomicLazyInitializer<>();
 	private final LoaderInitializer loaderInitializer = new LoaderInitializer();
-	private volatile List<ModuleWire> providedWires;
-	private volatile List<ModuleWire> requiredWires;
+	private volatile NamespaceList<ModuleWire> providedWires;
+	private volatile NamespaceList<ModuleWire> requiredWires;
 	volatile boolean isValid = true;
 	private final AtomicReference<Set<String>> dynamicMissRef = new AtomicReference<>();
 
-	ModuleWiring(ModuleRevision revision, List<ModuleCapability> capabilities, List<ModuleRequirement> requirements, List<ModuleWire> providedWires, List<ModuleWire> requiredWires, Collection<String> substitutedPkgNames) {
+	ModuleWiring(ModuleRevision revision, List<ModuleCapability> capabilities, List<ModuleRequirement> requirements,
+			List<ModuleWire> providedWires, List<ModuleWire> requiredWires, Collection<String> substitutedPkgNames) {
+		super();
+		this.revision = revision;
+		this.capabilities = new NamespaceList<>(capabilities, CAPABILITY);
+		this.requirements = new NamespaceList<>(requirements, REQUIREMENT);
+		this.providedWires = new NamespaceList<>(providedWires, WIRE);
+		this.requiredWires = new NamespaceList<>(requiredWires, WIRE);
+		this.substitutedPkgNames = substitutedPkgNames.isEmpty() ? Collections.<String> emptyList() : substitutedPkgNames;
+	}
+
+	ModuleWiring(ModuleRevision revision, NamespaceList<ModuleCapability> capabilities,
+			NamespaceList<ModuleRequirement> requirements, NamespaceList<ModuleWire> providedWires,
+			NamespaceList<ModuleWire> requiredWires, Collection<String> substitutedPkgNames) {
 		super();
 		this.revision = revision;
 		this.capabilities = capabilities;
 		this.requirements = requirements;
 		this.providedWires = providedWires;
 		this.requiredWires = requiredWires;
-		this.substitutedPkgNames = substitutedPkgNames.isEmpty() ? Collections.<String> emptyList() : substitutedPkgNames;
+		this.substitutedPkgNames = substitutedPkgNames.isEmpty() ? Collections.<String>emptyList()
+				: substitutedPkgNames;
 	}
 
 	@Override
@@ -98,21 +132,10 @@
 	 * @see #getCapabilities(String)
 	 */
 	public List<ModuleCapability> getModuleCapabilities(String namespace) {
-		return getModuleCapabilities(namespace, capabilities);
-	}
-
-	private List<ModuleCapability> getModuleCapabilities(String namespace, List<ModuleCapability> allCapabilities) {
-		if (!isValid)
+		if (!isValid) {
 			return null;
-		if (namespace == null)
-			return new ArrayList<>(allCapabilities);
-		List<ModuleCapability> result = new ArrayList<>();
-		for (ModuleCapability capability : allCapabilities) {
-			if (namespace.equals(capability.getNamespace())) {
-				result.add(capability);
-			}
 		}
-		return result;
+		return capabilities.getList(namespace);
 	}
 
 	/**
@@ -123,14 +146,17 @@
 	 * @see #getRequirements(String)
 	 */
 	public List<ModuleRequirement> getModuleRequirements(String namespace) {
-		return getModuleRequirements(namespace, requirements);
+		if (!isValid) {
+			return null;
+		}
+		return requirements.getList(namespace);
 	}
 
 	List<ModuleRequirement> getPersistentRequirements() {
-		List<ModuleRequirement> persistentRequriements = getModuleRequirements(null);
-		if (persistentRequriements == null) {
+		if (!isValid) {
 			return null;
 		}
+		List<ModuleRequirement> persistentRequriements = requirements.copyList();
 		for (Iterator<ModuleRequirement> iRequirements = persistentRequriements.iterator(); iRequirements.hasNext();) {
 			ModuleRequirement requirement = iRequirements.next();
 			if (PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())) {
@@ -142,20 +168,6 @@
 		return persistentRequriements;
 	}
 
-	private List<ModuleRequirement> getModuleRequirements(String namespace, List<ModuleRequirement> allRequirements) {
-		if (!isValid)
-			return null;
-		if (namespace == null)
-			return new ArrayList<>(allRequirements);
-		List<ModuleRequirement> result = new ArrayList<>();
-		for (ModuleRequirement requirement : allRequirements) {
-			if (namespace.equals(requirement.getNamespace())) {
-				result.add(requirement);
-			}
-		}
-		return result;
-	}
-
 	@Override
 	public List<BundleCapability> getCapabilities(String namespace) {
 		return InternalUtils.asListBundleCapability(getModuleCapabilities(namespace));
@@ -197,11 +209,11 @@
 		return getPersistentWires(requiredWires);
 	}
 
-	private List<ModuleWire> getPersistentWires(List<ModuleWire> allWires) {
-		List<ModuleWire> persistentWires = getWires(null, allWires);
-		if (persistentWires == null) {
+	private List<ModuleWire> getPersistentWires(NamespaceList<ModuleWire> allWires) {
+		if (!isValid) {
 			return null;
 		}
+		List<ModuleWire> persistentWires = allWires.copyList();
 		for (Iterator<ModuleWire> iWires = persistentWires.iterator(); iWires.hasNext();) {
 			ModuleWire wire = iWires.next();
 			if (PackageNamespace.PACKAGE_NAMESPACE.equals(wire.getRequirement().getNamespace())) {
@@ -223,18 +235,11 @@
 		return InternalUtils.asListBundleWire(getWires(namespace, requiredWires));
 	}
 
-	private List<ModuleWire> getWires(String namespace, List<ModuleWire> allWires) {
-		if (!isValid)
+	private List<ModuleWire> getWires(String namespace, NamespaceList<ModuleWire> wires) {
+		if (!isValid) {
 			return null;
-		if (namespace == null)
-			return new ArrayList<>(allWires);
-		List<ModuleWire> result = new ArrayList<>();
-		for (ModuleWire moduleWire : allWires) {
-			if (namespace.equals(moduleWire.getCapability().getNamespace())) {
-				result.add(moduleWire);
-			}
 		}
-		return result;
+		return wires.getList(namespace);
 	}
 
 	@Override
@@ -332,18 +337,22 @@
 		return revision;
 	}
 
-	void setProvidedWires(List<ModuleWire> providedWires) {
+	void setProvidedWires(NamespaceList<ModuleWire> providedWires) {
 		this.providedWires = providedWires;
 	}
 
-	void setRequiredWires(List<ModuleWire> requiredWires) {
+	void setRequiredWires(NamespaceList<ModuleWire> requiredWires) {
 		this.requiredWires = requiredWires;
 	}
 
-	void setCapabilities(List<ModuleCapability> capabilities) {
+	void setCapabilities(NamespaceList<ModuleCapability> capabilities) {
 		this.capabilities = capabilities;
 	}
 
+	void setRequirements(NamespaceList<ModuleRequirement> requirements) {
+		this.requirements = requirements;
+	}
+
 	void unload() {
 		// When unloading a wiring we need to release the loader.
 		// This is so that the loaders are not pinned when stopping the framework.
@@ -427,9 +436,15 @@
 		ModuleDatabase moduleDatabase = revision.getRevisions().getContainer().moduleDatabase;
 		moduleDatabase.writeLock();
 		try {
-			List<ModuleRequirement> updatedRequirements = new ArrayList<>(requirements);
-			updatedRequirements.addAll(newRequirements);
-			requirements = updatedRequirements;
+			List<ModuleRequirement> updatedRequirements = requirements.copyList();
+			Entry<Integer, Integer> packageStartEnd = requirements
+					.getNamespaceIndex(PackageNamespace.PACKAGE_NAMESPACE);
+			if (packageStartEnd == null) {
+				updatedRequirements.addAll(newRequirements);
+			} else {
+				updatedRequirements.addAll(packageStartEnd.getValue(), newRequirements);
+			}
+			requirements = new NamespaceList<>(updatedRequirements, REQUIREMENT);
 		} finally {
 			moduleDatabase.writeUnlock();
 		}
@@ -468,15 +483,29 @@
 		}
 		// Could cache this, but seems unnecessary since it will only be used by the resolver
 		List<Wire> substitutionWires = new ArrayList<>(substitutedPkgNames.size());
-		List<ModuleWire> current = requiredWires;
+		List<ModuleWire> current = requiredWires.getList(PackageNamespace.PACKAGE_NAMESPACE);
 		for (ModuleWire wire : current) {
 			Capability cap = wire.getCapability();
-			if (PackageNamespace.PACKAGE_NAMESPACE.equals(cap.getNamespace())) {
-				if (substitutedPkgNames.contains(cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) {
-					substitutionWires.add(wire);
-				}
+			if (substitutedPkgNames.contains(cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) {
+				substitutionWires.add(wire);
 			}
 		}
 		return substitutionWires;
 	}
+
+	NamespaceList<ModuleCapability> getCapabilities() {
+		return capabilities;
+	}
+
+	NamespaceList<ModuleWire> getProvidedWires() {
+		return providedWires;
+	}
+
+	NamespaceList<ModuleRequirement> getRequirements() {
+		return requirements;
+	}
+
+	NamespaceList<ModuleWire> getRequiredWires() {
+		return requiredWires;
+	}
 }
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
new file mode 100644
index 0000000..74375c1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/NamespaceList.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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
+ *******************************************************************************/
+package org.eclipse.osgi.internal.container;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Function;
+import org.eclipse.osgi.container.ModuleCapability;
+import org.eclipse.osgi.container.ModuleRequirement;
+import org.eclipse.osgi.container.ModuleWire;
+
+/**
+ * An immutable list of elements for which each element has a name space. All
+ * elements are kept in a single list to avoid creating a list for each name
+ * space. The elements provided at construction are assumed to be ordered such
+ * that all elements with the same name space are ordered together in one
+ * continuous sequence An indexes list is used to keep track of the beginning
+ * indexes for each namespace contained in the list. Assuming the number of
+ * namespaces used by a bundle is relatively small compared to the overall
+ * number of elements this makes it faster to find the first index for a
+ * specific namespace without having to iterate over the complete list of
+ * elements.
+ * 
+ * @param <E> The element type which will have a name space associated with it
+ */
+public class NamespaceList<E> {
+
+	public final static Function<ModuleWire, String> WIRE = wire -> {
+		return wire.getCapability().getNamespace();
+	};
+	public final static Function<ModuleCapability, String> CAPABILITY = ModuleCapability::getNamespace;
+	public final static Function<ModuleRequirement, String> REQUIREMENT = ModuleRequirement::getNamespace;
+
+	private final List<E> elements;
+	private final Map<String, List<E>> namespaces;
+
+	/**
+	 * Create a new name space list with the specified elements. The elements must
+	 * be sorted properly by name space.
+	 * 
+	 * @param elements     the ordered list of elements
+	 * @param getNamespace a function that retrieves the namespace for each element
+	 */
+	public NamespaceList(List<E> elements, Function<E, String> getNamespace) {
+		List<E> unmodifiableElements = Collections.unmodifiableList(elements);
+
+		Map<String, List<E>> tmpNamespaces = new LinkedHashMap<>(5);
+		int size = unmodifiableElements.size();
+		int currentStart = 0;
+		String current = null;
+
+		boolean consolidated = false;
+		for (int i = 0; i < size; i++) {
+			String namespace = getNamespace.apply(unmodifiableElements.get(i));
+			if (current == null) {
+				current = namespace;
+			}
+			if (!current.equals(namespace)) {
+				int currentEnd = i;
+				consolidated |= addNamespaceList(tmpNamespaces, unmodifiableElements, current, currentStart,
+						currentEnd);
+				current = namespace;
+				currentStart = i;
+			}
+		}
+		if (size > 0) {
+			// add the last namespace
+			consolidated |= addNamespaceList(tmpNamespaces, unmodifiableElements, current, currentStart, size);
+		}
+		if (consolidated) {
+			List<E> tmpElements = new ArrayList<>(size);
+			for (Entry<String, List<E>> e : tmpNamespaces.entrySet()) {
+				tmpElements.addAll(e.getValue());
+			}
+			this.elements = Collections.unmodifiableList(tmpElements);
+		} else {
+			this.elements = unmodifiableElements;
+		}
+		this.namespaces = Collections.unmodifiableMap(tmpNamespaces);
+	}
+
+	private boolean addNamespaceList(Map<String, List<E>> tmpNamespaces, List<E> unmodifiableElements, String namespace,
+			int start, int end) {
+		List<E> namespaceList = unmodifiableElements.subList(start, end);
+		List<E> existing = tmpNamespaces.get(namespace);
+		boolean consolidated = false;
+		if (existing != null) {
+			// This should be an error, consolidate the lists
+			List<E> consolidateList = new ArrayList<>(existing);
+			consolidateList.addAll(namespaceList);
+			namespaceList = Collections.unmodifiableList(consolidateList);
+			consolidated = true;
+		}
+		tmpNamespaces.put(namespace, namespaceList);
+		return consolidated;
+	}
+
+	public boolean isEmpty() {
+		return elements.isEmpty();
+	}
+
+	/**
+	 * returns an unmodifiable list of elements with the specified name space. An
+	 * empty list is returned if there are no elements with the specified namespace.
+	 * A {@code null} namespace can be used to get all elements.
+	 * 
+	 * @param namespace the name space of the elements to return. May be
+	 *                  {@code null}
+	 * @return The list of elements found
+	 */
+	public List<E> getList(String namespace) {
+		if (namespace == null) {
+			return elements;
+		}
+
+		return namespaces.getOrDefault(namespace, Collections.emptyList());
+	}
+
+	/**
+	 * Returns the beginning index (inclusively) and ending index (exclusively) or
+	 * {@code null} if no element has the specified namespace
+	 * 
+	 * @param namespace the name space to find the indexes for
+	 * @return indexes found for the namespace or {@code null} if no elements exist
+	 *         with the name space.
+	 */
+	public Entry<Integer, Integer> getNamespaceIndex(String namespace) {
+		int startIndex = 0;
+		for (Entry<String, ? extends List<E>> entry : namespaces.entrySet()) {
+			if (entry.getKey().equals(namespace)) {
+				int end = startIndex + entry.getValue().size();
+				return new AbstractMap.SimpleEntry<>(startIndex, end);
+			}
+			startIndex += entry.getValue().size();
+		}
+		return null;
+	}
+
+	/**
+	 * Returns a copy of all the elements in this list
+	 * 
+	 * @return a copy of all the elements in this list
+	 */
+	public List<E> copyList() {
+		return new ArrayList<>(elements);
+	}
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
index af785f6..a1f451e 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
@@ -174,7 +174,7 @@
 		// init the provided packages set
 		exportSources = new BundleLoaderSources(this);
 		List<ModuleCapability> exports = wiring.getModuleCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
-		exports = exports == null ? new ArrayList<>(0) : exports;
+		exports = exports == null ? Collections.emptyList() : exports;
 		exportedPackages = Collections.synchronizedCollection(exports.size() > 10 ? new HashSet<String>(exports.size()) : new ArrayList<String>(exports.size()));
 		initializeExports(exports, exportSources, exportedPackages);
 
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/NativeCodeFinder.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/NativeCodeFinder.java
index b7d5821..bc3b4f9 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/NativeCodeFinder.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/NativeCodeFinder.java
@@ -15,8 +15,14 @@
 package org.eclipse.osgi.storage;
 
 import java.io.File;
-import java.util.*;
-import org.eclipse.osgi.container.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.osgi.container.ModuleRevision;
+import org.eclipse.osgi.container.ModuleWire;
+import org.eclipse.osgi.container.ModuleWiring;
 import org.eclipse.osgi.internal.debug.Debug;
 import org.eclipse.osgi.internal.framework.FilterImpl;
 import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook;
@@ -174,7 +180,7 @@
 		}
 
 		List<ModuleWire> nativeCode = wiring.getRequiredModuleWires(NativeNamespace.NATIVE_NAMESPACE);
-		if (nativeCode.isEmpty()) {
+		if (nativeCode == null || nativeCode.isEmpty()) {
 			return Collections.emptyList();
 		}