OSDN Git Service

use multi-catch.
[mikutoga/TogaGem.git] / src / main / java / jp / sfjp / mikutoga / typical / TypicalBone.java
1 /*
2  * typical bone information
3  *
4  * License : The MIT License
5  * Copyright(c) 2011 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.typical;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.util.Collections;
13 import java.util.LinkedList;
14 import java.util.List;
15 import javax.xml.parsers.ParserConfigurationException;
16 import org.w3c.dom.Element;
17 import org.w3c.dom.NodeList;
18 import org.xml.sax.SAXException;
19
20 /**
21  * 一般的な標準ボーン構成に関する情報。
22  *
23  * <p>各ボーン情報はひとつ以上のプライマリ名(≒日本語名)と
24  * ゼロ個以上のグローバル名(≒英語名)を持つ。
25  *
26  * <p>選択基準は独断。
27  *
28  * <p>和英対訳はMMD Ver7.39の同梱モデルにほぼ準拠。
29  */
30 public final class TypicalBone extends I18nAlias {
31
32     private static final Class<?> THISCLASS = TypicalBone.class;
33     private static final String BONE_XML = "resources/typicalBone.xml";
34
35     private static final String ELEM_BONE    = "bone";
36     private static final String ELEM_ROOT    = "root";
37     private static final String ELEM_PRIMARY = "primary";
38     private static final String ELEM_GLOBAL  = "global";
39     private static final String ATTR_NAME    = "name";
40
41     private static final List<TypicalBone> BONE_LIST =
42             new LinkedList<>();
43     private static final AliasMap<TypicalBone> BONE_ALIAS_MAP =
44             new AliasMap<>();
45
46     private static final List<TypicalBone> BONE_UNMODLIST =
47             Collections.unmodifiableList(BONE_LIST);
48
49     static{
50         InputStream is = THISCLASS.getResourceAsStream(BONE_XML);
51
52         Element top;
53         try{
54             top = I18nAlias.loadXml(is);
55         }catch(ParserConfigurationException | SAXException | IOException e){
56             throw new ExceptionInInitializerError(e);
57         }
58
59         parse(top);
60
61         numbering();
62     }
63
64
65     private boolean isRoot;
66
67
68     /**
69      * コンストラクタ。
70      *
71      * <p>各初期数が0以下の場合は、
72      * 状況に応じて伸長する連結リストが用意される。
73      *
74      * @param primaryNum プライマリ名初期数。
75      * @param globalNum グローバル名初期数。
76      */
77     private TypicalBone(int primaryNum, int globalNum){
78         super(primaryNum, globalNum);
79         assert this.getClass() == THISCLASS;
80         return;
81     }
82
83
84     /**
85      * XML文書の最上位構造を解読する。
86      * @param top 最上位要素
87      */
88     private static void parse(Element top) {
89         NodeList boneList = top.getElementsByTagName(ELEM_BONE);
90         int boneNo = boneList.getLength();
91         for(int idx = 0; idx < boneNo; idx++){
92             Element boneElem = (Element) boneList.item(idx);
93             TypicalBone typBone = parseBone(boneElem);
94             BONE_LIST.add(typBone);
95             BONE_ALIAS_MAP.addAlias(typBone);
96         }
97
98         return;
99     }
100
101     /**
102      * bone要素を解読する。
103      * @param boneElem bone要素
104      * @return ボーン情報
105      */
106     private static TypicalBone parseBone(Element boneElem){
107         NodeList primaryNodes = boneElem.getElementsByTagName(ELEM_PRIMARY);
108         NodeList globalNodes  = boneElem.getElementsByTagName(ELEM_GLOBAL);
109         int primaryNo = primaryNodes.getLength();
110         int globalNo  = globalNodes.getLength();
111
112         assert primaryNo > 0;
113
114         TypicalBone bone = new TypicalBone(primaryNo, globalNo);
115
116         for(int idx = 0; idx < primaryNo; idx++){
117             Element primaryElem = (Element) primaryNodes.item(idx);
118             String name = primaryElem.getAttribute(ATTR_NAME);
119             bone.addPrimaryName(name);
120         }
121
122         for(int idx = 0; idx < globalNo; idx++){
123             Element globalElem = (Element) globalNodes.item(idx);
124             String name = globalElem.getAttribute(ATTR_NAME);
125             bone.addGlobalName(name);
126         }
127
128         NodeList rootNodes = boneElem.getElementsByTagName(ELEM_ROOT);
129         if(rootNodes.getLength() > 0){
130             bone.isRoot = true;
131         }else{
132             bone.isRoot = false;
133         }
134
135         return bone;
136     }
137
138     /**
139      * 全ボーン情報に通し番号を付ける。
140      *
141      * <p>XMLでの定義順が反映される。
142      */
143     private static void numbering(){
144         int order = 0;
145         for(TypicalBone bone : BONE_LIST){
146             bone.setOrderNo(order++);
147         }
148
149         return;
150     }
151
152     /**
153      * 全ボーンの不変リストを返す。
154      * @return 全ボーンのリスト
155      */
156     public static List<TypicalBone> getTypicalBoneList(){
157         return BONE_UNMODLIST;
158     }
159
160     /**
161      * プライマリ名の合致するボーン情報を返す。
162      * NFKCで正規化されたプライマリ名で検索される。
163      * @param primaryName プライマリ名
164      * @return モーフ情報。見つからなければnull
165      */
166     public static TypicalBone findWithPrimary(String primaryName){
167         TypicalBone result = BONE_ALIAS_MAP.getAliasByPrimary(primaryName);
168         return result;
169     }
170
171     /**
172      * グローバル名の合致するボーン情報を返す。
173      * NFKCで正規化されたグローバル名で検索される。
174      * @param globalName グローバル名
175      * @return モーフ情報。見つからなければnull
176      */
177     public static TypicalBone findWithGlobal(String globalName){
178         TypicalBone result = BONE_ALIAS_MAP.getAliasByGlobal(globalName);
179         return result;
180     }
181
182     /**
183      * プライマリ名をグローバル名に変換する。
184      * @param primaryName プライマリ名
185      * @return グローバル名。見つからなければnull
186      */
187     public static String primary2global(String primaryName){
188         String globalName = BONE_ALIAS_MAP.primary2global(primaryName);
189         return globalName;
190     }
191
192     /**
193      * グローバル名をプライマリ名へ変換する。
194      * @param globalName グローバル名
195      * @return プライマリ名。見つからなければnull
196      */
197     public static String global2primary(String globalName){
198         String primaryName = BONE_ALIAS_MAP.global2primary(globalName);
199         return primaryName;
200     }
201
202
203     /**
204      * このボーンが親を持たないルートボーンとして扱われる慣習なのか
205      * 判定する。
206      *
207      * <p>※「全親」ボーンに関する慣習は無視される。
208      *
209      * @return 親を持たなければtrue
210      */
211     public boolean isRoot(){
212         return this.isRoot;
213     }
214
215 }