OSDN Git Service

dmg作成バージョンの取得方法の修正
[charactermanaj/CharacterManaJ.git] / src / charactermanaj / model / io / AbstractCharacterDataArchiveFile.java
1 package charactermanaj.model.io;\r
2 \r
3 import java.awt.image.BufferedImage;\r
4 import java.io.File;\r
5 import java.io.IOException;\r
6 import java.io.InputStream;\r
7 import java.net.URI;\r
8 import java.net.URISyntaxException;\r
9 import java.util.ArrayList;\r
10 import java.util.Collection;\r
11 import java.util.Collections;\r
12 import java.util.HashMap;\r
13 import java.util.HashSet;\r
14 import java.util.Locale;\r
15 import java.util.Map;\r
16 import java.util.logging.Level;\r
17 import java.util.logging.Logger;\r
18 \r
19 import javax.imageio.ImageIO;\r
20 \r
21 import charactermanaj.graphics.io.PNGFileImageHeader;\r
22 import charactermanaj.graphics.io.PNGFileImageHeaderReader;\r
23 import charactermanaj.model.CharacterData;\r
24 import charactermanaj.model.Layer;\r
25 import charactermanaj.model.PartsCategory;\r
26 import charactermanaj.model.PartsManageData;\r
27 import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion;\r
28 \r
29 public abstract class AbstractCharacterDataArchiveFile\r
30                 implements\r
31                         CharacterDataArchiveFile {\r
32 \r
33         private static final Logger logger = Logger\r
34                         .getLogger(AbstractCharacterDataArchiveFile.class.getName());\r
35 \r
36         protected File archiveFile;\r
37 \r
38         protected String rootPrefix = "";\r
39 \r
40         public interface FileContent {\r
41 \r
42                 String getEntryName();\r
43 \r
44                 long lastModified();\r
45 \r
46                 InputStream openStream() throws IOException;\r
47 \r
48         }\r
49 \r
50         @Override\r
51         public String toString() {\r
52                 return "CharacterDataArchiveFile(file=" + archiveFile + ")";\r
53         }\r
54 \r
55         public static final class CategoryLayerPair {\r
56 \r
57                 private PartsCategory partsCategory;\r
58 \r
59                 private Layer layer;\r
60 \r
61                 public CategoryLayerPair(PartsCategory partsCategory, Layer layer) {\r
62                         if (partsCategory == null || layer == null) {\r
63                                 throw new IllegalArgumentException();\r
64                         }\r
65                         this.partsCategory = partsCategory;\r
66                         this.layer = layer;\r
67                 }\r
68 \r
69                 @Override\r
70                 public int hashCode() {\r
71                         return partsCategory.hashCode() ^ layer.hashCode();\r
72                 }\r
73 \r
74                 @Override\r
75                 public boolean equals(Object obj) {\r
76                         if (obj == this) {\r
77                                 return true;\r
78                         }\r
79                         if (obj != null && obj instanceof CategoryLayerPair) {\r
80                                 CategoryLayerPair o = (CategoryLayerPair) obj;\r
81                                 return partsCategory.equals(o.partsCategory)\r
82                                                 && layer.equals(o.layer);\r
83                         }\r
84                         return false;\r
85                 }\r
86 \r
87                 public Layer getLayer() {\r
88                         return layer;\r
89                 }\r
90 \r
91                 public PartsCategory getPartsCategory() {\r
92                         return partsCategory;\r
93                 }\r
94 \r
95                 @Override\r
96                 public String toString() {\r
97                         return "(" + partsCategory + ":" + layer + ")";\r
98                 }\r
99         }\r
100 \r
101         public static final class PartsImageContent implements FileContent {\r
102 \r
103                 private final FileContent fileContent;\r
104 \r
105                 private final Collection<CategoryLayerPair> categoryLayerPairs;\r
106 \r
107                 private final String dirName;\r
108 \r
109                 private final String partsName;\r
110 \r
111                 private final String fileName;\r
112 \r
113                 private final PNGFileImageHeader pngFileImageHeader;\r
114 \r
115                 /**\r
116                  * パーツイメージを示す.<br>\r
117                  * \r
118                  * @param fileContent\r
119                  *            ファイルコンテンツ\r
120                  * @param categoryLayerPairs\r
121                  *            カテゴリとレイヤーのペア\r
122                  * @param partsName\r
123                  *            ファイル名(ファイル名のみ。拡張子を含まない。パーツ名の元として用いることを想定。)\r
124                  * @param pngFileImageHeader\r
125                  *            PNGファイルヘッダ\r
126                  */\r
127                 protected PartsImageContent(FileContent fileContent,\r
128                                 Collection<CategoryLayerPair> categoryLayerPairs,\r
129                                 String fileName, String partsName,\r
130                                 PNGFileImageHeader pngFileImageHeader) {\r
131                         if (fileContent == null || categoryLayerPairs == null\r
132                                         || categoryLayerPairs.isEmpty() || fileName == null\r
133                                         || partsName == null || pngFileImageHeader == null) {\r
134                                 throw new IllegalArgumentException();\r
135                         }\r
136                         this.fileContent = fileContent;\r
137                         this.categoryLayerPairs = Collections\r
138                                         .unmodifiableCollection(categoryLayerPairs);\r
139                         this.fileName = fileName;\r
140                         this.partsName = partsName;\r
141                         this.pngFileImageHeader = pngFileImageHeader;\r
142 \r
143                         CategoryLayerPair categoryLayerPair = categoryLayerPairs.iterator()\r
144                                         .next();\r
145                         dirName = categoryLayerPair.getLayer().getDir();\r
146                 }\r
147 \r
148                 @Override\r
149                 public int hashCode() {\r
150                         return getEntryName().hashCode();\r
151                 }\r
152 \r
153                 @Override\r
154                 public boolean equals(Object obj) {\r
155                         if (obj == this) {\r
156                                 return true;\r
157                         }\r
158                         if (obj != null && obj instanceof PartsImageContent) {\r
159                                 return getEntryName().equals(\r
160                                                 ((PartsImageContent) obj).getEntryName());\r
161                         }\r
162                         return false;\r
163                 }\r
164 \r
165                 public Collection<CategoryLayerPair> getCategoryLayerPairs() {\r
166                         return categoryLayerPairs;\r
167                 }\r
168 \r
169                 public String getDirName() {\r
170                         return dirName;\r
171                 }\r
172 \r
173                 public String getEntryName() {\r
174                         return fileContent.getEntryName();\r
175                 }\r
176 \r
177                 public String getFileName() {\r
178                         return fileName;\r
179                 }\r
180 \r
181                 public String getPartsName() {\r
182                         return partsName;\r
183                 }\r
184 \r
185                 public PNGFileImageHeader getPngFileImageHeader() {\r
186                         return pngFileImageHeader;\r
187                 }\r
188 \r
189                 public long lastModified() {\r
190                         return fileContent.lastModified();\r
191                 }\r
192 \r
193                 public InputStream openStream() throws IOException {\r
194                         return fileContent.openStream();\r
195                 }\r
196 \r
197                 @Override\r
198                 public String toString() {\r
199                         return fileContent.getEntryName();\r
200                 }\r
201         }\r
202 \r
203         protected HashMap<String, FileContent> entries = new HashMap<String, FileContent>();\r
204 \r
205         protected AbstractCharacterDataArchiveFile(File archiveFile) {\r
206                 if (archiveFile == null) {\r
207                         throw new IllegalArgumentException();\r
208                 }\r
209                 this.archiveFile = archiveFile;\r
210         }\r
211 \r
212         public File getArchiveFile() {\r
213                 return this.archiveFile;\r
214         }\r
215 \r
216         protected void addEntry(FileContent fileContent) {\r
217                 if (fileContent == null) {\r
218                         throw new IllegalArgumentException();\r
219                 }\r
220                 if (logger.isLoggable(Level.FINE)) {\r
221                         logger.log(Level.FINE, fileContent.getEntryName());\r
222                 }\r
223                 entries.put(fileContent.getEntryName(), fileContent);\r
224         }\r
225 \r
226         /**\r
227          * アーカイブファイルをベースとしたURIを返す.<br>\r
228          * \r
229          * @param name\r
230          *            コンテンツ名\r
231          * @return URI\r
232          * @throws IOException\r
233          *             URIを生成できない場合\r
234          */\r
235         protected URI getContentURI(String name) throws IOException {\r
236                 try {\r
237                         URI baseURI = archiveFile.toURI();\r
238                         return new URI("jar:" + baseURI.toString() + "/!" + name);\r
239 \r
240                 } catch (URISyntaxException ex) {\r
241                         IOException iex = new IOException(ex.getMessage());\r
242                         iex.initCause(ex);\r
243                         throw iex;\r
244                 }\r
245         }\r
246 \r
247         /**\r
248          * 指定したコンテンツが存在するか?\r
249          * \r
250          * @param name\r
251          *            コンテンツ名\r
252          * @return 存在すればtrue、存在しなければfalse\r
253          */\r
254         public boolean hasContent(String name) {\r
255                 return entries.containsKey(name);\r
256         }\r
257 \r
258         /**\r
259          * 指定したコンテンツを取得する.<br>\r
260          * 存在しない場合はnullを返す.<br>\r
261          * \r
262          * @param name\r
263          *            コンテンツ名\r
264          * @return 存在すればコンテンツ、存在しなければnull\r
265          */\r
266         public FileContent getContent(String name) {\r
267                 return entries.get(name);\r
268         }\r
269 \r
270         public String getRootPrefix() {\r
271                 return this.rootPrefix;\r
272         }\r
273 \r
274         /**\r
275          * アーカイブのルート上に単一のフォルダしかない場合、そのフォルダを真のルートとして設定する.<br>\r
276          * 返されるルート名には末尾に「/」を含む.<br>\r
277          * ルート上に複数のフォルダがあるかファイルが存在する場合は空文字を設定する.<br>\r
278          */\r
279         protected void searchRootPrefix() {\r
280                 HashSet<String> dirs = new HashSet<String>();\r
281                 for (String name : entries.keySet()) {\r
282                         int pos = name.indexOf('/');\r
283                         if (pos < 0) {\r
284                                 // ルート上にファイルがあるので、ここがルート\r
285                                 rootPrefix = "";\r
286                                 return;\r
287                         }\r
288                         if (pos >= 0) {\r
289                                 String dir = name.substring(0, pos + 1);\r
290                                 dirs.add(dir);\r
291                         }\r
292                 }\r
293                 if (dirs.size() == 1) {\r
294                         // ルート上に単一のフォルダしかないので、\r
295                         // このフォルダが真のルート\r
296                         rootPrefix = dirs.iterator().next();\r
297                         return;\r
298                 }\r
299                 // ルート上に複数のフォルダがあるので、ここがルート\r
300                 rootPrefix = "";\r
301         }\r
302 \r
303         /**\r
304          * 指定したディレクトリ(フルパス指定)のファイルのみを取り出す.<br>\r
305          * サブフォルダは含まない.<br>\r
306          * ディレクトリに空文字またはnullまたは「/」を指定した場合はルートを意味する.<br>\r
307          * \r
308          * @param dir\r
309          *            ディレクトリ\r
310          * @return ファイルへのフルパスのコレクション\r
311          */\r
312         public Map<String, FileContent> getFiles(String dir) {\r
313                 if (dir == null) {\r
314                         dir = "";\r
315                 }\r
316                 if (dir.length() > 0 && !dir.endsWith("/")) {\r
317                         dir += "/";\r
318                 }\r
319                 if (dir.equals("/")) {\r
320                         dir = ""; // アーカイブ内コンテンツのパスは先頭は「/」ではないため。\r
321                 }\r
322 \r
323                 HashMap<String, FileContent> files = new HashMap<String, FileContent>();\r
324 \r
325                 int ep = dir.length();\r
326                 for (Map.Entry<String, FileContent> entry : entries.entrySet()) {\r
327                         String name = entry.getKey();\r
328                         FileContent fileContent = entry.getValue();\r
329                         if (name.startsWith(dir)) {\r
330                                 String suffix = name.substring(ep);\r
331                                 int sep = suffix.indexOf('/');\r
332                                 if (sep < 0) {\r
333                                         files.put(name, fileContent);\r
334                                 }\r
335                         }\r
336                 }\r
337 \r
338                 return files;\r
339         }\r
340 \r
341         /**\r
342          * キャラクター定義を読み込む.<br>\r
343          * アーカイブに存在しないか、妥当性がない場合はnullを返す.<br>\r
344          * \r
345          * @return キャラクター定義、もしくはnull\r
346          */\r
347         public CharacterData readCharacterData() {\r
348                 FileContent characterFile = entries.get(rootPrefix\r
349                                 + CharacterDataPersistent.CONFIG_FILE);\r
350                 if (characterFile == null) {\r
351                         return null;\r
352                 }\r
353 \r
354                 CharacterDataXMLReader xmlReader = new CharacterDataXMLReader();\r
355 \r
356                 try {\r
357                         // character.xmlを読み込む\r
358                         CharacterData cd;\r
359                         InputStream is = characterFile.openStream();\r
360                         try {\r
361                                 URI docBase = getContentURI(rootPrefix\r
362                                                 + CharacterDataPersistent.CONFIG_FILE);\r
363                                 cd = xmlReader.loadCharacterDataFromXML(is, docBase);\r
364 \r
365                         } finally {\r
366                                 is.close();\r
367                         }\r
368 \r
369                         return cd;\r
370 \r
371                 } catch (Exception ex) {\r
372                         logger.log(Level.INFO, "character.xml load failed.", ex);\r
373                         return null;\r
374                 }\r
375         }\r
376 \r
377         /**\r
378          * キャラクター定義をINIファイルより読み取る.<br>\r
379          * アーカイブのコンテンツルート上のcharacter.iniを探す.<br>\r
380          * それがなければ、アーカイブ上のどこかにある/character.iniを探して、もっとも短い場所にある1つを採用する.<br>\r
381          * character.iniが何処にも存在しないか、読み取り時にエラーとなった場合はnullを返す.<br>\r
382          * 「キャラクターなんとか機 v2.2」の設定ファイルを想定している.<br>\r
383          * ただし、character.iniの下にeye_colorフォルダがある場合は「ver3」とみなす。<br>\r
384          * (設定ファイルの形式はv2, v3の間で変わらず)\r
385          * \r
386          * @return キャラクター定義、もしくはnull\r
387          */\r
388         public CharacterData readCharacterINI() {\r
389                 FileContent characterFile = null;\r
390                 characterFile = entries.get(rootPrefix\r
391                                 + CharacterDataPersistent.COMPATIBLE_CONFIG_NAME);\r
392 \r
393                 // どこかにあるcharacter.iniを探す\r
394                 // および、eye_colorフォルダがあるか?(あればver3形式とみなす。)\r
395                 boolean hasEyeColorFolder = false;\r
396                 ArrayList<String> characterInis = new ArrayList<String>();\r
397                 for (Map.Entry<String, FileContent> entry : entries.entrySet()) {\r
398                         String entryName = entry.getKey();\r
399                         if (entryName.endsWith("/"\r
400                                         + CharacterDataPersistent.COMPATIBLE_CONFIG_NAME)) {\r
401                                 characterInis.add(entryName);\r
402                         }\r
403                         if (entryName.contains("/eye_color/")\r
404                                         || entryName.endsWith("/eye_color")) {\r
405                                 hasEyeColorFolder = true;\r
406                         }\r
407                 }\r
408 \r
409                 if (characterFile == null) {\r
410                         // もっとも短い名前のものを採用\r
411                         Collections.sort(characterInis);\r
412                         if (characterInis.size() > 0) {\r
413                                 characterFile = entries.get(characterInis.get(0));\r
414                         }\r
415                 }\r
416                 if (characterFile == null) {\r
417                         // character.iniがないので何もしない.\r
418                         return null;\r
419                 }\r
420 \r
421                 DefaultCharacterDataVersion version;\r
422                 if (hasEyeColorFolder) {\r
423                         // "eye_color"フォルダがあればver3形式とみなす\r
424                         version = DefaultCharacterDataVersion.V3;\r
425                 } else {\r
426                         version = DefaultCharacterDataVersion.V2;\r
427                 }\r
428 \r
429                 try {\r
430                         // デフォルトのキャラクター定義を構築する.\r
431                         CharacterData cd;\r
432                         InputStream is = characterFile.openStream();\r
433                         try {\r
434                                 CharacterDataIniReader iniReader = new CharacterDataIniReader();\r
435                                 cd = iniReader.readCharacterDataFromIni(is, version);\r
436                         } finally {\r
437                                 is.close();\r
438                         }\r
439 \r
440                         // docBaseを設定する.\r
441                         URI docBase = getContentURI(rootPrefix\r
442                                         + CharacterDataPersistent.COMPATIBLE_CONFIG_NAME);\r
443                         cd.setDocBase(docBase);\r
444 \r
445                         return cd;\r
446 \r
447                 } catch (Exception ex) {\r
448                         logger.log(Level.INFO, "character.ini load failed", ex);\r
449                         return null;\r
450                 }\r
451         }\r
452 \r
453         /**\r
454          * お気に入りを読み込みキャラクター定義に追加する.<br>\r
455          * アーカイブにお気に入り(favorites.xml)が存在しなければ何もしない.<br>\r
456          * 読み取り中に失敗した場合は、その時点で読み込みを止めて戻る.<br>\r
457          * \r
458          * @param characterData\r
459          *            キャラクター定義(お気に入りが追加される)\r
460          */\r
461         public void readFavorites(CharacterData characterData) {\r
462                 if (characterData == null) {\r
463                         throw new IllegalArgumentException("characterDataにnullは指定できません。");\r
464                 }\r
465                 FileContent favoritesXml = entries.get(rootPrefix + "favorites.xml");\r
466                 if (favoritesXml == null) {\r
467                         // favorites.xmlがなければ何もしない\r
468                         return;\r
469                 }\r
470 \r
471                 CharacterDataXMLReader xmlReader = new CharacterDataXMLReader();\r
472 \r
473                 try {\r
474                         // favorites.xmlを読み込む\r
475                         InputStream is = favoritesXml.openStream();\r
476                         try {\r
477                                 xmlReader.loadPartsSet(characterData, is);\r
478 \r
479                         } catch (Exception ex) {\r
480                                 logger.log(Level.INFO, "favorites.xml load failed.", ex);\r
481 \r
482                         } finally {\r
483                                 is.close();\r
484                         }\r
485 \r
486                 } catch (Exception ex) {\r
487                         logger.log(Level.INFO, "favorites.xml load failed", ex);\r
488                 }\r
489         }\r
490 \r
491         /**\r
492          * サンプルピクチャを読み込む.<br>\r
493          * アーカイブに存在しないか、画像として読み取れなかった場合はnull\r
494          * \r
495          * @return サンプルピクチャ、もしくはnull\r
496          */\r
497         public BufferedImage readSamplePicture() {\r
498                 FileContent samplePictureFile = entries.get(rootPrefix + "preview.png");\r
499                 if (samplePictureFile == null) {\r
500                         Map<String, FileContent> files = getFiles(rootPrefix);\r
501 \r
502                         samplePictureFile = files.get("preview.jpg");\r
503                         if (samplePictureFile == null) {\r
504                                 samplePictureFile = files.get("preview.jpeg");\r
505                         }\r
506 \r
507                         if (samplePictureFile == null) {\r
508                                 for (Map.Entry<String, FileContent> entry : files.entrySet()) {\r
509                                         String name = entry.getKey();\r
510                                         if (name.endsWith(".jpg") || name.endsWith(".jpeg")\r
511                                                         || name.endsWith(".png")) {\r
512                                                 samplePictureFile = entry.getValue();\r
513                                                 break;\r
514                                         }\r
515                                 }\r
516                         }\r
517                 }\r
518                 if (samplePictureFile == null) {\r
519                         return null;\r
520                 }\r
521 \r
522                 try {\r
523                         BufferedImage pic;\r
524                         InputStream is = samplePictureFile.openStream();\r
525                         try {\r
526                                 pic = ImageIO.read(is);\r
527                         } finally {\r
528                                 is.close();\r
529                         }\r
530 \r
531                         return pic;\r
532 \r
533                 } catch (Exception ex) {\r
534                         logger.log(Level.INFO, "sample picture load failed.", ex);\r
535                         return null;\r
536                 }\r
537         }\r
538 \r
539         /**\r
540          * アーカイブにある「readme.txt」、もしくは「readme」というファイルをテキストファイルとして読み込む.<br>\r
541          * readme.txtが優先される。ともに存在しない場合はnull.<br>\r
542          * 改行コードはプラットフォーム固有の改行コードに変換して返される.<br>\r
543          * 読み取れなかった場合はnull.<br>\r
544          * \r
545          * @return テキスト、もしくはnull\r
546          */\r
547         public String readReadMe() {\r
548 \r
549                 Locale locale = Locale.getDefault();\r
550                 String lang = locale.getLanguage();\r
551 \r
552                 ArrayList<FileContent> candiates = new ArrayList<FileContent>();\r
553 \r
554                 Map<String, FileContent> files = getFiles(rootPrefix);\r
555                 for (String findName : new String[]{"readme_" + lang + ".txt",\r
556                                 "readme_" + lang, "readme.txt", "readme", null}) {\r
557                         for (Map.Entry<String, FileContent> entry : files.entrySet()) {\r
558                                 String name = entry.getKey().toLowerCase();\r
559                                 if (findName == null && name.endsWith(".txt")) {\r
560                                         candiates.add(entry.getValue());\r
561                                         break;\r
562                                 }\r
563                                 if (name.equals(findName)) {\r
564                                         candiates.add(entry.getValue());\r
565                                 }\r
566                         }\r
567                 }\r
568                 if (candiates.isEmpty()) {\r
569                         return null;\r
570                 }\r
571 \r
572                 // みつかったファイルについて読み込みを優先順で試行する.\r
573                 for (FileContent file : candiates) {\r
574                         try {\r
575                                 return readTextUTF16(file);\r
576 \r
577                         } catch (Exception ex) {\r
578                                 logger.log(Level.WARNING, "read file failed. :" + file, ex);\r
579                                 // 無視して次の候補を試行する.\r
580                         }\r
581                 }\r
582 \r
583                 // すべて失敗したか、そもそもファイルがみつからなかった。\r
584                 return null;\r
585         }\r
586 \r
587         /**\r
588          * ファイルをテキストとして読み取り、返す.<br>\r
589          * UTF-16LE/BE/UTF-8についてはBOMにより判定する.<br>\r
590          * BOMがない場合はUTF-16/8ともに判定できない.<br>\r
591          * BOMがなければMS932もしくはEUC_JPであると仮定して読み込む.<br>\r
592          * 改行コードはプラットフォーム固有の改行コードに変換して返される.<br>\r
593          * \r
594          * @param name\r
595          *            コンテンツ名\r
596          * @return テキスト、コンテンツが存在しない場合はnull\r
597          * @throws IOException\r
598          *             読み込みに失敗した場合\r
599          */\r
600         public String readTextFile(String name) throws IOException {\r
601                 if (name == null) {\r
602                         throw new IllegalArgumentException();\r
603                 }\r
604                 FileContent content = entries.get(name);\r
605                 if (content == null) {\r
606                         return null;\r
607                 }\r
608                 return readTextUTF16(content);\r
609         }\r
610 \r
611         /**\r
612          * ファイルをテキストとして読み取り、返す.<br>\r
613          * UTF-16LE/BE/UTF-8についてはBOMにより判定する.<br>\r
614          * BOMがない場合はUTF-16/8ともに判定できない.<br>\r
615          * BOMがなければMS932もしくはEUC_JPであると仮定して読み込む.<br>\r
616          * \r
617          * @param content\r
618          *            コンテンツ\r
619          * @return テキスト\r
620          * @throws IOException\r
621          *             読み込みに失敗した場合\r
622          */\r
623         public String readTextUTF16(FileContent content) throws IOException {\r
624                 if (content == null) {\r
625                         throw new IllegalArgumentException();\r
626                 }\r
627                 return TextReadHelper.readTextTryEncoding(content.openStream());\r
628         }\r
629 \r
630         /**\r
631          * キャラクター定義のカテゴリと、そのレイヤー情報から、画像のディレクトリの一覧をアーカイブ上のディレクトリの一覧として返す.<br>\r
632          * ディレクトリの末尾は常にスラ付きとなる.<br>\r
633          * enabledRootPefixがtrueの場合、ディレクトリの先頭はアーカイブのコンテンツルートとなる.<br>\r
634          * 同一のディレクトリに対して複数のレイヤー(複数カテゴリを含む)が参照している場合、それらを含めて返す.<br>\r
635          * 参照されているディレクトリがない場合は返される結果には含まれない.<br>\r
636          * \r
637          * @param characterData\r
638          *            キャラクター定義\r
639          * @param enabledRootPrefix\r
640          *            ルートプレフィックス(アーカイブのコンテンツルート)を付与する場合\r
641          * @return \r
642          *         パーツで使用する画像のディレクトリとして認識されるディレクトリの一覧、キーはアーカイブのディレクトリ位置、値は参照する1つ以上のレイヤー\r
643          */\r
644         protected Map<String, Collection<CategoryLayerPair>> getLayerDirs(\r
645                         CharacterData characterData, boolean enabledRootPrefix) {\r
646                 if (characterData == null) {\r
647                         return Collections.emptyMap();\r
648                 }\r
649                 // イメージディレクトリの一覧\r
650                 String rootPrefix = getRootPrefix();\r
651                 HashMap<String, Collection<CategoryLayerPair>> layerDirs = new HashMap<String, Collection<CategoryLayerPair>>();\r
652                 for (PartsCategory partsCategory : characterData.getPartsCategories()) {\r
653                         for (Layer layer : partsCategory.getLayers()) {\r
654                                 String dir = layer.getDir();\r
655                                 if (!dir.endsWith("/")) {\r
656                                         dir += "/"; // スラ付きにする.\r
657                                 }\r
658                                 if (enabledRootPrefix) {\r
659                                         // アーカイブのルートコンテキストからのディレクトリ位置とする.\r
660                                         dir = rootPrefix + dir;\r
661                                 }\r
662                                 Collection<CategoryLayerPair> sameDirLayers = layerDirs\r
663                                                 .get(dir);\r
664                                 if (sameDirLayers == null) {\r
665                                         sameDirLayers = new ArrayList<CategoryLayerPair>();\r
666                                         layerDirs.put(dir, sameDirLayers);\r
667                                 }\r
668                                 sameDirLayers.add(new CategoryLayerPair(partsCategory, layer));\r
669                         }\r
670                 }\r
671                 return layerDirs;\r
672         }\r
673 \r
674         /**\r
675          * アーカイブに含まれるフォルダをもつpngファイルからパーツイメージを取得する.<br>\r
676          * \r
677          * @param インポート先のキャラクターデータ\r
678          *            、フォルダ名などを判別するため。nullの場合はディレクトリなしとみなす.<br>\r
679          * @param newly\r
680          *            新規インポート用であるか?(新規でない場合は引数で指定したキャラクターセットと同じパーツは読み込まれない).\r
681          *            (アーカイブファイルからの読み込みでは無視される)\r
682          * @return パーツイメージコンテンツのコレクション、なければ空\r
683          */\r
684         public Collection<PartsImageContent> getPartsImageContents(\r
685                         CharacterData characterData, boolean newly) {\r
686                 // コンテンツルートからの絶対位置指定でパーツイメージを取得する.\r
687                 Collection<PartsImageContent> results = getPartsImageContentsStrict(characterData);\r
688                 if (results.isEmpty()) {\r
689                         // コンテンツルートからの絶対位置にパーツがない場合は、任意のディレクトリ位置からパーツイメージを推定する.\r
690                         results = getPartsImageContentsLazy(characterData);\r
691                 }\r
692                 return results;\r
693         }\r
694 \r
695         /**\r
696          * コンテンツルートからの絶対位置のフォルダからpngファイルからパーツイメージを取得する.<br>\r
697          * \r
698          * @param インポート先のキャラクターデータ\r
699          *            、フォルダ名などを判別するため。nullの場合はディレクトリなしとみなす.<br>\r
700          * @return パーツイメージコンテンツのコレクション、なければ空\r
701          */\r
702         protected Collection<PartsImageContent> getPartsImageContentsStrict(\r
703                         CharacterData characterData) {\r
704                 final Map<String, Collection<CategoryLayerPair>> layerDirMap = getLayerDirs(\r
705                                 characterData, true);\r
706 \r
707                 CategoryLayerPairResolveStrategy strategy = new CategoryLayerPairResolveStrategy() {\r
708                         public Collection<CategoryLayerPair> resolveCategoryLayerPairs(\r
709                                         String dir) {\r
710                                 Collection<CategoryLayerPair> categoryLayerPairs = layerDirMap\r
711                                                 .get(dir);\r
712                                 if (categoryLayerPairs == null || categoryLayerPairs.isEmpty()) {\r
713                                         // ディレクトリ名に一致するものがないので、この画像は無視する\r
714                                         return null;\r
715                                 }\r
716                                 return categoryLayerPairs;\r
717                         }\r
718                 };\r
719 \r
720                 return getPartsImageContents(strategy);\r
721         }\r
722 \r
723         /**\r
724          * アーカイブに含まれる任意のフォルダからpngファイルからパーツイメージを取得する.<br>\r
725          * ディレクトリ名の大文字・小文字は区別されません.<br>\r
726          * \r
727          * @param インポート先のキャラクターデータ\r
728          *            、フォルダ名などを判別するため。nullの場合はディレクトリなしとみなす.<br>\r
729          * @return パーツイメージコンテンツのコレクション、なければ空\r
730          */\r
731         protected Collection<PartsImageContent> getPartsImageContentsLazy(\r
732                         CharacterData characterData) {\r
733                 final Map<String, Collection<CategoryLayerPair>> layerDirMap = getLayerDirs(\r
734                                 characterData, false);\r
735 \r
736                 CategoryLayerPairResolveStrategy strategy = new CategoryLayerPairResolveStrategy() {\r
737                         public Collection<CategoryLayerPair> resolveCategoryLayerPairs(\r
738                                         String dir) {\r
739                                 dir = (dir == null) ? "" : dir.toLowerCase();\r
740                                 for (Map.Entry<String, Collection<CategoryLayerPair>> entry : layerDirMap\r
741                                                 .entrySet()) {\r
742                                         String dirSuffix = entry.getKey().toLowerCase();\r
743                                         Collection<CategoryLayerPair> categoryLayerPairs = entry\r
744                                                         .getValue();\r
745                                         if (dir.endsWith(dirSuffix)) {\r
746                                                 return categoryLayerPairs;\r
747                                         }\r
748                                 }\r
749                                 return null;\r
750                         }\r
751                 };\r
752 \r
753                 return getPartsImageContents(strategy);\r
754         }\r
755 \r
756         /**\r
757          * ディレクトリ名からカテゴリとレイヤーを取得するためのインターフェイス.<br>\r
758          * \r
759          * @author seraphy\r
760          */\r
761         protected interface CategoryLayerPairResolveStrategy {\r
762 \r
763                 /**\r
764                  * ディレクトリを指定して、それに該当するカテゴリとレイヤーペアのコレクションを返します.<br>\r
765                  * 同一のディレクトリに対して複数のレイヤーが割り当てられている可能性があるためコレクションで返されます.<br>\r
766                  * 空のコレクションにはなりません.<br>\r
767                  * レイヤーとして認識されていないディレクトリの場合はnullを返します.<br>\r
768                  * \r
769                  * @param dir\r
770                  *            ディレクトリ\r
771                  * @return カテゴリ・レイヤーのペアのコレクション、またはnull (空のコレクションにはならない。)\r
772                  */\r
773                 Collection<CategoryLayerPair> resolveCategoryLayerPairs(String dir);\r
774 \r
775         }\r
776 \r
777         /**\r
778          * アーカイブに含まれるフォルダをもつpngファイルからパーツイメージを取得する。\r
779          * \r
780          * @param strategy\r
781          *            ディレクトリが売れ入れ可能であるか判断するストラテジー\r
782          * @return パーツイメージコンテンツのコレクション、なければ空\r
783          */\r
784         protected Collection<PartsImageContent> getPartsImageContents(\r
785                         CategoryLayerPairResolveStrategy strategy) {\r
786                 if (strategy == null) {\r
787                         throw new IllegalArgumentException();\r
788                 }\r
789 \r
790                 ArrayList<PartsImageContent> results = new ArrayList<PartsImageContent>();\r
791                 for (Map.Entry<String, FileContent> entry : entries.entrySet()) {\r
792                         String name = entry.getKey();\r
793                         FileContent fileContent = entry.getValue();\r
794 \r
795                         String[] split = name.split("/");\r
796                         if (split.length < 2) {\r
797                                 // 最低でもフォルダ下になければならない\r
798                                 continue;\r
799                         }\r
800                         String lastName = split[split.length - 1];\r
801                         if (!lastName.toLowerCase().endsWith(".png")) {\r
802                                 // png拡張子でなければならない\r
803                                 continue;\r
804                         }\r
805 \r
806                         // ディレクトリ名\r
807                         String dir = name.substring(0, name.length() - lastName.length());\r
808 \r
809                         // ディレクトリ名から対応するレイヤーを取得します.\r
810                         Collection<CategoryLayerPair> categoryLayerPairs = strategy\r
811                                         .resolveCategoryLayerPairs(dir);\r
812                         if (categoryLayerPairs == null) {\r
813                                 // パーツイメージのディレクトリとして定義されていない場合は、この画像は無視される.\r
814                                 continue;\r
815                         }\r
816 \r
817                         // PNGファイルヘッダの取得と確認\r
818                         PNGFileImageHeader pngFileHeader = readPNGFileHeader(fileContent);\r
819                         if (pngFileHeader == null) {\r
820                                 // PNGファイルとして不正なものは無視する.\r
821                                 logger.log(Level.WARNING, "invalid png: " + name);\r
822                                 continue;\r
823                         }\r
824 \r
825                         // パーツ名(拡張子を除いたもの)\r
826                         String partsName;\r
827                         int extpos = lastName.lastIndexOf('.');\r
828                         partsName = lastName.substring(0, extpos);\r
829 \r
830                         PartsImageContent partsImageContent = new PartsImageContent(\r
831                                         fileContent, categoryLayerPairs, lastName, partsName,\r
832                                         pngFileHeader);\r
833 \r
834                         results.add(partsImageContent);\r
835                 }\r
836                 return results;\r
837         }\r
838 \r
839         /**\r
840          * PNGファイルとしてファイルを読み込みPNGヘッダ情報を返します.<br>\r
841          * PNGでないか、ファイルの読み込みに失敗した場合はnullを返します.<br>\r
842          * \r
843          * @param fileContent\r
844          *            画像ファイル\r
845          * @return PNGヘッダ情報、またはnull\r
846          */\r
847         protected PNGFileImageHeader readPNGFileHeader(FileContent fileContent) {\r
848                 PNGFileImageHeaderReader pngHeaderReader = PNGFileImageHeaderReader\r
849                                 .getInstance();\r
850                 PNGFileImageHeader pngFileHeader = null;\r
851                 try {\r
852                         InputStream is = fileContent.openStream();\r
853                         try {\r
854                                 pngFileHeader = pngHeaderReader.readHeader(is);\r
855                         } finally {\r
856                                 is.close();\r
857                         }\r
858                 } catch (IOException ex) {\r
859                         logger.log(Level.WARNING, "not png header. " + fileContent, ex);\r
860                         pngFileHeader = null;\r
861                         // 無視する.\r
862                 }\r
863                 return pngFileHeader;\r
864         }\r
865 \r
866         /**\r
867          * アーカイブに含まれるparts-info.xmlを読み込み返す.<br>\r
868          * 存在しなければ空のインスタンスを返す.<br>\r
869          * \r
870          * @return パーツ管理データ\r
871          * @throws IOException\r
872          */\r
873         public PartsManageData getPartsManageData() throws IOException {\r
874                 FileContent content = entries.get(rootPrefix + "parts-info.xml");\r
875                 if (content == null) {\r
876                         return new PartsManageData();\r
877                 }\r
878 \r
879                 PartsManageData partsManageData;\r
880 \r
881                 InputStream is = content.openStream();\r
882                 try {\r
883                         PartsInfoXMLReader xmlWriter = new PartsInfoXMLReader();\r
884                         partsManageData = xmlWriter.loadPartsManageData(is);\r
885                 } finally {\r
886                         is.close();\r
887                 }\r
888 \r
889                 return partsManageData;\r
890         }\r
891 }\r