OSDN Git Service

キャラクターデータの初回インボード時に必ず失敗していた問題の修正
[charactermanaj/CharacterManaJ.git] / src / main / java / charactermanaj / model / io / CharacterDataDefaultProvider.java
1 package charactermanaj.model.io;
2
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
5 import java.io.File;
6 import java.io.FileInputStream;
7 import java.io.FileNotFoundException;
8 import java.io.FileOutputStream;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.lang.ref.SoftReference;
12 import java.net.URI;
13 import java.net.URL;
14 import java.sql.Timestamp;
15 import java.util.Enumeration;
16 import java.util.LinkedHashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Properties;
20 import java.util.logging.Level;
21 import java.util.logging.Logger;
22
23 import charactermanaj.model.CharacterData;
24 import charactermanaj.model.CustomLayerOrder;
25 import charactermanaj.model.CustomLayerOrderKey;
26 import charactermanaj.model.PartsCategoryResolver;
27 import charactermanaj.util.ConfigurationDirUtilities;
28 import charactermanaj.util.LocalizedResourcePropertyLoader;
29 import charactermanaj.util.ResourceLoader;
30 import charactermanaj.util.SetupLocalization;
31
32 /**
33  * デフォルトキャラクターセットのプロバイダ
34  *
35  * @author seraphy
36  */
37 public class CharacterDataDefaultProvider {
38
39         /**
40          * リソースに格納されているデフォルトのキャラクター定義のリソースパスまでのプレフィックス.<br>
41          */
42         public static final String DEFAULT_CHARACTER_PREFIX = "template/";
43
44         /**
45          * テンプレートをリストしているXML形式のプロパティファイル名
46          */
47         public static final String TEMPLATE_LIST_XML = "characterDataTemplates";
48
49         /**
50          * デフォルトのキャラクターセット名(ver2)
51          */
52         public static final String DEFAULT_CHARACTER_NAME_V2 = "character2.xml";
53
54         /**
55          * デフォルトのキャラクターセット名(ver3)
56          */
57         public static final String DEFAULT_CHARACTER_NAME_V3 = "character3.xml";
58
59         /**
60          * カスタムレイヤー順定義ファイルの末尾名。
61          * リソース名「xxxxx.xml」に対して「xxxxx-customlayerorders.xml」のようになる。
62          */
63         private static final String CUSTOM_LAYER_ORDERS_SUFFIX = "-customlayerorders.xml";
64
65         /**
66          * リソースローダー
67          * ローカルファイルをクラスパスより優先する。
68          */
69         private final ResourceLoader resourceLoader = new ResourceLoader(true);
70
71         /**
72          * ロガー
73          */
74         private static final Logger logger = Logger
75                         .getLogger(CharacterDataDefaultProvider.class.getName());
76
77         public enum DefaultCharacterDataVersion {
78                 V2(DEFAULT_CHARACTER_NAME_V2),
79                 V3(DEFAULT_CHARACTER_NAME_V3);
80
81                 DefaultCharacterDataVersion(String reskey) {
82                         this.reskey = reskey;
83                 }
84
85                 private final String reskey;
86
87                 private transient SoftReference<CharacterData> cache;
88
89                 public String getResourceName() {
90                         return reskey;
91                 }
92
93                 public CharacterData create(CharacterDataDefaultProvider prov) {
94                         if (prov == null) {
95                                 throw new IllegalArgumentException();
96                         }
97                         try {
98                                 CharacterData cd = (cache != null) ? cache.get() : null;
99                                 if (cd == null) {
100                                         cd = prov.loadPredefinedCharacterData(reskey);
101                                         cache = new SoftReference<CharacterData>(cd);
102                                 }
103                                 return cd.duplicateBasicInfo();
104
105                         } catch (IOException ex) {
106                                 throw new RuntimeException(
107                                                 "can not create the default profile from application's resource",
108                                                 ex);
109                         }
110                 }
111
112                 public Map<CustomLayerOrderKey, List<CustomLayerOrder>> createCustomLayerOrderMap(
113                                 PartsCategoryResolver partsCategoryResolver, CharacterDataDefaultProvider prov) {
114                         if (prov == null) {
115                                 throw new IllegalArgumentException();
116                         }
117                         try {
118                                 return prov.loadPredefinedCustomLayerOrder(partsCategoryResolver, reskey);
119
120                         } catch (IOException ex) {
121                                 throw new RuntimeException(
122                                                 "can not create the default profile from application's resource",
123                                                 ex);
124                         }
125                 }
126         }
127
128         /**
129          * デフォルトのキャラクター定義を生成して返す.<br>
130          * 一度生成された場合はキャッシュされる.<br>
131          * 生成されたキャラクター定義のdocBaseはnullであるため、docBaseをセットすること.<br>
132          *
133          * @param version
134          *            デフォルトキャラクターセットのバージョン
135          * @return キャラクター定義
136          */
137         public synchronized CharacterData createDefaultCharacterData(
138                         DefaultCharacterDataVersion version) {
139                 if (version == null) {
140                         throw new IllegalArgumentException();
141                 }
142                 return version.create(this);
143         }
144
145         /**
146          * デフォルトのキャラクター定義に付随するカスタムレイヤーパターンを生成して返す。
147          *
148          * 引数のpartsCategoryResolverは、カスタムレイヤーパターンはパーツカテゴリインスタンスを保持するため、
149          * そのキャラクターデータと同じインスタンスのパーツカテゴリインスタンスを取得できるようにするためのものである。
150          * @param partsCategoryResolver カテゴリIDでカテゴリを索引するリゾルバ
151          * @param version バージョン
152          * @return 付随するカスタムレイヤーパターン、なければnull
153          */
154         public Map<CustomLayerOrderKey, List<CustomLayerOrder>> createDefaultCustomLayerOrderMap(
155                         PartsCategoryResolver partsCategoryResolver, DefaultCharacterDataVersion version) {
156                 if (partsCategoryResolver == null) {
157                         throw new NullPointerException("categories is required.");
158                 }
159                 if (version == null) {
160                         throw new NullPointerException("version is required.");
161                 }
162                 return version.createCustomLayerOrderMap(partsCategoryResolver, this);
163         }
164
165         /**
166          * キャラクターデータのxmlファイル名をキーとし、表示名を値とするマップ.<br>
167          * 表示順序でアクセス可能.<br>
168          *
169          * @return 順序付マップ(キーはテンプレートxmlのファイル名、値は表示名)
170          */
171         public Map<String, String> getCharacterDataTemplates() {
172                 // キャラクターデータのxmlファイル名をキーとし、表示名を値とするマップ
173                 final LinkedHashMap<String, String> templateNameMap = new LinkedHashMap<String, String>();
174
175                 // テンプレートの定義プロパティのロード
176                 // テンプレートリソースは実行中に増減する可能性があるため、共有キャッシュには入れない.
177                 LocalizedResourcePropertyLoader propLoader = LocalizedResourcePropertyLoader.getNonCachedInstance();
178                 Properties props = propLoader.getLocalizedProperties(DEFAULT_CHARACTER_PREFIX + TEMPLATE_LIST_XML, null);
179
180                 // 順序優先のキーに、テンプレート名がカンマ区切りになっているので、
181                 // このキーにあるものを先に順番に登録する
182                 String strOrders = props.getProperty("displayOrder");
183                 if (strOrders != null) {
184                         for (String templateFileName : strOrders.split(",")) {
185                                 templateFileName = templateFileName.trim();
186                                 String displayName = props.getProperty(templateFileName);
187                                 if (displayName != null && displayName.trim().length() > 0) {
188                                         String resKey = DEFAULT_CHARACTER_PREFIX + templateFileName;
189                                         if (getResource(resKey) != null) {
190                                                 // 現存するテンプレートのみ登録
191                                                 templateNameMap.put(templateFileName, displayName);
192                                         }
193                                 }
194                         }
195                 }
196
197                 // 順序で指定されていないアイテムの追加
198                 Enumeration<?> enm = props.propertyNames();
199                 while (enm.hasMoreElements()) {
200                         String templateFileName = (String) enm.nextElement();
201                         if (!templateNameMap.containsKey(templateFileName)) {
202                                 if (templateFileName.endsWith(".xml")) {
203                                         String displayName = props.getProperty(templateFileName);
204                                         String resKey = DEFAULT_CHARACTER_PREFIX + templateFileName;
205                                         if (getResource(resKey) != null) {
206                                                 // 現存するテンプレートのみ登録
207                                                 templateNameMap.put(templateFileName, displayName);
208                                         }
209                                 }
210                         }
211                 }
212
213                 // ローカルフォルダにある未登録のxmlファイルもテンプレート一覧に加える
214                 // (ただし、テンプレートリストプロパティ、カスタムレイヤーパターン定義を除く)
215                 try {
216                         File templDir = getUserTemplateDir();
217                         if (templDir.exists() && templDir.isDirectory()) {
218                                 File[] files = templDir.listFiles(new java.io.FileFilter() {
219                                         public boolean accept(File pathname) {
220                                                 String name = pathname.getName();
221                                                 if (templateNameMap.containsKey(name)) {
222                                                         // すでに登録済みなのでスキップする.
223                                                         return false;
224                                                 }
225                                                 if (name.startsWith(TEMPLATE_LIST_XML)) {
226                                                         // テンプレートリストプロパティファイルは除外する.
227                                                         return false;
228                                                 }
229                                                 if (name.endsWith(CUSTOM_LAYER_ORDERS_SUFFIX)) {
230                                                         // カスタムレイヤーパターン定義ファイルは除外する.
231                                                         return false;
232                                                 }
233                                                 return pathname.isFile() && name.endsWith(".xml");
234                                         }
235                                 });
236                                 if (files == null) {
237                                         files = new File[0];
238                                 }
239                                 CharacterDataPersistent persist = CharacterDataPersistent
240                                                 .getInstance();
241                                 for (File file : files) {
242                                         try {
243                                                 URI docBase = file.toURI();
244                                                 CharacterData cd = persist.loadProfile(docBase);
245                                                 if (cd != null && cd.isValid()) {
246                                                         String name = file.getName();
247                                                         templateNameMap.put(name, cd.getName());
248                                                 }
249                                         } catch (IOException ex) {
250                                                 logger.log(Level.WARNING, "failed to read templatedir."
251                                                                 + file, ex);
252                                         }
253                                 }
254                         }
255
256                 } catch (IOException ex) {
257                         // ディレクトリの一覧取得に失敗しても無視する.
258                         logger.log(Level.FINE, "failed to read templatedir.", ex);
259                 }
260
261                 return templateNameMap;
262         }
263
264         /**
265          * XMLリソースファイルから、定義済みのキャラクターデータを生成して返す.<br>
266          * (現在のロケールの言語に対応するデータを取得し、なければ最初の言語で代替する.)<br>
267          * 生成されたキャラクター定義のdocBaseはnullであるため、使用する場合はdocBaseをセットすること.<br>
268          * 都度、XMLファイルから読み込まれる.<br>
269          *
270          * @return デフォルトキャラクターデータ
271          * @throws IOException
272          *             失敗
273          */
274         public CharacterData loadPredefinedCharacterData(String name)
275                         throws IOException {
276                 CharacterData cd;
277                 String resKey = DEFAULT_CHARACTER_PREFIX + name;
278                 URL predefinedCharacter = getResource(resKey);
279                 if (predefinedCharacter == null) {
280                         throw new FileNotFoundException(resKey);
281                 }
282                 InputStream is = predefinedCharacter.openStream();
283                 try {
284                         logger.log(Level.INFO, "load a predefined characterdata. resKey="
285                                         + resKey);
286                         CharacterDataXMLReader characterDataXmlReader = new CharacterDataXMLReader();
287                         cd = characterDataXmlReader.loadCharacterDataFromXML(is, null);
288
289                 } finally {
290                         is.close();
291                 }
292                 return cd;
293         }
294
295         /**
296          * XMLリソースファイルから、定義済みのカスタムレイヤーパターンを生成して返す。
297          * 指定されたリソース名に対して「-customlayerorders.xml」のような末尾に変えたリソースで検索される。
298          * 定義がない場合はnullを返す。
299          * @param partsCategoryResolver
300          * @param name リソース名
301          * @return
302          * @throws IOException
303          */
304         public Map<CustomLayerOrderKey, List<CustomLayerOrder>> loadPredefinedCustomLayerOrder(
305                         PartsCategoryResolver partsCategoryResolver, String name) throws IOException {
306                 // キャラクター定義xmlへのリソース名から、カスタムレイヤー定義のリソース名を組み立てる
307                 int pt = name.lastIndexOf(".");
308                 String nameBody = name.substring(0, pt);
309                 String customLayerMappingXml = nameBody + CUSTOM_LAYER_ORDERS_SUFFIX;
310
311                 String resKey = DEFAULT_CHARACTER_PREFIX + customLayerMappingXml;
312                 URL predefinedCharacter = getResource(resKey);
313                 if (predefinedCharacter == null) {
314                         // リソースがない
315                         return null;
316                 }
317
318                 InputStream is = predefinedCharacter.openStream();
319                 try {
320                         logger.log(Level.INFO, "load a predefined custom layer orders. resKey=" + resKey);
321                         CustomLayerOrderXMLReader xmlReader = new CustomLayerOrderXMLReader(partsCategoryResolver);
322                         return xmlReader.read(is);
323
324                 } finally {
325                         is.close();
326                 }
327         }
328
329         /**
330          * リソースを取得する.<br>
331          *
332          * @param resKey
333          *            リソースキー
334          * @return リソース、なければnull
335          */
336         protected URL getResource(String resKey) {
337                 return resourceLoader.getResource(resKey);
338         }
339
340         /**
341          * ユーザー定義のカスタマイズ用のテンプレートディレクトリを取得する.<br>
342          * (ディレクトリが実在しない場合もありえる)
343          *
344          * @return テンプレートディレクトリ
345          */
346         public File getUserTemplateDir() throws IOException {
347                 File baseDir = ConfigurationDirUtilities.getUserDataDir();
348                 SetupLocalization setup = new SetupLocalization(baseDir);
349                 File resourceDir = setup.getResourceDir();
350                 return new File(resourceDir, DEFAULT_CHARACTER_PREFIX);
351         }
352
353         /**
354          * "characterDataTemplates*.xml"のファイルは管理ファイルのため、 <br>
355          * ユーザによる書き込みは禁止とする.<br>
356          *
357          * @param name
358          * @return 書き込み可能であるか?
359          */
360         public boolean canFileSave(String name) {
361                 if (name.trim().startsWith("characterDataTemplates")) {
362                         return false;
363                 }
364                 return true;
365         }
366
367         /**
368          * 指定したキャラクターデータをテンプレートとして保存する.<br>
369          *
370          * @param name
371          *            保存するテンプレートファイル名
372          * @param cd
373          *            キャラクターデータ
374          * @param localizedName
375          *            表示名
376          * @param customLayerPattern
377          *            カスタムレイヤーパターン、なければnull
378          * @throws IOException
379          */
380         public void saveTemplate(String name, CharacterData cd, String localizedName,
381                         Map<CustomLayerOrderKey, List<CustomLayerOrder>> customLayerPatterns) throws IOException {
382                 if (name == null || !canFileSave(name)) {
383                         throw new IllegalArgumentException();
384                 }
385
386                 // テンプレートファイル位置の準備
387                 // (ディレクトリが存在しない場合は作成する)
388                 File templDir = getUserTemplateDir();
389                 templDir.mkdirs();
390
391                 File templFile = new File(templDir, name);
392
393                 // キャラクターデータをXML形式でテンプレートファイルへ保存
394                 CharacterDataXMLWriter characterDataXmlWriter = new CharacterDataXMLWriter();
395                 BufferedOutputStream bos = new BufferedOutputStream(
396                                 new FileOutputStream(templFile));
397                 try {
398                         // パーツセットなしの状態とし、名前をローカライズ名に設定する.
399                         CharacterData templCd = cd.duplicateBasicInfo(false);
400                         templCd.setName(localizedName);
401
402                         characterDataXmlWriter.writeXMLCharacterData(templCd, bos);
403
404                 } finally {
405                         bos.close();
406                 }
407
408                 // カスタムレイヤーパターンを保存する
409                 if (customLayerPatterns != null && cd.isEnableCustonLayerPattern()) {
410                         // 拡張子を取り除いた名前を取得する
411                         int pt = name.lastIndexOf(".");
412                         String nameBody = (pt > 0) ? name.substring(0, pt) : name;
413                         // カスタムレイヤーパターンとして識別される末尾文字列を付与する
414                         File templCustomLayerFile = new File(templDir, nameBody + CUSTOM_LAYER_ORDERS_SUFFIX);
415
416                         // カスタムレイヤーパターンXMLファイルを作成する
417                         CustomLayerOrderXMLWriter xmlWriter = new CustomLayerOrderXMLWriter();
418                         BufferedOutputStream bos2 = new BufferedOutputStream(new FileOutputStream(templCustomLayerFile));
419                         try {
420                                 xmlWriter.write(customLayerPatterns, bos2);
421                         } finally {
422                                 bos2.close();
423                         }
424                 }
425
426                 // ユーザー定義テンプレートのプロパティファイルをロードする
427                 Properties userTemplDefProp = loadUserDefineTemplateDef();
428
429                 // テンプレート一覧の更新
430                 userTemplDefProp.put(name, localizedName);
431                 saveUserDefineTemplateDef(userTemplDefProp);
432         }
433
434         private File getUserTemplateDefPropertyFile() throws IOException {
435                 File templDir = getUserTemplateDir();
436                 return new File(templDir, TEMPLATE_LIST_XML + ".xml");
437         }
438
439         private Properties loadUserDefineTemplateDef() throws IOException {
440                 File userTemplDefPropFile = getUserTemplateDefPropertyFile();
441                 Properties userTemplDefProp = new Properties();
442                 if (userTemplDefPropFile.exists() && userTemplDefPropFile.length() > 0) {
443                         InputStream is = new BufferedInputStream(new FileInputStream(userTemplDefPropFile));
444                         try {
445                                 userTemplDefProp.loadFromXML(is);
446                         } finally {
447                                 is.close();
448                         }
449                 }
450                 return userTemplDefProp;
451         }
452
453         private void saveUserDefineTemplateDef(Properties userTemplDefProp) throws IOException {
454                 if (userTemplDefProp == null) {
455                         userTemplDefProp = new Properties();
456                 }
457
458                 File userTemplDefPropFile = getUserTemplateDefPropertyFile();
459
460                 // テンプレート一覧の保存
461                 BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(userTemplDefPropFile));
462                 try {
463                         userTemplDefProp.storeToXML(fos, new Timestamp(System.currentTimeMillis()).toString());
464
465                 } finally {
466                         fos.close();
467                 }
468         }
469 }