OSDN Git Service

スタートアップ処理の改善
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / config / ConfigStore.java
1 /*
2  * config store
3  *
4  * License : The MIT License
5  * Copyright(c) 2012 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.config;
9
10 import java.io.BufferedInputStream;
11 import java.io.BufferedOutputStream;
12 import java.io.BufferedReader;
13 import java.io.BufferedWriter;
14 import java.io.File;
15 import java.io.FileInputStream;
16 import java.io.FileNotFoundException;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.InputStreamReader;
21 import java.io.OutputStream;
22 import java.io.OutputStreamWriter;
23 import java.io.Reader;
24 import java.io.Writer;
25 import java.nio.charset.Charset;
26 import java.util.logging.Level;
27 import java.util.logging.Logger;
28 import jp.sourceforge.jovsonz.JsComposition;
29 import jp.sourceforge.jovsonz.JsObject;
30 import jp.sourceforge.jovsonz.JsParseException;
31 import jp.sourceforge.jovsonz.JsTypes;
32 import jp.sourceforge.jovsonz.JsVisitException;
33 import jp.sourceforge.jovsonz.Json;
34
35 /**
36  * 各種設定の永続化関連。
37  */
38 public class ConfigStore {
39
40     /** 検索履歴ファイル。 */
41     public static final File HIST_FILE = new File("searchHistory.json");
42     /** 原稿ファイル。 */
43     public static final File DRAFT_FILE = new File("draft.json");
44     /** ネットワーク設定ファイル。 */
45     public static final File NETCONFIG_FILE = new File("netconfig.json");
46     /** 台詞表示設定ファイル。 */
47     public static final File TALKCONFIG_FILE = new File("talkconfig.json");
48
49     private static final String LOCKFILE = "lock";
50
51     private static final Charset CHARSET_JSON = Charset.forName("UTF-8");
52
53     private static final Logger LOGGER = Logger.getAnonymousLogger();
54
55
56     private boolean useStoreFile;
57     private boolean isImplicitPath;
58     private File configPath;
59
60
61     /**
62      * コンストラクタ。
63      * @param useStoreFile 設定ディレクトリへの永続化機能を使うならtrue
64      * @param configPath 設定ディレクトリ。
65      * 設定ディレクトリを使わない場合は無視され、nullとして扱われる。
66      */
67     public ConfigStore(boolean useStoreFile, File configPath ){
68         this(useStoreFile, true, configPath);
69         return;
70     }
71
72     /**
73      * コンストラクタ。
74      * @param useStoreFile 設定ディレクトリへの永続化機能を使うならtrue
75      * @param isImplicitPath コマンドラインで指定されたディレクトリならfalse
76      * @param configPath 設定ディレクトリ。
77      * 設定ディレクトリを使わない場合は無視され、nullとして扱われる。
78      */
79     public ConfigStore(boolean useStoreFile,
80                          boolean isImplicitPath,
81                          File configPath ){
82         super();
83
84         this.useStoreFile = useStoreFile;
85
86         if(this.useStoreFile){
87             this.isImplicitPath = isImplicitPath;
88         }else{
89             this.isImplicitPath = true;
90         }
91
92         if(this.useStoreFile){
93             this.configPath = configPath;
94         }else{
95             this.configPath = null;
96         }
97
98         return;
99     }
100
101
102     /**
103      * 設定ディレクトリを使うか否か判定する。
104      * @return 設定ディレクトリを使うならtrue。
105      */
106     public boolean useStoreFile(){
107         return this.useStoreFile;
108     }
109
110     /**
111      * 設定ディレクトリを返す。
112      * @return 設定ディレクトリ。設定ディレクトリを使わない場合はnull
113      */
114     public File getConfigPath(){
115         File result;
116         if(this.useStoreFile) result = this.configPath;
117         else                  result = null;
118         return result;
119     }
120
121     /**
122      * 設定ディレクトリの存在を確認し、なければ作る。
123      * <p>設定ディレクトリを使わない場合は何もしない。
124      */
125     public void prepareConfigDir(){
126         if( ! this.useStoreFile ) return;
127
128         if( ! this.configPath.exists() ){
129             File created =
130                 ConfigFile.buildConfigDirectory(this.configPath,
131                                                 this.isImplicitPath );
132             ConfigFile.checkAccessibility(created);
133         }else{
134             ConfigFile.checkAccessibility(this.configPath);
135         }
136
137         return;
138     }
139
140     /**
141      * ロックファイルの取得を試みる。
142      */
143     public void tryLock(){
144         if( ! this.useStoreFile ) return;
145
146         File lockFile = new File(this.configPath, LOCKFILE);
147         InterVMLock lock = new InterVMLock(lockFile);
148
149         lock.tryLock();
150
151         if( ! lock.isFileOwner() ){
152             ConfigFile.confirmLockError(lock);
153             if( ! lock.isFileOwner() ){
154                 this.useStoreFile = false;
155                 this.configPath = null;
156             }
157         }
158
159         return;
160     }
161
162     /**
163      * 設定ディレクトリ上のOBJECT型JSONファイルを読み込む。
164      * @param file JSONファイルの相対パス。
165      * @return JSON object。
166      * 設定ディレクトリを使わない設定、
167      * もしくはJSONファイルが存在しない、
168      * もしくはOBJECT型でなかった、
169      * もしくは入力エラーがあればnull
170      */
171     public JsObject loadJsObject(File file){
172         JsComposition<?> root = loadJson(file);
173         if(root == null || root.getJsTypes() != JsTypes.OBJECT) return null;
174         JsObject result = (JsObject) root;
175         return result;
176     }
177
178     /**
179      * 設定ディレクトリ上のJSONファイルを読み込む。
180      * @param file JSONファイルの相対パス
181      * @return JSON objectまたはarray。
182      * 設定ディレクトリを使わない設定、
183      * もしくはJSONファイルが存在しない、
184      * もしくは入力エラーがあればnull
185      */
186     public JsComposition<?> loadJson(File file){
187         if( ! this.useStoreFile ) return null;
188
189         File absFile;
190         if(file.isAbsolute()){
191             absFile = file;
192         }else{
193             if(this.configPath == null) return null;
194             absFile = new File(this.configPath, file.getPath());
195             if( ! absFile.exists() ) return null;
196             if( ! absFile.isAbsolute() ) return null;
197         }
198         String absPath = absFile.getPath();
199
200         InputStream istream;
201         try{
202             istream = new FileInputStream(absFile);
203         }catch(FileNotFoundException e){
204             assert false;
205             return null;
206         }
207         istream = new BufferedInputStream(istream);
208
209         JsComposition<?> root;
210         try{
211             root = loadJson(istream);
212         }catch(IOException e){
213             LOGGER.log(Level.SEVERE,
214                     "JSONファイル["
215                     + absPath
216                     + "]の読み込み時に支障がありました。", e);
217             return null;
218         }catch(JsParseException e){
219             LOGGER.log(Level.SEVERE,
220                     "JSONファイル["
221                     + absPath
222                     + "]の内容に不備があります。", e);
223             return null;
224         }finally{
225             try{
226                 istream.close();
227             }catch(IOException e){
228                 LOGGER.log(Level.SEVERE,
229                         "JSONファイル["
230                         + absPath
231                         + "]を閉じることができません。", e);
232                 return null;
233             }
234         }
235
236         return root;
237     }
238
239     /**
240      * バイトストリーム上のJSONデータを読み込む。
241      * <p>バイトストリームはUTF-8と解釈される。
242      * @param is バイトストリーム
243      * @return JSON objectまたはarray。
244      * @throws IOException 入力エラー
245      * @throws JsParseException 構文エラー
246      */
247     protected JsComposition<?> loadJson(InputStream is)
248             throws IOException, JsParseException {
249         Reader reader = new InputStreamReader(is, CHARSET_JSON);
250         reader = new BufferedReader(reader);
251         JsComposition<?> root = loadJson(reader);
252         return root;
253     }
254
255     /**
256      * 文字ストリーム上のJSONデータを読み込む。
257      * @param reader 文字ストリーム
258      * @return JSON objectまたはarray。
259      * @throws IOException 入力エラー
260      * @throws JsParseException 構文エラー
261      */
262     protected JsComposition<?> loadJson(Reader reader)
263             throws IOException, JsParseException {
264         JsComposition<?> root = Json.parseJson(reader);
265         return root;
266     }
267
268     /**
269      * 設定ディレクトリ上のJSONファイルに書き込む。
270      * @param file JSONファイルの相対パス
271      * @param root JSON objectまたはarray
272      * @return 正しくセーブが行われればtrue。
273      * 何らかの理由でセーブが完了できなければfalse
274      */
275     public boolean saveJson(File file, JsComposition<?> root){
276         if( ! this.useStoreFile ) return false;
277
278         // TODO テンポラリファイルを用いたより安全なファイル更新
279         File absFile = new File(this.configPath, file.getPath());
280         String absPath = absFile.getPath();
281
282         absFile.delete();
283         try{
284             if(absFile.createNewFile() != true) return false;
285         }catch(IOException e){
286             LOGGER.log(Level.SEVERE,
287                     "JSONファイル["
288                     + absPath
289                     + "]の新規生成ができません。", e);
290             return false;
291         }
292
293         OutputStream ostream;
294         try{
295             ostream = new FileOutputStream(absFile);
296         }catch(FileNotFoundException e){
297             assert false;
298             return false;
299         }
300         ostream = new BufferedOutputStream(ostream);
301
302         try{
303             saveJson(ostream, root);
304         }catch(JsVisitException e){
305             LOGGER.log(Level.SEVERE,
306                     "JSONファイル["
307                     + absPath
308                     + "]の出力処理で支障がありました。", e);
309             return false;
310         }catch(IOException e){
311             LOGGER.log(Level.SEVERE,
312                     "JSONファイル["
313                     + absPath
314                     + "]の書き込み時に支障がありました。", e);
315             return false;
316         }finally{
317             try{
318                 ostream.close();
319             }catch(IOException e){
320                 LOGGER.log(Level.SEVERE,
321                         "JSONファイル["
322                         + absPath
323                         + "]を閉じることができません。", e);
324                 return false;
325             }
326         }
327
328         return true;
329     }
330
331     /**
332      * バイトストリームにJSONデータを書き込む。
333      * <p>バイトストリームはUTF-8と解釈される。
334      * @param os バイトストリーム出力
335      * @param root JSON objectまたはarray
336      * @throws IOException 出力エラー
337      * @throws JsVisitException 構造エラー
338      */
339     protected void saveJson(OutputStream os, JsComposition<?> root)
340             throws IOException, JsVisitException {
341         Writer writer = new OutputStreamWriter(os, CHARSET_JSON);
342         writer = new BufferedWriter(writer);
343         saveJson(writer, root);
344         return;
345     }
346
347     /**
348      * 文字ストリームにJSONデータを書き込む。
349      * @param writer 文字ストリーム出力
350      * @param root JSON objectまたはarray
351      * @throws IOException 出力エラー
352      * @throws JsVisitException 構造エラー
353      */
354     protected void saveJson(Writer writer, JsComposition<?> root)
355             throws IOException, JsVisitException {
356         Json.dumpJson(writer, root);
357         return;
358     }
359
360     /**
361      * 検索履歴ファイルを読み込む。
362      * @return 履歴データ。履歴を読まないもしくは読めない場合はnull
363      */
364     public JsObject loadHistoryConfig(){
365         JsObject result = loadJsObject(HIST_FILE);
366         return result;
367     }
368
369     /**
370      * 原稿ファイルを読み込む。
371      * @return 原稿データ。原稿を読まないもしくは読めない場合はnull
372      */
373     public JsObject loadDraftConfig(){
374         JsObject result = loadJsObject(DRAFT_FILE);
375         return result;
376     }
377
378     /**
379      * ネットワーク設定ファイルを読み込む。
380      * @return ネットワーク設定データ。
381      * 設定を読まないもしくは読めない場合はnull
382      */
383     public JsObject loadNetConfig(){
384         JsObject result = loadJsObject(NETCONFIG_FILE);
385         return result;
386     }
387
388     /**
389      * 台詞表示設定ファイルを読み込む。
390      * @return 台詞表示設定データ。
391      * 設定を読まないもしくは読めない場合はnull
392      */
393     public JsObject loadTalkConfig(){
394         JsObject result = loadJsObject(TALKCONFIG_FILE);
395         return result;
396     }
397
398     /**
399      * 検索履歴ファイルに書き込む。
400      * @param root 履歴データ
401      * @return 書き込まなかったもしくは書き込めなかった場合はfalse
402      */
403     public boolean saveHistoryConfig(JsComposition<?> root){
404         boolean result = saveJson(HIST_FILE, root);
405         return result;
406     }
407
408     /**
409      * 原稿ファイルに書き込む。
410      * @param root 原稿データ
411      * @return 書き込まなかったもしくは書き込めなかった場合はfalse
412      */
413     public boolean saveDraftConfig(JsComposition<?> root){
414         boolean result = saveJson(DRAFT_FILE, root);
415         return result;
416     }
417
418     /**
419      * ネットワーク設定ファイルに書き込む。
420      * @param root ネットワーク設定
421      * @return 書き込まなかったもしくは書き込めなかった場合はfalse
422      */
423     public boolean saveNetConfig(JsComposition<?> root){
424         boolean result = saveJson(NETCONFIG_FILE, root);
425         return result;
426     }
427
428     /**
429      * 台詞表示設定ファイルに書き込む。
430      * @param root 台詞表示設定
431      * @return 書き込まなかったもしくは書き込めなかった場合はfalse
432      */
433     public boolean saveTalkConfig(JsComposition<?> root){
434         boolean result = saveJson(TALKCONFIG_FILE, root);
435         return result;
436     }
437
438 }