1 package charactermanaj.model.io;
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
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;
14 import java.sql.Timestamp;
15 import java.util.Enumeration;
16 import java.util.LinkedHashMap;
17 import java.util.List;
19 import java.util.Properties;
20 import java.util.logging.Level;
21 import java.util.logging.Logger;
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;
33 * デフォルトキャラクターセットのプロバイダ
37 public class CharacterDataDefaultProvider {
40 * リソースに格納されているデフォルトのキャラクター定義のリソースパスまでのプレフィックス.<br>
42 public static final String DEFAULT_CHARACTER_PREFIX = "template/";
45 * テンプレートをリストしているXML形式のプロパティファイル名
47 public static final String TEMPLATE_LIST_XML = "characterDataTemplates";
50 * デフォルトのキャラクターセット名(ver2)
52 public static final String DEFAULT_CHARACTER_NAME_V2 = "character2.xml";
55 * デフォルトのキャラクターセット名(ver3)
57 public static final String DEFAULT_CHARACTER_NAME_V3 = "character3.xml";
60 * カスタムレイヤー順定義ファイルの末尾名。
61 * リソース名「xxxxx.xml」に対して「xxxxx-customlayerorders.xml」のようになる。
63 private static final String CUSTOM_LAYER_ORDERS_SUFFIX = "-customlayerorders.xml";
67 * ローカルファイルをクラスパスより優先する。
69 private final ResourceLoader resourceLoader = new ResourceLoader(true);
74 private static final Logger logger = Logger
75 .getLogger(CharacterDataDefaultProvider.class.getName());
77 public enum DefaultCharacterDataVersion {
78 V2(DEFAULT_CHARACTER_NAME_V2),
79 V3(DEFAULT_CHARACTER_NAME_V3);
81 DefaultCharacterDataVersion(String reskey) {
85 private final String reskey;
87 private transient SoftReference<CharacterData> cache;
89 public String getResourceName() {
93 public CharacterData create(CharacterDataDefaultProvider prov) {
95 throw new IllegalArgumentException();
98 CharacterData cd = (cache != null) ? cache.get() : null;
100 cd = prov.loadPredefinedCharacterData(reskey);
101 cache = new SoftReference<CharacterData>(cd);
103 return cd.duplicateBasicInfo();
105 } catch (IOException ex) {
106 throw new RuntimeException(
107 "can not create the default profile from application's resource",
112 public Map<CustomLayerOrderKey, List<CustomLayerOrder>> createCustomLayerOrderMap(
113 PartsCategoryResolver partsCategoryResolver, CharacterDataDefaultProvider prov) {
115 throw new IllegalArgumentException();
118 return prov.loadPredefinedCustomLayerOrder(partsCategoryResolver, reskey);
120 } catch (IOException ex) {
121 throw new RuntimeException(
122 "can not create the default profile from application's resource",
129 * デフォルトのキャラクター定義を生成して返す.<br>
130 * 一度生成された場合はキャッシュされる.<br>
131 * 生成されたキャラクター定義のdocBaseはnullであるため、docBaseをセットすること.<br>
134 * デフォルトキャラクターセットのバージョン
137 public synchronized CharacterData createDefaultCharacterData(
138 DefaultCharacterDataVersion version) {
139 if (version == null) {
140 throw new IllegalArgumentException();
142 return version.create(this);
146 * デフォルトのキャラクター定義に付随するカスタムレイヤーパターンを生成して返す。
148 * 引数のpartsCategoryResolverは、カスタムレイヤーパターンはパーツカテゴリインスタンスを保持するため、
149 * そのキャラクターデータと同じインスタンスのパーツカテゴリインスタンスを取得できるようにするためのものである。
150 * @param partsCategoryResolver カテゴリIDでカテゴリを索引するリゾルバ
151 * @param version バージョン
152 * @return 付随するカスタムレイヤーパターン、なければnull
154 public Map<CustomLayerOrderKey, List<CustomLayerOrder>> createDefaultCustomLayerOrderMap(
155 PartsCategoryResolver partsCategoryResolver, DefaultCharacterDataVersion version) {
156 if (partsCategoryResolver == null) {
157 throw new NullPointerException("categories is required.");
159 if (version == null) {
160 throw new NullPointerException("version is required.");
162 return version.createCustomLayerOrderMap(partsCategoryResolver, this);
166 * キャラクターデータのxmlファイル名をキーとし、表示名を値とするマップ.<br>
169 * @return 順序付マップ(キーはテンプレートxmlのファイル名、値は表示名)
171 public Map<String, String> getCharacterDataTemplates() {
172 // キャラクターデータのxmlファイル名をキーとし、表示名を値とするマップ
173 final LinkedHashMap<String, String> templateNameMap = new LinkedHashMap<String, String>();
175 // テンプレートの定義プロパティのロード
176 // テンプレートリソースは実行中に増減する可能性があるため、共有キャッシュには入れない.
177 LocalizedResourcePropertyLoader propLoader = LocalizedResourcePropertyLoader.getNonCachedInstance();
178 Properties props = propLoader.getLocalizedProperties(DEFAULT_CHARACTER_PREFIX + TEMPLATE_LIST_XML, null);
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) {
191 templateNameMap.put(templateFileName, displayName);
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) {
207 templateNameMap.put(templateFileName, displayName);
213 // ローカルフォルダにある未登録のxmlファイルもテンプレート一覧に加える
214 // (ただし、テンプレートリストプロパティ、カスタムレイヤーパターン定義を除く)
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)) {
225 if (name.startsWith(TEMPLATE_LIST_XML)) {
226 // テンプレートリストプロパティファイルは除外する.
229 if (name.endsWith(CUSTOM_LAYER_ORDERS_SUFFIX)) {
230 // カスタムレイヤーパターン定義ファイルは除外する.
233 return pathname.isFile() && name.endsWith(".xml");
239 CharacterDataPersistent persist = CharacterDataPersistent
241 for (File file : files) {
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());
249 } catch (IOException ex) {
250 logger.log(Level.WARNING, "failed to read templatedir."
256 } catch (IOException ex) {
257 // ディレクトリの一覧取得に失敗しても無視する.
258 logger.log(Level.FINE, "failed to read templatedir.", ex);
261 return templateNameMap;
265 * XMLリソースファイルから、定義済みのキャラクターデータを生成して返す.<br>
266 * (現在のロケールの言語に対応するデータを取得し、なければ最初の言語で代替する.)<br>
267 * 生成されたキャラクター定義のdocBaseはnullであるため、使用する場合はdocBaseをセットすること.<br>
268 * 都度、XMLファイルから読み込まれる.<br>
270 * @return デフォルトキャラクターデータ
271 * @throws IOException
274 public CharacterData loadPredefinedCharacterData(String name)
277 String resKey = DEFAULT_CHARACTER_PREFIX + name;
278 URL predefinedCharacter = getResource(resKey);
279 if (predefinedCharacter == null) {
280 throw new FileNotFoundException(resKey);
282 InputStream is = predefinedCharacter.openStream();
284 logger.log(Level.INFO, "load a predefined characterdata. resKey="
286 CharacterDataXMLReader characterDataXmlReader = new CharacterDataXMLReader();
287 cd = characterDataXmlReader.loadCharacterDataFromXML(is, null);
296 * XMLリソースファイルから、定義済みのカスタムレイヤーパターンを生成して返す。
297 * 指定されたリソース名に対して「-customlayerorders.xml」のような末尾に変えたリソースで検索される。
299 * @param partsCategoryResolver
302 * @throws IOException
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;
311 String resKey = DEFAULT_CHARACTER_PREFIX + customLayerMappingXml;
312 URL predefinedCharacter = getResource(resKey);
313 if (predefinedCharacter == null) {
318 InputStream is = predefinedCharacter.openStream();
320 logger.log(Level.INFO, "load a predefined custom layer orders. resKey=" + resKey);
321 CustomLayerOrderXMLReader xmlReader = new CustomLayerOrderXMLReader(partsCategoryResolver);
322 return xmlReader.read(is);
334 * @return リソース、なければnull
336 protected URL getResource(String resKey) {
337 return resourceLoader.getResource(resKey);
341 * ユーザー定義のカスタマイズ用のテンプレートディレクトリを取得する.<br>
342 * (ディレクトリが実在しない場合もありえる)
344 * @return テンプレートディレクトリ
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);
354 * "characterDataTemplates*.xml"のファイルは管理ファイルのため、 <br>
355 * ユーザによる書き込みは禁止とする.<br>
358 * @return 書き込み可能であるか?
360 public boolean canFileSave(String name) {
361 if (name.trim().startsWith("characterDataTemplates")) {
368 * 指定したキャラクターデータをテンプレートとして保存する.<br>
374 * @param localizedName
376 * @param customLayerPattern
377 * カスタムレイヤーパターン、なければnull
378 * @throws IOException
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();
387 // (ディレクトリが存在しない場合は作成する)
388 File templDir = getUserTemplateDir();
391 File templFile = new File(templDir, name);
393 // キャラクターデータをXML形式でテンプレートファイルへ保存
394 CharacterDataXMLWriter characterDataXmlWriter = new CharacterDataXMLWriter();
395 BufferedOutputStream bos = new BufferedOutputStream(
396 new FileOutputStream(templFile));
398 // パーツセットなしの状態とし、名前をローカライズ名に設定する.
399 CharacterData templCd = cd.duplicateBasicInfo(false);
400 templCd.setName(localizedName);
402 characterDataXmlWriter.writeXMLCharacterData(templCd, bos);
409 if (customLayerPatterns != null && cd.isEnableCustonLayerPattern()) {
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);
416 // カスタムレイヤーパターンXMLファイルを作成する
417 CustomLayerOrderXMLWriter xmlWriter = new CustomLayerOrderXMLWriter();
418 BufferedOutputStream bos2 = new BufferedOutputStream(new FileOutputStream(templCustomLayerFile));
420 xmlWriter.write(customLayerPatterns, bos2);
426 // ユーザー定義テンプレートのプロパティファイルをロードする
427 Properties userTemplDefProp = loadUserDefineTemplateDef();
430 userTemplDefProp.put(name, localizedName);
431 saveUserDefineTemplateDef(userTemplDefProp);
434 private File getUserTemplateDefPropertyFile() throws IOException {
435 File templDir = getUserTemplateDir();
436 return new File(templDir, TEMPLATE_LIST_XML + ".xml");
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));
445 userTemplDefProp.loadFromXML(is);
450 return userTemplDefProp;
453 private void saveUserDefineTemplateDef(Properties userTemplDefProp) throws IOException {
454 if (userTemplDefProp == null) {
455 userTemplDefProp = new Properties();
458 File userTemplDefPropFile = getUserTemplateDefPropertyFile();
461 BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(userTemplDefPropFile));
463 userTemplDefProp.storeToXML(fos, new Timestamp(System.currentTimeMillis()).toString());