Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEike Stepper2012-07-18 15:45:38 +0000
committerEike Stepper2012-07-18 15:45:38 +0000
commitd15279d0ff9f2e45e72e3d6fc578364bc77c72c5 (patch)
tree8631e8a117cf548626b9b75381aa6929c0748782 /plugins
parent5827e90ea843cfbfc6b8131b2b1d62a90cbeef69 (diff)
downloadcdo-d15279d0ff9f2e45e72e3d6fc578364bc77c72c5.tar.gz
cdo-d15279d0ff9f2e45e72e3d6fc578364bc77c72c5.tar.xz
cdo-d15279d0ff9f2e45e72e3d6fc578364bc77c72c5.zip
[385268] Reattachment issue with EOpposite
https://bugs.eclipse.org/bugs/show_bug.cgi?id=385268
Diffstat (limited to 'plugins')
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_385268_Test.java134
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java5
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOStateMachine.java100
-rw-r--r--plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java1613
4 files changed, 1057 insertions, 795 deletions
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_385268_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_385268_Test.java
new file mode 100644
index 0000000000..80b0381aed
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_385268_Test.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2004 - 2012 Eike Stepper (Berlin, Germany) and others.
+ * 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:
+ * Eike Stepper - initial API and implementation
+ */
+package org.eclipse.emf.cdo.tests.bugzilla;
+
+import org.eclipse.emf.cdo.CDOObject;
+import org.eclipse.emf.cdo.eresource.CDOResource;
+import org.eclipse.emf.cdo.session.CDOSession;
+import org.eclipse.emf.cdo.tests.AbstractCDOTest;
+import org.eclipse.emf.cdo.tests.model1.Company;
+import org.eclipse.emf.cdo.tests.model1.PurchaseOrder;
+import org.eclipse.emf.cdo.tests.model1.Supplier;
+import org.eclipse.emf.cdo.transaction.CDOTransaction;
+import org.eclipse.emf.cdo.util.CDOUtil;
+
+import org.eclipse.net4j.util.concurrent.RWOLockManager;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.ecore.EObject;
+
+import java.util.Collections;
+
+/**
+ * @author Eike Stepper
+ */
+public class Bugzilla_385268_Test extends AbstractCDOTest
+{
+ public void testDetachment() throws Exception
+ {
+ Supplier supplier = getModel1Factory().createSupplier();
+ PurchaseOrder purchaseOrder = getModel1Factory().createPurchaseOrder();
+ supplier.getPurchaseOrders().add(purchaseOrder);
+
+ CDOSession session = openSession();
+ CDOTransaction transaction = session.openTransaction();
+ CDOResource resource = transaction.createResource(getResourcePath("test1"));
+
+ EList<EObject> contents = resource.getContents();
+ contents.add(supplier);
+ contents.add(purchaseOrder);
+
+ transaction.commit();
+
+ contents.remove(purchaseOrder);
+ assertDirty(supplier, transaction); // supplier must be dirty so that a posible ext-ref can be committed later.
+ }
+
+ public void testReattachment() throws Exception
+ {
+ Supplier supplier = getModel1Factory().createSupplier();
+ PurchaseOrder purchaseOrder = getModel1Factory().createPurchaseOrder();
+ supplier.getPurchaseOrders().add(purchaseOrder);
+
+ CDOSession session = openSession();
+ CDOTransaction transaction = session.openTransaction();
+ CDOResource resource = transaction.createResource(getResourcePath("test1"));
+
+ EList<EObject> contents = resource.getContents();
+ contents.add(supplier);
+ contents.add(purchaseOrder);
+
+ transaction.commit();
+
+ contents.remove(purchaseOrder);
+ contents.add(purchaseOrder);
+ assertClean(supplier, transaction);
+ }
+
+ public void testReattachment2() throws Exception
+ {
+ System.err.println("\nMain");
+ CDOSession session = openSession();
+ CDOTransaction transaction = session.openTransaction();
+ CDOResource resource = transaction.createResource(getResourcePath("test1"));
+
+ Company company = getModel1Factory().createCompany();
+ Supplier supplier = getModel1Factory().createSupplier();
+ PurchaseOrder purchaseOrder = getModel1Factory().createPurchaseOrder();
+
+ EList<PurchaseOrder> purchaseOrders = company.getPurchaseOrders();
+ purchaseOrders.add(purchaseOrder);
+ supplier.getPurchaseOrders().add(purchaseOrder);
+ company.getSuppliers().add(supplier);
+
+ resource.getContents().add(company);
+ transaction.commit();
+
+ System.err.println("\nRemoteUser");
+ RemoteUser remoteUser = new RemoteUser();
+ remoteUser.changeSupplierAndLockIt();
+
+ System.err.println("\nMain");
+ for (int i = 0; i < 1000; i++)
+ {
+ purchaseOrders.clear();
+ purchaseOrders.add(purchaseOrder);
+ }
+
+ transaction.commit();
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class RemoteUser
+ {
+ private CDOTransaction transaction;
+
+ private CDOResource resource;
+
+ public RemoteUser()
+ {
+ CDOSession session = openSession();
+ transaction = session.openTransaction();
+ resource = transaction.getResource(getResourcePath("test1"));
+ }
+
+ public void changeSupplierAndLockIt() throws InterruptedException
+ {
+ Company company = (Company)resource.getContents().get(0);
+ Supplier supplier = company.getSuppliers().get(0);
+ CDOObject cdoObject = CDOUtil.getCDOObject(supplier);
+
+ transaction.lockObjects(Collections.singleton(cdoObject), RWOLockManager.LockType.WRITE, DEFAULT_TIMEOUT);
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
index f248163111..7b22e8dae3 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
@@ -381,6 +381,11 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa
return dirty;
}
+ public void setDirty(boolean dirty)
+ {
+ this.dirty = dirty;
+ }
+
@Override
public synchronized boolean hasConflict()
{
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOStateMachine.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOStateMachine.java
index 1599b2ae86..19cd560dc0 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOStateMachine.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOStateMachine.java
@@ -22,12 +22,15 @@ import org.eclipse.emf.cdo.common.revision.CDORevisionFactory;
import org.eclipse.emf.cdo.common.revision.CDORevisionKey;
import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta;
+import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
+import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
import org.eclipse.emf.cdo.common.security.NoPermissionException;
import org.eclipse.emf.cdo.common.util.PartialCollectionLoadingNotSupportedException;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageInfo;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionCache;
+import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager;
import org.eclipse.emf.cdo.transaction.CDOTransaction;
import org.eclipse.emf.cdo.view.CDOInvalidationPolicy;
@@ -36,6 +39,7 @@ import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.internal.cdo.CDOObjectImpl;
import org.eclipse.emf.internal.cdo.bundle.OM;
import org.eclipse.emf.internal.cdo.object.CDONotificationBuilder;
+import org.eclipse.emf.internal.cdo.transaction.CDOTransactionImpl;
import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.net4j.util.fsm.FiniteStateMachine;
@@ -63,6 +67,8 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentMap;
/**
* @author Eike Stepper
@@ -711,6 +717,100 @@ public final class CDOStateMachine extends FiniteStateMachine<CDOState, CDOEvent
// Add the object to the set of reattached objects
Map<CDOID, CDOObject> reattachedObjects = transaction.getLastSavepoint().getReattachedObjects();
reattachedObjects.put(id, object);
+
+ // Bug 385268
+ InternalEObject reattachedObject = object.cdoInternalInstance();
+ processRevisionDeltas(reattachedObject, transaction);
+ }
+
+ private void processRevisionDeltas(InternalEObject reattachedObject, InternalCDOTransaction transaction)
+ {
+ InternalCDOSavepoint lastSavepoint = transaction.getLastSavepoint();
+ ConcurrentMap<CDOID, CDORevisionDelta> revisionDeltas = lastSavepoint.getRevisionDeltas();
+ for (Iterator<Entry<CDOID, CDORevisionDelta>> it = revisionDeltas.entrySet().iterator(); it.hasNext();)
+ {
+ Entry<CDOID, CDORevisionDelta> entry = it.next();
+ CDORevisionDelta revisionDelta = entry.getValue();
+
+ Map<EStructuralFeature, CDOFeatureDelta> map = ((InternalCDORevisionDelta)revisionDelta).getFeatureDeltaMap();
+ processFeatureDeltas(reattachedObject, map);
+
+ if (revisionDelta.isEmpty())
+ {
+ it.remove();
+
+ CDOID id = revisionDelta.getID();
+ InternalCDOObject cleanObject = (InternalCDOObject)lastSavepoint.getDirtyObjects().remove(id);
+ cleanObject.cdoInternalSetState(CDOState.CLEAN);
+ }
+ }
+
+ if (revisionDeltas.isEmpty())
+ {
+ makeTransactionClean(transaction);
+ }
+ }
+
+ private void processFeatureDeltas(InternalEObject reattachedObject, Map<EStructuralFeature, CDOFeatureDelta> map)
+ {
+ for (Iterator<Entry<EStructuralFeature, CDOFeatureDelta>> it = map.entrySet().iterator(); it.hasNext();)
+ {
+ Entry<EStructuralFeature, CDOFeatureDelta> entry = it.next();
+ CDOFeatureDelta featureDelta = entry.getValue();
+ processFeatureDelta(reattachedObject, it, featureDelta);
+ }
+ }
+
+ private void processFeatureDelta(InternalEObject reattachedObject, Iterator<?> it, CDOFeatureDelta featureDelta)
+ {
+ switch (featureDelta.getType())
+ {
+ case SET:
+ CDOSetFeatureDelta setFeatureDelta = (CDOSetFeatureDelta)featureDelta;
+ Object oldValue = setFeatureDelta.getOldValue();
+ if (oldValue instanceof InternalCDOObject)
+ {
+ oldValue = ((InternalCDOObject)oldValue).cdoInternalInstance();
+ }
+
+ Object newValue = setFeatureDelta.getValue();
+ if (newValue instanceof InternalCDOObject)
+ {
+ newValue = ((InternalCDOObject)newValue).cdoInternalInstance();
+ }
+
+ if (reattachedObject == oldValue && reattachedObject == newValue)
+ {
+ it.remove();
+ }
+
+ break;
+
+ case LIST:
+ CDOListFeatureDelta listFeatureDelta = (CDOListFeatureDelta)featureDelta;
+ List<CDOFeatureDelta> listChanges = listFeatureDelta.getListChanges();
+ for (Iterator<CDOFeatureDelta> listIt = listChanges.iterator(); listIt.hasNext();)
+ {
+ CDOFeatureDelta singleFeatureDelta = listIt.next();
+ processFeatureDelta(reattachedObject, listIt, singleFeatureDelta);
+ }
+
+ if (listChanges.isEmpty())
+ {
+ it.remove();
+ }
+
+ break;
+ }
+ }
+
+ private void makeTransactionClean(InternalCDOTransaction transaction)
+ {
+ if (transaction instanceof CDOTransactionImpl)
+ {
+ CDOTransactionImpl impl = (CDOTransactionImpl)transaction;
+ impl.setDirty(false); // TODO make setDirty() SPI
+ }
}
}
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java
index 7988bf8757..a07cc86ef5 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java
@@ -1,795 +1,818 @@
-/*
- * Copyright (c) 2004 - 2012 Eike Stepper (Berlin, Germany) and others.
- * 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:
- * Caspar De Groot - initial API and implementation
- */
-package org.eclipse.net4j.util.concurrent;
-
-import org.eclipse.net4j.util.CheckUtil;
-import org.eclipse.net4j.util.ObjectUtil;
-import org.eclipse.net4j.util.collection.HashBag;
-import org.eclipse.net4j.util.lifecycle.Lifecycle;
-
-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.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Keeps track of locks on objects. Locks are owned by contexts. A particular combination of locks and their owners, for
- * a given object, is represented by instances of the {@link LockState} class. This class is also repsonsible for
- * deciding whether or not a new lock can be granted, based on the locks already present.
- *
- * @author Caspar De Groot
- * @since 3.2
- */
-public class RWOLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWOLockManager<OBJECT, CONTEXT>
-{
- private final List<LockState<OBJECT, CONTEXT>> EMPTY_RESULT = Collections.emptyList();
-
- private final Map<OBJECT, LockState<OBJECT, CONTEXT>> objectToLockStateMap = createObjectToLocksMap();
-
- /**
- * A mapping of contexts (owners of locks) to the lock states that they are involved in. Here, an 'involvement' means
- * that the context owns at least one lock on the object that the lock state is for. To determine exactly what kind of
- * lock, the lock state object obtained from this map must be queried.
- * <p>
- * This map is a performance optimization to avoid having to scan all lock states.
- */
- private final Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> contextToLockStates = createContextToLocksMap();
-
- public void lock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout)
- throws InterruptedException
- {
- lock2(type, context, objectsToLock, timeout);
- }
-
- public List<LockState<OBJECT, CONTEXT>> lock2(LockType type, CONTEXT context,
- Collection<? extends OBJECT> objectsToLock, long timeout) throws InterruptedException
- {
- if (objectsToLock.isEmpty())
- {
- return EMPTY_RESULT;
- }
-
- // Must come before the synchronized block!
- long startTime = timeout == WAIT ? 0L : currentTimeMillis();
-
- // Do not synchronize the entire method as it would corrupt the timeout!
- synchronized (this)
- {
- int count = objectsToLock.size();
- List<LockState<OBJECT, CONTEXT>> lockStates = new ArrayList<LockState<OBJECT, CONTEXT>>(count);
-
- for (;;)
- {
- if (canLockInContext(type, context, objectsToLock, lockStates))
- {
- for (int i = 0; i < count; i++)
- {
- LockState<OBJECT, CONTEXT> lockState = lockStates.get(i);
- lockState.lock(type, context);
- addContextToLockStateMapping(context, lockState);
- }
-
- return lockStates;
- }
-
- wait(startTime, timeout);
- }
- }
- }
-
- public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException
- {
- // Do not synchronize the entire method as it would corrupt the timeout!
- lock(type, context, Collections.singleton(objectToLock), timeout);
- }
-
- public synchronized void unlock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
- {
- unlock2(type, context, objectsToUnlock);
- }
-
- public synchronized List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context,
- Collection<? extends OBJECT> objectsToUnlock)
- {
- if (objectsToUnlock.isEmpty())
- {
- return EMPTY_RESULT;
- }
-
- Set<LockState<OBJECT, CONTEXT>> lockStates = new HashSet<LockState<OBJECT, CONTEXT>>();
-
- Iterator<? extends OBJECT> it = objectsToUnlock.iterator();
- while (it.hasNext())
- {
- OBJECT o = it.next();
- LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
-
- if (lockState == null)
- {
- continue;
- }
-
- for (LockType lockType : LockType.values())
- {
- while (lockState.canUnlock(lockType, context))
- {
- lockState.unlock(lockType, context);
- lockStates.add(lockState);
- }
- }
- }
-
- for (LockState<OBJECT, CONTEXT> lockState : lockStates)
- {
- removeLockStateForContext(context, lockState);
-
- if (lockState.hasNoLocks())
- {
- objectToLockStateMap.remove(lockState.getLockedObject());
- }
- }
-
- notifyAll();
-
- return new LinkedList<RWOLockManager.LockState<OBJECT, CONTEXT>>(lockStates);
- }
-
- public synchronized List<LockState<OBJECT, CONTEXT>> unlock2(LockType type, CONTEXT context,
- Collection<? extends OBJECT> objectsToUnlock)
- {
- if (objectsToUnlock.isEmpty())
- {
- return EMPTY_RESULT;
- }
-
- List<LockState<OBJECT, CONTEXT>> lockStates = new LinkedList<LockState<OBJECT, CONTEXT>>();
-
- Iterator<? extends OBJECT> it = objectsToUnlock.iterator();
- while (it.hasNext())
- {
- OBJECT o = it.next();
- LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
- if (lockState == null || !lockState.canUnlock(type, context))
- {
- throw new IllegalMonitorStateException();
- }
-
- lockStates.add(lockState);
- }
-
- for (LockState<OBJECT, CONTEXT> lockState : lockStates)
- {
- lockState.unlock(type, context);
- if (!lockState.hasLocks(context))
- {
- removeLockStateForContext(context, lockState);
- }
-
- if (lockState.hasNoLocks())
- {
- objectToLockStateMap.remove(lockState.getLockedObject());
- }
- }
-
- notifyAll();
-
- return lockStates;
- }
-
- public synchronized void unlock(CONTEXT context)
- {
- unlock2(context);
- }
-
- public synchronized List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context)
- {
- Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
- if (lockStates == null)
- {
- return EMPTY_RESULT;
- }
-
- List<OBJECT> objectsWithoutLocks = new LinkedList<OBJECT>();
-
- for (LockState<OBJECT, CONTEXT> lockState : lockStates)
- {
- for (LockType lockType : LockType.values())
- {
- if (lockState.hasLock(lockType, context, false))
- {
- lockState.unlock(lockType, context);
- }
- }
-
- if (lockState.hasNoLocks())
- {
- OBJECT o = lockState.getLockedObject();
- objectsWithoutLocks.add(o);
- }
- }
-
- contextToLockStates.remove(context);
-
- // This must be done outside the above iteration, in order to avoid ConcurrentModEx
- for (OBJECT o : objectsWithoutLocks)
- {
- objectToLockStateMap.remove(o);
- }
-
- notifyAll();
-
- return toList(lockStates);
- }
-
- @SuppressWarnings("unchecked")
- private List<LockState<OBJECT, CONTEXT>> toList(Set<LockState<OBJECT, CONTEXT>> lockStates)
- {
- if (lockStates instanceof List)
- {
- return (List<LockState<OBJECT, CONTEXT>>)lockStates;
- }
-
- List<LockState<OBJECT, CONTEXT>> list = new LinkedList<LockState<OBJECT, CONTEXT>>();
- for (LockState<OBJECT, CONTEXT> lockState : lockStates)
- {
- list.add(lockState);
- }
-
- return list;
- }
-
- public synchronized boolean hasLock(LockType type, CONTEXT context, OBJECT objectToLock)
- {
- LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
- return lockState != null && lockState.hasLock(type, context, false);
- }
-
- public synchronized boolean hasLockByOthers(LockType type, CONTEXT context, OBJECT objectToLock)
- {
- LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
- return lockState != null && lockState.hasLock(type, context, true);
- }
-
- protected synchronized void changeContext(CONTEXT oldContext, CONTEXT newContext)
- {
- for (LockState<OBJECT, CONTEXT> lockState : objectToLockStateMap.values())
- {
- lockState.replaceContext(oldContext, newContext);
- }
-
- Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.remove(oldContext);
- if (lockStates != null)
- {
- contextToLockStates.put(newContext, lockStates);
- }
- }
-
- protected long currentTimeMillis()
- {
- return System.currentTimeMillis();
- }
-
- protected Map<OBJECT, LockState<OBJECT, CONTEXT>> createObjectToLocksMap()
- {
- return new HashMap<OBJECT, LockState<OBJECT, CONTEXT>>();
- }
-
- protected Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> createContextToLocksMap()
- {
- return new HashMap<CONTEXT, Set<LockState<OBJECT, CONTEXT>>>();
- }
-
- /**
- * All access to the returned map must be properly synchronized on this {@link RWOLockManager}.
- */
- protected final Map<OBJECT, LockState<OBJECT, CONTEXT>> getObjectToLocksMap()
- {
- return objectToLockStateMap;
- }
-
- /**
- * All access to the returned map must be properly synchronized on this {@link RWOLockManager}.
- */
- protected final Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> getContextToLocksMap()
- {
- return contextToLockStates;
- }
-
- public LockState<OBJECT, CONTEXT> getLockState(OBJECT key)
- {
- return objectToLockStateMap.get(key);
- }
-
- public synchronized void setLockState(OBJECT key, LockState<OBJECT, CONTEXT> lockState)
- {
- objectToLockStateMap.put(key, lockState);
-
- for (CONTEXT readLockOwner : lockState.getReadLockOwners())
- {
- addContextToLockStateMapping(readLockOwner, lockState);
- }
- CONTEXT writeLockOwner = lockState.getWriteLockOwner();
- if (writeLockOwner != null)
- {
- addContextToLockStateMapping(writeLockOwner, lockState);
- }
- CONTEXT writeOptionOwner = lockState.getWriteOptionOwner();
- if (writeOptionOwner != null)
- {
- addContextToLockStateMapping(writeOptionOwner, lockState);
- }
- }
-
- private LockState<OBJECT, CONTEXT> getOrCreateLockState(OBJECT o)
- {
- LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
- if (lockState == null)
- {
- lockState = new LockState<OBJECT, CONTEXT>(o);
- objectToLockStateMap.put(o, lockState);
- }
-
- return lockState;
- }
-
- private boolean canLockInContext(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock,
- List<LockState<OBJECT, CONTEXT>> lockStatesToFill)
- {
- Iterator<? extends OBJECT> it = objectsToLock.iterator();
- for (int i = 0; i < objectsToLock.size(); i++)
- {
- OBJECT o = it.next();
- LockState<OBJECT, CONTEXT> lockState = getOrCreateLockState(o);
- if (!lockState.canLock(type, context))
- {
- return false;
- }
-
- lockStatesToFill.add(lockState);
- }
-
- return true;
- }
-
- private void addContextToLockStateMapping(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
- {
- Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
- if (lockStates == null)
- {
- lockStates = new HashSet<LockState<OBJECT, CONTEXT>>();
- contextToLockStates.put(context, lockStates);
- }
-
- lockStates.add(lockState);
- }
-
- /**
- * Removes a lockState from the set of all lockStates that the given context is involved in. If the lockState being
- * removed is the last one for the given context, then the set becomes empty, and is therefore removed from the
- * contextToLockStates mp.
- */
- private void removeLockStateForContext(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
- {
- Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
- lockStates.remove(lockState);
- if (lockStates.isEmpty())
- {
- contextToLockStates.remove(context);
- }
- }
-
- private void wait(long startTime, long timeout) throws InterruptedException
- {
- if (timeout == WAIT)
- {
- wait();
- }
- else
- {
- long elapsedTime = currentTimeMillis() - startTime;
- long waitTime = timeout - elapsedTime;
- if (waitTime < 1)
- {
- throw new TimeoutRuntimeException("Could not lock objects within " + timeout + " milli seconds");
- }
-
- wait(waitTime);
- }
- }
-
- /**
- * Represents a combination of locks for one OBJECT. The different lock types are represented by the values of the
- * enum {@link IRWLockManager.LockType}
- * <p>
- * The locking semantics established by this class are as follows:
- * <li>a read lock prevents a write lock by another, but allows read locks by others and allows a write option by
- * another, and is therefore <b>non-exclusive</b></li>
- * <li>a write lock prevents read locks by others, a write lock by another, and a write option by another, and is
- * therefore <b>exclusive</b></li>
- * <li>a write option prevents write locks by others and a write option by another, but allows read locks by others,
- * and is therefore <b>exclusive</b></li>
- * <p>
- *
- * @author Caspar De Groot
- * @since 3.2
- */
- public static class LockState<OBJECT, CONTEXT>
- {
- private final OBJECT lockedObject;
-
- private final HashBag<CONTEXT> readLockOwners = new HashBag<CONTEXT>();
-
- private CONTEXT writeLockOwner;
-
- private CONTEXT writeOptionOwner;
-
- private int writeLockCounter;
-
- LockState(OBJECT lockedObject)
- {
- CheckUtil.checkArg(lockedObject, "lockedObject");
- this.lockedObject = lockedObject;
- }
-
- public OBJECT getLockedObject()
- {
- return lockedObject;
- }
-
- public boolean hasLock(org.eclipse.net4j.util.concurrent.IRWLockManager.LockType type, CONTEXT view,
- boolean byOthers)
- {
- CheckUtil.checkArg(view, "view");
-
- switch (type)
- {
- case READ:
- if (byOthers)
- {
- return readLockOwners.size() > 1 || readLockOwners.size() == 1 && !readLockOwners.contains(view);
- }
-
- return readLockOwners.contains(view);
-
- case WRITE:
- if (byOthers)
- {
- return writeLockOwner != null && writeLockOwner != view;
- }
-
- return writeLockOwner == view;
-
- case OPTION:
- if (byOthers)
- {
- return writeOptionOwner != null && writeOptionOwner != view;
- }
-
- return writeOptionOwner == view;
- }
-
- return false;
- }
-
- public boolean hasLock(org.eclipse.net4j.util.concurrent.IRWLockManager.LockType type)
- {
- switch (type)
- {
- case READ:
- return readLockOwners.size() > 0;
-
- case WRITE:
- return writeLockOwner != null;
-
- case OPTION:
- return writeOptionOwner != null;
- }
-
- return false;
- }
-
- @Override
- public String toString()
- {
- StringBuilder builder = new StringBuilder("LockState[target=");
- builder.append(lockedObject);
-
- if (readLockOwners.size() > 0)
- {
- builder.append(", read=");
- boolean first = true;
- for (CONTEXT view : readLockOwners)
- {
- if (first)
- {
- first = false;
- }
- else
- {
- builder.append(", ");
- }
-
- builder.append(view);
- }
-
- builder.deleteCharAt(builder.length() - 1);
- }
-
- if (writeLockOwner != null)
- {
- builder.append(", write=");
- builder.append(writeLockOwner);
- }
-
- if (writeOptionOwner != null)
- {
- builder.append(", option=");
- builder.append(writeOptionOwner);
- }
-
- builder.append(']');
- return builder.toString();
- }
-
- void lock(LockType type, CONTEXT context)
- {
- CheckUtil.checkArg(context, "context");
- switch (type)
- {
- case READ:
- doReadLock(context);
- return;
-
- case WRITE:
- doWriteLock(context);
- return;
-
- case OPTION:
- doWriteOption(context);
- return;
- }
-
- throw new AssertionError("Unknown lock type " + type);
- }
-
- boolean canLock(LockType type, CONTEXT context)
- {
- CheckUtil.checkArg(context, "context");
- switch (type)
- {
- case READ:
- return canReadLock(context);
-
- case WRITE:
- return canWriteLock(context);
-
- case OPTION:
- return canWriteOption(context);
- }
-
- throw new AssertionError("Unknown lock type " + type);
- }
-
- boolean canUnlock(LockType type, CONTEXT context)
- {
- CheckUtil.checkArg(context, "context");
- switch (type)
- {
- case READ:
- return canReadUnlock(context);
-
- case WRITE:
- return canWriteUnlock(context);
-
- case OPTION:
- return canWriteUnoption(context);
- }
-
- throw new AssertionError("Unknown lock type " + type);
- }
-
- void unlock(LockType type, CONTEXT context)
- {
- CheckUtil.checkArg(context, "context");
- switch (type)
- {
- case READ:
- doReadUnlock(context);
- return;
-
- case WRITE:
- doWriteUnlock(context);
- return;
-
- case OPTION:
- doWriteUnoption(context);
- return;
- }
-
- throw new AssertionError("Unknown lock type " + type);
- }
-
- void replaceContext(CONTEXT oldContext, CONTEXT newContext)
- {
- int readLocksOwnedByOldView = readLockOwners.getCounterFor(oldContext);
- if (readLocksOwnedByOldView > 0)
- {
- for (int i = 0; i < readLocksOwnedByOldView; i++)
- {
- readLockOwners.remove(oldContext);
- readLockOwners.add(newContext);
- }
- }
-
- if (ObjectUtil.equals(writeLockOwner, oldContext))
- {
- writeLockOwner = newContext;
- }
-
- if (ObjectUtil.equals(writeOptionOwner, oldContext))
- {
- writeOptionOwner = newContext;
- }
- }
-
- boolean hasNoLocks()
- {
- return readLockOwners.isEmpty() && writeLockOwner == null && writeOptionOwner == null;
- }
-
- boolean hasLocks(CONTEXT context)
- {
- return readLockOwners.contains(context) || writeLockOwner == context || writeOptionOwner == context;
- }
-
- private boolean canReadLock(CONTEXT context)
- {
- if (writeLockOwner != null && writeLockOwner != context)
- {
- return false;
- }
-
- return true;
- }
-
- private void doReadLock(CONTEXT context)
- {
- readLockOwners.add(context);
- }
-
- private boolean canWriteLock(CONTEXT context)
- {
- // If another context owns a writeLock, we can't write-lock
- if (writeLockOwner != null && writeLockOwner != context)
- {
- return false;
- }
-
- // If another context owns a writeOption, we can't write-lock
- if (writeOptionOwner != null && writeOptionOwner != context)
- {
- return false;
- }
-
- // If another context owns a readLock, we can't write-lock
- if (readLockOwners.size() > 1)
- {
- return false;
- }
-
- if (readLockOwners.size() == 1)
- {
- if (!readLockOwners.contains(context))
- {
- return false;
- }
- }
-
- return true;
- }
-
- private void doWriteLock(CONTEXT context)
- {
- writeLockOwner = context;
- writeLockCounter++;
- }
-
- private boolean canWriteOption(CONTEXT context)
- {
- if (writeOptionOwner != null && writeOptionOwner != context)
- {
- return false;
- }
-
- if (writeLockOwner != null && writeLockOwner != context)
- {
- return false;
- }
-
- return true;
- }
-
- private void doWriteOption(CONTEXT context)
- {
- writeOptionOwner = context;
- }
-
- private boolean canReadUnlock(CONTEXT context)
- {
- if (!readLockOwners.contains(context))
- {
- return false;
- }
-
- return true;
- }
-
- private void doReadUnlock(CONTEXT context)
- {
- readLockOwners.remove(context);
- }
-
- private boolean canWriteUnlock(CONTEXT context)
- {
- if (writeLockOwner != context)
- {
- return false;
- }
-
- return true;
- }
-
- private void doWriteUnlock(CONTEXT context)
- {
- writeLockCounter--;
- if (writeLockCounter == 0)
- {
- writeLockOwner = null;
- }
- }
-
- private boolean canWriteUnoption(CONTEXT context)
- {
- if (writeOptionOwner != context)
- {
- return false;
- }
-
- return true;
- }
-
- private void doWriteUnoption(CONTEXT context)
- {
- writeOptionOwner = null;
- }
-
- public Set<CONTEXT> getReadLockOwners()
- {
- return Collections.unmodifiableSet(readLockOwners);
- }
-
- public CONTEXT getWriteLockOwner()
- {
- return writeLockOwner;
- }
-
- public CONTEXT getWriteOptionOwner()
- {
- return writeOptionOwner;
- }
- }
-}
+/*
+ * Copyright (c) 2004 - 2012 Eike Stepper (Berlin, Germany) and others.
+ * 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:
+ * Caspar De Groot - initial API and implementation
+ */
+package org.eclipse.net4j.util.concurrent;
+
+import org.eclipse.net4j.internal.util.bundle.OM;
+import org.eclipse.net4j.util.CheckUtil;
+import org.eclipse.net4j.util.ObjectUtil;
+import org.eclipse.net4j.util.collection.HashBag;
+import org.eclipse.net4j.util.lifecycle.Lifecycle;
+import org.eclipse.net4j.util.om.trace.ContextTracer;
+
+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.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Keeps track of locks on objects. Locks are owned by contexts. A particular combination of locks and their owners, for
+ * a given object, is represented by instances of the {@link LockState} class. This class is also repsonsible for
+ * deciding whether or not a new lock can be granted, based on the locks already present.
+ *
+ * @author Caspar De Groot
+ * @since 3.2
+ */
+public class RWOLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWOLockManager<OBJECT, CONTEXT>
+{
+ private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_CONCURRENCY, RWOLockManager.class);
+
+ private final List<LockState<OBJECT, CONTEXT>> EMPTY_RESULT = Collections.emptyList();
+
+ private final Map<OBJECT, LockState<OBJECT, CONTEXT>> objectToLockStateMap = createObjectToLocksMap();
+
+ /**
+ * A mapping of contexts (owners of locks) to the lock states that they are involved in. Here, an 'involvement' means
+ * that the context owns at least one lock on the object that the lock state is for. To determine exactly what kind of
+ * lock, the lock state object obtained from this map must be queried.
+ * <p>
+ * This map is a performance optimization to avoid having to scan all lock states.
+ */
+ private final Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> contextToLockStates = createContextToLocksMap();
+
+ public void lock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout)
+ throws InterruptedException
+ {
+ lock2(type, context, objectsToLock, timeout);
+ }
+
+ public List<LockState<OBJECT, CONTEXT>> lock2(LockType type, CONTEXT context,
+ Collection<? extends OBJECT> objectsToLock, long timeout) throws InterruptedException
+ {
+ if (objectsToLock.isEmpty())
+ {
+ return EMPTY_RESULT;
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Lock: {0} --> {1}", objectsToLock, context); //$NON-NLS-1$
+ }
+
+ // Must come before the synchronized block!
+ long startTime = timeout == WAIT ? 0L : currentTimeMillis();
+
+ // Do not synchronize the entire method as it would corrupt the timeout!
+ synchronized (this)
+ {
+ int count = objectsToLock.size();
+ List<LockState<OBJECT, CONTEXT>> lockStates = new ArrayList<LockState<OBJECT, CONTEXT>>(count);
+
+ for (;;)
+ {
+ if (canLockInContext(type, context, objectsToLock, lockStates))
+ {
+ for (int i = 0; i < count; i++)
+ {
+ LockState<OBJECT, CONTEXT> lockState = lockStates.get(i);
+ lockState.lock(type, context);
+ addContextToLockStateMapping(context, lockState);
+ }
+
+ return lockStates;
+ }
+
+ wait(startTime, timeout);
+ }
+ }
+ }
+
+ public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException
+ {
+ // Do not synchronize the entire method as it would corrupt the timeout!
+ lock(type, context, Collections.singleton(objectToLock), timeout);
+ }
+
+ public synchronized void unlock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock)
+ {
+ unlock2(type, context, objectsToUnlock);
+ }
+
+ public synchronized List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context,
+ Collection<? extends OBJECT> objectsToUnlock)
+ {
+ if (objectsToUnlock.isEmpty())
+ {
+ return EMPTY_RESULT;
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Unlock", objectsToUnlock, context); //$NON-NLS-1$
+ }
+
+ Set<LockState<OBJECT, CONTEXT>> lockStates = new HashSet<LockState<OBJECT, CONTEXT>>();
+
+ Iterator<? extends OBJECT> it = objectsToUnlock.iterator();
+ while (it.hasNext())
+ {
+ OBJECT o = it.next();
+ LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
+
+ if (lockState == null)
+ {
+ continue;
+ }
+
+ for (LockType lockType : LockType.values())
+ {
+ while (lockState.canUnlock(lockType, context))
+ {
+ lockState.unlock(lockType, context);
+ lockStates.add(lockState);
+ }
+ }
+ }
+
+ for (LockState<OBJECT, CONTEXT> lockState : lockStates)
+ {
+ removeLockStateForContext(context, lockState);
+
+ if (lockState.hasNoLocks())
+ {
+ objectToLockStateMap.remove(lockState.getLockedObject());
+ }
+ }
+
+ notifyAll();
+
+ return new LinkedList<RWOLockManager.LockState<OBJECT, CONTEXT>>(lockStates);
+ }
+
+ public synchronized List<LockState<OBJECT, CONTEXT>> unlock2(LockType type, CONTEXT context,
+ Collection<? extends OBJECT> objectsToUnlock)
+ {
+ if (objectsToUnlock.isEmpty())
+ {
+ return EMPTY_RESULT;
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Unlock", objectsToUnlock, context); //$NON-NLS-1$
+ }
+
+ List<LockState<OBJECT, CONTEXT>> lockStates = new LinkedList<LockState<OBJECT, CONTEXT>>();
+
+ Iterator<? extends OBJECT> it = objectsToUnlock.iterator();
+ while (it.hasNext())
+ {
+ OBJECT o = it.next();
+ LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
+ if (lockState == null || !lockState.canUnlock(type, context))
+ {
+ throw new IllegalMonitorStateException();
+ }
+
+ lockStates.add(lockState);
+ }
+
+ for (LockState<OBJECT, CONTEXT> lockState : lockStates)
+ {
+ lockState.unlock(type, context);
+ if (!lockState.hasLocks(context))
+ {
+ removeLockStateForContext(context, lockState);
+ }
+
+ if (lockState.hasNoLocks())
+ {
+ objectToLockStateMap.remove(lockState.getLockedObject());
+ }
+ }
+
+ notifyAll();
+
+ return lockStates;
+ }
+
+ public synchronized void unlock(CONTEXT context)
+ {
+ unlock2(context);
+ }
+
+ public synchronized List<LockState<OBJECT, CONTEXT>> unlock2(CONTEXT context)
+ {
+ Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
+ if (lockStates == null)
+ {
+ return EMPTY_RESULT;
+ }
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("Unlock", lockStates, context); //$NON-NLS-1$
+ }
+
+ List<OBJECT> objectsWithoutLocks = new LinkedList<OBJECT>();
+
+ for (LockState<OBJECT, CONTEXT> lockState : lockStates)
+ {
+ for (LockType lockType : LockType.values())
+ {
+ if (lockState.hasLock(lockType, context, false))
+ {
+ lockState.unlock(lockType, context);
+ }
+ }
+
+ if (lockState.hasNoLocks())
+ {
+ OBJECT o = lockState.getLockedObject();
+ objectsWithoutLocks.add(o);
+ }
+ }
+
+ contextToLockStates.remove(context);
+
+ // This must be done outside the above iteration, in order to avoid ConcurrentModEx
+ for (OBJECT o : objectsWithoutLocks)
+ {
+ objectToLockStateMap.remove(o);
+ }
+
+ notifyAll();
+
+ return toList(lockStates);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<LockState<OBJECT, CONTEXT>> toList(Set<LockState<OBJECT, CONTEXT>> lockStates)
+ {
+ if (lockStates instanceof List)
+ {
+ return (List<LockState<OBJECT, CONTEXT>>)lockStates;
+ }
+
+ List<LockState<OBJECT, CONTEXT>> list = new LinkedList<LockState<OBJECT, CONTEXT>>();
+ for (LockState<OBJECT, CONTEXT> lockState : lockStates)
+ {
+ list.add(lockState);
+ }
+
+ return list;
+ }
+
+ public synchronized boolean hasLock(LockType type, CONTEXT context, OBJECT objectToLock)
+ {
+ LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
+ return lockState != null && lockState.hasLock(type, context, false);
+ }
+
+ public synchronized boolean hasLockByOthers(LockType type, CONTEXT context, OBJECT objectToLock)
+ {
+ LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(objectToLock);
+ return lockState != null && lockState.hasLock(type, context, true);
+ }
+
+ protected synchronized void changeContext(CONTEXT oldContext, CONTEXT newContext)
+ {
+ for (LockState<OBJECT, CONTEXT> lockState : objectToLockStateMap.values())
+ {
+ lockState.replaceContext(oldContext, newContext);
+ }
+
+ Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.remove(oldContext);
+ if (lockStates != null)
+ {
+ contextToLockStates.put(newContext, lockStates);
+ }
+ }
+
+ protected long currentTimeMillis()
+ {
+ return System.currentTimeMillis();
+ }
+
+ protected Map<OBJECT, LockState<OBJECT, CONTEXT>> createObjectToLocksMap()
+ {
+ return new HashMap<OBJECT, LockState<OBJECT, CONTEXT>>();
+ }
+
+ protected Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> createContextToLocksMap()
+ {
+ return new HashMap<CONTEXT, Set<LockState<OBJECT, CONTEXT>>>();
+ }
+
+ /**
+ * All access to the returned map must be properly synchronized on this {@link RWOLockManager}.
+ */
+ protected final Map<OBJECT, LockState<OBJECT, CONTEXT>> getObjectToLocksMap()
+ {
+ return objectToLockStateMap;
+ }
+
+ /**
+ * All access to the returned map must be properly synchronized on this {@link RWOLockManager}.
+ */
+ protected final Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> getContextToLocksMap()
+ {
+ return contextToLockStates;
+ }
+
+ public LockState<OBJECT, CONTEXT> getLockState(OBJECT key)
+ {
+ return objectToLockStateMap.get(key);
+ }
+
+ public synchronized void setLockState(OBJECT key, LockState<OBJECT, CONTEXT> lockState)
+ {
+ objectToLockStateMap.put(key, lockState);
+
+ for (CONTEXT readLockOwner : lockState.getReadLockOwners())
+ {
+ addContextToLockStateMapping(readLockOwner, lockState);
+ }
+ CONTEXT writeLockOwner = lockState.getWriteLockOwner();
+ if (writeLockOwner != null)
+ {
+ addContextToLockStateMapping(writeLockOwner, lockState);
+ }
+ CONTEXT writeOptionOwner = lockState.getWriteOptionOwner();
+ if (writeOptionOwner != null)
+ {
+ addContextToLockStateMapping(writeOptionOwner, lockState);
+ }
+ }
+
+ private LockState<OBJECT, CONTEXT> getOrCreateLockState(OBJECT o)
+ {
+ LockState<OBJECT, CONTEXT> lockState = objectToLockStateMap.get(o);
+ if (lockState == null)
+ {
+ lockState = new LockState<OBJECT, CONTEXT>(o);
+ objectToLockStateMap.put(o, lockState);
+ }
+
+ return lockState;
+ }
+
+ private boolean canLockInContext(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock,
+ List<LockState<OBJECT, CONTEXT>> lockStatesToFill)
+ {
+ Iterator<? extends OBJECT> it = objectsToLock.iterator();
+ for (int i = 0; i < objectsToLock.size(); i++)
+ {
+ OBJECT o = it.next();
+ LockState<OBJECT, CONTEXT> lockState = getOrCreateLockState(o);
+ if (!lockState.canLock(type, context))
+ {
+ return false;
+ }
+
+ lockStatesToFill.add(lockState);
+ }
+
+ return true;
+ }
+
+ private void addContextToLockStateMapping(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
+ {
+ Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
+ if (lockStates == null)
+ {
+ lockStates = new HashSet<LockState<OBJECT, CONTEXT>>();
+ contextToLockStates.put(context, lockStates);
+ }
+
+ lockStates.add(lockState);
+ }
+
+ /**
+ * Removes a lockState from the set of all lockStates that the given context is involved in. If the lockState being
+ * removed is the last one for the given context, then the set becomes empty, and is therefore removed from the
+ * contextToLockStates mp.
+ */
+ private void removeLockStateForContext(CONTEXT context, LockState<OBJECT, CONTEXT> lockState)
+ {
+ Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLockStates.get(context);
+ lockStates.remove(lockState);
+ if (lockStates.isEmpty())
+ {
+ contextToLockStates.remove(context);
+ }
+ }
+
+ private void wait(long startTime, long timeout) throws InterruptedException
+ {
+ if (timeout == WAIT)
+ {
+ wait();
+ }
+ else
+ {
+ long elapsedTime = currentTimeMillis() - startTime;
+ long waitTime = timeout - elapsedTime;
+ if (waitTime < 1)
+ {
+ throw new TimeoutRuntimeException("Could not lock objects within " + timeout + " milli seconds");
+ }
+
+ wait(waitTime);
+ }
+ }
+
+ /**
+ * Represents a combination of locks for one OBJECT. The different lock types are represented by the values of the
+ * enum {@link IRWLockManager.LockType}
+ * <p>
+ * The locking semantics established by this class are as follows:
+ * <li>a read lock prevents a write lock by another, but allows read locks by others and allows a write option by
+ * another, and is therefore <b>non-exclusive</b></li>
+ * <li>a write lock prevents read locks by others, a write lock by another, and a write option by another, and is
+ * therefore <b>exclusive</b></li>
+ * <li>a write option prevents write locks by others and a write option by another, but allows read locks by others,
+ * and is therefore <b>exclusive</b></li>.
+ *
+ * @author Caspar De Groot
+ * @since 3.2
+ */
+ public static class LockState<OBJECT, CONTEXT>
+ {
+ private final OBJECT lockedObject;
+
+ private final HashBag<CONTEXT> readLockOwners = new HashBag<CONTEXT>();
+
+ private CONTEXT writeLockOwner;
+
+ private CONTEXT writeOptionOwner;
+
+ private int writeLockCounter;
+
+ LockState(OBJECT lockedObject)
+ {
+ CheckUtil.checkArg(lockedObject, "lockedObject");
+ this.lockedObject = lockedObject;
+ }
+
+ public OBJECT getLockedObject()
+ {
+ return lockedObject;
+ }
+
+ public boolean hasLock(org.eclipse.net4j.util.concurrent.IRWLockManager.LockType type, CONTEXT view,
+ boolean byOthers)
+ {
+ CheckUtil.checkArg(view, "view");
+
+ switch (type)
+ {
+ case READ:
+ if (byOthers)
+ {
+ return readLockOwners.size() > 1 || readLockOwners.size() == 1 && !readLockOwners.contains(view);
+ }
+
+ return readLockOwners.contains(view);
+
+ case WRITE:
+ if (byOthers)
+ {
+ return writeLockOwner != null && writeLockOwner != view;
+ }
+
+ return writeLockOwner == view;
+
+ case OPTION:
+ if (byOthers)
+ {
+ return writeOptionOwner != null && writeOptionOwner != view;
+ }
+
+ return writeOptionOwner == view;
+ }
+
+ return false;
+ }
+
+ public boolean hasLock(org.eclipse.net4j.util.concurrent.IRWLockManager.LockType type)
+ {
+ switch (type)
+ {
+ case READ:
+ return readLockOwners.size() > 0;
+
+ case WRITE:
+ return writeLockOwner != null;
+
+ case OPTION:
+ return writeOptionOwner != null;
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder("LockState[target=");
+ builder.append(lockedObject);
+
+ if (readLockOwners.size() > 0)
+ {
+ builder.append(", read=");
+ boolean first = true;
+ for (CONTEXT view : readLockOwners)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ builder.append(", ");
+ }
+
+ builder.append(view);
+ }
+
+ builder.deleteCharAt(builder.length() - 1);
+ }
+
+ if (writeLockOwner != null)
+ {
+ builder.append(", write=");
+ builder.append(writeLockOwner);
+ }
+
+ if (writeOptionOwner != null)
+ {
+ builder.append(", option=");
+ builder.append(writeOptionOwner);
+ }
+
+ builder.append(']');
+ return builder.toString();
+ }
+
+ void lock(LockType type, CONTEXT context)
+ {
+ CheckUtil.checkArg(context, "context");
+ switch (type)
+ {
+ case READ:
+ doReadLock(context);
+ return;
+
+ case WRITE:
+ doWriteLock(context);
+ return;
+
+ case OPTION:
+ doWriteOption(context);
+ return;
+ }
+
+ throw new AssertionError("Unknown lock type " + type);
+ }
+
+ boolean canLock(LockType type, CONTEXT context)
+ {
+ CheckUtil.checkArg(context, "context");
+ switch (type)
+ {
+ case READ:
+ return canReadLock(context);
+
+ case WRITE:
+ return canWriteLock(context);
+
+ case OPTION:
+ return canWriteOption(context);
+ }
+
+ throw new AssertionError("Unknown lock type " + type);
+ }
+
+ boolean canUnlock(LockType type, CONTEXT context)
+ {
+ CheckUtil.checkArg(context, "context");
+ switch (type)
+ {
+ case READ:
+ return canReadUnlock(context);
+
+ case WRITE:
+ return canWriteUnlock(context);
+
+ case OPTION:
+ return canWriteUnoption(context);
+ }
+
+ throw new AssertionError("Unknown lock type " + type);
+ }
+
+ void unlock(LockType type, CONTEXT context)
+ {
+ CheckUtil.checkArg(context, "context");
+ switch (type)
+ {
+ case READ:
+ doReadUnlock(context);
+ return;
+
+ case WRITE:
+ doWriteUnlock(context);
+ return;
+
+ case OPTION:
+ doWriteUnoption(context);
+ return;
+ }
+
+ throw new AssertionError("Unknown lock type " + type);
+ }
+
+ void replaceContext(CONTEXT oldContext, CONTEXT newContext)
+ {
+ int readLocksOwnedByOldView = readLockOwners.getCounterFor(oldContext);
+ if (readLocksOwnedByOldView > 0)
+ {
+ for (int i = 0; i < readLocksOwnedByOldView; i++)
+ {
+ readLockOwners.remove(oldContext);
+ readLockOwners.add(newContext);
+ }
+ }
+
+ if (ObjectUtil.equals(writeLockOwner, oldContext))
+ {
+ writeLockOwner = newContext;
+ }
+
+ if (ObjectUtil.equals(writeOptionOwner, oldContext))
+ {
+ writeOptionOwner = newContext;
+ }
+ }
+
+ boolean hasNoLocks()
+ {
+ return readLockOwners.isEmpty() && writeLockOwner == null && writeOptionOwner == null;
+ }
+
+ boolean hasLocks(CONTEXT context)
+ {
+ return readLockOwners.contains(context) || writeLockOwner == context || writeOptionOwner == context;
+ }
+
+ private boolean canReadLock(CONTEXT context)
+ {
+ if (writeLockOwner != null && writeLockOwner != context)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void doReadLock(CONTEXT context)
+ {
+ readLockOwners.add(context);
+ }
+
+ private boolean canWriteLock(CONTEXT context)
+ {
+ // If another context owns a writeLock, we can't write-lock
+ if (writeLockOwner != null && writeLockOwner != context)
+ {
+ return false;
+ }
+
+ // If another context owns a writeOption, we can't write-lock
+ if (writeOptionOwner != null && writeOptionOwner != context)
+ {
+ return false;
+ }
+
+ // If another context owns a readLock, we can't write-lock
+ if (readLockOwners.size() > 1)
+ {
+ return false;
+ }
+
+ if (readLockOwners.size() == 1)
+ {
+ if (!readLockOwners.contains(context))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void doWriteLock(CONTEXT context)
+ {
+ writeLockOwner = context;
+ writeLockCounter++;
+ }
+
+ private boolean canWriteOption(CONTEXT context)
+ {
+ if (writeOptionOwner != null && writeOptionOwner != context)
+ {
+ return false;
+ }
+
+ if (writeLockOwner != null && writeLockOwner != context)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void doWriteOption(CONTEXT context)
+ {
+ writeOptionOwner = context;
+ }
+
+ private boolean canReadUnlock(CONTEXT context)
+ {
+ if (!readLockOwners.contains(context))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void doReadUnlock(CONTEXT context)
+ {
+ readLockOwners.remove(context);
+ }
+
+ private boolean canWriteUnlock(CONTEXT context)
+ {
+ if (writeLockOwner != context)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void doWriteUnlock(CONTEXT context)
+ {
+ writeLockCounter--;
+ if (writeLockCounter == 0)
+ {
+ writeLockOwner = null;
+ }
+ }
+
+ private boolean canWriteUnoption(CONTEXT context)
+ {
+ if (writeOptionOwner != context)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void doWriteUnoption(CONTEXT context)
+ {
+ writeOptionOwner = null;
+ }
+
+ public Set<CONTEXT> getReadLockOwners()
+ {
+ return Collections.unmodifiableSet(readLockOwners);
+ }
+
+ public CONTEXT getWriteLockOwner()
+ {
+ return writeLockOwner;
+ }
+
+ public CONTEXT getWriteOptionOwner()
+ {
+ return writeOptionOwner;
+ }
+ }
+}

Back to the top