// Path: src/main/java/javafixes/common/function/Task.java
// @FunctionalInterface
// public interface Task {
// 
//     /**
//      * Executes a task or throws an exception if unable to do so.
//      *
//      * @throws Exception if unable to execute task
//      * @see Runner
//      * @see MonitoringTaskSubmitter
//      */
//     void run() throws Exception;
// }
// 
// Path: src/main/java/javafixes/object/Tuple.java
// public class Tuple<A, B> extends DataObject {
// 
//     public final A a;
//     public final B b;
// 
//     /**
//      * Constructor of {@code Tuple} with two values to wrap
//      *
//      * @param a first value
//      * @param b second value
//      */
//     public Tuple(A a, B b) {
//         this.a = a;
//         this.b = b;
//     }
// 
//     /**
//      * Factory method to create {@code Tuple}.
//      *
//      * @param a   first value
//      * @param b   second value
//      * @param <A> first value type
//      * @param <B> second value type
//      * @return new {@code Tuple}
//      */
//     public static <A, B> Tuple<A, B> tuple(A a, B b) {
//         return new Tuple<>(a, b);
//     }
// 
//     /**
//      * Returns first value
//      *
//      * @return first value
//      */
//     public A getA() {
//         return a;
//     }
// 
//     /**
//      * Returns second value
//      *
//      * @return second value
//      */
//     public B getB() {
//         return b;
//     }
// 
//     /**
//      * Returns first value
//      *
//      * @return first value
//      */
//     public A a() {
//         return a;
//     }
// 
//     /**
//      * Returns second value
//      *
//      * @return second value
//      */
//     public B b() {
//         return b;
//     }
// 
//     /**
//      * Maps wrapped values using a provided {@code BiFunction}
//      *
//      * @param mapper function to map wrapped values
//      * @param <C>    the type of generated value
//      * @return value generated via the mapping function
//      */
//     public <C> C map(BiFunction<? super A, ? super B, ? extends C> mapper) {
//         if (mapper == null) {
//             throw new IllegalArgumentException("Tuple mapper can't be null");
//         }
//         return mapper.apply(a, b);
//     }
// }
// 
// Path: src/main/java/javafixes/object/Tuple.java
// public static <A, B> Tuple<A, B> tuple(A a, B b) {
//     return new Tuple<>(a, b);
// }


import javafixes.common.function.Task;
import javafixes.object.Tuple;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock;
import static javafixes.object.Tuple.tuple;
     * @param time the maximum time to wait for the lock
     * @param unit the time unit of the {@code time} argument
     * @param action action that should be executed
     *
     * @throws TimeoutException if we can't acquire lock within the defined time
     * @throws InterruptedException if the current thread is interrupted before acquiring the lock
     * @throws WrappedException any exception thrown from the provided {@code action} is wrapped into a {@link WrappedException}
     */
    public void synchronizeOn(K key, long time, TimeUnit unit, Task action) throws TimeoutException, InterruptedException, WrappedException {
        StampedLock lock = acquireLock(key);
        try {
            long stamp = lock.tryWriteLock(time, unit);
            if (stamp == 0) {
                throw new TimeoutException("Timed out while acquiring lock for key: " + key);
            }

            try {
                action.run();
            } catch (Exception e) {
                throw new WrappedException("Failed to execute action", e);
            } finally {
                lock.unlock(stamp);
            }
        } finally {
            releaseLock(key);
        }
    }

    private StampedLock acquireLock(K key) {
        synchronized (counterWithLocks) {
// Path: src/main/java/javafixes/common/function/Task.java
// @FunctionalInterface
// public interface Task {
// 
//     /**
//      * Executes a task or throws an exception if unable to do so.
//      *
//      * @throws Exception if unable to execute task
//      * @see Runner
//      * @see MonitoringTaskSubmitter
//      */
//     void run() throws Exception;
// }
// 
// Path: src/main/java/javafixes/object/Tuple.java
// public class Tuple<A, B> extends DataObject {
// 
//     public final A a;
//     public final B b;
// 
//     /**
//      * Constructor of {@code Tuple} with two values to wrap
//      *
//      * @param a first value
//      * @param b second value
//      */
//     public Tuple(A a, B b) {
//         this.a = a;
//         this.b = b;
//     }
// 
//     /**
//      * Factory method to create {@code Tuple}.
//      *
//      * @param a   first value
//      * @param b   second value
//      * @param <A> first value type
//      * @param <B> second value type
//      * @return new {@code Tuple}
//      */
//     public static <A, B> Tuple<A, B> tuple(A a, B b) {
//         return new Tuple<>(a, b);
//     }
// 
//     /**
//      * Returns first value
//      *
//      * @return first value
//      */
//     public A getA() {
//         return a;
//     }
// 
//     /**
//      * Returns second value
//      *
//      * @return second value
//      */
//     public B getB() {
//         return b;
//     }
// 
//     /**
//      * Returns first value
//      *
//      * @return first value
//      */
//     public A a() {
//         return a;
//     }
// 
//     /**
//      * Returns second value
//      *
//      * @return second value
//      */
//     public B b() {
//         return b;
//     }
// 
//     /**
//      * Maps wrapped values using a provided {@code BiFunction}
//      *
//      * @param mapper function to map wrapped values
//      * @param <C>    the type of generated value
//      * @return value generated via the mapping function
//      */
//     public <C> C map(BiFunction<? super A, ? super B, ? extends C> mapper) {
//         if (mapper == null) {
//             throw new IllegalArgumentException("Tuple mapper can't be null");
//         }
//         return mapper.apply(a, b);
//     }
// }
// 
// Path: src/main/java/javafixes/object/Tuple.java
// public static <A, B> Tuple<A, B> tuple(A a, B b) {
//     return new Tuple<>(a, b);
// }


// Path: src/main/java/javafixes/concurrency/Synchronizer.java
import javafixes.common.function.Task;
import javafixes.object.Tuple;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock;
import static javafixes.object.Tuple.tuple;

     * @param time the maximum time to wait for the lock
     * @param unit the time unit of the {@code time} argument
     * @param action action that should be executed
     *
     * @throws TimeoutException if we can't acquire lock within the defined time
     * @throws InterruptedException if the current thread is interrupted before acquiring the lock
     * @throws WrappedException any exception thrown from the provided {@code action} is wrapped into a {@link WrappedException}
     */
    public void synchronizeOn(K key, long time, TimeUnit unit, Task action) throws TimeoutException, InterruptedException, WrappedException {
        StampedLock lock = acquireLock(key);
        try {
            long stamp = lock.tryWriteLock(time, unit);
            if (stamp == 0) {
                throw new TimeoutException("Timed out while acquiring lock for key: " + key);
            }

            try {
                action.run();
            } catch (Exception e) {
                throw new WrappedException("Failed to execute action", e);
            } finally {
                lock.unlock(stamp);
            }
        } finally {
            releaseLock(key);
        }
    }

    private StampedLock acquireLock(K key) {
        synchronized (counterWithLocks) {
            Tuple<AtomicInteger, StampedLock> counterWithLock = counterWithLocks.computeIfAbsent(