001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.lock.rw;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Map.Entry;
027
028import lucee.commons.lang.ExceptionUtil;
029import lucee.commons.lock.Lock;
030import lucee.commons.lock.LockException;
031import lucee.commons.lock.LockInterruptedException;
032import lucee.runtime.exp.PageRuntimeException;
033import lucee.runtime.op.Caster;
034
035public class RWKeyLock<K> {
036
037        private Map<K,RWLock<K>> locks=new HashMap<K,RWLock<K>>();
038        
039        public Lock lock(K token, long timeout, boolean readOnly) throws LockException, LockInterruptedException {
040                if(timeout<=0) throw new LockException("timeout must be a postive number");
041                
042                RWWrap<K> wrap;
043                //K token=key;
044                synchronized (locks) {
045                        RWLock<K> lock;
046                        lock=locks.get(token);
047                        if(lock==null) {
048                                locks.put(token, lock=new RWLock<K>(token));
049                        }
050                        lock.inc();
051                        wrap= new RWWrap<K>(lock, readOnly);
052                }
053                try{
054                        wrap.lock(timeout);
055                }
056                catch(LockException e){
057                        synchronized (locks) {wrap.getLock().dec();}
058                        throw e;
059                }
060                catch(LockInterruptedException e){
061                        synchronized (locks) {wrap.getLock().dec();}
062                        throw e;
063                }
064                catch(Throwable t){
065                ExceptionUtil.rethrowIfNecessary(t);
066                        synchronized (locks) {wrap.getLock().dec();}
067                        throw new PageRuntimeException(Caster.toPageException(t));
068                }
069                return wrap;
070        }
071
072        public void unlock(Lock lock) {
073                if(!(lock instanceof RWWrap)) {
074                        return;
075                }
076                
077                lock.unlock();
078                
079                synchronized (locks) {
080                        ((RWWrap)lock).getLock().dec();
081                        if(lock.getQueueLength()==0){
082                                locks.remove(((RWWrap)lock).getLabel());
083                        }
084                }
085        }
086        
087        public List<K> getOpenLockNames() {
088                Iterator<Entry<K, RWLock<K>>> it = locks.entrySet().iterator();
089                Entry<K, RWLock<K>> entry;
090                List<K> list=new ArrayList<K>();
091                while(it.hasNext()){
092                        entry = it.next();
093                        if(entry.getValue().getQueueLength()>0)
094                                list.add(entry.getKey());
095                }
096                return list;
097        }
098        
099        /**
100         * Queries if the write lock is held by any thread on given lock token, returns null when lock with this token does not exists
101     * @param token name of the lock to check
102         * @return
103         */
104        public Boolean isWriteLocked(K token) {
105                RWLock<K> lock = locks.get(token);
106                if(lock==null) return null;
107                return lock.isWriteLocked();
108        }
109
110        /**
111         * Queries if one or more read lock is held by any thread on given lock token, returns null when lock with this token does not exists
112     * @param token name of the lock to check
113         * @return
114         */
115        public Boolean isReadLocked(K token) {
116                RWLock<K> lock = locks.get(token);
117                if(lock==null) return null;
118                return lock.isReadLocked();
119        }
120
121        public void clean() {
122                Iterator<Entry<K, RWLock<K>>> it = locks.entrySet().iterator();
123                Entry<K, RWLock<K>> entry;
124                
125                while(it.hasNext()){
126                        entry = it.next();
127                        if(entry.getValue().getQueueLength()==0){
128                                synchronized (locks) {
129                                        if(entry.getValue().getQueueLength()==0){
130                                                locks.remove(entry.getKey());
131                                        }
132                                }
133                        }
134                }
135        }
136}
137
138class RWWrap<L> implements Lock {
139        
140        private RWLock<L> lock;
141        private boolean readOnly;
142
143        public RWWrap(RWLock<L> lock, boolean readOnly){
144                this.lock=lock;
145                this.readOnly=readOnly;
146        }
147
148        public void lock(long timeout) throws LockException, LockInterruptedException {
149                lock.lock(timeout, readOnly);
150        }
151
152        public void unlock() {
153                lock.unlock(readOnly);
154        }
155        public int getQueueLength() {
156                return lock.getQueueLength();
157        }
158
159        public L getLabel(){
160                return lock.getLabel();
161        }
162
163        public RWLock<L> getLock(){
164                return lock;
165        }
166        public boolean isReadOnly(){
167                return readOnly;
168        }
169        
170}