OSDN Git Service

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