/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.id;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
/**
* Utility class for working with {@link VersionCorrection} instances.
*/
public final class VersionCorrectionUtils {
private static final Logger s_logger = LoggerFactory.getLogger(VersionCorrectionUtils.class);
/**
* Listener for locking events.
*/
public interface VersionCorrectionLockListener {
/**
* Called when the last lock on a version/correction pair is released.
*
* @param unlocked the version/correction pair unlocked
* @param locked the version/correction pairs still locked
*/
void versionCorrectionUnlocked(VersionCorrection unlocked, Collection<VersionCorrection> locked);
}
private static final Map<VersionCorrection, AtomicInteger> s_locks = new HashMap<VersionCorrection, AtomicInteger>();
private static final Set<VersionCorrectionLockListener> s_listeners = Sets.newSetFromMap(new MapMaker().weakKeys().<VersionCorrectionLockListener, Boolean>makeMap());
private static final Map<Reference<Object>, VersionCorrection> s_autoLocks = new ConcurrentHashMap<Reference<Object>, VersionCorrection>();
private static final ReferenceQueue<Object> s_autoUnlocks = new ReferenceQueue<Object>();
/**
* Prevents instantiation.
*/
private VersionCorrectionUtils() {
}
/**
* Acquires a lock on a version/correction pair. It is possible for other threads to determine whether there are any outstanding locks, or to execute actions when the last lock is released. Must be
* paired with a call to {@link #unlock}.
*
* @param versionCorrection the version/correction pair to lock, not null
*/
public static void lock(final VersionCorrection versionCorrection) {
synchronized (s_locks) {
s_logger.info("Acquiring lock on {}", versionCorrection);
AtomicInteger locked = s_locks.get(versionCorrection);
if (locked == null) {
locked = new AtomicInteger(1);
s_locks.put(versionCorrection, locked);
s_logger.debug("First lock acquired on {}", versionCorrection);
} else {
final int count = locked.incrementAndGet();
s_logger.debug("Lock {} acquired on {}", count);
}
}
}
/**
* Acquires a lock on a version/correction pair for the lifetime of the monitor object. It is possible for other threads to determine whether there are any outstanding locks, or to execute actions
* when the last lock is released. Must be paired with a call to {@link #unlock}.
*
* @param versionCorrection the version/correction pair to lock, not null
* @param monitor the monitor object - the lock will be released when this falls out of scope, not null
*/
public static void lockForLifetime(VersionCorrection versionCorrection, final Object monitor) {
lock(versionCorrection);
s_autoLocks.put(new PhantomReference<Object>(monitor, s_autoUnlocks), versionCorrection);
Reference<? extends Object> ref = s_autoUnlocks.poll();
while (ref != null) {
versionCorrection = s_autoLocks.remove(ref);
if (versionCorrection != null) {
unlock(versionCorrection);
}
ref = s_autoUnlocks.poll();
}
}
/**
* Releases a lock on a version/correction pair. It is possible for other threads to determine whether there are any outstanding locks, or to execute actions when the last lock is released. Must be
* paired with a call to {@link #unlock}.
*
* @param versionCorrection the version/correction pair to lock, not null
*/
public static void unlock(final VersionCorrection versionCorrection) {
final Set<VersionCorrection> remaining;
synchronized (s_locks) {
s_logger.info("Releasing lock on {}", versionCorrection);
AtomicInteger locked = s_locks.get(versionCorrection);
if (locked == null) {
s_logger.warn("{} not locked", versionCorrection);
throw new IllegalStateException();
}
final int count = locked.decrementAndGet();
if (count > 0) {
s_logger.debug("Released lock on {}, {} remaining", versionCorrection, count);
return;
}
assert count == 0;
s_logger.debug("Last lock on {} released", versionCorrection);
s_locks.remove(versionCorrection);
remaining = new HashSet<VersionCorrection>(s_locks.keySet());
}
for (VersionCorrectionLockListener listener : s_listeners) {
listener.versionCorrectionUnlocked(versionCorrection, remaining);
}
}
public static void addVersionCorrectionLockListener(final VersionCorrectionLockListener listener) {
s_listeners.add(listener);
}
public static void removeVersionCorrectionLockListener(final VersionCorrectionLockListener listener) {
s_listeners.remove(listener);
}
}