bug 361905: encapsulate bundle id to region mapping
diff --git a/bundles/org.eclipse.equinox.region.tests/src/org/eclipse/equinox/internal/region/BundleIdBasedRegionTests.java b/bundles/org.eclipse.equinox.region.tests/src/org/eclipse/equinox/internal/region/BundleIdBasedRegionTests.java
index 804aa7e..76171ef 100644
--- a/bundles/org.eclipse.equinox.region.tests/src/org/eclipse/equinox/internal/region/BundleIdBasedRegionTests.java
+++ b/bundles/org.eclipse.equinox.region.tests/src/org/eclipse/equinox/internal/region/BundleIdBasedRegionTests.java
@@ -16,8 +16,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import java.util.*;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.HashSet;
+import java.util.Iterator;
 import org.easymock.EasyMock;
 import org.eclipse.equinox.region.*;
 import org.eclipse.equinox.region.RegionDigraph.FilteredRegion;
@@ -57,13 +57,11 @@
 	RegionFilter mockRegionFilter;
 
 	private ThreadLocal<Region> threadLocal;
-	private Object globalUpdateMonitor = new Object();
-	private Map<Long, Region> globalBundleToRegion = new HashMap<Long, Region>();
-	private AtomicLong globalTimeStamp = new AtomicLong();
+
+	private BundleIdToRegionMapping bundleIdToRegionMapping;
 
 	@Before
 	public void setUp() throws Exception {
-		this.globalBundleToRegion.clear();
 		this.threadLocal = new ThreadLocal<Region>();
 		this.mockBundle = EasyMock.createMock(Bundle.class);
 		EasyMock.expect(this.mockBundle.getSymbolicName()).andReturn(BUNDLE_SYMBOLIC_NAME).anyTimes();
@@ -98,6 +96,7 @@
 		this.mockGraph = EasyMock.createMock(RegionDigraph.class);
 		this.mockGraph.connect(EasyMock.isA(Region.class), EasyMock.eq(this.mockRegionFilter), EasyMock.eq(this.mockRegion));
 		EasyMock.expectLastCall().anyTimes();
+		this.bundleIdToRegionMapping = new StandardBundleIdToRegionMapping();
 	}
 
 	private void replayMocks() {
@@ -113,10 +112,18 @@
 	public void testGetName() {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		assertEquals(REGION_NAME, r.getName());
 	}
 
+	private BundleIdBasedRegion createDefaultBundleIdBasedRegion() {
+		return createBundleIdBasedRegion(REGION_NAME);
+	}
+
+	private BundleIdBasedRegion createBundleIdBasedRegion(String regionName) {
+		return new BundleIdBasedRegion(regionName, this.mockGraph, this.bundleIdToRegionMapping, this.mockBundleContext, this.threadLocal);
+	}
+
 	private void defaultSetUp() {
 		EasyMock.expect(this.mockGraph.iterator()).andReturn(this.regionIterator).anyTimes();
 		EasyMock.expect(this.mockGraph.getEdges(EasyMock.isA(Region.class))).andReturn(new HashSet<FilteredRegion>()).anyTimes();
@@ -144,7 +151,7 @@
 		EasyMock.expect(this.mockGraph.getEdges(EasyMock.isA(Region.class))).andReturn(edges).anyTimes();
 		replayMocks();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.addBundle(this.mockBundle);
 	}
 
@@ -152,7 +159,7 @@
 	public void testAddExistingBundle() throws BundleException {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.addBundle(this.mockBundle);
 		r.addBundle(this.mockBundle);
 	}
@@ -167,7 +174,7 @@
 		EasyMock.expect(mockBundle2.getBundleId()).andReturn(BUNDLE_ID_2).anyTimes();
 		EasyMock.replay(mockBundle2);
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.addBundle(this.mockBundle);
 		r.addBundle(mockBundle2);
 	}
@@ -184,7 +191,7 @@
 		r.addBundle(this.mockBundle.getBundleId());
 	}
 
