OSDN Git Service

use multi-catch
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / config / InterVMLock.java
1 /*
2  * inter-VM file locking
3  *
4  * License : The MIT License
5  * Copyright(c) 2009 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.config;
9
10 import java.io.File;
11 import java.io.FileInputStream;
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.LinkedList;
18 import java.util.concurrent.atomic.AtomicBoolean;
19
20 /**
21  * ロックファイルを用いたVM間ロックオブジェクト。
22  *
23  * <p>大昔のNFSではうまく動かないかも。
24  *
25  * <p>一度でもロックに成功したロックファイルは、
26  * VM終了時に消されてしまうので注意。
27  */
28 public class InterVMLock{
29
30     /** 所持するロックオブジェクト一覧。 */
31     private static final Collection<InterVMLock> OWNEDLOCKSET =
32             Collections.synchronizedCollection(
33                 new LinkedList<InterVMLock>()
34             );
35     private static final AtomicBoolean SHUTDOWNGOING =
36             new AtomicBoolean(false);
37
38     static{
39         Runtime runtime = Runtime.getRuntime();
40         runtime.addShutdownHook(new Thread(){
41             /** {@inheritDoc} */
42             @Override
43             public void run(){
44                 shutdown();
45             }
46         });
47     }
48
49
50     private final File lockFile;
51     private boolean isFileOwner = false;
52     private InputStream stream = null;
53     private final Object thisLock = new Object();
54
55
56     /**
57      * コンストラクタ。
58      * この時点ではまだロックファイルの存在は確認されない。
59      * @param lockFile ロックファイル
60      * @throws NullPointerException 引数がnull
61      */
62     public InterVMLock(File lockFile) throws NullPointerException{
63         if(lockFile == null) throw new NullPointerException();
64         this.lockFile = lockFile;
65         return;
66     }
67
68
69     /**
70      * 所持するロックオブジェクトすべてを解放しロックファイルを削除する。
71      */
72     private static void shutdown(){
73         if( ! SHUTDOWNGOING.compareAndSet(false, true) ) return;
74
75         synchronized(OWNEDLOCKSET){
76             for(InterVMLock lock : OWNEDLOCKSET){
77                 lock.releaseImpl();
78             }
79             OWNEDLOCKSET.clear();
80         }
81         return;
82     }
83
84     /**
85      * シャットダウン処理進行中or完了済みか否か判定する。
86      * @return 進行中or完了済みならtrue
87      */
88     protected static boolean isShutdownGoing(){
89         boolean going = SHUTDOWNGOING.get();
90         return going;
91     }
92
93
94     /**
95      * このオブジェクトがロックファイルの作者であるか判定する。
96      * @return 作者ならtrue
97      */
98     public boolean isFileOwner(){
99         boolean result = this.isFileOwner;
100         return result;
101     }
102
103     /**
104      * ロックファイルがディスク上に存在するか判定する。
105      * @return 存在すればtrue
106      */
107     public boolean isExistsFile(){
108         if(this.lockFile.exists()){
109             return true;
110         }
111         return false;
112     }
113
114     /**
115      * ロック対象のファイルを返す。
116      *
117      * <p>勝手に作ったり消したりしないように。
118      *
119      * @return ロック対象ファイル
120      */
121     public File getLockFile(){
122         return this.lockFile;
123     }
124
125     /**
126      * ロックファイルのオープン中のストリームを返す。
127      * ※ 排他制御目的のリソースなので、
128      * 勝手に読み込んだりクローズしたりしないように。
129      * @return オープン中のストリーム。オープンしてなければnull
130      */
131     protected InputStream getOpenedStream(){
132         InputStream result = null;
133
134         synchronized(this.thisLock){
135             if(this.isFileOwner){
136                 result = this.stream;
137             }
138         }
139
140         return result;
141     }
142
143     /**
144      * ロックファイルの強制削除を試みる。
145      * @return 強制削除に成功すればtrue
146      */
147     public boolean forceRemove(){
148         synchronized(this.thisLock){
149             if(this.isFileOwner) release();
150
151             if( ! isExistsFile() ) return true;
152
153             try{
154                 boolean result = this.lockFile.delete();
155                 if( ! result ) return false;
156             }catch(SecurityException e){
157                 return false;
158             }
159
160             if(isExistsFile()) return false;
161         }
162
163         return true;
164     }
165
166     /**
167      * ロックを試みる。
168      * このメソッドは実行をブロックしない。
169      * @return すでにロック済みもしくはロックに成功すればtrue
170      */
171     public boolean tryLock(){
172         if(isShutdownGoing()) return false;
173
174         synchronized(this.thisLock){
175             if(hasLockedByMe()) return true;
176             if(touchLockFile()) return true;
177         }
178
179         return false;
180     }
181
182     /**
183      * 自身によるロックに成功しているか判定する。
184      * @return 自身によるロック中であればtrue
185      */
186     public boolean hasLockedByMe(){
187         boolean result;
188         synchronized(this.thisLock){
189             if( ! this.isFileOwner ){
190                 result = false;
191             }else if( ! this.lockFile.exists() ){
192                 this.isFileOwner = false;
193                 result = false;
194             }else{
195                 result = true;
196             }
197         }
198         return result;
199     }
200
201     /**
202      * ロックファイルを生成する。{@link #tryLock()}の下請け。
203      * 生成されるロックファイルはVM終了時に削除されるよう登録される。
204      * このメソッド実行中にVM終了が重なると、
205      * ロックファイルが正しく削除されない場合がありうる。
206      * @return 成功すればtrue
207      */
208     protected boolean touchLockFile(){
209         synchronized(this.thisLock){
210             boolean created = false;
211             try{
212                 created = this.lockFile.createNewFile();
213             }catch(IOException | SecurityException e){
214                 assert true;   // IGNORE
215             }finally{
216                 if(created){
217                     this.isFileOwner = true;
218                     this.lockFile.deleteOnExit();
219                 }else{
220                     this.isFileOwner = false;
221                 }
222             }
223
224             if( ! created )  return false;
225
226             try{
227                 this.stream = new FileInputStream(this.lockFile);
228             }catch(FileNotFoundException e){
229                 this.isFileOwner = false;
230                 this.stream = null;
231                 try{
232                     this.lockFile.delete();
233                 }catch(SecurityException e2){
234                     assert true; // IGNORE
235                 }
236                 return false;
237             }
238
239             synchronized(OWNEDLOCKSET){
240                 OWNEDLOCKSET.add(this);
241             }
242         }
243
244         return true;
245     }
246
247     /**
248      * ロックを解除する。
249      *
250      * <p>自分が作者であるロックファイルは閉じられ削除される。
251      *
252      * <p>削除に失敗しても無視。
253      *
254      * <p>シャットダウン処理進行中の場合は何もしない。
255      */
256     public void release(){
257         if(isShutdownGoing()) return;
258
259         releaseImpl();
260
261         synchronized(OWNEDLOCKSET){
262             OWNEDLOCKSET.remove(this);
263         }
264
265         return;
266     }
267
268     /**
269      * ロックを解除する。{@link #release()}の下請け。
270      *
271      * <p>自分が作者であるロックファイルは閉じられ削除される。
272      *
273      * <p>削除に失敗しても無視。
274      *
275      * <p>シャットダウン処理進行中か否かは無視される。
276      */
277     protected void releaseImpl(){
278         synchronized(this.thisLock){
279             if( ! this.isFileOwner ) return;
280
281             try{
282                 this.stream.close();
283             }catch(IOException e){
284                 assert true;   // IGNORE
285             }finally{
286                 this.stream = null;
287                 try{
288                     this.lockFile.delete();
289                 }catch(SecurityException e){
290                     assert true;   // IGNORE
291                 }finally{
292                     this.isFileOwner = false;
293                 }
294             }
295         }
296
297         return;
298     }
299
300     // TODO {@link java.nio.channels.FileChannnel}によるロック機構の併用。
301
302 }