Add usage tests

This class is intended to test scenarios that ModuleContainer could be
used for outside of the framework.

Change-Id: If252d7d8d29a04d2652c6085fd22c5afa1abb84b
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
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 df5439b..47c527c 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
@@ -13,13 +13,16 @@
  *******************************************************************************/
 package org.eclipse.osgi.tests.container;
 
-import junit.framework.*;
+import junit.framework.JUnit4TestAdapter;
+import junit.framework.Test;
+import junit.framework.TestSuite;
 
 public class AllTests {
 	public static Test suite() {
 		TestSuite suite = new TestSuite(AllTests.class.getName());
 		suite.addTest(new JUnit4TestAdapter(TestModuleContainer.class));
 		suite.addTest(new JUnit4TestAdapter(ResolutionReportTest.class));
+		suite.addTest(new JUnit4TestAdapter(ModuleContainerUsageTest.class));
 		return suite;
 	}
 }
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/ModuleContainerUsageTest.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/ModuleContainerUsageTest.java
new file mode 100644
index 0000000..112f557
--- /dev/null
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/container/ModuleContainerUsageTest.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (c) 2020 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.tests.container;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.osgi.container.Module;
+import org.eclipse.osgi.container.Module.State;
+import org.eclipse.osgi.container.ModuleCapability;
+import org.eclipse.osgi.container.ModuleContainer;
+import org.eclipse.osgi.container.ModuleRevision;
+import org.eclipse.osgi.container.ModuleRevisionBuilder;
+import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory;
+import org.eclipse.osgi.report.resolution.ResolutionReport;
+import org.eclipse.osgi.tests.container.dummys.DummyContainerAdaptor;
+import org.junit.Test;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+
+public class ModuleContainerUsageTest extends AbstractTest {
+	static final String EE_INDEX = "internal.ee.index";
+
+	@Test
+	public void testMultipleExecutionEnvironments() throws BundleException {
+
+		final List<BundleCapability> executionEnvironments = Collections.synchronizedList(new ArrayList<>());
+
+		/**
+		 * A resolver hook that filters packages based on the required EE of a module
+		 */
+		ResolverHook resolverHook = new ResolverHook() {
+
+			@Override
+			public void filterSingletonCollisions(BundleCapability singleton,
+					Collection<BundleCapability> collisionCandidates) {
+				// don't care about possible collisions
+				collisionCandidates.clear();
+			}
+
+			@Override
+			public void filterResolvable(Collection<BundleRevision> candidates) {
+				// don't stop any revision from resolving
+			}
+
+			@Override
+			public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
+				if (PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())) {
+					// only filter based on EE for package requirements
+					filterBasedOnEE(
+							(AtomicReference<Long>) ((ModuleRevision) requirement.getRevision()).getRevisionInfo(),
+							candidates);
+				}
+			}
+
+			private void filterBasedOnEE(AtomicReference<Long> revisionInfo,
+					Collection<BundleCapability> candidates) {
+				Long eeIndex = revisionInfo.get();
+				// if the eeIndex is set for the revision then check if the package is
+				// available in the specified EE
+				if (eeIndex != null) {
+					Iterator<BundleCapability> iCandidates = candidates.iterator();
+					while (iCandidates.hasNext()) {
+						ModuleCapability candidate = (ModuleCapability) iCandidates.next();
+						// only check the ee index for the packages from the system module (id=0)
+						if (candidate.getRevision().getRevisions().getModule().getId() == 0) {
+							Object candidateEEIndex = candidate.getAttributes().get(EE_INDEX);
+							if (candidateEEIndex != null && !eeIndex.equals(candidateEEIndex)) {
+								iCandidates.remove();
+							}
+						}
+					}
+				}
+			}
+
+			@Override
+			public void end() {
+				// do nothing special at the end of resolution
+			}
+		};
+		DummyContainerAdaptor adaptor = createDummyAdaptor(resolverHook);
+		ModuleContainer container = adaptor.getContainer();
+
+		// Notice that there are duplicate packages exported for each EE_INDEX
+		StringBuilder systemPackages = new StringBuilder();
+		systemPackages.append("test.pkg1; " + EE_INDEX + ":Long=1");
+		systemPackages.append(",test.pkg1; test.pkg2; " + EE_INDEX + ":Long=2");
+		systemPackages.append(",test.pkg1; test.pkg2; test.pkg3; " + EE_INDEX + ":Long=3");
+		systemPackages.append(",test.pkg1; test.pkg2; test.pkg3; test.pkg4; " + EE_INDEX + ":Long=4");
+		systemPackages.append(",test.pkg1; test.pkg2; test.pkg3; test.pkg4; test.pkg5; " + EE_INDEX + ":Long=5");
+
+		// Notice that each defined EE capability has an EE_INDEX defined that matches
+		// the set of packages
+		// above for the specified EE_INDEX
+		StringBuilder systemEEs = new StringBuilder();
+		systemEEs.append(
+				"osgi.ee; osgi.ee=\"JavaSE\"; version:List<Version>=\"1.0, 1.1, 1.2, 1.3, 1.4, 1.5\"; " + EE_INDEX
+						+ ":Long=1");
+		systemEEs.append(
+				",osgi.ee; osgi.ee=\"JavaSE\"; version:List<Version>=\"1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6\"; " + EE_INDEX
+						+ ":Long=2");
+		systemEEs.append(
+				",osgi.ee; osgi.ee=\"JavaSE\"; version:List<Version>=\"1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7\"; "
+						+ EE_INDEX + ":Long=3");
+		systemEEs.append(
+				",osgi.ee; osgi.ee=\"JavaSE\"; version:List<Version>=\"1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8\"; "
+						+ EE_INDEX + ":Long=4");
+		systemEEs.append(
+				",osgi.ee; osgi.ee=\"JavaSE\"; version:List<Version>=\"1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 9.0\"; "
+						+ EE_INDEX + ":Long=5");
+
+		ModuleRevisionBuilder systemModuleBuilder = OSGiManifestBuilderFactory.createBuilder(getSystemManifest(),
+				Constants.SYSTEM_BUNDLE_SYMBOLICNAME, systemPackages.toString(), systemEEs.toString());
+		Module systemBundle = container.install(null, Constants.SYSTEM_BUNDLE_LOCATION, systemModuleBuilder,
+				new AtomicReference<Long>());
+		executionEnvironments.addAll(systemBundle.getCurrentRevision()
+				.getDeclaredCapabilities(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE));
+
+		Module m1 = installModule(container, "m1", "1.0", "test.pkg1", "JavaSE-1.5",
+				executionEnvironments);
+		Module m2 = installModule(container, "m2", "1.0", "test.pkg1,test.pkg2", "JavaSE-1.5",
+				executionEnvironments);
+
+		Module m3 = installModule(container, "m3", "1.0", "test.pkg1,test.pkg2,test.pkg3", "JavaSE-1.7",
+				executionEnvironments);
+		Module m4 = installModule(container, "m4", "1.0", "test.pkg1,test.pkg2,test.pkg3,test.pkg4", "JavaSE-1.7",
+				executionEnvironments);
+
+		ResolutionReport report = container.resolve(Arrays.asList(m1, m2, m3, m4), false);
+		assertEquals("Wrong state for m1.", State.RESOLVED, m1.getState());
+		assertEquals("Wrong state for m2.", State.INSTALLED, m2.getState());
+		assertEquals("Wrong state for m3.", State.RESOLVED, m3.getState());
+		assertEquals("Wrong state for m4.", State.INSTALLED, m4.getState());
+
+		String resolutionErrorM2 = report.getResolutionReportMessage(m2.getCurrentRevision());
+		assertNotNull("Expected resolution message", resolutionErrorM2);
+		assertTrue("Wrong resolution message.", resolutionErrorM2.contains("test.pkg2"));
+
+		String resolutionErrorM4 = report.getResolutionReportMessage(m4.getCurrentRevision());
+		assertNotNull("Expected resolution message", resolutionErrorM4);
+		assertTrue("Wrong resolution message.", resolutionErrorM4.contains("test.pkg4"));
+	}
+
+	private Map<String, String> getSystemManifest() {
+		Map<String, String> result = new HashMap<>();
+		result.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		result.put(Constants.BUNDLE_SYMBOLICNAME, "org.eclipse.osgi");
+		result.put(Constants.BUNDLE_VERSION, "1.0");
+		result.put(Constants.EXPORT_PACKAGE, "org.osgi.framework; version=1.10");
+		return result;
+	}
+
+	Module installModule(ModuleContainer container, String bsn, String version, String imports, String eeReqs,
+			List<BundleCapability> availableEEs) throws BundleException {
+		ModuleRevisionBuilder builder = createModuleRevisionBuilder(bsn, version, imports, eeReqs);
+		Module m = container.install(null, bsn, builder, new AtomicReference<Long>());
+		// Use a revision info that holds the EE_INDEX of the bundle based
+		// on the EE capability that matches the modules EE requirement
+		Collection<BundleRequirement> eeBundleReqs = m.getCurrentRevision()
+				.getDeclaredRequirements(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE);
+
+		eeBundleReqs.stream().findFirst().ifPresent((r) -> {
+			// notice that we depend on the available EEs
+			// to be ordered from least to greatest
+			for (BundleCapability ee : availableEEs) {
+				if (r.matches(ee)) {
+					((AtomicReference<Long>) m.getCurrentRevision().getRevisionInfo())
+							.set((Long) ee.getAttributes().get(EE_INDEX));
+					return;
+				}
+			}
+		});
+		return m;
+	}
+
+	@SuppressWarnings("deprecation")
+	ModuleRevisionBuilder createModuleRevisionBuilder(String bsn, String version, String imports, String eeReq)
+			throws BundleException {
+		Map<String, String> manifest = new HashMap<>();
+		manifest.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		manifest.put(Constants.BUNDLE_SYMBOLICNAME, bsn);
+		manifest.put(Constants.BUNDLE_VERSION, version);
+		manifest.put(Constants.IMPORT_PACKAGE, imports);
+		manifest.put(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, eeReq);
+		return OSGiManifestBuilderFactory.createBuilder(manifest);
+	}
+}