-	private Region regionForBundlePersentInAnotherRegionTest() {
+	private Region regionForBundlePersentInAnotherRegionTest() throws BundleException {
 		this.regionIterator = new Iterator<Region>() {
 
 			private int next = 2;
@@ -213,11 +220,11 @@
 		EasyMock.expect(this.mockGraph.getEdges(EasyMock.isA(Region.class))).andReturn(new HashSet<FilteredRegion>()).anyTimes();
 		EasyMock.expect(this.mockRegion.contains(EasyMock.eq(BUNDLE_ID))).andReturn(true).anyTimes();
 		EasyMock.expect(this.mockRegion2.contains(EasyMock.eq(BUNDLE_ID))).andReturn(false).anyTimes();
-		this.globalBundleToRegion.put(BUNDLE_ID, mockRegion);
+		this.bundleIdToRegionMapping.associateBundleWithRegion(BUNDLE_ID, mockRegion);
 
 		replayMocks();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		return r;
 	}
 
@@ -239,7 +246,7 @@
 	public void testContains() throws BundleException {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.addBundle(this.mockBundle);
 		assertTrue(r.contains(this.mockBundle));
 	}
@@ -248,7 +255,7 @@
 	public void testDoesNotContain() {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		assertFalse(r.contains(this.mockBundle));
 	}
 
@@ -256,7 +263,7 @@
 	public void testGetBundle() throws BundleException {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.addBundle(this.mockBundle);
 		assertEquals(this.mockBundle, r.getBundle(BUNDLE_SYMBOLIC_NAME, BUNDLE_VERSION));
 	}
@@ -265,7 +272,7 @@
 	public void testGetBundleNotFound() throws BundleException {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.addBundle(this.mockBundle);
 		assertNull(r.getBundle(BUNDLE_SYMBOLIC_NAME_2, BUNDLE_VERSION));
 	}
@@ -274,7 +281,7 @@
 	public void testConnectRegion() throws BundleException {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.connectRegion(this.mockRegion, this.mockRegionFilter);
 	}
 
@@ -282,8 +289,8 @@
 	public void testEquals() {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
-		Region s = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
+		Region s = createDefaultBundleIdBasedRegion();
 		assertEquals(r, r);
 		assertEquals(r, s);
 		assertEquals(r.hashCode(), s.hashCode());
@@ -293,8 +300,8 @@
 	public void testNotEqual() {
 		defaultSetUp();
 
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
-		Region s = new BundleIdBasedRegion(OTHER_REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
+		Region s = createBundleIdBasedRegion(OTHER_REGION_NAME);
 		assertFalse(r.equals(s));
 		assertFalse(r.equals(null));
 	}
@@ -302,7 +309,7 @@
 	@Test
 	public void testAddRemoveBundleId() throws BundleException {
 		defaultSetUp();
-		Region r = new BundleIdBasedRegion(REGION_NAME, this.mockGraph, this.mockBundleContext, this.threadLocal, this.globalUpdateMonitor, this.globalTimeStamp, this.globalBundleToRegion);
+		Region r = createDefaultBundleIdBasedRegion();
 		r.addBundle(TEST_BUNDLE_ID);
 		assertTrue(r.contains(TEST_BUNDLE_ID));
 		r.removeBundle(TEST_BUNDLE_ID);
diff --git a/bundles/org.eclipse.equinox.region.tests/src/org/eclipse/equinox/internal/region/StandardBundleIdToRegionMappingTests.java b/bundles/org.eclipse.equinox.region.tests/src/org/eclipse/equinox/internal/region/StandardBundleIdToRegionMappingTests.java
new file mode 100644
index 0000000..04bdcc2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.region.tests/src/org/eclipse/equinox/internal/region/StandardBundleIdToRegionMappingTests.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2011 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.region;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.easymock.EasyMock;
+import org.eclipse.equinox.region.Region;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleException;
+
+public class StandardBundleIdToRegionMappingTests {
+
+	private BundleIdToRegionMapping bundleIdToRegionMapping;
+
+	private Region mockRegion;
+
+	private static final long TEST_BUNDLE_ID = 1L;
+
+	private static final long OTHER_TEST_BUNDLE_ID = 2L;
+
+	@Before
+	public void setUp() throws Exception {
+		this.bundleIdToRegionMapping = new StandardBundleIdToRegionMapping();
+		this.mockRegion = EasyMock.createMock(Region.class);
+	}
+
+	@Test
+	public void testAssociateBundleWithRegion() throws BundleException {
+		assertNull(this.bundleIdToRegionMapping.getRegion(TEST_BUNDLE_ID));
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		assertEquals(mockRegion, this.bundleIdToRegionMapping.getRegion(TEST_BUNDLE_ID));
+	}
+
+	@Test
+	public void testAssociateBundleAlreadyAssociatedWithRegion() throws BundleException {
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+	}
+
+	@Test(expected = BundleException.class)
+	public void testAssociateBundleAlreadyAssociatedWithOtherRegion() throws BundleException {
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, EasyMock.createMock(Region.class));
+	}
+
+	@Test
+	public void testDissociateBundle() throws BundleException {
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		this.bundleIdToRegionMapping.dissociateBundle(TEST_BUNDLE_ID);
+		assertNull(this.bundleIdToRegionMapping.getRegion(TEST_BUNDLE_ID));
+	}
+
+	@Test
+	public void testIsBundleAssociatedWithRegion() throws BundleException {
+		assertFalse(this.bundleIdToRegionMapping.isBundleAssociatedWithRegion(TEST_BUNDLE_ID, mockRegion));
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		assertTrue(this.bundleIdToRegionMapping.isBundleAssociatedWithRegion(TEST_BUNDLE_ID, mockRegion));
+	}
+
+	@Test
+	public void testGetBundleIds() throws BundleException {
+		assertEquals(0, this.bundleIdToRegionMapping.getBundleIds(mockRegion).size());
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		this.bundleIdToRegionMapping.associateBundleWithRegion(OTHER_TEST_BUNDLE_ID, mockRegion);
+		assertEquals(2, this.bundleIdToRegionMapping.getBundleIds(mockRegion).size());
+		assertTrue(this.bundleIdToRegionMapping.getBundleIds(mockRegion).contains(TEST_BUNDLE_ID));
+		assertTrue(this.bundleIdToRegionMapping.getBundleIds(mockRegion).contains(OTHER_TEST_BUNDLE_ID));
+	}
+
+	@Test
+	public void testClear() throws BundleException {
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		this.bundleIdToRegionMapping.clear();
+		assertNull(this.bundleIdToRegionMapping.getRegion(TEST_BUNDLE_ID));
+	}
+
+	@Test
+	public void testGetRegion() throws BundleException {
+		assertNull(this.bundleIdToRegionMapping.getRegion(TEST_BUNDLE_ID));
+		this.bundleIdToRegionMapping.associateBundleWithRegion(TEST_BUNDLE_ID, mockRegion);
+		this.bundleIdToRegionMapping.associateBundleWithRegion(OTHER_TEST_BUNDLE_ID, mockRegion);
+		assertEquals(mockRegion, this.bundleIdToRegionMapping.getRegion(TEST_BUNDLE_ID));
+		assertEquals(mockRegion, this.bundleIdToRegionMapping.getRegion(OTHER_TEST_BUNDLE_ID));
+	}
+
+}
diff --git a/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/BundleIdBasedRegion.java b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/BundleIdBasedRegion.java
index 5904537..d6a3670 100644
--- a/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/BundleIdBasedRegion.java
+++ b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/BundleIdBasedRegion.java
@@ -15,8 +15,7 @@
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.Set;
 import org.eclipse.equinox.region.*;
 import org.eclipse.equinox.region.RegionDigraph.FilteredRegion;
 import org.osgi.framework.*;
@@ -35,43 +34,34 @@
 
 	private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
 
-	// Note that this global digraph monitor locks modifications and read operations on the RegionDigraph
-	// This includes modifying and reading the bunlde ids included in this region
-	// It should be considered a global lock on the complete digraph.
-	private final Object globalUpdateMonitor;
-	private final AtomicLong globalTimeStamp;
-	//TODO: avoid sharing this map across classes.
-	private final Map<Long, Region> globalBundleToRegion;
-
 	private final String regionName;
 
 	private final RegionDigraph regionDigraph;
 
+	private final BundleIdToRegionMapping bundleIdToRegionMapping;
+
 	private final BundleContext bundleContext;
 
 	private final ThreadLocal<Region> threadLocal;
 
-	BundleIdBasedRegion(String regionName, RegionDigraph regionDigraph, BundleContext bundleContext, ThreadLocal<Region> threadLocal, Object globalUpdateMonitor, AtomicLong globalTimeStamp, Map<Long, Region> globalBundleToRegion) {
+	BundleIdBasedRegion(String regionName, RegionDigraph regionDigraph, BundleIdToRegionMapping bundleIdToRegionMapping, BundleContext bundleContext, ThreadLocal<Region> threadLocal) {
 		if (regionName == null)
 			throw new IllegalArgumentException("The region name must not be null"); //$NON-NLS-1$
 		if (regionDigraph == null)
 			throw new IllegalArgumentException("The region digraph must not be null"); //$NON-NLS-1$
-		if (globalUpdateMonitor == null)
-			throw new IllegalArgumentException("The global update monitor must not be null"); //$NON-NLS-1$
-		if (globalBundleToRegion == null)
-			throw new IllegalArgumentException("The global bundle to region must not be null"); //$NON-NLS-1$
+		if (bundleIdToRegionMapping == null)
+			throw new IllegalArgumentException("The bundle id to region mapping must not be null"); //$NON-NLS-1$
 		this.regionName = regionName;
 		this.regionDigraph = regionDigraph;
+		this.bundleIdToRegionMapping = bundleIdToRegionMapping;
 		this.bundleContext = bundleContext;
 		this.threadLocal = threadLocal;
-		this.globalUpdateMonitor = globalUpdateMonitor;
-		this.globalTimeStamp = globalTimeStamp;
-		this.globalBundleToRegion = globalBundleToRegion;
 	}
 
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public String getName() {
 		return this.regionName;
 	}
@@ -79,6 +69,7 @@
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public void addBundle(Bundle bundle) throws BundleException {
 		addBundle(bundle.getBundleId());
 	}
@@ -86,22 +77,15 @@
 	/**
 	 * {@inheritDoc}
 	 */
-	// There is a global lock obtained to ensure consistency across the complete digraph
+	@Override
 	public void addBundle(long bundleId) throws BundleException {
-		synchronized (this.globalUpdateMonitor) {
-			Region r = this.globalBundleToRegion.get(bundleId);
-			if (r != null && r != this) {
-				throw new BundleException("Bundle '" + bundleId + "' is already associated with region '" + r + "'", BundleException.INVALID_OPERATION); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-			}
-			this.globalBundleToRegion.put(bundleId, this);
-			this.globalTimeStamp.incrementAndGet();
-		}
-
+		this.bundleIdToRegionMapping.associateBundleWithRegion(bundleId, this);
 	}
 
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public Bundle installBundle(String location, InputStream input) throws BundleException {
 		if (this.bundleContext == null)
 			throw new BundleException("This region is not connected to an OSGi Framework.", BundleException.INVALID_OPERATION); //$NON-NLS-1$
@@ -129,6 +113,7 @@
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public Bundle installBundle(String location) throws BundleException {
 		return installBundle(location, null);
 	}
@@ -146,6 +131,7 @@
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public Bundle getBundle(String symbolicName, Version version) {
 		if (bundleContext == null)
 			return null; // this region is not connected to an OSGi framework
@@ -163,6 +149,7 @@
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public void connectRegion(Region headRegion, RegionFilter filter) throws BundleException {
 		this.regionDigraph.connect(this, filter, headRegion);
 	}
@@ -170,19 +157,20 @@
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public boolean contains(long bundleId) {
-		synchronized (globalUpdateMonitor) {
-			return globalBundleToRegion.get(bundleId) == this;
-		}
+		return this.bundleIdToRegionMapping.isBundleAssociatedWithRegion(bundleId, this);
 	}
 
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public boolean contains(Bundle bundle) {
 		return contains(bundle.getBundleId());
 	}
 
+	@Override
 	public int hashCode() {
 		final int prime = 31;
 		int result = 1;
@@ -190,6 +178,7 @@
 		return result;
 	}
 
+	@Override
 	public boolean equals(Object obj) {
 		if (this == obj) {
 			return true;
@@ -207,6 +196,7 @@
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public void removeBundle(Bundle bundle) {
 		removeBundle(bundle.getBundleId());
 
@@ -215,11 +205,9 @@
 	/**
 	 * {@inheritDoc}
 	 */
+	@Override
 	public void removeBundle(long bundleId) {
-		synchronized (this.globalUpdateMonitor) {
-			this.globalBundleToRegion.remove(bundleId);
-			this.globalTimeStamp.incrementAndGet();
-		}
+		this.bundleIdToRegionMapping.dissociateBundle(bundleId);
 	}
 
 	/**
@@ -230,22 +218,26 @@
 		return getName();
 	}
 
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
 	public Set<Long> getBundleIds() {
-		Set<Long> bundleIds = new HashSet<Long>();
-		synchronized (this.globalUpdateMonitor) {
-			for (Map.Entry<Long, Region> entry : globalBundleToRegion.entrySet()) {
-				if (entry.getValue() == this) {
-					bundleIds.add(entry.getKey());
-				}
-			}
-		}
-		return bundleIds;
+		return this.bundleIdToRegionMapping.getBundleIds(this);
 	}
 
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
 	public Set<FilteredRegion> getEdges() {
 		return this.regionDigraph.getEdges(this);
 	}
 
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
 	public void visitSubgraph(RegionDigraphVisitor visitor) {
 		this.regionDigraph.visitSubgraph(this, visitor);
 	}
diff --git a/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/BundleIdToRegionMapping.java b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/BundleIdToRegionMapping.java
new file mode 100644
index 0000000..865e71e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/BundleIdToRegionMapping.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2011 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.region;
+
+import java.util.Set;
+import org.eclipse.equinox.region.Region;
+import org.osgi.framework.BundleException;
+
+/**
+ * This internal interface is used to track which bundles belong to which regions.
+ * 
+ * <strong>Concurrent Semantics</strong><br />
+ * Implementations must be thread safe. 
+ */
+public interface BundleIdToRegionMapping {
+
+	/**
+	 * Associates the given bundle id with the given region. If the bundle
+	 * id is already associated with a different region, throws {@link BundleException}.
+	 * If the bundle id is already associated with the given region, there is no
+	 * effect on the association and no exception is thrown.
+	 * 
+	 * TODO: need to check type of Region? Validity of region?
+	 * 
+	 * @param bundleId the bundle id to be associated
+	 * @param region the {@link Region} with which the bundle id is to be associated
+	 * @throws BundleException
+	 */
+	void associateBundleWithRegion(long bundleId, Region region) throws BundleException;
+
+	/**
+	 * Dissociates the given bundle id with any region with which it may be
+	 * associated.
+	 * 
+	 * @param bundleId the bundle id to be dissociated
+	 */
+	void dissociateBundle(long bundleId);
+
+	/**
+	 * Returns the {@link Region} associated with the given bundle id or <code>null</code>
+	 * if the given bundle id is not associated with a region.
+	 * 
+	 * @param bundleId the bundle id whose region is required
+	 * @return the {@link Region} associated with the given bundle id or or <code>null</code>
+	 * if the given bundle id is not associated with a region
+	 */
+	Region getRegion(long bundleId);
+
+	/**
+	 * Checks the association of the given bundle id with the given region and returns
+	 * <code>true</code> if and only if the given bundle id is associated with
+	 * the given region
+	 * 
+	 * @param bundleId the bundle id to be checked
+	 * @param region the {@link Region} to be checked
+	 * @return <code>true</code> if and only if the given bundle id is associated with
+	 * the given region
+	 */
+	boolean isBundleAssociatedWithRegion(long bundleId, Region region);
+
+	/**
+	 * Returns a set of bundle ids associated with the given region. Never
+	 * returns <code>null</code>.
+	 * 
+	 * @param region the {@link Region} whose bundle ids are required
+	 * @return the {@link Set} of bundle ids associated with the given region
+	 */
+	Set<Long> getBundleIds(Region region);
+
+	/**
+	 * Dissociates all bundle ids and regions.
+	 */
+	void clear();
+
+}
diff --git a/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/StandardBundleIdToRegionMapping.java b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/StandardBundleIdToRegionMapping.java
new file mode 100644
index 0000000..9210535
--- /dev/null
+++ b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/StandardBundleIdToRegionMapping.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2011 VMware Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   VMware Inc. - initial contribution
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.region;
+
+import java.util.*;
+import org.eclipse.equinox.region.Region;
+import org.osgi.framework.BundleException;
+
+final class StandardBundleIdToRegionMapping implements BundleIdToRegionMapping {
+
+	private final Object monitor = new Object();
+
+	/*
+	 * bundleToRegion maps a given bundle id to the region for which it belongs.
+	 * this is a global map for all regions in the digraph
+	 */
+	private final Map<Long, Region> bundleToRegion = new HashMap<Long, Region>();
+
+	/**
+	 * {@inheritDoc} 
+	 **/
+	@Override
+	public void associateBundleWithRegion(long bundleId, Region region) throws BundleException {
+		synchronized (this.monitor) {
+			Region r = this.bundleToRegion.get(bundleId);
+			if (r != null && r != region) {
+				throw new BundleException("Bundle '" + bundleId + "' is already associated with region '" + r + "'", BundleException.INVALID_OPERATION); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+			}
+			this.bundleToRegion.put(bundleId, region);
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void dissociateBundle(long bundleId) {
+		synchronized (this.monitor) {
+			this.bundleToRegion.remove(bundleId);
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public boolean isBundleAssociatedWithRegion(long bundleId, Region region) {
+		synchronized (this.monitor) {
+			return this.bundleToRegion.get(bundleId) == region;
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public Set<Long> getBundleIds(Region region) {
+		Set<Long> bundleIds = new HashSet<Long>();
+		synchronized (this.monitor) {
+			for (Map.Entry<Long, Region> entry : this.bundleToRegion.entrySet()) {
+				if (entry.getValue() == region) {
+					bundleIds.add(entry.getKey());
+				}
+			}
+		}
+		return Collections.unmodifiableSet(bundleIds);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void clear() {
+		synchronized (this.monitor) {
+			this.bundleToRegion.clear();
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public Region getRegion(long bundleId) {
+		synchronized (this.monitor) {
+			return this.bundleToRegion.get(bundleId);
+		}
+	}
+}
diff --git a/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/StandardRegionDigraph.java b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/StandardRegionDigraph.java
index 06b7fc9..4837dd8 100644
--- a/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/StandardRegionDigraph.java
+++ b/bundles/org.eclipse.equinox.region/src/org/eclipse/equinox/internal/region/StandardRegionDigraph.java
@@ -29,7 +29,7 @@
  * Thread safe.
  * 
  */
-public final class StandardRegionDigraph implements RegionDigraph {
+public final class StandardRegionDigraph implements BundleIdToRegionMapping, RegionDigraph {
 
 	private static final Set<FilteredRegion> EMPTY_EDGE_SET = Collections.unmodifiableSet(new HashSet<FilteredRegion>());
 
@@ -39,11 +39,7 @@
 
 	private final Set<Region> regions = new HashSet<Region>();
 
-	/*
-	 * bundleToRegion maps a given bundle id to the region for which it belongs.
-	 * this is a global map for all regions in the digraph
-	 */
-	private final Map<Long, Region> bundleToRegion = new HashMap<Long, Region>();
+	private final BundleIdToRegionMapping bundleIdToRegionMapping;
 
 	/* edges maps a given region to an immutable set of edges with their tail at the given region. To update
 	 * the edges for a region, the corresponding immutable set is replaced atomically. */
@@ -64,8 +60,8 @@
 	private final ResolverHookFactory resolverHookFactory;
 	private final StandardRegionDigraph origin;
 	// Guarded by the origin monitor
-	private long originTimeStamp;
-	private final AtomicLong timeStamp = new AtomicLong();
+	private long originUpdateCount;
+	private final AtomicLong updateCount = new AtomicLong();
 
 	private volatile Region defaultRegion;
 
@@ -80,6 +76,7 @@
 
 	private StandardRegionDigraph(BundleContext bundleContext, ThreadLocal<Region> threadLocal, StandardRegionDigraph origin) throws BundleException {
 		this.subgraphTraverser = new SubgraphTraverser();
+		this.bundleIdToRegionMapping = new StandardBundleIdToRegionMapping();
 		this.bundleContext = bundleContext;
 		this.threadLocal = threadLocal;
 
@@ -102,11 +99,11 @@
 		this.origin = origin;
 		if (origin != null) {
 			synchronized (origin.monitor) {
-				this.originTimeStamp = origin.timeStamp.get();
+				this.originUpdateCount = origin.updateCount.get();
 				this.replace(origin, false);
 			}
 		} else {
-			this.originTimeStamp = -1;
+			this.originUpdateCount = -1;
 		}
 		this.defaultRegion = null;
 	}
@@ -115,14 +112,14 @@
 	 * {@inheritDoc}
 	 */
 	public Region createRegion(String regionName) throws BundleException {
-		Region region = new BundleIdBasedRegion(regionName, this, this.bundleContext, this.threadLocal, this.monitor, this.timeStamp, this.bundleToRegion);
+		Region region = new BundleIdBasedRegion(regionName, this, this, this.bundleContext, this.threadLocal);
 		synchronized (this.monitor) {
 			if (getRegion(regionName) != null) {
 				throw new BundleException("Region '" + regionName + "' already exists", BundleException.UNSUPPORTED_OPERATION); //$NON-NLS-1$ //$NON-NLS-2$
 			}
 			this.regions.add(region);
 			this.edges.put(region, EMPTY_EDGE_SET);
-			this.timeStamp.incrementAndGet();
+			incrementUpdateCount();
 		}
 		notifyAdded(region);
 		return region;
@@ -166,7 +163,7 @@
 			headAdded = this.regions.add(headRegion);
 			connections.add(new StandardFilteredRegion(headRegion, filter));
 			this.edges.put(tailRegion, Collections.unmodifiableSet(connections));
-			this.timeStamp.incrementAndGet();
+			incrementUpdateCount();
 		}
 		if (tailAdded) {
 			notifyAdded(tailRegion);
@@ -248,9 +245,7 @@
 	 * {@inheritDoc}
 	 */
 	public Region getRegion(long bundleId) {
-		synchronized (this.monitor) {
-			return bundleToRegion.get(bundleId);
-		}
+		return this.bundleIdToRegionMapping.getRegion(bundleId);
 	}
 
 	/**
@@ -277,7 +272,7 @@
 					}
 				}
 			}
-			this.timeStamp.incrementAndGet();
+			incrementUpdateCount();
 		}
 	}
 
@@ -410,12 +405,12 @@
 			throw new IllegalArgumentException("The replacement digraph is not a copy of this digraph."); //$NON-NLS-1$
 		Map<Region, Set<FilteredRegion>> filteredRegions = replacement.getFilteredRegions();
 		synchronized (this.monitor) {
-			if (check && this.timeStamp.get() != replacement.originTimeStamp) {
-				throw new BundleException("The origin timestamp has changed since the replacement copy was created.", BundleException.INVALID_OPERATION); //$NON-NLS-1$
+			if (check && this.updateCount.get() != replacement.originUpdateCount) {
+				throw new BundleException("The origin update count has changed since the replacement copy was created.", BundleException.INVALID_OPERATION); //$NON-NLS-1$
 			}
 			this.regions.clear();
 			this.edges.clear();
-			this.bundleToRegion.clear();
+			this.bundleIdToRegionMapping.clear();
 			for (Region original : filteredRegions.keySet()) {
 				Region copy = this.createRegion(original.getName());
 				for (Long id : original.getBundleIds()) {
@@ -429,13 +424,16 @@
 					this.connect(tailRegion, headFilter.getFilter(), headRegion);
 				}
 			}
-			this.timeStamp.incrementAndGet();
+			incrementUpdateCount();
 			if (check) {
-				replacement.originTimeStamp = this.timeStamp.get();
+				replacement.originUpdateCount = this.updateCount.get();
 			}
 		}
 	}
 
+	/** 
+	 * {@inheritDoc}
+	 */
 	@Override
 	public ResolverHookFactory getResolverHookFactory() {
 		return resolverHookFactory;
@@ -445,27 +443,42 @@
 		return bundleCollisionHook;
 	}
 
+	/** 
+	 * {@inheritDoc}
+	 */
 	@Override
 	public EventHook getBundleEventHook() {
 		return bundleEventHook;
 	}
 
+	/** 
+	 * {@inheritDoc}
+	 */
 	@Override
 	public FindHook getBundleFindHook() {
 		return bundleFindHook;
 	}
 
+	/** 
+	 * {@inheritDoc}
+	 */
 	@SuppressWarnings("deprecation")
 	@Override
 	public org.osgi.framework.hooks.service.EventHook getServiceEventHook() {
 		return serviceEventHook;
 	}
 
+	/** 
+	 * {@inheritDoc}
+	 */
 	@Override
 	public org.osgi.framework.hooks.service.FindHook getServiceFindHook() {
 		return serviceFindHook;
 	}
 
+	/** 
+	 * {@inheritDoc}
+	 */
 	@Override
 	public void setDefaultRegion(Region defaultRegion) {
 		if (this.regions.contains(defaultRegion) || defaultRegion == null) {
@@ -475,8 +488,47 @@
 		}
 	}
 
+	/** 
+	 * {@inheritDoc}
+	 */
 	@Override
 	public Region getDefaultRegion() {
 		return this.defaultRegion;
 	}
+
+	@Override
+	public void associateBundleWithRegion(long bundleId, Region region) throws BundleException {
+		this.bundleIdToRegionMapping.associateBundleWithRegion(bundleId, region);
+		incrementUpdateCount();
+	}
+
+	private void incrementUpdateCount() {
+		synchronized (this.monitor) {
+			this.updateCount.incrementAndGet();
+		}
+
+	}
+
+	@Override
+	public void dissociateBundle(long bundleId) {
+		this.bundleIdToRegionMapping.dissociateBundle(bundleId);
+		incrementUpdateCount();
+	}
+
+	@Override
+	public boolean isBundleAssociatedWithRegion(long bundleId, Region region) {
+		return this.bundleIdToRegionMapping.isBundleAssociatedWithRegion(bundleId, region);
+	}
+
+	@Override
+	public Set<Long> getBundleIds(Region region) {
+		return this.bundleIdToRegionMapping.getBundleIds(region);
+	}
+
+	@Override
+	public void clear() {
+		this.bundleIdToRegionMapping.clear();
+		incrementUpdateCount();
+	}
+
 }