ingo@775: package de.intevation.gnv.utils;
ingo@775: 
ingo@775: import java.util.HashMap;
ingo@775: 
ingo@775: /**
ingo@775:  * This class can be used to synchronize threads with a given key. To use this
ingo@815:  * synchronization, you first need to do call {@link #acquire(java.lang.Object)}
ingo@815:  * to retrieve a {@link UniqueKey}. After this, you can call the code being
sascha@778:  * synchronized. After this execution, you need to call
ingo@775:  * {@link #release(UniqueKey)} with your token you retrieved from {@link
ingo@815:  * #acquire(java.lang.Object)}. A thread needs to wait for another thread if their keys
ingo@775:  * are equal. Threads with different keys don't need to wait for each other.
ingo@775:  *
sascha@780:  * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
sascha@780:  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
ingo@775:  */
ingo@775: public final class ExclusiveExec
ingo@775: {
ingo@806:     /**
ingo@806:      * The only instance of this singleton.
ingo@806:      */
ingo@775:     public static final ExclusiveExec INSTANCE = new ExclusiveExec();
ingo@775: 
ingo@775:     private HashMap tokens;
ingo@775: 
ingo@806:     /**
ingo@806:      * This class represents a unique key with a reference counter.
ingo@806:      */
ingo@775:     public static class UniqueKey {
ingo@775:         Object key;
ingo@775:         int [] refs;
ingo@806: 
ingo@806:         /**
ingo@806:          * Constructs a new UniqueKey.
ingo@806:          *
ingo@806:          * @param key The key of this unique key.
ingo@806:          */
ingo@775:         public UniqueKey(Object key) {
ingo@775:             this.key = key;
ingo@775:             refs = new int[1];
ingo@775:         }
ingo@775:     }
ingo@775: 
ingo@775:     /**
ingo@775:      * Private constructor. Use {@link #INSTANCE} instead.
ingo@775:      */
ingo@775:     private ExclusiveExec() {
ingo@775:         tokens = new HashMap();
ingo@775:     }
ingo@775: 
ingo@775:     /**
ingo@775:      * This method serves a {@link UniqueKey} and starts a synchronized code
ingo@775:      * block.
ingo@775:      *
ingo@775:      * @param key The key used to identify same threads.
ingo@775:      * @return UniqueKey. Use this object to call {@link #release(UniqueKey)}
ingo@775:      * at the end of your code being synchronized.
ingo@775:      */
ingo@775:     public UniqueKey acquire(Object key) {
ingo@775: 
ingo@775:         try {
ingo@775:             UniqueKey internalKey = null;
ingo@775:             synchronized (tokens) {
ingo@775:                 internalKey = (UniqueKey)tokens.get(key);
ingo@775: 
ingo@775:                 if (internalKey == null) {
ingo@775:                     tokens.put(key, internalKey = new UniqueKey(key));
ingo@775:                 }
ingo@775:             }
ingo@775: 
ingo@775:             synchronized (internalKey) {
ingo@775:                 ++internalKey.refs[0];
ingo@775:                 while (internalKey.refs[0] > 1) {
ingo@775:                     internalKey.wait(10000L);
ingo@775:                 }
ingo@775:             }
ingo@775: 
ingo@775:             return internalKey;
ingo@775:         }
ingo@775:         catch (InterruptedException ie) {
ingo@775:             return null;
ingo@775:         }
ingo@775:     }
ingo@775: 
ingo@775:     /**
ingo@775:      * This method releases a lock. Call this method at the end of your code
ingo@775:      * being synchronized.
ingo@775:      *
ingo@775:      * @param internalKey Token retrieved by {@link #acquire(Object)}.
ingo@775:      */
ingo@775:     public void release(UniqueKey internalKey) {
ingo@775:         if (internalKey != null) {
ingo@775:             synchronized (internalKey) {
ingo@775:                 if (--internalKey.refs[0] < 1) {
ingo@775:                     synchronized (tokens) {
ingo@775:                         tokens.remove(internalKey.key);
ingo@775:                     }
ingo@775:                 }
ingo@775:                 internalKey.notifyAll();
ingo@775:             }
ingo@775:         }
ingo@775:     }
ingo@775: }
ingo@775: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :