| /* |
| * Copyright (c) 2008-2012, 2015 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: |
| * Simon McDuff - initial API and implementation |
| * Eike Stepper - maintenance |
| */ |
| package org.eclipse.net4j.util.concurrent; |
| |
| import org.eclipse.net4j.util.ObjectUtil; |
| import org.eclipse.net4j.util.collection.HashBag; |
| import org.eclipse.net4j.util.lifecycle.Lifecycle; |
| |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * Support Multiple reads/no write and upgrade lock from read to write. Many context could request |
| * {@link IRWLockManager.LockType#WRITE write} lock at the same time. It will privileges first context that has already |
| * a {@link IRWLockManager.LockType#READ read} lock. If no one has any read lock, it's "first come first serve". |
| * |
| * @author Simon McDuff |
| * @since 2.0 |
| * @deprecated Use {@link RWOLockManager} |
| */ |
| @Deprecated |
| public class RWLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWLockManager<OBJECT, CONTEXT> |
| { |
| private LockStrategy<OBJECT, CONTEXT> readLockStrategy = new LockStrategy<OBJECT, CONTEXT>() |
| { |
| public boolean isLocked(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.isReadLock(context); |
| } |
| |
| public boolean isLockedByOthers(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.isReadLockByOthers(context); |
| } |
| |
| public boolean canObtainLock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.canObtainReadLock(context); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> lock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.readLock(context); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> unlock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.readUnlock(context); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "ReadLockStrategy"; |
| } |
| }; |
| |
| private LockStrategy<OBJECT, CONTEXT> writeLockStrategy = new LockStrategy<OBJECT, CONTEXT>() |
| { |
| public boolean isLocked(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.isWriteLock(context); |
| } |
| |
| public boolean isLockedByOthers(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.isWriteLockByOthers(context); |
| } |
| |
| public boolean canObtainLock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.canObtainWriteLock(context); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> lock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.writeLock(context); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> unlock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context) |
| { |
| return entry.writeUnlock(context); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "WriteLockStrategy"; |
| } |
| }; |
| |
| private Map<OBJECT, LockEntry<OBJECT, CONTEXT>> lockEntries = new HashMap<OBJECT, LockEntry<OBJECT, CONTEXT>>(); |
| |
| private LockChanged lockChanged = new LockChanged(); |
| |
| /** |
| * @since 3.0 |
| */ |
| public void lock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout) |
| throws InterruptedException |
| { |
| LockStrategy<OBJECT, CONTEXT> lockingStrategy = getLockingStrategy(type); |
| lock(lockingStrategy, context, objectsToLock, timeout); |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException |
| { |
| List<OBJECT> objectsToLock = Collections.singletonList(objectToLock); |
| lock(type, context, objectsToLock, timeout); |
| } |
| |
| /** |
| * Attempts to release for a given locktype, context and objects. |
| * |
| * @throws IllegalMonitorStateException |
| * Unlocking objects without lock. |
| * @since 3.0 |
| */ |
| public void unlock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock) |
| { |
| LockStrategy<OBJECT, CONTEXT> lockingStrategy = getLockingStrategy(type); |
| unlock(lockingStrategy, context, objectsToUnlock); |
| } |
| |
| /** |
| * Attempts to release all locks(read and write) for a given context. |
| */ |
| public void unlock(CONTEXT context) |
| { |
| synchronized (lockChanged) |
| { |
| List<LockEntry<OBJECT, CONTEXT>> lockEntrysToRemove = new ArrayList<LockEntry<OBJECT, CONTEXT>>(); |
| List<LockEntry<OBJECT, CONTEXT>> lockEntrysToAdd = new ArrayList<LockEntry<OBJECT, CONTEXT>>(); |
| |
| for (Entry<OBJECT, LockEntry<OBJECT, CONTEXT>> entry : lockEntries.entrySet()) |
| { |
| LockEntry<OBJECT, CONTEXT> lockedContext = entry.getValue(); |
| LockEntry<OBJECT, CONTEXT> newEntry = lockedContext.clearLock(context); |
| if (newEntry == null) |
| { |
| lockEntrysToRemove.add(lockedContext); |
| } |
| else if (newEntry != entry) |
| { |
| lockEntrysToAdd.add(newEntry); |
| } |
| } |
| |
| for (LockEntry<OBJECT, CONTEXT> lockEntry : lockEntrysToRemove) |
| { |
| OBJECT object = lockEntry.getObject(); |
| lockEntries.remove(object); |
| } |
| |
| for (LockEntry<OBJECT, CONTEXT> lockEntry : lockEntrysToAdd) |
| { |
| OBJECT object = lockEntry.getObject(); |
| lockEntries.put(object, lockEntry); |
| } |
| |
| lockChanged.notifyAll(); |
| } |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public boolean hasLock(LockType type, CONTEXT context, OBJECT objectToLock) |
| { |
| LockStrategy<OBJECT, CONTEXT> lockingStrategy = getLockingStrategy(type); |
| return hasLock(lockingStrategy, context, objectToLock); |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| public boolean hasLockByOthers(LockType type, CONTEXT context, OBJECT objectToLock) |
| { |
| LockStrategy<OBJECT, CONTEXT> lockingStrategy = getLockingStrategy(type); |
| LockEntry<OBJECT, CONTEXT> entry = getLockEntry(objectToLock); |
| return entry != null && lockingStrategy.isLockedByOthers(entry, context); |
| } |
| |
| /** |
| * @since 3.1 |
| */ |
| protected void handleLockEntries(CONTEXT context, LockEntryHandler<OBJECT, CONTEXT> handler) |
| { |
| synchronized (lockChanged) |
| { |
| for (LockEntry<OBJECT, CONTEXT> lockEntry : lockEntries.values()) |
| { |
| if (context == null || lockEntry.hasContext(context)) |
| { |
| if (!handler.handleLockEntry(lockEntry)) |
| { |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @since 3.1 |
| */ |
| protected LockEntry<OBJECT, CONTEXT> getLockEntry(OBJECT objectToLock) |
| { |
| synchronized (lockChanged) |
| { |
| return lockEntries.get(objectToLock); |
| } |
| } |
| |
| /** |
| * @since 3.1 |
| */ |
| protected LockStrategy<OBJECT, CONTEXT> getLockingStrategy(LockType type) |
| { |
| if (type == LockType.READ) |
| { |
| return readLockStrategy; |
| } |
| |
| if (type == LockType.WRITE) |
| { |
| return writeLockStrategy; |
| } |
| |
| throw new IllegalArgumentException("Invalid lock type: " + type); |
| } |
| |
| /** |
| * @since 3.1 |
| */ |
| protected void changeContext(CONTEXT oldContext, CONTEXT newContext) |
| { |
| synchronized (lockChanged) |
| { |
| for (LockEntry<OBJECT, CONTEXT> lockEntry : lockEntries.values()) |
| { |
| lockEntry.changeContext(oldContext, newContext); |
| } |
| } |
| } |
| |
| /** |
| * Attempts to release this lock. |
| * <p> |
| * If the number of context is now zero then the lock is made available for write lock attempts. |
| * |
| * @throws IllegalMonitorStateException |
| * Unlocking object not locked. |
| */ |
| private void unlock(LockStrategy<OBJECT, CONTEXT> lockingStrategy, CONTEXT context, |
| Collection<? extends OBJECT> objectsToLock) |
| { |
| synchronized (lockChanged) |
| { |
| List<LockEntry<OBJECT, CONTEXT>> lockEntrysToRemove = new ArrayList<LockEntry<OBJECT, CONTEXT>>(); |
| List<LockEntry<OBJECT, CONTEXT>> lockEntrysToAdd = new ArrayList<LockEntry<OBJECT, CONTEXT>>(); |
| for (OBJECT objectToLock : objectsToLock) |
| { |
| LockEntry<OBJECT, CONTEXT> entry = lockEntries.get(objectToLock); |
| if (entry == null) |
| { |
| throw new IllegalMonitorStateException(); |
| } |
| |
| LockEntry<OBJECT, CONTEXT> newEntry = lockingStrategy.unlock(entry, context); |
| if (newEntry == null) |
| { |
| lockEntrysToRemove.add(entry); |
| } |
| else if (newEntry != entry) |
| { |
| lockEntrysToAdd.add(newEntry); |
| } |
| } |
| |
| for (LockEntry<OBJECT, CONTEXT> lockEntry : lockEntrysToRemove) |
| { |
| OBJECT object = lockEntry.getObject(); |
| lockEntries.remove(object); |
| } |
| |
| for (LockEntry<OBJECT, CONTEXT> lockEntry : lockEntrysToAdd) |
| { |
| OBJECT object = lockEntry.getObject(); |
| lockEntries.put(object, lockEntry); |
| } |
| |
| lockChanged.notifyAll(); |
| } |
| } |
| |
| private boolean hasLock(LockStrategy<OBJECT, CONTEXT> lockingStrategy, CONTEXT context, OBJECT objectToLock) |
| { |
| LockEntry<OBJECT, CONTEXT> entry = getLockEntry(objectToLock); |
| return entry != null && lockingStrategy.isLocked(entry, context); |
| } |
| |
| private void lock(LockStrategy<OBJECT, CONTEXT> lockStrategy, CONTEXT context, |
| Collection<? extends OBJECT> objectsToLocks, long timeout) throws InterruptedException |
| { |
| long startTime = System.currentTimeMillis(); |
| while (true) |
| { |
| synchronized (lockChanged) |
| { |
| OBJECT conflict = obtainLock(lockStrategy, context, objectsToLocks); |
| if (conflict == null) |
| { |
| lockChanged.notifyAll(); |
| return; |
| } |
| |
| long elapsedTime = System.currentTimeMillis() - startTime; |
| if (timeout != WAIT && elapsedTime > timeout) |
| { |
| throw new TimeoutRuntimeException("Could not lock " + conflict + " within " + timeout + " milli seconds"); //$NON-NLS-1$ |
| } |
| |
| if (timeout == WAIT) |
| { |
| lockChanged.wait(); |
| } |
| else |
| { |
| lockChanged.wait(Math.max(1, timeout - elapsedTime)); |
| } |
| } |
| } |
| } |
| |
| private OBJECT obtainLock(LockStrategy<OBJECT, CONTEXT> lockingStrategy, CONTEXT context, |
| Collection<? extends OBJECT> objectsToLock) |
| { |
| List<LockEntry<OBJECT, CONTEXT>> lockEntrys = new ArrayList<LockEntry<OBJECT, CONTEXT>>(); |
| for (OBJECT objectToLock : objectsToLock) |
| { |
| LockEntry<OBJECT, CONTEXT> entry = lockEntries.get(objectToLock); |
| if (entry == null) |
| { |
| entry = new NoLockEntry<OBJECT, CONTEXT>(objectToLock); |
| } |
| |
| if (lockingStrategy.canObtainLock(entry, context)) |
| { |
| lockEntrys.add(entry); |
| } |
| else |
| { |
| return objectToLock; |
| } |
| } |
| |
| for (LockEntry<OBJECT, CONTEXT> lockEntry : lockEntrys) |
| { |
| OBJECT object = lockEntry.getObject(); |
| LockEntry<OBJECT, CONTEXT> lock = lockingStrategy.lock(lockEntry, context); |
| lockEntries.put(object, lock); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * @author Simon McDuff |
| * @since 3.1 |
| * @deprecated Use {@link RWOLockManager} |
| */ |
| @Deprecated |
| protected interface LockStrategy<OBJECT, CONTEXT> |
| { |
| public boolean isLocked(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context); |
| |
| public boolean isLockedByOthers(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context); |
| |
| public boolean canObtainLock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context); |
| |
| public LockEntry<OBJECT, CONTEXT> lock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context); |
| |
| public LockEntry<OBJECT, CONTEXT> unlock(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context); |
| } |
| |
| /** |
| * @author Simon McDuff |
| * @since 3.1 |
| * @deprecated Use {@link RWOLockManager} |
| */ |
| @Deprecated |
| protected interface LockEntry<OBJECT, CONTEXT> |
| { |
| public OBJECT getObject(); |
| |
| public boolean isReadLock(CONTEXT context); |
| |
| public boolean isWriteLock(CONTEXT context); |
| |
| public boolean isReadLockByOthers(CONTEXT context); |
| |
| public boolean isWriteLockByOthers(CONTEXT context); |
| |
| public boolean canObtainReadLock(CONTEXT context); |
| |
| public boolean canObtainWriteLock(CONTEXT context); |
| |
| public LockEntry<OBJECT, CONTEXT> readLock(CONTEXT context); |
| |
| public LockEntry<OBJECT, CONTEXT> writeLock(CONTEXT context); |
| |
| public LockEntry<OBJECT, CONTEXT> readUnlock(CONTEXT context); |
| |
| public LockEntry<OBJECT, CONTEXT> writeUnlock(CONTEXT context); |
| |
| public LockEntry<OBJECT, CONTEXT> clearLock(CONTEXT context); |
| |
| /** |
| * @since 3.1 |
| */ |
| public void changeContext(CONTEXT oldContext, CONTEXT newContext); |
| |
| /** |
| * @since 3.1 |
| */ |
| public boolean hasContext(CONTEXT context); |
| } |
| |
| /** |
| * @author Eike Stepper |
| * @since 3.1 |
| * @deprecated Use {@link RWOLockManager} |
| */ |
| @Deprecated |
| protected interface LockEntryHandler<OBJECT, CONTEXT> |
| { |
| public boolean handleLockEntry(LockEntry<OBJECT, CONTEXT> lockEntry); |
| } |
| |
| /** |
| * @author Simon McDuff |
| */ |
| private static final class ReadLockEntry<OBJECT, CONTEXT> implements LockEntry<OBJECT, CONTEXT> |
| { |
| private OBJECT object; |
| |
| private Set<CONTEXT> contexts = new HashBag<CONTEXT>(); |
| |
| public ReadLockEntry(OBJECT objectToLock, CONTEXT context) |
| { |
| this.object = objectToLock; |
| contexts.add(context); |
| } |
| |
| public OBJECT getObject() |
| { |
| return object; |
| } |
| |
| public boolean isReadLock(CONTEXT context) |
| { |
| return contexts.contains(context); |
| } |
| |
| public boolean isWriteLock(CONTEXT context) |
| { |
| return false; |
| } |
| |
| public boolean isReadLockByOthers(CONTEXT context) |
| { |
| if (contexts.isEmpty()) |
| { |
| return false; |
| } |
| |
| return contexts.size() > (isReadLock(context) ? 1 : 0); |
| } |
| |
| public boolean isWriteLockByOthers(CONTEXT context) |
| { |
| return false; |
| } |
| |
| public boolean canObtainReadLock(CONTEXT context) |
| { |
| return true; |
| } |
| |
| public boolean canObtainWriteLock(CONTEXT context) |
| { |
| return contexts.size() == 1 && contexts.contains(context); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> readLock(CONTEXT context) |
| { |
| contexts.add(context); |
| return this; |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> writeLock(CONTEXT context) |
| { |
| return new WriteLockEntry<OBJECT, CONTEXT>(object, context, this); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> readUnlock(CONTEXT context) |
| { |
| contexts.remove(context); |
| return contexts.isEmpty() ? null : this; |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> writeUnlock(CONTEXT context) |
| { |
| throw new IllegalMonitorStateException(); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> clearLock(CONTEXT context) |
| { |
| while (contexts.remove(context)) |
| { |
| } |
| |
| return contexts.isEmpty() ? null : this; |
| } |
| |
| public void changeContext(CONTEXT oldContext, CONTEXT newContext) |
| { |
| if (contexts.remove(oldContext)) |
| { |
| contexts.add(newContext); |
| } |
| } |
| |
| public boolean hasContext(CONTEXT context) |
| { |
| return contexts.contains(context); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MessageFormat.format("ReadLockEntry[object={0}, contexts={1}]", object, contexts); |
| } |
| } |
| |
| /** |
| * @author Simon McDuff |
| */ |
| private static final class WriteLockEntry<OBJECT, CONTEXT> implements LockEntry<OBJECT, CONTEXT> |
| { |
| private OBJECT object; |
| |
| private CONTEXT context; |
| |
| private int count; |
| |
| private ReadLockEntry<OBJECT, CONTEXT> readLock; |
| |
| public WriteLockEntry(OBJECT object, CONTEXT context, ReadLockEntry<OBJECT, CONTEXT> readLock) |
| { |
| this.object = object; |
| this.context = context; |
| this.readLock = readLock; |
| this.count = 1; |
| } |
| |
| public OBJECT getObject() |
| { |
| return object; |
| } |
| |
| public boolean isReadLock(CONTEXT context) |
| { |
| return readLock != null ? readLock.isReadLock(context) : false; |
| } |
| |
| public boolean isWriteLock(CONTEXT context) |
| { |
| return ObjectUtil.equals(this.context, context); |
| } |
| |
| public boolean isReadLockByOthers(CONTEXT context) |
| { |
| return readLock != null ? readLock.isReadLockByOthers(context) : false; |
| } |
| |
| public boolean isWriteLockByOthers(CONTEXT context) |
| { |
| return context != this.context; |
| } |
| |
| public boolean canObtainWriteLock(CONTEXT context) |
| { |
| return ObjectUtil.equals(this.context, context); |
| } |
| |
| public boolean canObtainReadLock(CONTEXT context) |
| { |
| return ObjectUtil.equals(this.context, context); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> readLock(CONTEXT context) |
| { |
| ReadLockEntry<OBJECT, CONTEXT> lock = getReadLock(); |
| lock.readLock(context); |
| return this; |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> writeLock(CONTEXT context) |
| { |
| count++; |
| return this; |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> readUnlock(CONTEXT context) |
| { |
| if (readLock != null) |
| { |
| if (readLock.readUnlock(context) == null) |
| { |
| readLock = null; |
| } |
| |
| return this; |
| } |
| |
| throw new IllegalMonitorStateException(); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> writeUnlock(CONTEXT context) |
| { |
| return --count <= 0 ? readLock : this; |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> clearLock(CONTEXT context) |
| { |
| if (readLock != null) |
| { |
| if (readLock.clearLock(context) == null) |
| { |
| readLock = null; |
| } |
| } |
| |
| return ObjectUtil.equals(this.context, context) ? readLock : this; |
| } |
| |
| public void changeContext(CONTEXT oldContext, CONTEXT newContext) |
| { |
| if (ObjectUtil.equals(context, oldContext)) |
| { |
| context = newContext; |
| } |
| } |
| |
| public boolean hasContext(CONTEXT context) |
| { |
| return ObjectUtil.equals(this.context, context); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MessageFormat.format("WriteLockEntry[object={0}, context={1}, count={2}]", object, context, count); |
| } |
| |
| private ReadLockEntry<OBJECT, CONTEXT> getReadLock() |
| { |
| if (readLock == null) |
| { |
| readLock = new ReadLockEntry<OBJECT, CONTEXT>(object, context); |
| } |
| |
| return readLock; |
| } |
| } |
| |
| /** |
| * @author Simon McDuff |
| */ |
| private static final class NoLockEntry<OBJECT, CONTEXT> implements LockEntry<OBJECT, CONTEXT> |
| { |
| private OBJECT object; |
| |
| public NoLockEntry(OBJECT objectToLock) |
| { |
| this.object = objectToLock; |
| } |
| |
| public OBJECT getObject() |
| { |
| return object; |
| } |
| |
| public boolean isReadLock(CONTEXT context) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean isWriteLock(CONTEXT context) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean isReadLockByOthers(CONTEXT context) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean isWriteLockByOthers(CONTEXT context) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public boolean canObtainWriteLock(CONTEXT context) |
| { |
| return true; |
| } |
| |
| public boolean canObtainReadLock(CONTEXT context) |
| { |
| return true; |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> readLock(CONTEXT context) |
| { |
| return new ReadLockEntry<OBJECT, CONTEXT>(object, context); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> writeLock(CONTEXT context) |
| { |
| return new WriteLockEntry<OBJECT, CONTEXT>(object, context, null); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> readUnlock(CONTEXT context) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> writeUnlock(CONTEXT context) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public LockEntry<OBJECT, CONTEXT> clearLock(CONTEXT context) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public void changeContext(CONTEXT oldContext, CONTEXT newContext) |
| { |
| // Do nothing |
| } |
| |
| public boolean hasContext(CONTEXT context) |
| { |
| return false; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MessageFormat.format("NoLockEntry[object={0}]", object); |
| } |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class LockChanged |
| { |
| } |
| } |